diff --git a/CMakeLists.txt b/CMakeLists.txt index b7fa55d4a2..2451ab240a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ add_definitions(-DGLM_FORCE_RADIANS) if (WIN32) add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1 ") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") elseif (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) #SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic") #SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unknown-pragmas") diff --git a/interface/external/oculus/readme.txt b/interface/external/oculus/readme.txt index f689f81478..f68818d1ee 100644 --- a/interface/external/oculus/readme.txt +++ b/interface/external/oculus/readme.txt @@ -10,4 +10,7 @@ You can download the Oculus SDK from https://developer.oculusvr.com/ (account cr You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different checkouts and different projects). If so our CMake find module expects you to set the ENV variable 'HIFI_LIB_DIR' to a directory containing a subfolder 'oculus' that contains the three folders mentioned above. -2. Clear your build directory, run cmake and build, and you should be all set. \ No newline at end of file + NOTE: For Windows users, you should copy libovr.lib and libovrd.lib from the \oculus\Lib\Win32\VS2010 directory to the \oculus\Lib\Win32\ directory. + +2. Clear your build directory, run cmake and build, and you should be all set. + diff --git a/interface/resources/shaders/perlin_modulate.frag b/interface/resources/shaders/perlin_modulate.frag index 8693b14e1b..23d31ff72e 100644 --- a/interface/resources/shaders/perlin_modulate.frag +++ b/interface/resources/shaders/perlin_modulate.frag @@ -11,18 +11,114 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// the texture containing our permutations and normals -uniform sampler2D permutationNormalTexture; +// implementation based on Ken Perlin's Improved Noise reference implementation (orig. in Java) at +// http://mrl.nyu.edu/~perlin/noise/ + +uniform sampler2D permutationTexture; // the noise frequency -const float frequency = 65536.0; // looks better with current TREE_SCALE, was 1024 when TREE_SCALE was either 512 or 128 +const float frequency = 256.0; +//const float frequency = 65536.0; // looks better with current TREE_SCALE, was 1024 when TREE_SCALE was either 512 or 128 // the noise amplitude -const float amplitude = 0.1; +const float amplitude = 0.5; // the position in model space varying vec3 position; +// gradient based on gradients from cube edge centers rather than random from texture lookup +float randomEdgeGrad(int hash, vec3 position){ + int h = int(mod(hash, 16)); + float u = h < 8 ? position.x : position.y; + float v = h < 4 ? position.y : h == 12 || h == 14 ? position.x : position.z; + bool even = mod(hash, 2) == 0; + bool four = mod(hash, 4) == 0; + return (even ? u : -u) + (four ? v : -v); +} + +// still have the option to lookup based on texture +float randomTextureGrad(int hash, vec3 position){ + float u = float(hash) / 256.0; + vec3 g = -1 + 2 * texture2D(permutationTexture, vec2(u, 0.75)).rgb; + return dot(position, g); +} + +float improvedGrad(int hash, vec3 position){ +// Untested whether texture lookup is faster than math, uncomment one line or the other to try out +// cube edge gradients versus random spherical gradients sent in texture. + +// return randomTextureGrad(hash, position); + return randomEdgeGrad(hash, position); +} + +// 5th order fade function to remove 2nd order discontinuties +vec3 fade3(vec3 t){ + return t * t * t * (t * (t * 6 - 15) + 10); +} + +int permutation(int index){ + float u = float(index) / 256.0; + float t = texture2D(permutationTexture, vec2(u, 0.25)).r; + return int(t * 256); +} + +float improvedNoise(vec3 position){ + int X = int(mod(floor(position.x), 256)); + int Y = int(mod(floor(position.y), 256)); + int Z = int(mod(floor(position.z), 256)); + + vec3 fracs = fract(position); + + vec3 fades = fade3(fracs); + + int A = permutation(X + 0) + Y; + int AA = permutation(A + 0) + Z; + int AB = permutation(A + 1) + Z; + int B = permutation(X + 1) + Y; + int BA = permutation(B + 0) + Z; + int BB = permutation(B + 1) + Z; + + float gradAA0 = improvedGrad(permutation(AA + 0), vec3(fracs.x , fracs.y , fracs.z )); + float gradBA0 = improvedGrad(permutation(BA + 0), vec3(fracs.x - 1, fracs.y , fracs.z )); + float gradAB0 = improvedGrad(permutation(AB + 0), vec3(fracs.x , fracs.y - 1, fracs.z )); + float gradBB0 = improvedGrad(permutation(BB + 0), vec3(fracs.x - 1, fracs.y - 1, fracs.z )); + float gradAA1 = improvedGrad(permutation(AA + 1), vec3(fracs.x , fracs.y , fracs.z - 1)); + float gradBA1 = improvedGrad(permutation(BA + 1), vec3(fracs.x - 1, fracs.y , fracs.z - 1)); + float gradAB1 = improvedGrad(permutation(AB + 1), vec3(fracs.x , fracs.y - 1, fracs.z - 1)); + float gradBB1 = improvedGrad(permutation(BB + 1), vec3(fracs.x - 1, fracs.y - 1, fracs.z - 1)); + + return mix(mix(mix(gradAA0, gradBA0, fades.x), mix(gradAB0, gradBB0, fades.x), fades.y), mix(mix(gradAA1, gradBA1, fades.x), mix(gradAB1, gradBB1, fades.x), fades.y), fades.z); +} + +float turbulence(vec3 position, float power){ + return (1.0f / power) * improvedNoise(power * position); +} + +float turb(vec3 position){ + return turbulence(position, 1) + + turbulence(position, 2), + + turbulence(position, 4) + + turbulence(position, 8) + + turbulence(position, 16) + + turbulence(position, 32) + + turbulence(position, 64) + + turbulence(position, 128) + ; +} + + +void main(void) { + + // get noise in range 0 .. 1 + float noise = clamp(0.5f + amplitude * turb(position * frequency), 0, 1); + + // apply vertex lighting + vec3 color = gl_Color.rgb * vec3(noise, noise, noise); + gl_FragColor = vec4(color, 1); +} + + +/* old implementation // returns the gradient at a single corner of our sampling cube vec3 grad(vec3 location) { float p1 = texture2D(permutationNormalTexture, vec2(location.x / 256.0, 0.25)).r; @@ -60,7 +156,4 @@ float perlin(vec3 location) { mix(mix(ffcv, cfcv, params.x), mix(fccv, cccv, params.x), params.y), params.z); } - -void main(void) { - gl_FragColor = vec4(gl_Color.rgb * (1.0 + amplitude*(perlin(position * frequency) - 1.0)), 1.0); -} +*/ \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7193a06125..b062c231bd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -399,18 +399,19 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : } Application::~Application() { + qInstallMessageHandler(NULL); + + saveSettings(); + storeSizeAndPosition(); + saveScripts(); + int DELAY_TIME = 1000; UserActivityLogger::getInstance().close(DELAY_TIME); - qInstallMessageHandler(NULL); - // make sure we don't call the idle timer any more delete idleTimer; - + _sharedVoxelSystem.changeTree(new VoxelTree); - - saveSettings(); - delete _voxelImporter; // let the avatar mixer know we're out @@ -433,8 +434,6 @@ Application::~Application() { _particleEditSender.terminate(); _modelEditSender.terminate(); - storeSizeAndPosition(); - saveScripts(); VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown Menu::getInstance()->deleteLater(); @@ -589,13 +588,17 @@ void Application::paintGL() { //Note, the camera distance is set in Camera::setMode() so we dont have to do it here. _myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing _myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition()); - _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation()); + if (OculusManager::isConnected()) { + _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation()); + } else { + _myCamera.setTargetRotation(_myAvatar->getHead()->getOrientation()); + } } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { _myCamera.setTightness(0.0f); _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _scaleMirror); _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); - _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition()); + _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition() + glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0)); } // Update camera position @@ -633,6 +636,10 @@ void Application::paintGL() { //If we aren't using the glow shader, we have to clear the color and depth buffer if (!glowEnabled) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } else if (OculusManager::isConnected()) { + //Clear the color buffer to ensure that there isnt any residual color + //Left over from when OR was not connected. + glClear(GL_COLOR_BUFFER_BIT); } if (OculusManager::isConnected()) { @@ -644,13 +651,8 @@ void Application::paintGL() { } } else if (TV3DManager::isConnected()) { - if (glowEnabled) { - _glowEffect.prepare(); - } + TV3DManager::display(whichCamera); - if (glowEnabled) { - _glowEffect.render(); - } } else { if (glowEnabled) { @@ -1138,7 +1140,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) { _lastMouseMove = usecTimestampNow(); - if (_mouseHidden && showMouse && !OculusManager::isConnected()) { + if (_mouseHidden && showMouse && !OculusManager::isConnected() && !TV3DManager::isConnected()) { getGLWidget()->setCursor(Qt::ArrowCursor); _mouseHidden = false; _seenMouseMove = true; diff --git a/interface/src/ScriptsModel.cpp b/interface/src/ScriptsModel.cpp index 77a9619d43..7b24587129 100644 --- a/interface/src/ScriptsModel.cpp +++ b/interface/src/ScriptsModel.cpp @@ -31,8 +31,6 @@ static const QString IS_TRUNCATED_NAME = "IsTruncated"; static const QString CONTAINER_NAME = "Contents"; static const QString KEY_NAME = "Key"; -static const int SCRIPT_PATH = Qt::UserRole; - ScriptItem::ScriptItem(const QString& filename, const QString& fullPath) : _filename(filename), _fullPath(fullPath) { diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index dbe444bf9b..199c313119 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -266,7 +266,7 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p ApplicationOverlay& applicationOverlay = Application::getInstance()->getApplicationOverlay(); - // We only need to render the overlays to a texture once, then we just render the texture as a quad + // We only need to render the overlays to a texture once, then we just render the texture on the hemisphere // PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay() applicationOverlay.renderOverlay(true); const bool displayOverlays = Menu::getInstance()->isOptionChecked(MenuOption::DisplayOculusOverlays); diff --git a/interface/src/devices/TV3DManager.cpp b/interface/src/devices/TV3DManager.cpp index b5cc28b07f..25d3ff892a 100644 --- a/interface/src/devices/TV3DManager.cpp +++ b/interface/src/devices/TV3DManager.cpp @@ -93,6 +93,18 @@ void TV3DManager::display(Camera& whichCamera) { int portalW = Application::getInstance()->getGLWidget()->width() / 2; int portalH = Application::getInstance()->getGLWidget()->height(); + const bool glowEnabled = Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect); + + ApplicationOverlay& applicationOverlay = Application::getInstance()->getApplicationOverlay(); + + // We only need to render the overlays to a texture once, then we just render the texture as a quad + // PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay() + applicationOverlay.renderOverlay(true); + + if (glowEnabled) { + Application::getInstance()->getGlowEffect()->prepare(); + } + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_SCISSOR_TEST); @@ -102,13 +114,21 @@ void TV3DManager::display(Camera& whichCamera) { glPushMatrix(); { + glMatrixMode(GL_PROJECTION); glLoadIdentity(); // reset projection matrix glFrustum(_leftEye.left, _leftEye.right, _leftEye.bottom, _leftEye.top, nearZ, farZ); // set left view frustum + GLfloat p[4][4]; + glGetFloatv(GL_PROJECTION_MATRIX, &(p[0][0])); + GLfloat cotangent = p[1][1]; + GLfloat fov = atan(1.0f / cotangent); glTranslatef(_leftEye.modelTranslation, 0.0, 0.0); // translate to cancel parallax + glMatrixMode(GL_MODELVIEW); glLoadIdentity(); Application::getInstance()->displaySide(whichCamera); + + applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov); } glPopMatrix(); glDisable(GL_SCISSOR_TEST); @@ -124,14 +144,25 @@ void TV3DManager::display(Camera& whichCamera) { glMatrixMode(GL_PROJECTION); glLoadIdentity(); // reset projection matrix glFrustum(_rightEye.left, _rightEye.right, _rightEye.bottom, _rightEye.top, nearZ, farZ); // set left view frustum + GLfloat p[4][4]; + glGetFloatv(GL_PROJECTION_MATRIX, &(p[0][0])); + GLfloat cotangent = p[1][1]; + GLfloat fov = atan(1.0f / cotangent); glTranslatef(_rightEye.modelTranslation, 0.0, 0.0); // translate to cancel parallax + glMatrixMode(GL_MODELVIEW); glLoadIdentity(); Application::getInstance()->displaySide(whichCamera); + + applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov); } glPopMatrix(); glDisable(GL_SCISSOR_TEST); // reset the viewport to how we started glViewport(0, 0, Application::getInstance()->getGLWidget()->width(), Application::getInstance()->getGLWidget()->height()); + + if (glowEnabled) { + Application::getInstance()->getGlowEffect()->render(); + } } diff --git a/interface/src/renderer/JointState.cpp b/interface/src/renderer/JointState.cpp index 8e4aecdf51..429084480d 100644 --- a/interface/src/renderer/JointState.cpp +++ b/interface/src/renderer/JointState.cpp @@ -11,6 +11,8 @@ #include +#include +//#include #include #include "JointState.h" @@ -18,7 +20,48 @@ JointState::JointState() : _animationPriority(0.0f), _fbxJoint(NULL), - _isConstrained(false) { + _constraint(NULL) { +} + +JointState::JointState(const JointState& other) : _constraint(NULL) { + _transform = other._transform; + _rotation = other._rotation; + _rotationInParentFrame = other._rotationInParentFrame; + _animationPriority = other._animationPriority; + _fbxJoint = other._fbxJoint; + // DO NOT copy _constraint +} + +JointState::~JointState() { + delete _constraint; + _constraint = NULL; + if (_constraint) { + delete _constraint; + _constraint = NULL; + } +} + +void JointState::setFBXJoint(const FBXJoint* joint) { + assert(joint != NULL); + _rotationInParentFrame = joint->rotation; + // NOTE: JointState does not own the FBXJoint to which it points. + _fbxJoint = joint; + if (_constraint) { + delete _constraint; + _constraint = NULL; + } +} + +void JointState::updateConstraint() { + if (_constraint) { + delete _constraint; + _constraint = NULL; + } + if (glm::distance2(glm::vec3(-PI), _fbxJoint->rotationMin) > EPSILON || + glm::distance2(glm::vec3(PI), _fbxJoint->rotationMax) > EPSILON ) { + // this joint has rotation constraints + _constraint = AngularConstraint::newAngularConstraint(_fbxJoint->rotationMin, _fbxJoint->rotationMax); + } } void JointState::copyState(const JointState& state) { @@ -30,18 +73,7 @@ void JointState::copyState(const JointState& state) { _visibleTransform = state._visibleTransform; _visibleRotation = extractRotation(_visibleTransform); _visibleRotationInParentFrame = state._visibleRotationInParentFrame; - // DO NOT copy _fbxJoint -} - -void JointState::setFBXJoint(const FBXJoint* joint) { - assert(joint != NULL); - _rotationInParentFrame = joint->rotation; - // NOTE: JointState does not own the FBXJoint to which it points. - _fbxJoint = joint; - // precompute whether there are any constraints or not - float distanceMin = glm::distance(_fbxJoint->rotationMin, glm::vec3(-PI)); - float distanceMax = glm::distance(_fbxJoint->rotationMax, glm::vec3(PI)); - _isConstrained = distanceMin > EPSILON || distanceMax > EPSILON; + // DO NOT copy _fbxJoint or _constraint } void JointState::computeTransform(const glm::mat4& parentTransform) { @@ -70,11 +102,15 @@ void JointState::restoreRotation(float fraction, float priority) { } } -void JointState::setRotationFromBindFrame(const glm::quat& rotation, float priority) { +void JointState::setRotationFromBindFrame(const glm::quat& rotation, float priority, bool constrain) { // rotation is from bind- to model-frame assert(_fbxJoint != NULL); if (priority >= _animationPriority) { - setRotationInParentFrame(_rotationInParentFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation)); + glm::quat targetRotation = _rotationInParentFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation); + if (constrain && _constraint) { + _constraint->softClamp(targetRotation, _rotationInParentFrame, 0.5f); + } + setRotationInParentFrame(targetRotation); _animationPriority = priority; } } @@ -99,7 +135,7 @@ void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, floa return; } _animationPriority = priority; - if (!constrain || !_isConstrained) { + if (!constrain || _constraint == NULL) { // no constraints _rotationInParentFrame = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation; _rotation = delta * _rotation; @@ -122,10 +158,12 @@ void JointState::mixRotationDelta(const glm::quat& delta, float mixFactor, float if (mixFactor > 0.0f && mixFactor <= 1.0f) { targetRotation = safeMix(targetRotation, _fbxJoint->rotation, mixFactor); } + if (_constraint) { + _constraint->softClamp(targetRotation, _rotationInParentFrame, 0.5f); + } setRotationInParentFrame(targetRotation); } - glm::quat JointState::computeParentRotation() const { // R = Rp * Rpre * r * Rpost // Rp = R * (Rpre * r * Rpost)^ diff --git a/interface/src/renderer/JointState.h b/interface/src/renderer/JointState.h index 8412cfd0cb..3bd752cdff 100644 --- a/interface/src/renderer/JointState.h +++ b/interface/src/renderer/JointState.h @@ -18,15 +18,19 @@ #include +class AngularConstraint; + class JointState { public: JointState(); - - void copyState(const JointState& state); + JointState(const JointState& other); + ~JointState(); void setFBXJoint(const FBXJoint* joint); const FBXJoint& getFBXJoint() const { return *_fbxJoint; } + void updateConstraint(); + void copyState(const JointState& state); void computeTransform(const glm::mat4& parentTransform); @@ -64,7 +68,7 @@ public: /// \param rotation is from bind- to model-frame /// computes and sets new _rotationInParentFrame /// NOTE: the JointState's model-frame transform/rotation are NOT updated! - void setRotationFromBindFrame(const glm::quat& rotation, float priority); + void setRotationFromBindFrame(const glm::quat& rotation, float priority, bool constrain = false); void setRotationInParentFrame(const glm::quat& targetRotation); const glm::quat& getRotationInParentFrame() const { return _rotationInParentFrame; } @@ -95,7 +99,7 @@ private: glm::quat _visibleRotationInParentFrame; const FBXJoint* _fbxJoint; // JointState does NOT own its FBXJoint - bool _isConstrained; + AngularConstraint* _constraint; // JointState owns its AngularConstraint }; #endif // hifi_JointState_h diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index ffca2c8ea6..86d742c949 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -561,8 +561,6 @@ bool Model::updateGeometry() { void Model::setJointStates(QVector states) { _jointStates = states; - // compute an approximate bounding radius for broadphase collision queries - // against PhysicsSimulation boundaries int numJoints = _jointStates.size(); float radius = 0.0f; for (int i = 0; i < numJoints; ++i) { @@ -570,6 +568,7 @@ void Model::setJointStates(QVector states) { if (distance > radius) { radius = distance; } + _jointStates[i].updateConstraint(); } for (int i = 0; i < _jointStates.size(); i++) { _jointStates[i].slaveVisibleTransform(); @@ -1159,14 +1158,9 @@ void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm: } glm::quat deltaRotation = rotationBetween(leverArm, targetPosition - pivot); - /* DON'T REMOVE! This code provides the gravitational effect on the IK solution. - * It is commented out for the moment because we're blending the IK solution with - * the default pose which provides similar stability, but we might want to use - * gravity again later. - - // We want to mix the shortest rotation with one that will pull the system down with gravity. - // So we compute a simplified center of mass, where each joint has a mass of 1.0 and we don't - // bother averaging it because we only need direction. + // We want to mix the shortest rotation with one that will pull the system down with gravity + // so that limbs don't float unrealistically. To do this we compute a simplified center of mass + // where each joint has unit mass and we don't bother averaging it because we only need direction. if (j > 1) { glm::vec3 centerOfMass(0.0f); @@ -1188,11 +1182,9 @@ void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm: } deltaRotation = safeMix(deltaRotation, gravityDelta, mixFactor); } - */ // Apply the rotation, but use mixRotationDelta() which blends a bit of the default pose - // at in the process. This provides stability to the IK solution and removes the necessity - // for the gravity effect. + // at in the process. This provides stability to the IK solution for most models. glm::quat oldNextRotation = nextState.getRotation(); float mixFactor = 0.03f; nextState.mixRotationDelta(deltaRotation, mixFactor, priority); @@ -1217,7 +1209,7 @@ void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm: } while (numIterations < MAX_ITERATION_COUNT && distanceToGo < ACCEPTABLE_IK_ERROR); // set final rotation of the end joint - endState.setRotationFromBindFrame(targetRotation, priority); + endState.setRotationFromBindFrame(targetRotation, priority, true); _shapesAreDirty = !_shapes.isEmpty(); } diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index 55a67ce854..01c3dc1cc1 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -85,6 +85,33 @@ void TextureCache::setFrameBufferSize(QSize frameBufferSize) { } } +// use fixed table of permutations. Could also make ordered list programmatically +// and then shuffle algorithm. For testing, this ensures consistent behavior in each run. +// this list taken from Ken Perlin's Improved Noise reference implementation (orig. in Java) at +// http://mrl.nyu.edu/~perlin/noise/ + +const int permutation[256] = +{ + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, + 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, + 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, + 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, + 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, + 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, + 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, + 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, + 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, + 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, + 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, + 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, + 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, + 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, + 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, + 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 +}; + +#define USE_CHRIS_NOISE 1 + GLuint TextureCache::getPermutationNormalTextureID() { if (_permutationNormalTextureID == 0) { glGenTextures(1, &_permutationNormalTextureID); @@ -92,10 +119,17 @@ GLuint TextureCache::getPermutationNormalTextureID() { // the first line consists of random permutation offsets unsigned char data[256 * 2 * 3]; +#if (USE_CHRIS_NOISE==1) + for (int i = 0; i < 256; i++) { + data[3*i+0] = permutation[i]; + data[3*i+1] = permutation[i]; + data[3*i+2] = permutation[i]; +#else for (int i = 0; i < 256 * 3; i++) { data[i] = rand() % 256; +#endif } - // the next, random unit normals + for (int i = 256 * 3; i < 256 * 3 * 2; i += 3) { glm::vec3 randvec = glm::sphericalRand(1.0f); data[i] = ((randvec.x + 1.0f) / 2.0f) * 255.0f; @@ -105,7 +139,6 @@ GLuint TextureCache::getPermutationNormalTextureID() { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glBindTexture(GL_TEXTURE_2D, 0); } return _permutationNormalTextureID; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 82a8f1c183..4c445958ec 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -205,7 +205,7 @@ void ApplicationOverlay::getClickLocation(int &x, int &y) const { } } -// Draws the FBO texture for Oculus rift. TODO: Draw a curved texture instead of plane. +// Draws the FBO texture for Oculus rift. void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { if (_alpha == 0.0f) { @@ -292,6 +292,107 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { } +// Draws the FBO texture for 3DTV. +void ApplicationOverlay::displayOverlayTexture3DTV(Camera& whichCamera, float aspectRatio, float fov) { + + if (_alpha == 0.0f) { + return; + } + + Application* application = Application::getInstance(); + + MyAvatar* myAvatar = application->getAvatar(); + const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation(); + + glActiveTexture(GL_TEXTURE0); + + glEnable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); + glBindTexture(GL_TEXTURE_2D, getFramebufferObject()->texture()); + glEnable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + glEnable(GL_TEXTURE_2D); + + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glLoadIdentity(); + // Transform to world space + glm::quat rotation = whichCamera.getRotation(); + glm::vec3 axis2 = glm::axis(rotation); + glRotatef(-glm::degrees(glm::angle(rotation)), axis2.x, axis2.y, axis2.z); + glTranslatef(viewMatrixTranslation.x, viewMatrixTranslation.y, viewMatrixTranslation.z); + + // Translate to the front of the camera + glm::vec3 pos = whichCamera.getPosition(); + glm::quat rot = myAvatar->getOrientation(); + glm::vec3 axis = glm::axis(rot); + + glTranslatef(pos.x, pos.y, pos.z); + glRotatef(glm::degrees(glm::angle(rot)), axis.x, axis.y, axis.z); + + glColor4f(1.0f, 1.0f, 1.0f, _alpha); + + //Render + // fov -= RADIANS_PER_DEGREE * 2.5f; //reduce by 5 degrees so it fits in the view + const GLfloat distance = 1.0f; + + const GLfloat halfQuadHeight = distance * tan(fov); + const GLfloat halfQuadWidth = halfQuadHeight * aspectRatio; + const GLfloat quadWidth = halfQuadWidth * 2.0f; + const GLfloat quadHeight = halfQuadHeight * 2.0f; + + GLfloat x = -halfQuadWidth; + GLfloat y = -halfQuadHeight; + glDisable(GL_DEPTH_TEST); + + glBegin(GL_QUADS); + + glTexCoord2f(0.0f, 1.0f); glVertex3f(x, y + quadHeight, -distance); + glTexCoord2f(1.0f, 1.0f); glVertex3f(x + quadWidth, y + quadHeight, -distance); + glTexCoord2f(1.0f, 0.0f); glVertex3f(x + quadWidth, y, -distance); + glTexCoord2f(0.0f, 0.0f); glVertex3f(x, y, -distance); + + glEnd(); + + if (_crosshairTexture == 0) { + _crosshairTexture = Application::getInstance()->getGLWidget()->bindTexture(QImage(Application::resourcesPath() + "images/sixense-reticle.png")); + } + + //draw the mouse pointer + glBindTexture(GL_TEXTURE_2D, _crosshairTexture); + + const float reticleSize = 40.0f / application->getGLWidget()->width() * quadWidth; + x -= reticleSize / 2.0f; + y += reticleSize / 2.0f; + const float mouseX = (application->getMouseX() / (float)application->getGLWidget()->width()) * quadWidth; + const float mouseY = (1.0 - (application->getMouseY() / (float)application->getGLWidget()->height())) * quadHeight; + + glBegin(GL_QUADS); + + glColor3f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2]); + + glTexCoord2d(0.0f, 0.0f); glVertex3f(x + mouseX, y + mouseY, -distance); + glTexCoord2d(1.0f, 0.0f); glVertex3f(x + mouseX + reticleSize, y + mouseY, -distance); + glTexCoord2d(1.0f, 1.0f); glVertex3f(x + mouseX + reticleSize, y + mouseY - reticleSize, -distance); + glTexCoord2d(0.0f, 1.0f); glVertex3f(x + mouseX, y + mouseY - reticleSize, -distance); + + glEnd(); + + glEnable(GL_DEPTH_TEST); + + glPopMatrix(); + + glDepthMask(GL_TRUE); + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); + glEnable(GL_LIGHTING); + + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); +} + //Renders optional pointers void ApplicationOverlay::renderPointers() { Application* application = Application::getInstance(); diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index b9f9596ccf..7c1f87d575 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -29,6 +29,7 @@ public: void renderOverlay(bool renderToTexture = false); void displayOverlayTexture(); void displayOverlayTextureOculus(Camera& whichCamera); + void displayOverlayTexture3DTV(Camera& whichCamera, float aspectRatio, float fov); void computeOculusPickRay(float x, float y, glm::vec3& direction) const; void getClickLocation(int &x, int &y) const; diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index aa18cb43ee..5b20c82263 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -83,17 +83,10 @@ void UserActivityLogger::close(int delayTime) { // In order to get the end of the session, we need to give the account manager enough time to send the packet. QEventLoop loop; - // Here we connect the callbacks to stop the event loop - JSONCallbackParameters params; - params.jsonCallbackReceiver = &loop; - params.errorCallbackReceiver = &loop; - params.jsonCallbackMethod = "quit"; - params.errorCallbackMethod = "quit"; - // In case something goes wrong, we also setup a timer so that the delai is not greater than delayTime QTimer timer; connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); // Now we can log it - logAction(ACTION_NAME, QJsonObject(), params); + logAction(ACTION_NAME, QJsonObject()); timer.start(delayTime); loop.exec(); } diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index d8d5887d97..0291690c3d 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -183,7 +183,6 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA) // MIN_VALID_SPEED is obtained by computing speed gained at one gravity after the shortest expected frame const float MIN_EXPECTED_FRAME_PERIOD = 0.0167f; // 1/60th of a second -const float HALTING_SPEED = 9.8 * MIN_EXPECTED_FRAME_PERIOD / (float)(TREE_SCALE); void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { // particles that are in hand, don't collide with avatars diff --git a/libraries/shared/src/AngularConstraint.cpp b/libraries/shared/src/AngularConstraint.cpp new file mode 100644 index 0000000000..4689568ac8 --- /dev/null +++ b/libraries/shared/src/AngularConstraint.cpp @@ -0,0 +1,201 @@ +// +// AngularConstraint.cpp +// interface/src/renderer +// +// Created by Andrew Meadows on 2014.05.30 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "AngularConstraint.h" +#include "SharedUtil.h" + +// helper function +/// \param angle radian angle to be clamped within angleMin and angleMax +/// \param angleMin minimum value +/// \param angleMax maximum value +/// \return value between minAngle and maxAngle closest to angle +float clampAngle(float angle, float angleMin, float angleMax) { + float minDistance = angle - angleMin; + float maxDistance = angle - angleMax; + if (maxDistance > 0.0f) { + minDistance = glm::min(minDistance, angleMin + TWO_PI - angle); + angle = (minDistance < maxDistance) ? angleMin : angleMax; + } else if (minDistance < 0.0f) { + maxDistance = glm::max(maxDistance, angleMax - TWO_PI - angle); + angle = (minDistance > maxDistance) ? angleMin : angleMax; + } + return angle; +} + +// static +AngularConstraint* AngularConstraint::newAngularConstraint(const glm::vec3& minAngles, const glm::vec3& maxAngles) { + float minDistance2 = glm::distance2(minAngles, glm::vec3(-PI, -PI, -PI)); + float maxDistance2 = glm::distance2(maxAngles, glm::vec3(PI, PI, PI)); + if (minDistance2 < EPSILON && maxDistance2 < EPSILON) { + // no constraint + return NULL; + } + // count the zero length elements + glm::vec3 rangeAngles = maxAngles - minAngles; + int pivotIndex = -1; + int numZeroes = 0; + for (int i = 0; i < 3; ++i) { + if (rangeAngles[i] < EPSILON) { + ++numZeroes; + } else { + pivotIndex = i; + } + } + if (numZeroes == 2) { + // this is a hinge + int forwardIndex = (pivotIndex + 1) % 3; + glm::vec3 forwardAxis(0.0f); + forwardAxis[forwardIndex] = 1.0f; + glm::vec3 rotationAxis(0.0f); + rotationAxis[pivotIndex] = 1.0f; + return new HingeConstraint(forwardAxis, rotationAxis, minAngles[pivotIndex], maxAngles[pivotIndex]); + } else if (numZeroes == 0) { + // approximate the angular limits with a cone roller + // we assume the roll is about z + glm::vec3 middleAngles = 0.5f * (maxAngles + minAngles); + glm::quat yaw = glm::angleAxis(middleAngles[1], glm::vec3(0.0f, 1.0f, 0.0f)); + glm::quat pitch = glm::angleAxis(middleAngles[0], glm::vec3(1.0f, 0.0f, 0.0f)); + glm::vec3 coneAxis = pitch * yaw * glm::vec3(0.0f, 0.0f, 1.0f); + // the coneAngle is half the average range of the two non-roll rotations + glm::vec3 range = maxAngles - minAngles; + float coneAngle = 0.25f * (range[0] + range[1]); + return new ConeRollerConstraint(coneAngle, coneAxis, minAngles.z, maxAngles.z); + } + return NULL; +} + +bool AngularConstraint::softClamp(glm::quat& targetRotation, const glm::quat& oldRotation, float mixFraction) { + glm::quat clampedTarget = targetRotation; + bool clamped = clamp(clampedTarget); + if (clamped) { + // check if oldRotation is also clamped + glm::quat clampedOld = oldRotation; + bool clamped2 = clamp(clampedOld); + if (clamped2) { + // oldRotation is already beyond the constraint + // we clamp again midway between targetRotation and clamped oldPosition + clampedTarget = glm::shortMix(clampedOld, targetRotation, mixFraction); + // and then clamp that + clamp(clampedTarget); + } + // finally we mix targetRotation with the clampedTarget + targetRotation = glm::shortMix(clampedTarget, targetRotation, mixFraction); + } + return clamped; +} + +HingeConstraint::HingeConstraint(const glm::vec3& forwardAxis, const glm::vec3& rotationAxis, float minAngle, float maxAngle) + : _minAngle(minAngle), _maxAngle(maxAngle) { + assert(_minAngle < _maxAngle); + // we accept the rotationAxis direction + assert(glm::length(rotationAxis) > EPSILON); + _rotationAxis = glm::normalize(rotationAxis); + // but we compute the final _forwardAxis + glm::vec3 otherAxis = glm::cross(_rotationAxis, forwardAxis); + assert(glm::length(otherAxis) > EPSILON); + _forwardAxis = glm::normalize(glm::cross(otherAxis, _rotationAxis)); +} + +// virtual +bool HingeConstraint::clamp(glm::quat& rotation) const { + glm::vec3 forward = rotation * _forwardAxis; + forward -= glm::dot(forward, _rotationAxis) * _rotationAxis; + float length = glm::length(forward); + if (length < EPSILON) { + // infinite number of solutions ==> choose the middle of the contrained range + rotation = glm::angleAxis(0.5f * (_minAngle + _maxAngle), _rotationAxis); + return true; + } + forward /= length; + float sign = (glm::dot(glm::cross(_forwardAxis, forward), _rotationAxis) > 0.0f ? 1.0f : -1.0f); + //float angle = sign * acos(glm::dot(forward, _forwardAxis) / length); + float angle = sign * acos(glm::dot(forward, _forwardAxis)); + glm::quat newRotation = glm::angleAxis(clampAngle(angle, _minAngle, _maxAngle), _rotationAxis); + if (fabsf(1.0f - glm::dot(newRotation, rotation)) > EPSILON * EPSILON) { + rotation = newRotation; + return true; + } + return false; +} + +bool HingeConstraint::softClamp(glm::quat& targetRotation, const glm::quat& oldRotation, float mixFraction) { + // the hinge works best without a soft clamp + return clamp(targetRotation); +} + +ConeRollerConstraint::ConeRollerConstraint(float coneAngle, const glm::vec3& coneAxis, float minRoll, float maxRoll) + : _coneAngle(coneAngle), _minRoll(minRoll), _maxRoll(maxRoll) { + assert(_maxRoll >= _minRoll); + float axisLength = glm::length(coneAxis); + assert(axisLength > EPSILON); + _coneAxis = coneAxis / axisLength; +} + +// virtual +bool ConeRollerConstraint::clamp(glm::quat& rotation) const { + bool applied = false; + glm::vec3 rotatedAxis = rotation * _coneAxis; + glm::vec3 perpAxis = glm::cross(rotatedAxis, _coneAxis); + float perpAxisLength = glm::length(perpAxis); + if (perpAxisLength > EPSILON) { + perpAxis /= perpAxisLength; + // enforce the cone + float angle = acosf(glm::dot(rotatedAxis, _coneAxis)); + if (angle > _coneAngle) { + rotation = glm::angleAxis(angle - _coneAngle, perpAxis) * rotation; + rotatedAxis = rotation * _coneAxis; + applied = true; + } + } else { + // the rotation is 100% roll + // there is no obvious perp axis so we must pick one + perpAxis = rotatedAxis; + // find the first non-zero element: + float iValue = 0.0f; + int i = 0; + for (i = 0; i < 3; ++i) { + if (fabsf(perpAxis[i]) > EPSILON) { + iValue = perpAxis[i]; + break; + } + } + assert(i != 3); + // swap or negate the next element + int j = (i + 1) % 3; + float jValue = perpAxis[j]; + if (fabsf(jValue - iValue) > EPSILON) { + perpAxis[i] = jValue; + perpAxis[j] = iValue; + } else { + perpAxis[i] = -iValue; + } + perpAxis = glm::cross(perpAxis, rotatedAxis); + perpAxisLength = glm::length(perpAxis); + assert(perpAxisLength > EPSILON); + perpAxis /= perpAxisLength; + } + // measure the roll + // NOTE: perpAxis is perpendicular to both _coneAxis and rotatedConeAxis, so we can + // rotate it again and we'll end up with an something that has only been rolled. + glm::vec3 rolledPerpAxis = rotation * perpAxis; + float sign = glm::dot(rotatedAxis, glm::cross(perpAxis, rolledPerpAxis)) > 0.0f ? 1.0f : -1.0f; + float roll = sign * angleBetween(rolledPerpAxis, perpAxis); + if (roll < _minRoll || roll > _maxRoll) { + float clampedRoll = clampAngle(roll, _minRoll, _maxRoll); + rotation = glm::normalize(glm::angleAxis(clampedRoll - roll, rotatedAxis) * rotation); + applied = true; + } + return applied; +} + + diff --git a/libraries/shared/src/AngularConstraint.h b/libraries/shared/src/AngularConstraint.h new file mode 100644 index 0000000000..929a58959b --- /dev/null +++ b/libraries/shared/src/AngularConstraint.h @@ -0,0 +1,55 @@ +// +// AngularConstraint.h +// interface/src/renderer +// +// Created by Andrew Meadows on 2014.05.30 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AngularConstraint_h +#define hifi_AngularConstraint_h + +#include + + +class AngularConstraint { +public: + /// \param minAngles minumum euler angles for the constraint + /// \param maxAngles minumum euler angles for the constraint + /// \return pointer to new AngularConstraint of the right type or NULL if none could be made + static AngularConstraint* newAngularConstraint(const glm::vec3& minAngles, const glm::vec3& maxAngles); + + AngularConstraint() {} + virtual ~AngularConstraint() {} + virtual bool clamp(glm::quat& rotation) const = 0; + virtual bool softClamp(glm::quat& targetRotation, const glm::quat& oldRotation, float mixFraction); +protected: +}; + +class HingeConstraint : public AngularConstraint { +public: + HingeConstraint(const glm::vec3& forwardAxis, const glm::vec3& rotationAxis, float minAngle, float maxAngle); + virtual bool clamp(glm::quat& rotation) const; + virtual bool softClamp(glm::quat& targetRotation, const glm::quat& oldRotation, float mixFraction); +protected: + glm::vec3 _forwardAxis; + glm::vec3 _rotationAxis; + float _minAngle; + float _maxAngle; +}; + +class ConeRollerConstraint : public AngularConstraint { +public: + ConeRollerConstraint(float coneAngle, const glm::vec3& coneAxis, float minRoll, float maxRoll); + virtual bool clamp(glm::quat& rotation) const; +private: + float _coneAngle; + glm::vec3 _coneAxis; + float _minRoll; + float _maxRoll; +}; + +#endif // hifi_AngularConstraint_h diff --git a/tests/shared/src/AngularConstraintTests.cpp b/tests/shared/src/AngularConstraintTests.cpp new file mode 100644 index 0000000000..00916a7267 --- /dev/null +++ b/tests/shared/src/AngularConstraintTests.cpp @@ -0,0 +1,476 @@ +// +// AngularConstraintTests.cpp +// tests/physics/src +// +// Created by Andrew Meadows on 2014.05.30 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include +#include +#include + +#include "AngularConstraintTests.h" + + +void AngularConstraintTests::testHingeConstraint() { + float minAngle = -PI; + float maxAngle = 0.0f; + glm::vec3 yAxis(0.0f, 1.0f, 0.0f); + glm::vec3 minAngles(0.0f, -PI, 0.0f); + glm::vec3 maxAngles(0.0f, 0.0f, 0.0f); + + AngularConstraint* c = AngularConstraint::newAngularConstraint(minAngles, maxAngles); + if (!c) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: newAngularConstraint() should make a constraint" << std::endl; + } + + { // test in middle of constraint + float angle = 0.5f * (minAngle + maxAngle); + glm::quat rotation = glm::angleAxis(angle, yAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should not clamp()" << std::endl; + } + if (rotation != newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should not change rotation" << std::endl; + } + } + { // test just inside min edge of constraint + float angle = minAngle + 10.f * EPSILON; + glm::quat rotation = glm::angleAxis(angle, yAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should not clamp()" << std::endl; + } + if (rotation != newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should not change rotation" << std::endl; + } + } + { // test just inside max edge of constraint + float angle = maxAngle - 10.f * EPSILON; + glm::quat rotation = glm::angleAxis(angle, yAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should not clamp()" << std::endl; + } + if (rotation != newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should not change rotation" << std::endl; + } + } + { // test just outside min edge of constraint + float angle = minAngle - 0.001f; + glm::quat rotation = glm::angleAxis(angle, yAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (!constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should clamp()" << std::endl; + } + if (rotation == newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should change rotation" << std::endl; + } + glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis); + float qDot = glm::dot(expectedRotation, newRotation); + if (fabsf(qDot - 1.0f) > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl; + } + } + { // test just outside max edge of constraint + float angle = maxAngle + 0.001f; + glm::quat rotation = glm::angleAxis(angle, yAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (!constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should clamp()" << std::endl; + } + if (rotation == newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should change rotation" << std::endl; + } + glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis); + float qDot = glm::dot(expectedRotation, newRotation); + if (fabsf(qDot - 1.0f) > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl; + } + } + { // test far outside min edge of constraint (wraps around to max) + float angle = minAngle - 0.75f * (TWO_PI - (maxAngle - minAngle)); + glm::quat rotation = glm::angleAxis(angle, yAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (!constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should clamp()" << std::endl; + } + if (rotation == newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should change rotation" << std::endl; + } + glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis); + float qDot = glm::dot(expectedRotation, newRotation); + if (fabsf(qDot - 1.0f) > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl; + } + } + { // test far outside max edge of constraint (wraps around to min) + float angle = maxAngle + 0.75f * (TWO_PI - (maxAngle - minAngle)); + glm::quat rotation = glm::angleAxis(angle, yAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (!constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should clamp()" << std::endl; + } + if (rotation == newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should change rotation" << std::endl; + } + glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis); + float qDot = glm::dot(expectedRotation, newRotation); + if (fabsf(qDot - 1.0f) > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl; + } + } + + float ACCEPTABLE_ERROR = 1.0e-4f; + { // test nearby but off-axis rotation + float offAngle = 0.1f; + glm::quat offRotation(offAngle, glm::vec3(1.0f, 0.0f, 0.0f)); + float angle = 0.5f * (maxAngle + minAngle); + glm::quat rotation = offRotation * glm::angleAxis(angle, yAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (!constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should clamp()" << std::endl; + } + if (rotation == newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should change rotation" << std::endl; + } + glm::quat expectedRotation = glm::angleAxis(angle, yAxis); + float qDot = glm::dot(expectedRotation, newRotation); + if (fabsf(qDot - 1.0f) > ACCEPTABLE_ERROR) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl; + } + } + { // test way off rotation > maxAngle + float offAngle = 0.5f; + glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f)); + float angle = maxAngle + 0.2f * (TWO_PI - (maxAngle - minAngle)); + glm::quat rotation = glm::angleAxis(angle, yAxis); + rotation = offRotation * glm::angleAxis(angle, yAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (!constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should clamp()" << std::endl; + } + if (rotation == newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should change rotation" << std::endl; + } + glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis); + float qDot = glm::dot(expectedRotation, newRotation); + if (fabsf(qDot - 1.0f) > ACCEPTABLE_ERROR) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl; + } + } + { // test way off rotation < minAngle + float offAngle = 0.5f; + glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f)); + float angle = minAngle - 0.2f * (TWO_PI - (maxAngle - minAngle)); + glm::quat rotation = glm::angleAxis(angle, yAxis); + rotation = offRotation * glm::angleAxis(angle, yAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (!constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should clamp()" << std::endl; + } + if (rotation == newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should change rotation" << std::endl; + } + glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis); + float qDot = glm::dot(expectedRotation, newRotation); + if (fabsf(qDot - 1.0f) > ACCEPTABLE_ERROR) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl; + } + } + { // test way off rotation > maxAngle with wrap over to minAngle + float offAngle = -0.5f; + glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f)); + float angle = maxAngle + 0.6f * (TWO_PI - (maxAngle - minAngle)); + glm::quat rotation = glm::angleAxis(angle, yAxis); + rotation = offRotation * glm::angleAxis(angle, yAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (!constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should clamp()" << std::endl; + } + if (rotation == newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should change rotation" << std::endl; + } + glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis); + float qDot = glm::dot(expectedRotation, newRotation); + if (fabsf(qDot - 1.0f) > ACCEPTABLE_ERROR) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl; + } + } + { // test way off rotation < minAngle with wrap over to maxAngle + float offAngle = -0.6f; + glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f)); + float angle = minAngle - 0.7f * (TWO_PI - (maxAngle - minAngle)); + glm::quat rotation = glm::angleAxis(angle, yAxis); + rotation = offRotation * glm::angleAxis(angle, yAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (!constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should clamp()" << std::endl; + } + if (rotation == newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint should change rotation" << std::endl; + } + glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis); + float qDot = glm::dot(expectedRotation, newRotation); + if (fabsf(qDot - 1.0f) > ACCEPTABLE_ERROR) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl; + } + } + delete c; +} + +void AngularConstraintTests::testConeRollerConstraint() { + float minAngleX = -PI / 5.0f; + float minAngleY = -PI / 5.0f; + float minAngleZ = -PI / 8.0f; + + float maxAngleX = PI / 4.0f; + float maxAngleY = PI / 3.0f; + float maxAngleZ = PI / 4.0f; + + glm::vec3 minAngles(minAngleX, minAngleY, minAngleZ); + glm::vec3 maxAngles(maxAngleX, maxAngleY, maxAngleZ); + AngularConstraint* c = AngularConstraint::newAngularConstraint(minAngles, maxAngles); + + float expectedConeAngle = 0.25 * (maxAngleX - minAngleX + maxAngleY - minAngleY); + glm::vec3 middleAngles = 0.5f * (maxAngles + minAngles); + glm::quat yaw = glm::angleAxis(middleAngles[1], glm::vec3(0.0f, 1.0f, 0.0f)); + glm::quat pitch = glm::angleAxis(middleAngles[0], glm::vec3(1.0f, 0.0f, 0.0f)); + glm::vec3 expectedConeAxis = pitch * yaw * glm::vec3(0.0f, 0.0f, 1.0f); + + glm::vec3 xAxis(1.0f, 0.0f, 0.0f); + glm::vec3 perpAxis = glm::normalize(xAxis - glm::dot(xAxis, expectedConeAxis) * expectedConeAxis); + + if (!c) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: newAngularConstraint() should make a constraint" << std::endl; + } + { // test in middle of constraint + glm::vec3 angles(PI/20.0f, 0.0f, PI/10.0f); + glm::quat rotation(angles); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should not clamp()" << std::endl; + } + if (rotation != newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should not change rotation" << std::endl; + } + } + float deltaAngle = 0.001f; + { // test just inside edge of cone + glm::quat rotation = glm::angleAxis(expectedConeAngle - deltaAngle, perpAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should not clamp()" << std::endl; + } + if (rotation != newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should not change rotation" << std::endl; + } + } + { // test just outside edge of cone + glm::quat rotation = glm::angleAxis(expectedConeAngle + deltaAngle, perpAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (!constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should clamp()" << std::endl; + } + if (rotation == newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should change rotation" << std::endl; + } + } + { // test just inside min edge of roll + glm::quat rotation = glm::angleAxis(minAngleZ + deltaAngle, expectedConeAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should not clamp()" << std::endl; + } + if (rotation != newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should not change rotation" << std::endl; + } + } + { // test just inside max edge of roll + glm::quat rotation = glm::angleAxis(maxAngleZ - deltaAngle, expectedConeAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should not clamp()" << std::endl; + } + if (rotation != newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should not change rotation" << std::endl; + } + } + { // test just outside min edge of roll + glm::quat rotation = glm::angleAxis(minAngleZ - deltaAngle, expectedConeAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (!constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should clamp()" << std::endl; + } + if (rotation == newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should change rotation" << std::endl; + } + glm::quat expectedRotation = glm::angleAxis(minAngleZ, expectedConeAxis); + if (fabsf(1.0f - glm::dot(newRotation, expectedRotation)) > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: rotation = " << newRotation << " but expected " << expectedRotation << std::endl; + } + } + { // test just outside max edge of roll + glm::quat rotation = glm::angleAxis(maxAngleZ + deltaAngle, expectedConeAxis); + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (!constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should clamp()" << std::endl; + } + if (rotation == newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should change rotation" << std::endl; + } + glm::quat expectedRotation = glm::angleAxis(maxAngleZ, expectedConeAxis); + if (fabsf(1.0f - glm::dot(newRotation, expectedRotation)) > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: rotation = " << newRotation << " but expected " << expectedRotation << std::endl; + } + } + deltaAngle = 0.25f * expectedConeAngle; + { // test far outside cone and min roll + glm::quat roll = glm::angleAxis(minAngleZ - deltaAngle, expectedConeAxis); + glm::quat pitchYaw = glm::angleAxis(expectedConeAngle + deltaAngle, perpAxis); + glm::quat rotation = pitchYaw * roll; + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (!constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should clamp()" << std::endl; + } + if (rotation == newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should change rotation" << std::endl; + } + glm::quat expectedRoll = glm::angleAxis(minAngleZ, expectedConeAxis); + glm::quat expectedPitchYaw = glm::angleAxis(expectedConeAngle, perpAxis); + glm::quat expectedRotation = expectedPitchYaw * expectedRoll; + if (fabsf(1.0f - glm::dot(newRotation, expectedRotation)) > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: rotation = " << newRotation << " but expected " << expectedRotation << std::endl; + } + } + { // test far outside cone and max roll + glm::quat roll = glm::angleAxis(maxAngleZ + deltaAngle, expectedConeAxis); + glm::quat pitchYaw = glm::angleAxis(- expectedConeAngle - deltaAngle, perpAxis); + glm::quat rotation = pitchYaw * roll; + + glm::quat newRotation = rotation; + bool constrained = c->clamp(newRotation); + if (!constrained) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should clamp()" << std::endl; + } + if (rotation == newRotation) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ConeRollerConstraint should change rotation" << std::endl; + } + glm::quat expectedRoll = glm::angleAxis(maxAngleZ, expectedConeAxis); + glm::quat expectedPitchYaw = glm::angleAxis(- expectedConeAngle, perpAxis); + glm::quat expectedRotation = expectedPitchYaw * expectedRoll; + if (fabsf(1.0f - glm::dot(newRotation, expectedRotation)) > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: rotation = " << newRotation << " but expected " << expectedRotation << std::endl; + } + } + delete c; +} + +void AngularConstraintTests::runAllTests() { + testHingeConstraint(); + testConeRollerConstraint(); +} diff --git a/tests/shared/src/AngularConstraintTests.h b/tests/shared/src/AngularConstraintTests.h new file mode 100644 index 0000000000..f0994f08c9 --- /dev/null +++ b/tests/shared/src/AngularConstraintTests.h @@ -0,0 +1,21 @@ +// +// AngularConstraintTests.h +// tests/physics/src +// +// Created by Andrew Meadows on 2014.05.30 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AngularConstraintTests_h +#define hifi_AngularConstraintTests_h + +namespace AngularConstraintTests { + void testHingeConstraint(); + void testConeRollerConstraint(); + void runAllTests(); +} + +#endif // hifi_AngularConstraintTests_h diff --git a/tests/shared/src/main.cpp b/tests/shared/src/main.cpp index 3ae1b7b34d..6215d394a8 100644 --- a/tests/shared/src/main.cpp +++ b/tests/shared/src/main.cpp @@ -8,9 +8,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "AngularConstraintTests.h" #include "MovingPercentileTests.h" int main(int argc, char** argv) { MovingPercentileTests::runAllTests(); + AngularConstraintTests::runAllTests(); return 0; }