diff --git a/BUILD.md b/BUILD.md index e89933fa7d..c1ccd3193e 100644 --- a/BUILD.md +++ b/BUILD.md @@ -2,8 +2,8 @@ * [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 3.3.2 * [Qt](http://www.qt.io/download-open-source) ~> 5.5.1 -* [OpenSSL](https://www.openssl.org/community/binaries.html) ~> 1.0.1 (highest letter) - * IMPORTANT: Using the recommended version of OpenSSL is critical to avoid security vulnerabilities. Make sure to check regularly to see if there is a new version. +* [OpenSSL](https://www.openssl.org/community/binaries.html) ~> 1.0.1m + * IMPORTANT: Using the recommended version of OpenSSL is critical to avoid security vulnerabilities. * [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) ####CMake External Project Dependencies diff --git a/BUILD_WIN.md b/BUILD_WIN.md index a80c279798..25e20952ca 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -73,8 +73,8 @@ Your system may already have several versions of the OpenSSL DLL's (ssleay32.dll QSslSocket: cannot resolve SSL_get0_next_proto_negotiated To prevent these problems, install OpenSSL yourself. Download one of the following binary packages [from this website](http://slproweb.com/products/Win32OpenSSL.html): -* Win32 OpenSSL v1.0.1 (highest letter) -* Win64 OpenSSL v1.0.1 (highest letter) +* Win32 OpenSSL v1.0.1q +* Win64 OpenSSL v1.0.1q Install OpenSSL into the Windows system directory, to make sure that Qt uses the version that you've just installed, and not some other version. diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6fdcd8f797..f48104235d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -739,7 +739,7 @@ void MyAvatar::saveData() { settings.endGroup(); } -float loadSetting(QSettings& settings, const char* name, float defaultValue) { +float loadSetting(Settings& settings, const QString& name, float defaultValue) { float value = settings.value(name, defaultValue).toFloat(); if (glm::isnan(value)) { value = defaultValue; diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 806e33819e..764ade2661 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -25,6 +25,8 @@ #include "AudioRingBuffer.h" #include "AudioLogging.h" +#include "AudioSRC.h" + #include "Sound.h" QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in) { @@ -89,49 +91,22 @@ void Sound::downSample(const QByteArray& rawAudioByteArray) { // we want to convert it to the format that the audio-mixer wants // which is signed, 16-bit, 24Khz - int numSourceSamples = rawAudioByteArray.size() / sizeof(AudioConstants::AudioSample); + int numChannels = _isStereo ? 2 : 1; + AudioSRC resampler(48000, AudioConstants::SAMPLE_RATE, numChannels); - if (_isStereo && numSourceSamples % 2 != 0){ - // in the unlikely case that we have stereo audio but we seem to be missing a sample - // (the sample for one channel is missing in a set of interleaved samples) - // then drop the odd sample - --numSourceSamples; - } + // resize to max possible output + int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample)); + int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames); + int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample); + _byteArray.resize(maxDestinationBytes); - int numDestinationSamples = numSourceSamples / 2.0f; - - if (_isStereo && numDestinationSamples % 2 != 0) { - // if this is stereo we need to make sure we produce stereo output - // which means we should have an even number of output samples - numDestinationSamples += 1; - } - - int numDestinationBytes = numDestinationSamples * sizeof(AudioConstants::AudioSample); + int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(), + (int16_t*)_byteArray.data(), + numSourceFrames); + // truncate to actual output + int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample); _byteArray.resize(numDestinationBytes); - - int16_t* sourceSamples = (int16_t*) rawAudioByteArray.data(); - int16_t* destinationSamples = (int16_t*) _byteArray.data(); - - if (_isStereo) { - for (int i = 0; i < numSourceSamples; i += 4) { - if (i + 2 >= numSourceSamples) { - destinationSamples[i / 2] = sourceSamples[i]; - destinationSamples[(i / 2) + 1] = sourceSamples[i + 1]; - } else { - destinationSamples[i / 2] = (sourceSamples[i] + sourceSamples[i + 2]) / 2; - destinationSamples[(i / 2) + 1] = (sourceSamples[i + 1] + sourceSamples[i + 3]) / 2; - } - } - } else { - for (int i = 1; i < numSourceSamples; i += 2) { - if (i + 1 >= numSourceSamples) { - destinationSamples[(i - 1) / 2] = (sourceSamples[i - 1] + sourceSamples[i]) / 2; - } else { - destinationSamples[(i - 1) / 2] = ((sourceSamples[i - 1] + sourceSamples[i + 1]) / 4) + (sourceSamples[i] / 2); - } - } - } } // diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 7b51283bac..7b3b3c3efe 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -599,11 +599,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { ShapeType type = getShapeType(); - if (type != SHAPE_TYPE_COMPOUND) { - ModelEntityItem::computeShapeInfo(info); - info.setParams(type, 0.5f * getDimensions()); - adjustShapeInfoByRegistration(info); - } else { + if (type == SHAPE_TYPE_COMPOUND) { updateModelBounds(); // should never fall in here when collision model not fully loaded @@ -612,25 +608,27 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { const FBXGeometry& renderGeometry = _model->getFBXGeometry(); const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry(); - _points.clear(); - unsigned int i = 0; + QVector>& points = info.getPoints(); + points.clear(); + uint32_t i = 0; // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. + const uint32_t TRIANGLE_STRIDE = 3; + const uint32_t QUAD_STRIDE = 4; foreach (const FBXMesh& mesh, collisionGeometry.meshes) { // each meshPart is a convex hull foreach (const FBXMeshPart &meshPart, mesh.parts) { - QVector pointsInPart; + points.push_back(QVector()); + QVector& pointsInPart = points[i]; // run through all the triangles and (uniquely) add each point to the hull - unsigned int triangleCount = meshPart.triangleIndices.size() / 3; - for (unsigned int j = 0; j < triangleCount; j++) { - unsigned int p0Index = meshPart.triangleIndices[j*3]; - unsigned int p1Index = meshPart.triangleIndices[j*3+1]; - unsigned int p2Index = meshPart.triangleIndices[j*3+2]; - glm::vec3 p0 = mesh.vertices[p0Index]; - glm::vec3 p1 = mesh.vertices[p1Index]; - glm::vec3 p2 = mesh.vertices[p2Index]; + uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size(); + assert(numIndices % TRIANGLE_STRIDE == 0); + for (uint32_t j = 0; j < numIndices; j += TRIANGLE_STRIDE) { + glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]]; + glm::vec3 p1 = mesh.vertices[meshPart.triangleIndices[j + 1]]; + glm::vec3 p2 = mesh.vertices[meshPart.triangleIndices[j + 2]]; if (!pointsInPart.contains(p0)) { pointsInPart << p0; } @@ -643,17 +641,13 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } // run through all the quads and (uniquely) add each point to the hull - unsigned int quadCount = meshPart.quadIndices.size() / 4; - assert((unsigned int)meshPart.quadIndices.size() == quadCount*4); - for (unsigned int j = 0; j < quadCount; j++) { - unsigned int p0Index = meshPart.quadIndices[j*4]; - unsigned int p1Index = meshPart.quadIndices[j*4+1]; - unsigned int p2Index = meshPart.quadIndices[j*4+2]; - unsigned int p3Index = meshPart.quadIndices[j*4+3]; - glm::vec3 p0 = mesh.vertices[p0Index]; - glm::vec3 p1 = mesh.vertices[p1Index]; - glm::vec3 p2 = mesh.vertices[p2Index]; - glm::vec3 p3 = mesh.vertices[p3Index]; + numIndices = (uint32_t)meshPart.quadIndices.size(); + assert(numIndices % QUAD_STRIDE == 0); + for (uint32_t j = 0; j < numIndices; j += QUAD_STRIDE) { + glm::vec3 p0 = mesh.vertices[meshPart.quadIndices[j]]; + glm::vec3 p1 = mesh.vertices[meshPart.quadIndices[j + 1]]; + glm::vec3 p2 = mesh.vertices[meshPart.quadIndices[j + 2]]; + glm::vec3 p3 = mesh.vertices[meshPart.quadIndices[j + 3]]; if (!pointsInPart.contains(p0)) { pointsInPart << p0; } @@ -670,14 +664,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { if (pointsInPart.size() == 0) { qCDebug(entitiesrenderer) << "Warning -- meshPart has no faces"; + points.pop_back(); continue; } - - // add next convex hull - QVector newMeshPoints; - _points << newMeshPoints; - // add points to the new convex hull - _points[i++] << pointsInPart; + ++i; } } @@ -691,23 +681,26 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { // multiply each point by scale before handing the point-set off to the physics engine. // also determine the extents of the collision model. AABox box; - for (int i = 0; i < _points.size(); i++) { - for (int j = 0; j < _points[i].size(); j++) { + for (int i = 0; i < points.size(); i++) { + for (int j = 0; j < points[i].size(); j++) { // compensate for registration - _points[i][j] += _model->getOffset(); + points[i][j] += _model->getOffset(); // scale so the collision points match the model points - _points[i][j] *= scale; + points[i][j] *= scale; // this next subtraction is done so we can give info the offset, which will cause // the shape-key to change. - _points[i][j] -= _model->getOffset(); - box += _points[i][j]; + points[i][j] -= _model->getOffset(); + box += points[i][j]; } } glm::vec3 collisionModelDimensions = box.getDimensions(); info.setParams(type, collisionModelDimensions, _compoundShapeURL); - info.setConvexHulls(_points); info.setOffset(_model->getOffset()); + } else { + ModelEntityItem::computeShapeInfo(info); + info.setParams(type, 0.5f * getDimensions()); + adjustShapeInfoByRegistration(info); } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index d2de45f538..339c907532 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -103,7 +103,6 @@ private: QVariantMap _currentTextures; QVariantMap _originalTextures; bool _originalTexturesRead = false; - QVector> _points; bool _dimensionsInitialized = true; AnimationPropertyGroup _renderAnimationProperties; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index ba39727ff9..89bf9f1a21 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -88,7 +88,7 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) { _lastEdited = usecTime > _created ? usecTime : _created; } -const char* shapeTypeNames[] = {"none", "box", "sphere", "ellipsoid", "plane", "compound", "capsule-x", +const char* shapeTypeNames[] = {"none", "box", "sphere", "plane", "compound", "capsule-x", "capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z"}; QHash stringToShapeTypeLookup; @@ -101,7 +101,6 @@ void buildStringToShapeTypeLookup() { addShapeType(SHAPE_TYPE_NONE); addShapeType(SHAPE_TYPE_BOX); addShapeType(SHAPE_TYPE_SPHERE); - addShapeType(SHAPE_TYPE_ELLIPSOID); addShapeType(SHAPE_TYPE_PLANE); addShapeType(SHAPE_TYPE_COMPOUND); addShapeType(SHAPE_TYPE_CAPSULE_X); diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 4d63562cf7..6a5ef20bac 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -60,7 +60,7 @@ class LineEntityItem : public EntityItem { const QVector& getLinePoints() const{ return _points; } - virtual ShapeType getShapeType() const { return SHAPE_TYPE_LINE; } + virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; } // never have a ray intersection pick a LineEntityItem. virtual bool supportsDetailedRayIntersection() const { return true; } diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index cf7b14dbf1..3231e7c5e1 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -78,7 +78,7 @@ class PolyLineEntityItem : public EntityItem { virtual bool needsToCallUpdate() const { return true; } - virtual ShapeType getShapeType() const { return SHAPE_TYPE_LINE; } + virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; } // never have a ray intersection pick a PolyLineEntityItem. virtual bool supportsDetailedRayIntersection() const { return true; } diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index 71b919b7ee..d667d1075d 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -17,6 +17,54 @@ #include "ShapeFactory.h" #include "BulletUtil.h" +// These are the same normalized directions used by the btShapeHull class. +// 12 points for the face centers of a duodecohedron plus another 30 points +// for the midpoints the edges, for a total of 42. +const uint32_t NUM_UNIT_SPHERE_DIRECTIONS = 42; +static const btVector3 _unitSphereDirections[NUM_UNIT_SPHERE_DIRECTIONS] = { + btVector3(btScalar(0.000000) , btScalar(-0.000000),btScalar(-1.000000)), + btVector3(btScalar(0.723608) , btScalar(-0.525725),btScalar(-0.447219)), + btVector3(btScalar(-0.276388) , btScalar(-0.850649),btScalar(-0.447219)), + btVector3(btScalar(-0.894426) , btScalar(-0.000000),btScalar(-0.447216)), + btVector3(btScalar(-0.276388) , btScalar(0.850649),btScalar(-0.447220)), + btVector3(btScalar(0.723608) , btScalar(0.525725),btScalar(-0.447219)), + btVector3(btScalar(0.276388) , btScalar(-0.850649),btScalar(0.447220)), + btVector3(btScalar(-0.723608) , btScalar(-0.525725),btScalar(0.447219)), + btVector3(btScalar(-0.723608) , btScalar(0.525725),btScalar(0.447219)), + btVector3(btScalar(0.276388) , btScalar(0.850649),btScalar(0.447219)), + btVector3(btScalar(0.894426) , btScalar(0.000000),btScalar(0.447216)), + btVector3(btScalar(-0.000000) , btScalar(0.000000),btScalar(1.000000)), + btVector3(btScalar(0.425323) , btScalar(-0.309011),btScalar(-0.850654)), + btVector3(btScalar(-0.162456) , btScalar(-0.499995),btScalar(-0.850654)), + btVector3(btScalar(0.262869) , btScalar(-0.809012),btScalar(-0.525738)), + btVector3(btScalar(0.425323) , btScalar(0.309011),btScalar(-0.850654)), + btVector3(btScalar(0.850648) , btScalar(-0.000000),btScalar(-0.525736)), + btVector3(btScalar(-0.525730) , btScalar(-0.000000),btScalar(-0.850652)), + btVector3(btScalar(-0.688190) , btScalar(-0.499997),btScalar(-0.525736)), + btVector3(btScalar(-0.162456) , btScalar(0.499995),btScalar(-0.850654)), + btVector3(btScalar(-0.688190) , btScalar(0.499997),btScalar(-0.525736)), + btVector3(btScalar(0.262869) , btScalar(0.809012),btScalar(-0.525738)), + btVector3(btScalar(0.951058) , btScalar(0.309013),btScalar(0.000000)), + btVector3(btScalar(0.951058) , btScalar(-0.309013),btScalar(0.000000)), + btVector3(btScalar(0.587786) , btScalar(-0.809017),btScalar(0.000000)), + btVector3(btScalar(0.000000) , btScalar(-1.000000),btScalar(0.000000)), + btVector3(btScalar(-0.587786) , btScalar(-0.809017),btScalar(0.000000)), + btVector3(btScalar(-0.951058) , btScalar(-0.309013),btScalar(-0.000000)), + btVector3(btScalar(-0.951058) , btScalar(0.309013),btScalar(-0.000000)), + btVector3(btScalar(-0.587786) , btScalar(0.809017),btScalar(-0.000000)), + btVector3(btScalar(-0.000000) , btScalar(1.000000),btScalar(-0.000000)), + btVector3(btScalar(0.587786) , btScalar(0.809017),btScalar(-0.000000)), + btVector3(btScalar(0.688190) , btScalar(-0.499997),btScalar(0.525736)), + btVector3(btScalar(-0.262869) , btScalar(-0.809012),btScalar(0.525738)), + btVector3(btScalar(-0.850648) , btScalar(0.000000),btScalar(0.525736)), + btVector3(btScalar(-0.262869) , btScalar(0.809012),btScalar(0.525738)), + btVector3(btScalar(0.688190) , btScalar(0.499997),btScalar(0.525736)), + btVector3(btScalar(0.525730) , btScalar(0.000000),btScalar(0.850652)), + btVector3(btScalar(0.162456) , btScalar(-0.499995),btScalar(0.850654)), + btVector3(btScalar(-0.425323) , btScalar(-0.309011),btScalar(0.850654)), + btVector3(btScalar(-0.425323) , btScalar(0.309011),btScalar(0.850654)), + btVector3(btScalar(0.162456) , btScalar(0.499995),btScalar(0.850654)) +}; btConvexHullShape* ShapeFactory::createConvexHull(const QVector& points) { @@ -66,15 +114,40 @@ btConvexHullShape* ShapeFactory::createConvexHull(const QVector& poin hull->addPoint(btVector3(correctedPoint[0], correctedPoint[1], correctedPoint[2]), false); } - if (points.size() > MAX_HULL_POINTS) { - // create hull approximation - btShapeHull shapeHull(hull); - shapeHull.buildHull(margin); + uint32_t numPoints = (uint32_t)hull->getNumPoints(); + if (numPoints > MAX_HULL_POINTS) { + // we have too many points, so we compute point projections along canonical unit vectors + // and keep the those that project the farthest + btVector3 btCenter = glmToBullet(center); + btVector3* shapePoints = hull->getUnscaledPoints(); + std::vector finalIndices; + finalIndices.reserve(NUM_UNIT_SPHERE_DIRECTIONS); + for (uint32_t i = 0; i < NUM_UNIT_SPHERE_DIRECTIONS; ++i) { + uint32_t bestIndex = 0; + btScalar maxDistance = _unitSphereDirections[i].dot(shapePoints[0] - btCenter); + for (uint32_t j = 1; j < numPoints; ++j) { + btScalar distance = _unitSphereDirections[i].dot(shapePoints[j] - btCenter); + if (distance > maxDistance) { + maxDistance = distance; + bestIndex = j; + } + } + bool keep = true; + for (uint32_t j = 0; j < finalIndices.size(); ++j) { + if (finalIndices[j] == bestIndex) { + keep = false; + break; + } + } + if (keep) { + finalIndices.push_back(bestIndex); + } + } + // we cannot copy Bullet shapes so we must create a new one... btConvexHullShape* newHull = new btConvexHullShape(); - const btVector3* newPoints = shapeHull.getVertexPointer(); - for (int i = 0; i < shapeHull.numVertices(); ++i) { - newHull->addPoint(newPoints[i], false); + for (uint32_t i = 0; i < finalIndices.size(); ++i) { + newHull->addPoint(shapePoints[finalIndices[i]], false); } // ...and delete the old one delete hull; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 8011b0c4c8..ded1184c24 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -35,7 +35,46 @@ int vec3VectorTypeId = qRegisterMetaType >(); float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; #define HTTP_INVALID_COM "http://invalid.com" -model::MaterialPointer Model::_collisionHullMaterial; +const int NUM_COLLISION_HULL_COLORS = 24; +std::vector _collisionHullMaterials; + +void initCollisionHullMaterials() { + // generates bright colors in red, green, blue, yellow, magenta, and cyan spectrums + // (no browns, greys, or dark shades) + float component[NUM_COLLISION_HULL_COLORS] = { + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.2f, 0.4f, 0.6f, 0.8f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 0.8f, 0.6f, 0.4f, 0.2f + }; + _collisionHullMaterials.reserve(NUM_COLLISION_HULL_COLORS); + + // each component gets the same cuve + // but offset by a multiple of one third the full width + int numComponents = 3; + int sectionWidth = NUM_COLLISION_HULL_COLORS / numComponents; + int greenPhase = sectionWidth; + int bluePhase = 2 * sectionWidth; + + // we stride through the colors to scatter adjacent shades + // so they don't tend to group together for large models + for (int i = 0; i < sectionWidth; ++i) { + for (int j = 0; j < numComponents; ++j) { + model::MaterialPointer material; + material = std::make_shared(); + int index = j * sectionWidth + i; + float red = component[index]; + float green = component[(index + greenPhase) % NUM_COLLISION_HULL_COLORS]; + float blue = component[(index + bluePhase) % NUM_COLLISION_HULL_COLORS]; + material->setAlbedo(glm::vec3(red, green, blue)); + material->setMetallic(0.02f); + material->setRoughness(0.5f); + _collisionHullMaterials.push_back(material); + } + } +} Model::Model(RigPointer rig, QObject* parent) : QObject(parent), @@ -1217,13 +1256,10 @@ void Model::segregateMeshGroups() { int totalParts = mesh.parts.size(); for (int partIndex = 0; partIndex < totalParts; partIndex++) { if (showingCollisionHull) { - if (!_collisionHullMaterial) { - _collisionHullMaterial = std::make_shared(); - _collisionHullMaterial->setAlbedo(glm::vec3(1.0f, 0.5f, 0.0f)); - _collisionHullMaterial->setMetallic(0.02f); - _collisionHullMaterial->setRoughness(0.5f); + if (_collisionHullMaterials.empty()) { + initCollisionHullMaterials(); } - _collisionRenderItemsSet << std::make_shared(networkMesh, partIndex, _collisionHullMaterial, transform, offset); + _collisionRenderItemsSet << std::make_shared(networkMesh, partIndex, _collisionHullMaterials[partIndex % NUM_COLLISION_HULL_COLORS], transform, offset); } else { _modelMeshRenderItemsSet << std::make_shared(this, i, partIndex, shapeID, transform, offset); } diff --git a/libraries/shared/src/SettingHandle.cpp b/libraries/shared/src/SettingHandle.cpp index d2e84d8b75..cad2a0286f 100644 --- a/libraries/shared/src/SettingHandle.cpp +++ b/libraries/shared/src/SettingHandle.cpp @@ -10,11 +10,76 @@ // #include "SettingHandle.h" +#include "SettingManager.h" #include + + const QString Settings::firstRun { "firstRun" }; +Settings::Settings() : + _manager(DependencyManager::get()), + _locker(&(_manager->getLock())) +{ +} + +Settings::~Settings() { +} + +void Settings::remove(const QString& key) { + _manager->remove(key); +} + +QStringList Settings::childGroups() const { + return _manager->childGroups(); +} + +QStringList Settings::childKeys() const { + return _manager->childKeys(); +} + +QStringList Settings::allKeys() const { + return _manager->allKeys(); +} + +bool Settings::contains(const QString& key) const { + return _manager->contains(key); +} + +int Settings::beginReadArray(const QString & prefix) { + return _manager->beginReadArray(prefix); +} + +void Settings::beginWriteArray(const QString& prefix, int size) { + _manager->beginWriteArray(prefix, size); +} + +void Settings::endArray() { + _manager->endArray(); +} + +void Settings::setArrayIndex(int i) { + _manager->setArrayIndex(i); +} + +void Settings::beginGroup(const QString& prefix) { + _manager->beginGroup(prefix); +} + +void Settings::endGroup() { + _manager->endGroup(); +} + +void Settings::setValue(const QString& name, const QVariant& value) { + _manager->setValue(name, value); +} + +QVariant Settings::value(const QString& name, const QVariant& defaultValue) const { + return _manager->value(name, defaultValue); +} + + void Settings::getFloatValueIfValid(const QString& name, float& floatValue) { const QVariant badDefaultValue = NAN; bool ok = true; diff --git a/libraries/shared/src/SettingHandle.h b/libraries/shared/src/SettingHandle.h index bef2daf84c..e83c563036 100644 --- a/libraries/shared/src/SettingHandle.h +++ b/libraries/shared/src/SettingHandle.h @@ -14,19 +14,40 @@ #include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include "SettingInterface.h" + // TODO: remove -class Settings : public QSettings { +class Settings { public: static const QString firstRun; + Settings(); + ~Settings(); + + void remove(const QString& key); + QStringList childGroups() const; + QStringList childKeys() const; + QStringList allKeys() const; + bool contains(const QString& key) const; + int beginReadArray(const QString & prefix); + void beginWriteArray(const QString& prefix, int size = -1); + void endArray(); + void setArrayIndex(int i); + + void beginGroup(const QString& prefix); + void endGroup(); + + void setValue(const QString& name, const QVariant& value); + QVariant value(const QString& name, const QVariant& defaultValue = QVariant()) const; void getFloatValueIfValid(const QString& name, float& floatValue); void getBoolValue(const QString& name, bool& boolValue); @@ -36,6 +57,9 @@ public: void setQuatValue(const QString& name, const glm::quat& quatValue); void getQuatValueIfValid(const QString& name, glm::quat& quatValue); + + QSharedPointer _manager; + QWriteLocker _locker; }; namespace Setting { diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index a931875771..630d52bf7d 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -9,27 +9,33 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "SettingInterface.h" + #include #include #include #include #include "PathUtils.h" -#include "SettingInterface.h" #include "SettingManager.h" #include "SharedLogging.h" namespace Setting { - static Manager* privateInstance = nullptr; + static QSharedPointer globalManager; + const QString Interface::FIRST_RUN { "firstRun" }; + // cleans up the settings private instance. Should only be run once at closing down. void cleanupPrivateInstance() { // grab the thread before we nuke the instance - QThread* settingsManagerThread = privateInstance->thread(); - + QThread* settingsManagerThread = DependencyManager::get()->thread(); + // tell the private instance to clean itself up on its thread - privateInstance->deleteLater(); - privateInstance = NULL; + DependencyManager::destroy(); + + // + globalManager->deleteLater(); + globalManager.reset(); // quit the settings manager thread and wait on it to make sure it's gone settingsManagerThread->quit(); @@ -63,14 +69,13 @@ namespace Setting { QThread* thread = new QThread(); Q_CHECK_PTR(thread); thread->setObjectName("Settings Thread"); - - privateInstance = new Manager(); - Q_CHECK_PTR(privateInstance); - QObject::connect(privateInstance, SIGNAL(destroyed()), thread, SLOT(quit())); - QObject::connect(thread, SIGNAL(started()), privateInstance, SLOT(startTimer())); + globalManager = DependencyManager::set(); + + QObject::connect(globalManager.data(), SIGNAL(destroyed()), thread, SLOT(quit())); + QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer())); QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - privateInstance->moveToThread(thread); + globalManager->moveToThread(thread); thread->start(); qCDebug(shared) << "Settings thread started."; @@ -79,7 +84,7 @@ namespace Setting { } void Interface::init() { - if (!privateInstance) { + if (!DependencyManager::isSet()) { // WARNING: As long as we are using QSettings this should always be triggered for each Setting::Handle // in an assignment-client - the QSettings backing we use for this means persistence of these // settings from an AC (when there can be multiple terminating at same time on one machine) @@ -87,9 +92,13 @@ namespace Setting { qWarning() << "Setting::Interface::init() for key" << _key << "- Manager not yet created." << "Settings persistence disabled."; } else { - // Register Handle - privateInstance->registerHandle(this); - _isInitialized = true; + _manager = DependencyManager::get(); + auto manager = _manager.lock(); + if (manager) { + // Register Handle + manager->registerHandle(this); + _isInitialized = true; + } // Load value from disk load(); @@ -97,11 +106,13 @@ namespace Setting { } void Interface::deinit() { - if (_isInitialized && privateInstance) { - // Save value to disk - save(); - - privateInstance->removeHandle(_key); + if (_isInitialized && _manager) { + auto manager = _manager.lock(); + if (manager) { + // Save value to disk + save(); + manager->removeHandle(_key); + } } } @@ -113,14 +124,16 @@ namespace Setting { } void Interface::save() { - if (privateInstance) { - privateInstance->saveSetting(this); + auto manager = _manager.lock(); + if (manager) { + manager->saveSetting(this); } } void Interface::load() { - if (privateInstance) { - privateInstance->loadSetting(this); + auto manager = _manager.lock(); + if (manager) { + manager->loadSetting(this); } } } diff --git a/libraries/shared/src/SettingInterface.h b/libraries/shared/src/SettingInterface.h index 3aad048dbe..2b32e8a3b4 100644 --- a/libraries/shared/src/SettingInterface.h +++ b/libraries/shared/src/SettingInterface.h @@ -12,17 +12,23 @@ #ifndef hifi_SettingInterface_h #define hifi_SettingInterface_h -#include -#include +#include +#include +#include +#include namespace Setting { + class Manager; + void preInit(); void init(); void cleanupSettings(); class Interface { public: - QString getKey() const { return _key; } + static const QString FIRST_RUN; + + const QString& getKey() const { return _key; } bool isSet() const { return _isSet; } virtual void setVariant(const QVariant& variant) = 0; @@ -44,6 +50,8 @@ namespace Setting { const QString _key; friend class Manager; + + QWeakPointer _manager; }; } diff --git a/libraries/shared/src/SettingManager.cpp b/libraries/shared/src/SettingManager.cpp index 7c0051c809..bacec2ee0c 100644 --- a/libraries/shared/src/SettingManager.cpp +++ b/libraries/shared/src/SettingManager.cpp @@ -9,13 +9,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include +#include +#include +#include #include "SettingInterface.h" #include "SettingManager.h" namespace Setting { + Manager::~Manager() { // Cleanup timer stopTimer(); @@ -27,6 +29,10 @@ namespace Setting { // sync will be called in the QSettings destructor } + // Custom deleter does nothing, because we need to shutdown later than the dependency manager + void Manager::customDeleter() { } + + void Manager::registerHandle(Setting::Interface* handle) { QString key = handle->getKey(); withWriteLock([&] { @@ -44,15 +50,29 @@ namespace Setting { } void Manager::loadSetting(Interface* handle) { - handle->setVariant(value(handle->getKey())); + const auto& key = handle->getKey(); + withWriteLock([&] { + QVariant loadedValue; + if (_pendingChanges.contains(key)) { + loadedValue = _pendingChanges[key]; + } else { + loadedValue = value(key); + } + handle->setVariant(loadedValue); + }); } + void Manager::saveSetting(Interface* handle) { + const auto& key = handle->getKey(); + QVariant handleValue = UNSET_VALUE; if (handle->isSet()) { - setValue(handle->getKey(), handle->getVariant()); - } else { - remove(handle->getKey()); + handleValue = handle->getVariant(); } + + withWriteLock([&] { + _pendingChanges[key] = handleValue; + }); } static const int SAVE_INTERVAL_MSEC = 5 * 1000; // 5 sec @@ -74,10 +94,20 @@ namespace Setting { } void Manager::saveAll() { - withReadLock([&] { - for (auto handle : _handles) { - saveSetting(handle); + withWriteLock([&] { + for (auto key : _pendingChanges.keys()) { + auto newValue = _pendingChanges[key]; + auto savedValue = value(key, UNSET_VALUE); + if (newValue == savedValue) { + continue; + } + if (newValue == UNSET_VALUE) { + remove(key); + } else { + setValue(key, newValue); + } } + _pendingChanges.clear(); }); // Restart timer diff --git a/libraries/shared/src/SettingManager.h b/libraries/shared/src/SettingManager.h index e4981f1bce..1f309c966f 100644 --- a/libraries/shared/src/SettingManager.h +++ b/libraries/shared/src/SettingManager.h @@ -12,17 +12,23 @@ #ifndef hifi_SettingManager_h #define hifi_SettingManager_h -#include -#include -#include +#include +#include +#include +#include +#include "DependencyManager.h" #include "shared/ReadWriteLockable.h" namespace Setting { class Interface; - class Manager : public QSettings, public ReadWriteLockable { + class Manager : public QSettings, public ReadWriteLockable, public Dependency { Q_OBJECT + + public: + void customDeleter() override; + protected: ~Manager(); void registerHandle(Interface* handle); @@ -40,6 +46,8 @@ namespace Setting { private: QHash _handles; QPointer _saveTimer = nullptr; + const QVariant UNSET_VALUE { QUuid::createUuid().variant() }; + QHash _pendingChanges; friend class Interface; friend void cleanupPrivateInstance(); diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 0974c88e73..9c1e5c3816 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -23,7 +23,6 @@ void ShapeInfo::clear() { void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) { _type = type; - _points.clear(); switch(type) { case SHAPE_TYPE_NONE: _halfExtents = glm::vec3(0.0f); @@ -52,7 +51,6 @@ void ShapeInfo::setBox(const glm::vec3& halfExtents) { _url = ""; _type = SHAPE_TYPE_BOX; _halfExtents = halfExtents; - _points.clear(); _doubleHashKey.clear(); } @@ -60,15 +58,6 @@ void ShapeInfo::setSphere(float radius) { _url = ""; _type = SHAPE_TYPE_SPHERE; _halfExtents = glm::vec3(radius, radius, radius); - _points.clear(); - _doubleHashKey.clear(); -} - -void ShapeInfo::setEllipsoid(const glm::vec3& halfExtents) { - _url = ""; - _type = SHAPE_TYPE_ELLIPSOID; - _halfExtents = halfExtents; - _points.clear(); _doubleHashKey.clear(); } @@ -82,7 +71,6 @@ void ShapeInfo::setCapsuleY(float radius, float halfHeight) { _url = ""; _type = SHAPE_TYPE_CAPSULE_Y; _halfExtents = glm::vec3(radius, halfHeight, radius); - _points.clear(); _doubleHashKey.clear(); } @@ -146,10 +134,6 @@ bool ShapeInfo::contains(const glm::vec3& point) const { switch(_type) { case SHAPE_TYPE_SPHERE: return glm::length(point) <= _halfExtents.x; - case SHAPE_TYPE_ELLIPSOID: { - glm::vec3 scaledPoint = glm::abs(point) / _halfExtents; - return glm::length(scaledPoint) <= 1.0f; - } case SHAPE_TYPE_CYLINDER_X: return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.z; case SHAPE_TYPE_CYLINDER_Y: diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 1632d22450..c853666d90 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -30,7 +30,6 @@ enum ShapeType { SHAPE_TYPE_NONE, SHAPE_TYPE_BOX, SHAPE_TYPE_SPHERE, - SHAPE_TYPE_ELLIPSOID, SHAPE_TYPE_PLANE, SHAPE_TYPE_COMPOUND, SHAPE_TYPE_CAPSULE_X, @@ -39,7 +38,7 @@ enum ShapeType { SHAPE_TYPE_CYLINDER_X, SHAPE_TYPE_CYLINDER_Y, SHAPE_TYPE_CYLINDER_Z, - SHAPE_TYPE_LINE + SHAPE_TYPE_STATIC_MESH }; class ShapeInfo { @@ -50,7 +49,6 @@ public: void setParams(ShapeType type, const glm::vec3& halfExtents, QString url=""); void setBox(const glm::vec3& halfExtents); void setSphere(float radius); - void setEllipsoid(const glm::vec3& halfExtents); void setConvexHulls(const QVector>& points); void setCapsuleY(float radius, float halfHeight); void setOffset(const glm::vec3& offset); @@ -60,10 +58,10 @@ public: const glm::vec3& getHalfExtents() const { return _halfExtents; } const glm::vec3& getOffset() const { return _offset; } + QVector>& getPoints() { return _points; } const QVector>& getPoints() const { return _points; } uint32_t getNumSubShapes() const; - void clearPoints () { _points.clear(); } void appendToPoints (const QVector& newPoints) { _points << newPoints; } int getMaxNumPoints() const; diff --git a/libraries/shared/src/shared/ReadWriteLockable.h b/libraries/shared/src/shared/ReadWriteLockable.h index 07b46bb92a..539678a73d 100644 --- a/libraries/shared/src/shared/ReadWriteLockable.h +++ b/libraries/shared/src/shared/ReadWriteLockable.h @@ -45,6 +45,8 @@ public: template bool withTryReadLock(F&& f, int timeout) const; + QReadWriteLock& getLock() const { return _lock; } + private: mutable QReadWriteLock _lock { QReadWriteLock::Recursive }; }; diff --git a/scripts/system/controllers/leapHands.js b/scripts/system/controllers/leapHands.js new file mode 100644 index 0000000000..1be0b1e5f6 --- /dev/null +++ b/scripts/system/controllers/leapHands.js @@ -0,0 +1,527 @@ +// +// leapHands.js +// examples +// +// Created by David Rowe on 8 Sep 2014. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example script that uses the Leap Motion to make the avatar's hands replicate the user's hand actions. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var leftTriggerValue = 0; +var rightTriggerValue = 0; + +var LEAP_TRIGGER_START_ANGLE = 15.0; +var LEAP_TRIGGER_END_ANGLE = 40.0; + +function getLeapMotionLeftTrigger() { + //print("left trigger = " + leftTriggerValue); + return leftTriggerValue; +} +function getLeapMotionRightTrigger() { + //print("right trigger = " + rightTriggerValue); + return rightTriggerValue; +} + +var leapHands = (function () { + + var isOnHMD, + LEAP_ON_HMD_MENU_ITEM = "Leap Motion on HMD", + LEAP_OFFSET = 0.019, // Thickness of Leap Motion plus HMD clip + HMD_OFFSET = 0.070, // Eyeballs to front surface of Oculus DK2 TODO: Confirm and make depend on device and eye relief + hasHandAndWristJoints, + handToWristOffset = [], // For avatars without a wrist joint we control an estimate of a proper hand joint position + HAND_OFFSET = 0.4, // Relative distance of wrist to hand versus wrist to index finger knuckle + handAnimationStateHandlers, + handAnimationStateFunctions, + handAnimationStateProperties, + hands, + wrists, + NUM_HANDS = 2, // 0 = left; 1 = right + fingers, + NUM_FINGERS = 5, // 0 = thumb; ...; 4 = pinky + THUMB = 0, + MIDDLE_FINGER = 2, + NUM_FINGER_JOINTS = 3, // 0 = metacarpal(hand)-proximal(finger) joint; ...; 2 = intermediate-distal joint + MAX_HAND_INACTIVE_COUNT = 20, + calibrationStatus, + UNCALIBRATED = 0, + CALIBRATING = 1, + CALIBRATED = 2, + CALIBRATION_TIME = 1000, // milliseconds + avatarScale, + avatarFaceModelURL, + avatarSkeletonModelURL, + settingsTimer, + HMD_CAMERA_TO_AVATAR_ROTATION = [ + Quat.angleAxis(180.0, { x: 0, y: 0, z: 1 }), + Quat.angleAxis(-180.0, { x: 0, y: 0, z: 1 }) + ], + DESKTOP_CAMERA_TO_AVATAR_ROTATION = + Quat.multiply(Quat.angleAxis(180.0, { x: 0, y: 1, z: 0 }), Quat.angleAxis(90.0, { x: 0, y: 0, z: 1 })), + LEAP_THUMB_ROOT_ADJUST = [Quat.fromPitchYawRollDegrees(0, 0, 20), Quat.fromPitchYawRollDegrees(0, 0, -20)]; + + function printSkeletonJointNames() { + var jointNames, + i; + + print(MyAvatar.skeletonModelURL); + + print("Skeleton joint names ..."); + jointNames = MyAvatar.getJointNames(); + for (i = 0; i < jointNames.length; i += 1) { + print(i + ": " + jointNames[i]); + } + print("... skeleton joint names"); + } + + function animateLeftHand() { + var ROTATION_AND_POSITION = 0; + + return { + leftHandType: ROTATION_AND_POSITION, + leftHandPosition: hands[0].position, + leftHandRotation: hands[0].rotation + }; + } + + function animateRightHand() { + var ROTATION_AND_POSITION = 0; + + return { + rightHandType: ROTATION_AND_POSITION, + rightHandPosition: hands[1].position, + rightHandRotation: hands[1].rotation + }; + } + + function finishCalibration() { + var avatarPosition, + handPosition, + middleFingerPosition, + leapHandHeight, + h; + + if (!isOnHMD) { + if (hands[0].controller.isActive() && hands[1].controller.isActive()) { + leapHandHeight = (hands[0].controller.getAbsTranslation().y + hands[1].controller.getAbsTranslation().y) / 2.0; + } else { + calibrationStatus = UNCALIBRATED; + return; + } + } + + avatarPosition = MyAvatar.position; + + for (h = 0; h < NUM_HANDS; h += 1) { + handPosition = MyAvatar.getJointPosition(hands[h].jointName); + if (!hasHandAndWristJoints) { + middleFingerPosition = MyAvatar.getJointPosition(fingers[h][MIDDLE_FINGER][0].jointName); + handToWristOffset[h] = Vec3.multiply(Vec3.subtract(handPosition, middleFingerPosition), 1.0 - HAND_OFFSET); + } + + if (isOnHMD) { + // Offset of Leap Motion origin from physical eye position + hands[h].zeroPosition = { x: 0.0, y: 0.0, z: HMD_OFFSET + LEAP_OFFSET }; + } else { + hands[h].zeroPosition = { + x: handPosition.x - avatarPosition.x, + y: handPosition.y - avatarPosition.y, + z: avatarPosition.z - handPosition.z + }; + hands[h].zeroPosition = Vec3.multiplyQbyV(MyAvatar.orientation, hands[h].zeroPosition); + hands[h].zeroPosition.y = hands[h].zeroPosition.y - leapHandHeight; + } + } + + MyAvatar.clearJointData("LeftHand"); + MyAvatar.clearJointData("LeftForeArm"); + MyAvatar.clearJointData("RightHand"); + MyAvatar.clearJointData("RightForeArm"); + + calibrationStatus = CALIBRATED; + print("Leap Motion: Calibrated"); + } + + function calibrate() { + var jointNames, + i; + + calibrationStatus = CALIBRATING; + + avatarScale = MyAvatar.scale; + avatarFaceModelURL = MyAvatar.faceModelURL; + avatarSkeletonModelURL = MyAvatar.skeletonModelURL; + + // Does this skeleton have both wrist and hand joints? + hasHandAndWristJoints = false; + jointNames = MyAvatar.getJointNames(); + for (i = 0; i < jointNames.length; i += 1) { + hasHandAndWristJoints = hasHandAndWristJoints || jointNames[i].toLowerCase() === "leftwrist"; + } + + // Set avatar arms vertical, forearms horizontal, as "zero" position for calibration + MyAvatar.setJointRotation("LeftForeArm", Quat.fromPitchYawRollDegrees(0.0, 0.0, 90.0)); + MyAvatar.setJointRotation("LeftHand", Quat.fromPitchYawRollDegrees(0.0, 90.0, 0.0)); + MyAvatar.setJointRotation("RightForeArm", Quat.fromPitchYawRollDegrees(0.0, 0.0, -90.0)); + MyAvatar.setJointRotation("RightHand", Quat.fromPitchYawRollDegrees(0.0, -90.0, 0.0)); + + // Wait for arms to assume their positions before calculating + Script.setTimeout(finishCalibration, CALIBRATION_TIME); + } + + function checkCalibration() { + + if (calibrationStatus === CALIBRATED) { + return true; + } + + if (calibrationStatus !== CALIBRATING) { + calibrate(); + } + + return false; + } + + function setIsOnHMD() { + isOnHMD = Menu.isOptionChecked(LEAP_ON_HMD_MENU_ITEM); + print("Leap Motion: " + (isOnHMD ? "Is on HMD" : "Is on desk")); + } + + function checkSettings() { + if (calibrationStatus > UNCALIBRATED && (MyAvatar.scale !== avatarScale + || MyAvatar.faceModelURL !== avatarFaceModelURL + || MyAvatar.skeletonModelURL !== avatarSkeletonModelURL + || Menu.isOptionChecked(LEAP_ON_HMD_MENU_ITEM) !== isOnHMD)) { + print("Leap Motion: Recalibrate..."); + calibrationStatus = UNCALIBRATED; + + setIsOnHMD(); + } + } + + function setUp() { + + wrists = [ + { + jointName: "LeftWrist", + controller: Controller.createInputController("Spatial", "joint_L_wrist") + }, + { + jointName: "RightWrist", + controller: Controller.createInputController("Spatial", "joint_R_wrist") + } + ]; + + hands = [ + { + jointName: "LeftHand", + controller: Controller.createInputController("Spatial", "joint_L_hand"), + inactiveCount: 0 + }, + { + jointName: "RightHand", + controller: Controller.createInputController("Spatial", "joint_R_hand"), + inactiveCount: 0 + } + ]; + + // The Leap controller's first joint is the hand-metacarpal joint but this joint's data is not used because it's too + // dependent on the model skeleton exactly matching the Leap skeleton; using just the second and subsequent joints + // seems to work better over all. + fingers = [{}, {}]; + fingers[0] = [ + [ + { jointName: "LeftHandThumb1", controller: Controller.createInputController("Spatial", "joint_L_thumb2") }, + { jointName: "LeftHandThumb2", controller: Controller.createInputController("Spatial", "joint_L_thumb3") }, + { jointName: "LeftHandThumb3", controller: Controller.createInputController("Spatial", "joint_L_thumb4") } + ], + [ + { jointName: "LeftHandIndex1", controller: Controller.createInputController("Spatial", "joint_L_index2") }, + { jointName: "LeftHandIndex2", controller: Controller.createInputController("Spatial", "joint_L_index3") }, + { jointName: "LeftHandIndex3", controller: Controller.createInputController("Spatial", "joint_L_index4") } + ], + [ + { jointName: "LeftHandMiddle1", controller: Controller.createInputController("Spatial", "joint_L_middle2") }, + { jointName: "LeftHandMiddle2", controller: Controller.createInputController("Spatial", "joint_L_middle3") }, + { jointName: "LeftHandMiddle3", controller: Controller.createInputController("Spatial", "joint_L_middle4") } + ], + [ + { jointName: "LeftHandRing1", controller: Controller.createInputController("Spatial", "joint_L_ring2") }, + { jointName: "LeftHandRing2", controller: Controller.createInputController("Spatial", "joint_L_ring3") }, + { jointName: "LeftHandRing3", controller: Controller.createInputController("Spatial", "joint_L_ring4") } + ], + [ + { jointName: "LeftHandPinky1", controller: Controller.createInputController("Spatial", "joint_L_pinky2") }, + { jointName: "LeftHandPinky2", controller: Controller.createInputController("Spatial", "joint_L_pinky3") }, + { jointName: "LeftHandPinky3", controller: Controller.createInputController("Spatial", "joint_L_pinky4") } + ] + ]; + fingers[1] = [ + [ + { jointName: "RightHandThumb1", controller: Controller.createInputController("Spatial", "joint_R_thumb2") }, + { jointName: "RightHandThumb2", controller: Controller.createInputController("Spatial", "joint_R_thumb3") }, + { jointName: "RightHandThumb3", controller: Controller.createInputController("Spatial", "joint_R_thumb4") } + ], + [ + { jointName: "RightHandIndex1", controller: Controller.createInputController("Spatial", "joint_R_index2") }, + { jointName: "RightHandIndex2", controller: Controller.createInputController("Spatial", "joint_R_index3") }, + { jointName: "RightHandIndex3", controller: Controller.createInputController("Spatial", "joint_R_index4") } + ], + [ + { jointName: "RightHandMiddle1", controller: Controller.createInputController("Spatial", "joint_R_middle2") }, + { jointName: "RightHandMiddle2", controller: Controller.createInputController("Spatial", "joint_R_middle3") }, + { jointName: "RightHandMiddle3", controller: Controller.createInputController("Spatial", "joint_R_middle4") } + ], + [ + { jointName: "RightHandRing1", controller: Controller.createInputController("Spatial", "joint_R_ring2") }, + { jointName: "RightHandRing2", controller: Controller.createInputController("Spatial", "joint_R_ring3") }, + { jointName: "RightHandRing3", controller: Controller.createInputController("Spatial", "joint_R_ring4") } + ], + [ + { jointName: "RightHandPinky1", controller: Controller.createInputController("Spatial", "joint_R_pinky2") }, + { jointName: "RightHandPinky2", controller: Controller.createInputController("Spatial", "joint_R_pinky3") }, + { jointName: "RightHandPinky3", controller: Controller.createInputController("Spatial", "joint_R_pinky4") } + ] + ]; + + handAnimationStateHandlers = [null, null]; + handAnimationStateFunctions = [animateLeftHand, animateRightHand]; + handAnimationStateProperties = [ + ["leftHandType", "leftHandPosition", "leftHandRotation"], + ["rightHandType", "rightHandPosition", "rightHandPosition"] + ]; + + setIsOnHMD(); + + settingsTimer = Script.setInterval(checkSettings, 2000); + + calibrationStatus = UNCALIBRATED; + + { + var mapping = Controller.newMapping("LeapmotionTrigger"); + mapping.from(getLeapMotionLeftTrigger).to(Controller.Standard.LT); + mapping.from(getLeapMotionRightTrigger).to(Controller.Standard.RT); + mapping.enable(); + } + } + + function moveHands() { + var h, + i, + j, + side, + handOffset, + wristOffset, + handRotation, + locRotation, + cameraOrientation, + inverseAvatarOrientation; + + for (h = 0; h < NUM_HANDS; h += 1) { + side = h === 0 ? -1.0 : 1.0; + + if (hands[h].controller.isActive()) { + + // Calibrate if necessary. + if (!checkCalibration()) { + return; + } + + // Hand animation handlers ... + if (handAnimationStateHandlers[h] === null) { + handAnimationStateHandlers[h] = MyAvatar.addAnimationStateHandler(handAnimationStateFunctions[h], + handAnimationStateProperties[h]); + } + + // Hand position ... + handOffset = hands[h].controller.getAbsTranslation(); + handRotation = hands[h].controller.getAbsRotation(); + + if (isOnHMD) { + + // Adjust to control wrist position if "hand" joint is at wrist ... + if (!hasHandAndWristJoints) { + wristOffset = Vec3.multiplyQbyV(handRotation, handToWristOffset[h]); + handOffset = Vec3.sum(handOffset, wristOffset); + } + + // Hand offset in camera coordinates ... + handOffset = { + x: -handOffset.x, + y: -handOffset.z, + z: -handOffset.y - hands[h].zeroPosition.z + }; + + // Hand offset in world coordinates ... + cameraOrientation = Camera.getOrientation(); + handOffset = Vec3.sum(Camera.getPosition(), Vec3.multiplyQbyV(cameraOrientation, handOffset)); + + // Hand offset in avatar coordinates ... + inverseAvatarOrientation = Quat.inverse(MyAvatar.orientation); + handOffset = Vec3.subtract(handOffset, MyAvatar.position); + handOffset = Vec3.multiplyQbyV(inverseAvatarOrientation, handOffset); + handOffset.z = -handOffset.z; + handOffset.x = -handOffset.x; + + + // Hand rotation in camera coordinates ... + handRotation = { + x: -handRotation.y, + y: -handRotation.z, + z: -handRotation.x, + w: handRotation.w + }; + + // Hand rotation in avatar coordinates ... + handRotation = Quat.multiply(HMD_CAMERA_TO_AVATAR_ROTATION[h], handRotation); + cameraOrientation = { + x: cameraOrientation.z, + y: cameraOrientation.y, + z: cameraOrientation.x, + w: cameraOrientation.w + }; + cameraOrientation = Quat.multiply(cameraOrientation, Quat.inverse(MyAvatar.orientation)); + handRotation = Quat.multiply(handRotation, cameraOrientation); // Works!!! + + } else { + + // Adjust to control wrist position if "hand" joint is at wrist ... + if (!hasHandAndWristJoints) { + wristOffset = Vec3.multiplyQbyV(handRotation, handToWristOffset[h]); + handOffset = Vec3.sum(handOffset, wristOffset); + } + + // Hand offset in camera coordinates ... + handOffset = { + x: -handOffset.x, + y: hands[h].zeroPosition.y + handOffset.y, + z: hands[h].zeroPosition.z - handOffset.z + }; + + // Hand rotation in camera coordinates ... + handRotation = { + x: handRotation.z, + y: handRotation.y, + z: handRotation.x, + w: handRotation.w + }; + + // Hand rotation in avatar coordinates ... + handRotation = Quat.multiply(DESKTOP_CAMERA_TO_AVATAR_ROTATION, handRotation); + } + + // Set hand position and orientation for animation state handler ... + hands[h].position = handOffset; + hands[h].rotation = handRotation; + + // Set finger joints ... + var summed = 0; + var closeAngle = 0; + for (i = 0; i < NUM_FINGERS; i += 1) { + for (j = 0; j < NUM_FINGER_JOINTS; j += 1) { + if (fingers[h][i][j].controller !== null) { + locRotation = fingers[h][i][j].controller.getLocRotation(); + var eulers = Quat.safeEulerAngles(locRotation); + closeAngle += eulers.x; + + summed++; + + if (i === THUMB) { + locRotation = { + x: side * locRotation.y, + y: side * -locRotation.z, + z: side * -locRotation.x, + w: locRotation.w + }; + if (j === 0) { + // Adjust avatar thumb root joint rotation to make avatar hands look better + locRotation = Quat.multiply(LEAP_THUMB_ROOT_ADJUST[h], locRotation); + } + } else { + locRotation = { + x: -locRotation.x, + y: -locRotation.z, + z: -locRotation.y, + w: locRotation.w + }; + } + MyAvatar.setJointRotation(fingers[h][i][j].jointName, locRotation); + } + } + } + + hands[h].inactiveCount = 0; + if (summed > 0) { + closeAngle /= summed; + } + + var triggerValue = (-closeAngle - LEAP_TRIGGER_START_ANGLE) / (LEAP_TRIGGER_END_ANGLE - LEAP_TRIGGER_START_ANGLE); + triggerValue = Math.max(0.0, Math.min(triggerValue, 1.0)); + + if (h == 0) { + leftTriggerValue = triggerValue; + } else { + rightTriggerValue = triggerValue; + + } + + } else { + + if (hands[h].inactiveCount < MAX_HAND_INACTIVE_COUNT) { + + hands[h].inactiveCount += 1; + + if (hands[h].inactiveCount === MAX_HAND_INACTIVE_COUNT) { + if (handAnimationStateHandlers[h] !== null) { + MyAvatar.removeAnimationStateHandler(handAnimationStateHandlers[h]); + handAnimationStateHandlers[h] = null; + leftTriggerValue = 0.0; + rightTriggerValue = 0.0; + } + } + } + } + } + } + + function tearDown() { + var h, + i, + j; + + Script.clearInterval(settingsTimer); + + for (h = 0; h < NUM_HANDS; h += 1) { + Controller.releaseInputController(hands[h].controller); + Controller.releaseInputController(wrists[h].controller); + if (handAnimationStateHandlers[h] !== null) { + MyAvatar.removeAnimationStateHandler(handAnimationStateHandlers[h]); + } + for (i = 0; i < NUM_FINGERS; i += 1) { + for (j = 0; j < NUM_FINGER_JOINTS; j += 1) { + if (fingers[h][i][j].controller !== null) { + Controller.releaseInputController(fingers[h][i][j].controller); + } + } + } + } + } + + return { + printSkeletonJointNames: printSkeletonJointNames, + setUp : setUp, + moveHands : moveHands, + tearDown : tearDown + }; +}()); + + +//leapHands.printSkeletonJointNames(); + +leapHands.setUp(); +Script.update.connect(leapHands.moveHands); +Script.scriptEnding.connect(leapHands.tearDown); diff --git a/scripts/system/libraries/toolBars.js b/scripts/system/libraries/toolBars.js index 9efe533457..5a84bf9027 100644 --- a/scripts/system/libraries/toolBars.js +++ b/scripts/system/libraries/toolBars.js @@ -256,7 +256,6 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit y: y - ToolBar.SPACING }); } - this.save(); } this.setAlpha = function(alpha, tool) { @@ -421,6 +420,9 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit for (var tool in that.tools) { that.tools[tool].buttonDown(false); } + if (that.mightBeDragging) { + that.save(); + } } this.mouseMove = function (event) { if (!that.mightBeDragging || !event.isLeftButton) {