Merge branch 'master' of github.com:highfidelity/hifi into permissions-grid

This commit is contained in:
Seth Alves 2016-06-05 08:27:59 -07:00
commit 880c4f18b5
22 changed files with 905 additions and 169 deletions

View file

@ -2,8 +2,8 @@
* [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 3.3.2 * [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 3.3.2
* [Qt](http://www.qt.io/download-open-source) ~> 5.5.1 * [Qt](http://www.qt.io/download-open-source) ~> 5.5.1
* [OpenSSL](https://www.openssl.org/community/binaries.html) ~> 1.0.1 (highest letter) * [OpenSSL](https://www.openssl.org/community/binaries.html) ~> 1.0.1m
* 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. * IMPORTANT: Using the recommended version of OpenSSL is critical to avoid security vulnerabilities.
* [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) * [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
####CMake External Project Dependencies ####CMake External Project Dependencies

View file

@ -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 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): 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) * Win32 OpenSSL v1.0.1q
* Win64 OpenSSL v1.0.1 (highest letter) * 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. 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.

View file

@ -739,7 +739,7 @@ void MyAvatar::saveData() {
settings.endGroup(); 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(); float value = settings.value(name, defaultValue).toFloat();
if (glm::isnan(value)) { if (glm::isnan(value)) {
value = defaultValue; value = defaultValue;

View file

@ -25,6 +25,8 @@
#include "AudioRingBuffer.h" #include "AudioRingBuffer.h"
#include "AudioLogging.h" #include "AudioLogging.h"
#include "AudioSRC.h"
#include "Sound.h" #include "Sound.h"
QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in) { 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 // we want to convert it to the format that the audio-mixer wants
// which is signed, 16-bit, 24Khz // 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){ // resize to max possible output
// in the unlikely case that we have stereo audio but we seem to be missing a sample int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample));
// (the sample for one channel is missing in a set of interleaved samples) int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames);
// then drop the odd sample int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
--numSourceSamples; _byteArray.resize(maxDestinationBytes);
}
int numDestinationSamples = numSourceSamples / 2.0f; int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(),
(int16_t*)_byteArray.data(),
if (_isStereo && numDestinationSamples % 2 != 0) { numSourceFrames);
// 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);
// truncate to actual output
int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
_byteArray.resize(numDestinationBytes); _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);
}
}
}
} }
// //

View file

@ -599,11 +599,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
ShapeType type = getShapeType(); ShapeType type = getShapeType();
if (type != SHAPE_TYPE_COMPOUND) { if (type == SHAPE_TYPE_COMPOUND) {
ModelEntityItem::computeShapeInfo(info);
info.setParams(type, 0.5f * getDimensions());
adjustShapeInfoByRegistration(info);
} else {
updateModelBounds(); updateModelBounds();
// should never fall in here when collision model not fully loaded // 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& renderGeometry = _model->getFBXGeometry();
const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry(); const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry();
_points.clear(); QVector<QVector<glm::vec3>>& points = info.getPoints();
unsigned int i = 0; 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 // 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. // 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) { foreach (const FBXMesh& mesh, collisionGeometry.meshes) {
// each meshPart is a convex hull // each meshPart is a convex hull
foreach (const FBXMeshPart &meshPart, mesh.parts) { foreach (const FBXMeshPart &meshPart, mesh.parts) {
QVector<glm::vec3> pointsInPart; points.push_back(QVector<glm::vec3>());
QVector<glm::vec3>& pointsInPart = points[i];
// run through all the triangles and (uniquely) add each point to the hull // run through all the triangles and (uniquely) add each point to the hull
unsigned int triangleCount = meshPart.triangleIndices.size() / 3; uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size();
for (unsigned int j = 0; j < triangleCount; j++) { assert(numIndices % TRIANGLE_STRIDE == 0);
unsigned int p0Index = meshPart.triangleIndices[j*3]; for (uint32_t j = 0; j < numIndices; j += TRIANGLE_STRIDE) {
unsigned int p1Index = meshPart.triangleIndices[j*3+1]; glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]];
unsigned int p2Index = meshPart.triangleIndices[j*3+2]; glm::vec3 p1 = mesh.vertices[meshPart.triangleIndices[j + 1]];
glm::vec3 p0 = mesh.vertices[p0Index]; glm::vec3 p2 = mesh.vertices[meshPart.triangleIndices[j + 2]];
glm::vec3 p1 = mesh.vertices[p1Index];
glm::vec3 p2 = mesh.vertices[p2Index];
if (!pointsInPart.contains(p0)) { if (!pointsInPart.contains(p0)) {
pointsInPart << 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 // run through all the quads and (uniquely) add each point to the hull
unsigned int quadCount = meshPart.quadIndices.size() / 4; numIndices = (uint32_t)meshPart.quadIndices.size();
assert((unsigned int)meshPart.quadIndices.size() == quadCount*4); assert(numIndices % QUAD_STRIDE == 0);
for (unsigned int j = 0; j < quadCount; j++) { for (uint32_t j = 0; j < numIndices; j += QUAD_STRIDE) {
unsigned int p0Index = meshPart.quadIndices[j*4]; glm::vec3 p0 = mesh.vertices[meshPart.quadIndices[j]];
unsigned int p1Index = meshPart.quadIndices[j*4+1]; glm::vec3 p1 = mesh.vertices[meshPart.quadIndices[j + 1]];
unsigned int p2Index = meshPart.quadIndices[j*4+2]; glm::vec3 p2 = mesh.vertices[meshPart.quadIndices[j + 2]];
unsigned int p3Index = meshPart.quadIndices[j*4+3]; glm::vec3 p3 = mesh.vertices[meshPart.quadIndices[j + 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];
if (!pointsInPart.contains(p0)) { if (!pointsInPart.contains(p0)) {
pointsInPart << p0; pointsInPart << p0;
} }
@ -670,14 +664,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
if (pointsInPart.size() == 0) { if (pointsInPart.size() == 0) {
qCDebug(entitiesrenderer) << "Warning -- meshPart has no faces"; qCDebug(entitiesrenderer) << "Warning -- meshPart has no faces";
points.pop_back();
continue; continue;
} }
++i;
// add next convex hull
QVector<glm::vec3> newMeshPoints;
_points << newMeshPoints;
// add points to the new convex hull
_points[i++] << pointsInPart;
} }
} }
@ -691,23 +681,26 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
// multiply each point by scale before handing the point-set off to the physics engine. // multiply each point by scale before handing the point-set off to the physics engine.
// also determine the extents of the collision model. // also determine the extents of the collision model.
AABox box; AABox box;
for (int i = 0; i < _points.size(); i++) { for (int i = 0; i < points.size(); i++) {
for (int j = 0; j < _points[i].size(); j++) { for (int j = 0; j < points[i].size(); j++) {
// compensate for registration // compensate for registration
_points[i][j] += _model->getOffset(); points[i][j] += _model->getOffset();
// scale so the collision points match the model points // 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 // this next subtraction is done so we can give info the offset, which will cause
// the shape-key to change. // the shape-key to change.
_points[i][j] -= _model->getOffset(); points[i][j] -= _model->getOffset();
box += _points[i][j]; box += points[i][j];
} }
} }
glm::vec3 collisionModelDimensions = box.getDimensions(); glm::vec3 collisionModelDimensions = box.getDimensions();
info.setParams(type, collisionModelDimensions, _compoundShapeURL); info.setParams(type, collisionModelDimensions, _compoundShapeURL);
info.setConvexHulls(_points);
info.setOffset(_model->getOffset()); info.setOffset(_model->getOffset());
} else {
ModelEntityItem::computeShapeInfo(info);
info.setParams(type, 0.5f * getDimensions());
adjustShapeInfoByRegistration(info);
} }
} }

View file

@ -103,7 +103,6 @@ private:
QVariantMap _currentTextures; QVariantMap _currentTextures;
QVariantMap _originalTextures; QVariantMap _originalTextures;
bool _originalTexturesRead = false; bool _originalTexturesRead = false;
QVector<QVector<glm::vec3>> _points;
bool _dimensionsInitialized = true; bool _dimensionsInitialized = true;
AnimationPropertyGroup _renderAnimationProperties; AnimationPropertyGroup _renderAnimationProperties;

View file

@ -88,7 +88,7 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) {
_lastEdited = usecTime > _created ? usecTime : _created; _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"}; "capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z"};
QHash<QString, ShapeType> stringToShapeTypeLookup; QHash<QString, ShapeType> stringToShapeTypeLookup;
@ -101,7 +101,6 @@ void buildStringToShapeTypeLookup() {
addShapeType(SHAPE_TYPE_NONE); addShapeType(SHAPE_TYPE_NONE);
addShapeType(SHAPE_TYPE_BOX); addShapeType(SHAPE_TYPE_BOX);
addShapeType(SHAPE_TYPE_SPHERE); addShapeType(SHAPE_TYPE_SPHERE);
addShapeType(SHAPE_TYPE_ELLIPSOID);
addShapeType(SHAPE_TYPE_PLANE); addShapeType(SHAPE_TYPE_PLANE);
addShapeType(SHAPE_TYPE_COMPOUND); addShapeType(SHAPE_TYPE_COMPOUND);
addShapeType(SHAPE_TYPE_CAPSULE_X); addShapeType(SHAPE_TYPE_CAPSULE_X);

View file

@ -60,7 +60,7 @@ class LineEntityItem : public EntityItem {
const QVector<glm::vec3>& getLinePoints() const{ return _points; } const QVector<glm::vec3>& 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. // never have a ray intersection pick a LineEntityItem.
virtual bool supportsDetailedRayIntersection() const { return true; } virtual bool supportsDetailedRayIntersection() const { return true; }

View file

@ -78,7 +78,7 @@ class PolyLineEntityItem : public EntityItem {
virtual bool needsToCallUpdate() const { return true; } 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. // never have a ray intersection pick a PolyLineEntityItem.
virtual bool supportsDetailedRayIntersection() const { return true; } virtual bool supportsDetailedRayIntersection() const { return true; }

View file

@ -17,6 +17,54 @@
#include "ShapeFactory.h" #include "ShapeFactory.h"
#include "BulletUtil.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<glm::vec3>& points) { btConvexHullShape* ShapeFactory::createConvexHull(const QVector<glm::vec3>& points) {
@ -66,15 +114,40 @@ btConvexHullShape* ShapeFactory::createConvexHull(const QVector<glm::vec3>& poin
hull->addPoint(btVector3(correctedPoint[0], correctedPoint[1], correctedPoint[2]), false); hull->addPoint(btVector3(correctedPoint[0], correctedPoint[1], correctedPoint[2]), false);
} }
if (points.size() > MAX_HULL_POINTS) { uint32_t numPoints = (uint32_t)hull->getNumPoints();
// create hull approximation if (numPoints > MAX_HULL_POINTS) {
btShapeHull shapeHull(hull); // we have too many points, so we compute point projections along canonical unit vectors
shapeHull.buildHull(margin); // and keep the those that project the farthest
btVector3 btCenter = glmToBullet(center);
btVector3* shapePoints = hull->getUnscaledPoints();
std::vector<uint32_t> 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... // we cannot copy Bullet shapes so we must create a new one...
btConvexHullShape* newHull = new btConvexHullShape(); btConvexHullShape* newHull = new btConvexHullShape();
const btVector3* newPoints = shapeHull.getVertexPointer(); for (uint32_t i = 0; i < finalIndices.size(); ++i) {
for (int i = 0; i < shapeHull.numVertices(); ++i) { newHull->addPoint(shapePoints[finalIndices[i]], false);
newHull->addPoint(newPoints[i], false);
} }
// ...and delete the old one // ...and delete the old one
delete hull; delete hull;

View file

@ -35,7 +35,46 @@ int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3> >();
float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f;
#define HTTP_INVALID_COM "http://invalid.com" #define HTTP_INVALID_COM "http://invalid.com"
model::MaterialPointer Model::_collisionHullMaterial; const int NUM_COLLISION_HULL_COLORS = 24;
std::vector<model::MaterialPointer> _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<model::Material>();
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) : Model::Model(RigPointer rig, QObject* parent) :
QObject(parent), QObject(parent),
@ -1217,13 +1256,10 @@ void Model::segregateMeshGroups() {
int totalParts = mesh.parts.size(); int totalParts = mesh.parts.size();
for (int partIndex = 0; partIndex < totalParts; partIndex++) { for (int partIndex = 0; partIndex < totalParts; partIndex++) {
if (showingCollisionHull) { if (showingCollisionHull) {
if (!_collisionHullMaterial) { if (_collisionHullMaterials.empty()) {
_collisionHullMaterial = std::make_shared<model::Material>(); initCollisionHullMaterials();
_collisionHullMaterial->setAlbedo(glm::vec3(1.0f, 0.5f, 0.0f));
_collisionHullMaterial->setMetallic(0.02f);
_collisionHullMaterial->setRoughness(0.5f);
} }
_collisionRenderItemsSet << std::make_shared<MeshPartPayload>(networkMesh, partIndex, _collisionHullMaterial, transform, offset); _collisionRenderItemsSet << std::make_shared<MeshPartPayload>(networkMesh, partIndex, _collisionHullMaterials[partIndex % NUM_COLLISION_HULL_COLORS], transform, offset);
} else { } else {
_modelMeshRenderItemsSet << std::make_shared<ModelMeshPartPayload>(this, i, partIndex, shapeID, transform, offset); _modelMeshRenderItemsSet << std::make_shared<ModelMeshPartPayload>(this, i, partIndex, shapeID, transform, offset);
} }

View file

@ -10,11 +10,76 @@
// //
#include "SettingHandle.h" #include "SettingHandle.h"
#include "SettingManager.h"
#include <math.h> #include <math.h>
const QString Settings::firstRun { "firstRun" }; const QString Settings::firstRun { "firstRun" };
Settings::Settings() :
_manager(DependencyManager::get<Setting::Manager>()),
_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) { void Settings::getFloatValueIfValid(const QString& name, float& floatValue) {
const QVariant badDefaultValue = NAN; const QVariant badDefaultValue = NAN;
bool ok = true; bool ok = true;

View file

@ -14,19 +14,40 @@
#include <type_traits> #include <type_traits>
#include <QSettings> #include <QtCore/QSettings>
#include <QString> #include <QtCore/QStack>
#include <QVariant> #include <QtCore/QString>
#include <QtCore/QVariant>
#include <QtCore/QReadWriteLock>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp> #include <glm/gtc/quaternion.hpp>
#include "SettingInterface.h" #include "SettingInterface.h"
// TODO: remove // TODO: remove
class Settings : public QSettings { class Settings {
public: public:
static const QString firstRun; 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 getFloatValueIfValid(const QString& name, float& floatValue);
void getBoolValue(const QString& name, bool& boolValue); void getBoolValue(const QString& name, bool& boolValue);
@ -36,6 +57,9 @@ public:
void setQuatValue(const QString& name, const glm::quat& quatValue); void setQuatValue(const QString& name, const glm::quat& quatValue);
void getQuatValueIfValid(const QString& name, glm::quat& quatValue); void getQuatValueIfValid(const QString& name, glm::quat& quatValue);
QSharedPointer<Setting::Manager> _manager;
QWriteLocker _locker;
}; };
namespace Setting { namespace Setting {

View file

@ -9,27 +9,33 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include "SettingInterface.h"
#include <QCoreApplication> #include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include <QFile> #include <QFile>
#include <QThread> #include <QThread>
#include "PathUtils.h" #include "PathUtils.h"
#include "SettingInterface.h"
#include "SettingManager.h" #include "SettingManager.h"
#include "SharedLogging.h" #include "SharedLogging.h"
namespace Setting { namespace Setting {
static Manager* privateInstance = nullptr; static QSharedPointer<Manager> globalManager;
const QString Interface::FIRST_RUN { "firstRun" };
// cleans up the settings private instance. Should only be run once at closing down. // cleans up the settings private instance. Should only be run once at closing down.
void cleanupPrivateInstance() { void cleanupPrivateInstance() {
// grab the thread before we nuke the instance // grab the thread before we nuke the instance
QThread* settingsManagerThread = privateInstance->thread(); QThread* settingsManagerThread = DependencyManager::get<Manager>()->thread();
// tell the private instance to clean itself up on its thread // tell the private instance to clean itself up on its thread
privateInstance->deleteLater(); DependencyManager::destroy<Manager>();
privateInstance = NULL;
//
globalManager->deleteLater();
globalManager.reset();
// quit the settings manager thread and wait on it to make sure it's gone // quit the settings manager thread and wait on it to make sure it's gone
settingsManagerThread->quit(); settingsManagerThread->quit();
@ -64,13 +70,12 @@ namespace Setting {
Q_CHECK_PTR(thread); Q_CHECK_PTR(thread);
thread->setObjectName("Settings Thread"); thread->setObjectName("Settings Thread");
privateInstance = new Manager(); globalManager = DependencyManager::set<Manager>();
Q_CHECK_PTR(privateInstance);
QObject::connect(privateInstance, SIGNAL(destroyed()), thread, SLOT(quit())); QObject::connect(globalManager.data(), SIGNAL(destroyed()), thread, SLOT(quit()));
QObject::connect(thread, SIGNAL(started()), privateInstance, SLOT(startTimer())); QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer()));
QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
privateInstance->moveToThread(thread); globalManager->moveToThread(thread);
thread->start(); thread->start();
qCDebug(shared) << "Settings thread started."; qCDebug(shared) << "Settings thread started.";
@ -79,7 +84,7 @@ namespace Setting {
} }
void Interface::init() { void Interface::init() {
if (!privateInstance) { if (!DependencyManager::isSet<Manager>()) {
// WARNING: As long as we are using QSettings this should always be triggered for each Setting::Handle // 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 // 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) // 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." << qWarning() << "Setting::Interface::init() for key" << _key << "- Manager not yet created." <<
"Settings persistence disabled."; "Settings persistence disabled.";
} else { } else {
// Register Handle _manager = DependencyManager::get<Manager>();
privateInstance->registerHandle(this); auto manager = _manager.lock();
_isInitialized = true; if (manager) {
// Register Handle
manager->registerHandle(this);
_isInitialized = true;
}
// Load value from disk // Load value from disk
load(); load();
@ -97,11 +106,13 @@ namespace Setting {
} }
void Interface::deinit() { void Interface::deinit() {
if (_isInitialized && privateInstance) { if (_isInitialized && _manager) {
// Save value to disk auto manager = _manager.lock();
save(); if (manager) {
// Save value to disk
privateInstance->removeHandle(_key); save();
manager->removeHandle(_key);
}
} }
} }
@ -113,14 +124,16 @@ namespace Setting {
} }
void Interface::save() { void Interface::save() {
if (privateInstance) { auto manager = _manager.lock();
privateInstance->saveSetting(this); if (manager) {
manager->saveSetting(this);
} }
} }
void Interface::load() { void Interface::load() {
if (privateInstance) { auto manager = _manager.lock();
privateInstance->loadSetting(this); if (manager) {
manager->loadSetting(this);
} }
} }
} }

View file

@ -12,17 +12,23 @@
#ifndef hifi_SettingInterface_h #ifndef hifi_SettingInterface_h
#define hifi_SettingInterface_h #define hifi_SettingInterface_h
#include <QString> #include <memory>
#include <QVariant> #include <QtCore/QWeakPointer>
#include <QtCore/QString>
#include <QtCore/QVariant>
namespace Setting { namespace Setting {
class Manager;
void preInit(); void preInit();
void init(); void init();
void cleanupSettings(); void cleanupSettings();
class Interface { class Interface {
public: public:
QString getKey() const { return _key; } static const QString FIRST_RUN;
const QString& getKey() const { return _key; }
bool isSet() const { return _isSet; } bool isSet() const { return _isSet; }
virtual void setVariant(const QVariant& variant) = 0; virtual void setVariant(const QVariant& variant) = 0;
@ -44,6 +50,8 @@ namespace Setting {
const QString _key; const QString _key;
friend class Manager; friend class Manager;
QWeakPointer<Manager> _manager;
}; };
} }

View file

@ -9,13 +9,15 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include <QThread> #include <QtCore/QThread>
#include <QDebug> #include <QtCore/QDebug>
#include <QtCore/QUuid>
#include "SettingInterface.h" #include "SettingInterface.h"
#include "SettingManager.h" #include "SettingManager.h"
namespace Setting { namespace Setting {
Manager::~Manager() { Manager::~Manager() {
// Cleanup timer // Cleanup timer
stopTimer(); stopTimer();
@ -27,6 +29,10 @@ namespace Setting {
// sync will be called in the QSettings destructor // 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) { void Manager::registerHandle(Setting::Interface* handle) {
QString key = handle->getKey(); QString key = handle->getKey();
withWriteLock([&] { withWriteLock([&] {
@ -44,15 +50,29 @@ namespace Setting {
} }
void Manager::loadSetting(Interface* handle) { 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) { void Manager::saveSetting(Interface* handle) {
const auto& key = handle->getKey();
QVariant handleValue = UNSET_VALUE;
if (handle->isSet()) { if (handle->isSet()) {
setValue(handle->getKey(), handle->getVariant()); handleValue = handle->getVariant();
} else {
remove(handle->getKey());
} }
withWriteLock([&] {
_pendingChanges[key] = handleValue;
});
} }
static const int SAVE_INTERVAL_MSEC = 5 * 1000; // 5 sec static const int SAVE_INTERVAL_MSEC = 5 * 1000; // 5 sec
@ -74,10 +94,20 @@ namespace Setting {
} }
void Manager::saveAll() { void Manager::saveAll() {
withReadLock([&] { withWriteLock([&] {
for (auto handle : _handles) { for (auto key : _pendingChanges.keys()) {
saveSetting(handle); 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 // Restart timer

View file

@ -12,17 +12,23 @@
#ifndef hifi_SettingManager_h #ifndef hifi_SettingManager_h
#define hifi_SettingManager_h #define hifi_SettingManager_h
#include <QPointer> #include <QtCore/QPointer>
#include <QSettings> #include <QtCore/QSettings>
#include <QTimer> #include <QtCore/QTimer>
#include <QtCore/QUuid>
#include "DependencyManager.h"
#include "shared/ReadWriteLockable.h" #include "shared/ReadWriteLockable.h"
namespace Setting { namespace Setting {
class Interface; class Interface;
class Manager : public QSettings, public ReadWriteLockable { class Manager : public QSettings, public ReadWriteLockable, public Dependency {
Q_OBJECT Q_OBJECT
public:
void customDeleter() override;
protected: protected:
~Manager(); ~Manager();
void registerHandle(Interface* handle); void registerHandle(Interface* handle);
@ -40,6 +46,8 @@ namespace Setting {
private: private:
QHash<QString, Interface*> _handles; QHash<QString, Interface*> _handles;
QPointer<QTimer> _saveTimer = nullptr; QPointer<QTimer> _saveTimer = nullptr;
const QVariant UNSET_VALUE { QUuid::createUuid().variant() };
QHash<QString, QVariant> _pendingChanges;
friend class Interface; friend class Interface;
friend void cleanupPrivateInstance(); friend void cleanupPrivateInstance();

View file

@ -23,7 +23,6 @@ void ShapeInfo::clear() {
void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) { void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) {
_type = type; _type = type;
_points.clear();
switch(type) { switch(type) {
case SHAPE_TYPE_NONE: case SHAPE_TYPE_NONE:
_halfExtents = glm::vec3(0.0f); _halfExtents = glm::vec3(0.0f);
@ -52,7 +51,6 @@ void ShapeInfo::setBox(const glm::vec3& halfExtents) {
_url = ""; _url = "";
_type = SHAPE_TYPE_BOX; _type = SHAPE_TYPE_BOX;
_halfExtents = halfExtents; _halfExtents = halfExtents;
_points.clear();
_doubleHashKey.clear(); _doubleHashKey.clear();
} }
@ -60,15 +58,6 @@ void ShapeInfo::setSphere(float radius) {
_url = ""; _url = "";
_type = SHAPE_TYPE_SPHERE; _type = SHAPE_TYPE_SPHERE;
_halfExtents = glm::vec3(radius, radius, radius); _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(); _doubleHashKey.clear();
} }
@ -82,7 +71,6 @@ void ShapeInfo::setCapsuleY(float radius, float halfHeight) {
_url = ""; _url = "";
_type = SHAPE_TYPE_CAPSULE_Y; _type = SHAPE_TYPE_CAPSULE_Y;
_halfExtents = glm::vec3(radius, halfHeight, radius); _halfExtents = glm::vec3(radius, halfHeight, radius);
_points.clear();
_doubleHashKey.clear(); _doubleHashKey.clear();
} }
@ -146,10 +134,6 @@ bool ShapeInfo::contains(const glm::vec3& point) const {
switch(_type) { switch(_type) {
case SHAPE_TYPE_SPHERE: case SHAPE_TYPE_SPHERE:
return glm::length(point) <= _halfExtents.x; 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: case SHAPE_TYPE_CYLINDER_X:
return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.z; return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.z;
case SHAPE_TYPE_CYLINDER_Y: case SHAPE_TYPE_CYLINDER_Y:

View file

@ -30,7 +30,6 @@ enum ShapeType {
SHAPE_TYPE_NONE, SHAPE_TYPE_NONE,
SHAPE_TYPE_BOX, SHAPE_TYPE_BOX,
SHAPE_TYPE_SPHERE, SHAPE_TYPE_SPHERE,
SHAPE_TYPE_ELLIPSOID,
SHAPE_TYPE_PLANE, SHAPE_TYPE_PLANE,
SHAPE_TYPE_COMPOUND, SHAPE_TYPE_COMPOUND,
SHAPE_TYPE_CAPSULE_X, SHAPE_TYPE_CAPSULE_X,
@ -39,7 +38,7 @@ enum ShapeType {
SHAPE_TYPE_CYLINDER_X, SHAPE_TYPE_CYLINDER_X,
SHAPE_TYPE_CYLINDER_Y, SHAPE_TYPE_CYLINDER_Y,
SHAPE_TYPE_CYLINDER_Z, SHAPE_TYPE_CYLINDER_Z,
SHAPE_TYPE_LINE SHAPE_TYPE_STATIC_MESH
}; };
class ShapeInfo { class ShapeInfo {
@ -50,7 +49,6 @@ public:
void setParams(ShapeType type, const glm::vec3& halfExtents, QString url=""); void setParams(ShapeType type, const glm::vec3& halfExtents, QString url="");
void setBox(const glm::vec3& halfExtents); void setBox(const glm::vec3& halfExtents);
void setSphere(float radius); void setSphere(float radius);
void setEllipsoid(const glm::vec3& halfExtents);
void setConvexHulls(const QVector<QVector<glm::vec3>>& points); void setConvexHulls(const QVector<QVector<glm::vec3>>& points);
void setCapsuleY(float radius, float halfHeight); void setCapsuleY(float radius, float halfHeight);
void setOffset(const glm::vec3& offset); void setOffset(const glm::vec3& offset);
@ -60,10 +58,10 @@ public:
const glm::vec3& getHalfExtents() const { return _halfExtents; } const glm::vec3& getHalfExtents() const { return _halfExtents; }
const glm::vec3& getOffset() const { return _offset; } const glm::vec3& getOffset() const { return _offset; }
QVector<QVector<glm::vec3>>& getPoints() { return _points; }
const QVector<QVector<glm::vec3>>& getPoints() const { return _points; } const QVector<QVector<glm::vec3>>& getPoints() const { return _points; }
uint32_t getNumSubShapes() const; uint32_t getNumSubShapes() const;
void clearPoints () { _points.clear(); }
void appendToPoints (const QVector<glm::vec3>& newPoints) { _points << newPoints; } void appendToPoints (const QVector<glm::vec3>& newPoints) { _points << newPoints; }
int getMaxNumPoints() const; int getMaxNumPoints() const;

View file

@ -45,6 +45,8 @@ public:
template <typename F> template <typename F>
bool withTryReadLock(F&& f, int timeout) const; bool withTryReadLock(F&& f, int timeout) const;
QReadWriteLock& getLock() const { return _lock; }
private: private:
mutable QReadWriteLock _lock { QReadWriteLock::Recursive }; mutable QReadWriteLock _lock { QReadWriteLock::Recursive };
}; };

View file

@ -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);

View file

@ -256,7 +256,6 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit
y: y - ToolBar.SPACING y: y - ToolBar.SPACING
}); });
} }
this.save();
} }
this.setAlpha = function(alpha, tool) { this.setAlpha = function(alpha, tool) {
@ -421,6 +420,9 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit
for (var tool in that.tools) { for (var tool in that.tools) {
that.tools[tool].buttonDown(false); that.tools[tool].buttonDown(false);
} }
if (that.mightBeDragging) {
that.save();
}
} }
this.mouseMove = function (event) { this.mouseMove = function (event) {
if (!that.mightBeDragging || !event.isLeftButton) { if (!that.mightBeDragging || !event.isLeftButton) {