diff --git a/.eslintrc.js b/.eslintrc.js
index 83fda730e5..bc5de1d65d 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -58,6 +58,7 @@ module.exports = {
"RayPick": false,
"LaserPointers": false,
"ContextOverlay": false
+ "module": false
},
"rules": {
"brace-style": ["error", "1tbs", { "allowSingleLine": false }],
diff --git a/interface/resources/icons/tablet-icons/clap-a.svg b/interface/resources/icons/tablet-icons/clap-a.svg
new file mode 100644
index 0000000000..60df3e0795
--- /dev/null
+++ b/interface/resources/icons/tablet-icons/clap-a.svg
@@ -0,0 +1,112 @@
+
+
+
+
diff --git a/interface/resources/icons/tablet-icons/clap-i.svg b/interface/resources/icons/tablet-icons/clap-i.svg
new file mode 100644
index 0000000000..fbd9ed0f9c
--- /dev/null
+++ b/interface/resources/icons/tablet-icons/clap-i.svg
@@ -0,0 +1,112 @@
+
+
+
+
diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp
index a0664f4f9b..04cf5a671e 100644
--- a/interface/src/commerce/Wallet.cpp
+++ b/interface/src/commerce/Wallet.cpp
@@ -18,10 +18,12 @@
#include
#include
+#include
#include
#include
#include
+#include
#include
#include
@@ -39,7 +41,8 @@
#endif
static const char* KEY_FILE = "hifikey";
-static const char* IMAGE_FILE = "hifi_image"; // eventually this will live in keyfile
+static const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n";
+static const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n";
void initialize() {
static bool initialized = false;
@@ -52,11 +55,8 @@ void initialize() {
}
QString keyFilePath() {
- return PathUtils::getAppDataFilePath(KEY_FILE);
-}
-
-QString imageFilePath() {
- return PathUtils::getAppDataFilePath(IMAGE_FILE);
+ auto accountManager = DependencyManager::get();
+ return PathUtils::getAppDataFilePath(QString("%1.%2").arg(accountManager->getAccountInfo().getUsername(), KEY_FILE));
}
// use the cached _passphrase if it exists, otherwise we need to prompt
@@ -131,13 +131,12 @@ bool writeKeys(const char* filename, RSA* keys) {
return retval;
}
-
-// BEGIN copied code - this will be removed/changed at some point soon
// copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp.
// We will have a different implementation in practice, but this gives us a start for now
//
-// NOTE: we don't really use the private keys returned - we can see how this evolves, but probably
+// TODO: we don't really use the private keys returned - we can see how this evolves, but probably
// we should just return a list of public keys?
+// or perhaps return the RSA* instead?
QPair generateRSAKeypair() {
RSA* keyPair = RSA_new();
@@ -265,6 +264,15 @@ RSA* readPrivateKey(const char* filename) {
return key;
}
+// QT's QByteArray will convert to Base64 without any embedded newlines. This just
+// writes it with embedded newlines, which is more readable.
+void outputBase64WithNewlines(QFile& file, const QByteArray& b64Array) {
+ for (int i = 0; i < b64Array.size(); i += 64) {
+ file.write(b64Array.mid(i, 64));
+ file.write("\n");
+ }
+}
+
void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArray& salt) {
// use the ones in the wallet
auto wallet = DependencyManager::get();
@@ -287,8 +295,7 @@ void Wallet::setPassphrase(const QString& passphrase) {
_publicKeys.clear();
}
-// encrypt some stuff
-bool Wallet::encryptFile(const QString& inputFilePath, const QString& outputFilePath) {
+bool Wallet::writeSecurityImage(const QPixmap* pixmap, const QString& outputFilePath) {
// aes requires a couple 128-bit keys (ckey and ivec). For now, I'll just
// use the md5 of the salt as the ckey (md5 is 128-bit), and ivec will be
// a constant. We can review this later - there are ways to generate keys
@@ -299,16 +306,12 @@ bool Wallet::encryptFile(const QString& inputFilePath, const QString& outputFile
initializeAESKeys(ivec, ckey, _salt);
int tempSize, outSize;
+ QByteArray inputFileBuffer;
+ QBuffer buffer(&inputFileBuffer);
+ buffer.open(QIODevice::WriteOnly);
- // read entire unencrypted file into memory
- QFile inputFile(inputFilePath);
- if (!inputFile.exists()) {
- qCDebug(commerce) << "cannot encrypt" << inputFilePath << "file doesn't exist";
- return false;
- }
- inputFile.open(QIODevice::ReadOnly);
- QByteArray inputFileBuffer = inputFile.readAll();
- inputFile.close();
+ // another spot where we are assuming only jpgs
+ pixmap->save(&buffer, "jpg");
// reserve enough capacity for encrypted bytes
unsigned char* outputFileBuffer = new unsigned char[inputFileBuffer.size() + AES_BLOCK_SIZE];
@@ -337,16 +340,21 @@ bool Wallet::encryptFile(const QString& inputFilePath, const QString& outputFile
EVP_CIPHER_CTX_free(ctx);
qCDebug(commerce) << "encrypted buffer size" << outSize;
QByteArray output((const char*)outputFileBuffer, outSize);
+
+ // now APPEND to the file,
+ QByteArray b64output = output.toBase64();
QFile outputFile(outputFilePath);
- outputFile.open(QIODevice::WriteOnly);
- outputFile.write(output);
+ outputFile.open(QIODevice::Append);
+ outputFile.write(IMAGE_HEADER);
+ outputBase64WithNewlines(outputFile, b64output);
+ outputFile.write(IMAGE_FOOTER);
outputFile.close();
delete[] outputFileBuffer;
return true;
}
-bool Wallet::decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) {
+bool Wallet::readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) {
unsigned char ivec[16];
unsigned char ckey[32];
initializeAESKeys(ivec, ckey, _salt);
@@ -357,9 +365,31 @@ bool Wallet::decryptFile(const QString& inputFilePath, unsigned char** outputBuf
qCDebug(commerce) << "cannot decrypt file" << inputFilePath << "it doesn't exist";
return false;
}
- inputFile.open(QIODevice::ReadOnly);
- QByteArray encryptedBuffer = inputFile.readAll();
+ inputFile.open(QIODevice::ReadOnly | QIODevice::Text);
+ bool foundHeader = false;
+ bool foundFooter = false;
+
+ QByteArray base64EncryptedBuffer;
+
+ while (!inputFile.atEnd()) {
+ QString line(inputFile.readLine());
+ if (!foundHeader) {
+ foundHeader = (line == IMAGE_HEADER);
+ } else {
+ foundFooter = (line == IMAGE_FOOTER);
+ if (!foundFooter) {
+ base64EncryptedBuffer.append(line);
+ }
+ }
+ }
inputFile.close();
+ if (! (foundHeader && foundFooter)) {
+ qCDebug(commerce) << "couldn't parse" << inputFilePath << foundHeader << foundFooter;
+ return false;
+ }
+
+ // convert to bytes
+ auto encryptedBuffer = QByteArray::fromBase64(base64EncryptedBuffer);
// setup decrypted buffer
unsigned char* outputBuffer = new unsigned char[encryptedBuffer.size()];
@@ -428,6 +458,9 @@ bool Wallet::generateKeyPair() {
qCInfo(commerce) << "Generating keypair.";
auto keyPair = generateRSAKeypair();
+
+ // TODO: redo this soon -- need error checking and so on
+ writeSecurityImage(_securityImage, keyFilePath());
sendKeyFilePathIfExists();
QString oldKey = _publicKeys.count() == 0 ? "" : _publicKeys.last();
QString key = keyPair.first->toBase64();
@@ -493,27 +526,30 @@ void Wallet::chooseSecurityImage(const QString& filename) {
if (_securityImage) {
delete _securityImage;
}
- // temporary...
QString path = qApp->applicationDirPath();
path.append("/resources/qml/hifi/commerce/wallet/");
path.append(filename);
+
// now create a new security image pixmap
_securityImage = new QPixmap();
qCDebug(commerce) << "loading data for pixmap from" << path;
_securityImage->load(path);
- // encrypt it and save.
- if (encryptFile(path, imageFilePath())) {
- qCDebug(commerce) << "emitting pixmap";
-
- updateImageProvider();
+ // update the image now
+ updateImageProvider();
+ // we could be choosing the _inital_ security image. If so, there
+ // will be no hifikey file yet. If that is the case, we are done. If
+ // there _is_ a keyfile, we need to update it (similar to changing the
+ // passphrase, we need to do so into a temp file and move it).
+ if (!QFile(keyFilePath()).exists()) {
emit securityImageResult(true);
- } else {
- qCDebug(commerce) << "failed to encrypt security image";
- emit securityImageResult(false);
+ return;
}
+
+ bool success = writeWallet();
+ emit securityImageResult(success);
}
void Wallet::getSecurityImage() {
@@ -526,10 +562,11 @@ void Wallet::getSecurityImage() {
return;
}
- // decrypt and return
- QString filePath(imageFilePath());
- QFileInfo fileInfo(filePath);
- if (fileInfo.exists() && decryptFile(filePath, &data, &dataLen)) {
+ bool success = false;
+ // decrypt and return. Don't bother if we have no file to decrypt, or
+ // no salt set yet.
+ QFileInfo fileInfo(keyFilePath());
+ if (fileInfo.exists() && _salt.size() > 0 && readSecurityImage(keyFilePath(), &data, &dataLen)) {
// create the pixmap
_securityImage = new QPixmap();
_securityImage->loadFromData(data, dataLen, "jpg");
@@ -538,11 +575,9 @@ void Wallet::getSecurityImage() {
updateImageProvider();
delete[] data;
- emit securityImageResult(true);
- } else {
- qCDebug(commerce) << "failed to decrypt security image (maybe none saved yet?)";
- emit securityImageResult(false);
+ success = true;
}
+ emit securityImageResult(success);
}
void Wallet::sendKeyFilePathIfExists() {
QString filePath(keyFilePath());
@@ -566,35 +601,47 @@ void Wallet::reset() {
QFile keyFile(keyFilePath());
- QFile imageFile(imageFilePath());
keyFile.remove();
- imageFile.remove();
+}
+bool Wallet::writeWallet(const QString& newPassphrase) {
+ RSA* keys = readKeys(keyFilePath().toStdString().c_str());
+ if (keys) {
+ // we read successfully, so now write to a new temp file
+ QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp"));
+ QString oldPassphrase = *_passphrase;
+ if (!newPassphrase.isEmpty()) {
+ setPassphrase(newPassphrase);
+ }
+ if (writeKeys(tempFileName.toStdString().c_str(), keys)) {
+ if (writeSecurityImage(_securityImage, tempFileName)) {
+ // ok, now move the temp file to the correct spot
+ QFile(QString(keyFilePath())).remove();
+ QFile(tempFileName).rename(QString(keyFilePath()));
+ qCDebug(commerce) << "wallet written successfully";
+ return true;
+ } else {
+ qCDebug(commerce) << "couldn't write security image to temp wallet";
+ }
+ } else {
+ qCDebug(commerce) << "couldn't write keys to temp wallet";
+ }
+ // if we are here, we failed, so cleanup
+ QFile(tempFileName).remove();
+ if (!newPassphrase.isEmpty()) {
+ setPassphrase(oldPassphrase);
+ }
+
+ } else {
+ qCDebug(commerce) << "couldn't read wallet - bad passphrase?";
+ // TODO: review this, but it seems best to reset the passphrase
+ // since we couldn't decrypt the existing wallet (or is doesn't
+ // exist perhaps).
+ setPassphrase("");
+ }
+ return false;
}
bool Wallet::changePassphrase(const QString& newPassphrase) {
qCDebug(commerce) << "changing passphrase";
- RSA* keys = readKeys(keyFilePath().toStdString().c_str());
- if (keys) {
- // we read successfully, so now write to a new temp file
- // save old passphrase just in case
- // TODO: force re-enter?
- QString oldPassphrase = *_passphrase;
- setPassphrase(newPassphrase);
- QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp"));
- if (writeKeys(tempFileName.toStdString().c_str(), keys)) {
- // ok, now move the temp file to the correct spot
- QFile(QString(keyFilePath())).remove();
- QFile(tempFileName).rename(QString(keyFilePath()));
- qCDebug(commerce) << "passphrase changed successfully";
- return true;
- } else {
- qCDebug(commerce) << "couldn't write keys";
- QFile(tempFileName).remove();
- setPassphrase(oldPassphrase);
- return false;
- }
- }
- qCDebug(commerce) << "couldn't decrypt keys with current passphrase, clearing";
- setPassphrase(QString(""));
- return false;
+ return writeWallet(newPassphrase);
}
diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h
index f3c2ac399b..b8913e9a5e 100644
--- a/interface/src/commerce/Wallet.h
+++ b/interface/src/commerce/Wallet.h
@@ -55,14 +55,15 @@ signals:
private:
QStringList _publicKeys{};
QPixmap* _securityImage { nullptr };
- QByteArray _salt {"iamsalt!"};
+ QByteArray _salt;
QByteArray _iv;
QByteArray _ckey;
QString* _passphrase { new QString("") };
+ bool writeWallet(const QString& newPassphrase = QString(""));
void updateImageProvider();
- bool encryptFile(const QString& inputFilePath, const QString& outputFilePath);
- bool decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen);
+ bool writeSecurityImage(const QPixmap* pixmap, const QString& outputFilePath);
+ bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen);
};
#endif // hifi_Wallet_h
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
index 22b2cffbcb..c6bad008e4 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -909,9 +909,6 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) {
QVector jointsData;
const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy
- auto& fbxJoints = _animation->getGeometry().joints;
- auto& originalFbxJoints = _model->getFBXGeometry().joints;
- bool allowTranslation = entity->getAnimationAllowTranslation();
int frameCount = frames.size();
if (frameCount <= 0) {
return;
@@ -946,19 +943,37 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) {
return;
}
+ QStringList animationJointNames = _animation->getGeometry().getJointNames();
+ auto& fbxJoints = _animation->getGeometry().joints;
+
+ auto& originalFbxJoints = _model->getFBXGeometry().joints;
+ auto& originalFbxIndices = _model->getFBXGeometry().jointIndices;
+
+ bool allowTranslation = entity->getAnimationAllowTranslation();
+
const QVector& rotations = frames[_lastKnownCurrentFrame].rotations;
const QVector& translations = frames[_lastKnownCurrentFrame].translations;
jointsData.resize(_jointMapping.size());
for (int j = 0; j < _jointMapping.size(); j++) {
int index = _jointMapping[j];
+
if (index >= 0) {
glm::mat4 translationMat;
- if (!allowTranslation){
- translationMat = glm::translate(originalFbxJoints[index].translation);
- } else if (index < translations.size()) {
- translationMat = glm::translate(translations[index]);
- }
+
+ if (allowTranslation) {
+ if(index < translations.size()){
+ translationMat = glm::translate(translations[index]);
+ }
+ } else if (index < animationJointNames.size()){
+ QString jointName = fbxJoints[index].name; // Pushing this here so its not done on every entity, with the exceptions of those allowing for translation
+
+ if (originalFbxIndices.contains(jointName)) {
+ // Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get it's translation.
+ int remappedIndex = originalFbxIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual.
+ translationMat = glm::translate(originalFbxJoints[remappedIndex].translation);
+ }
+ }
glm::mat4 rotationMat;
if (index < rotations.size()) {
rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * rotations[index] * fbxJoints[index].postRotation);
@@ -975,7 +990,6 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) {
jointData.rotationSet = true;
}
}
-
// Set the data in the entity
entity->setAnimationJointsData(jointsData);
diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp
index 5f66a78679..8257c883a2 100644
--- a/libraries/shared/src/RegisteredMetaTypes.cpp
+++ b/libraries/shared/src/RegisteredMetaTypes.cpp
@@ -761,6 +761,7 @@ QScriptValue rayPickResultToScriptValue(QScriptEngine* engine, const RayPickResu
obj.setProperty("distance", rayPickResult.distance);
QScriptValue intersection = vec3toScriptValue(engine, rayPickResult.intersection);
obj.setProperty("intersection", intersection);
+ obj.setProperty("intersects", rayPickResult.type != NONE);
QScriptValue surfaceNormal = vec3toScriptValue(engine, rayPickResult.surfaceNormal);
obj.setProperty("surfaceNormal", surfaceNormal);
return obj;
diff --git a/libraries/ui/src/ui/ImageProvider.cpp b/libraries/ui/src/ui/ImageProvider.cpp
index c74ed0cb44..7bbad43f2e 100644
--- a/libraries/ui/src/ui/ImageProvider.cpp
+++ b/libraries/ui/src/ui/ImageProvider.cpp
@@ -51,5 +51,9 @@ QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize
return _securityImage->copy();
}
}
- return QPixmap();
+ // otherwise just return a grey pixmap. This avoids annoying error messages in qml we would get
+ // when sending a 'null' pixmap (QPixmap())
+ QPixmap greyPixmap(200, 200);
+ greyPixmap.fill(QColor("darkGrey"));
+ return greyPixmap;
}
diff --git a/unpublishedScripts/marketplace/clap/animations/ClapAnimation.json b/unpublishedScripts/marketplace/clap/animations/ClapAnimation.json
new file mode 100644
index 0000000000..2dca45a0de
--- /dev/null
+++ b/unpublishedScripts/marketplace/clap/animations/ClapAnimation.json
@@ -0,0 +1,386 @@
+{
+ "RightShoulder": {
+ "rotations": {
+ "x": 0.5578474402427673,
+ "y": -0.44214877486228943,
+ "z": 0.4979960322380066,
+ "w": 0.4952884614467621
+ }
+ },
+ "RightArm": {
+ "rotations": {
+ "x": 0.6266362071037292,
+ "y": -0.02882515825331211,
+ "z": -0.3233867585659027,
+ "w": 0.7084611654281616
+ }
+ },
+ "RightForeArm": {
+ "rotations": {
+ "x": 0.00004018074105260894,
+ "y": 0.06418406218290329,
+ "z": -0.5193823575973511,
+ "w": 0.8521281480789185
+ }
+ },
+ "RightHand": {
+ "rotations": {
+ "x": -0.21368850767612457,
+ "y": 0.02043931558728218,
+ "z": -0.02227853797376156,
+ "w": 0.9764339923858643
+ }
+ },
+ "RightHandPinky1": {
+ "rotations": {
+ "x": -0.003497191471979022,
+ "y": -0.01721140556037426,
+ "z": 0.19736060500144958,
+ "w": 0.980173647403717
+ }
+ },
+ "RightHandPinky2": {
+ "rotations": {
+ "x": 0.000005483317181642633,
+ "y": -0.00008023700502235442,
+ "z": -0.06867633014917374,
+ "w": 0.997639000415802
+ }
+ },
+ "RightHandPinky3": {
+ "rotations": {
+ "x": 7.18885644346301e-8,
+ "y": -2.7131383717460267e-7,
+ "z": 0.007620905060321093,
+ "w": 0.9999709725379944
+ }
+ },
+ "RightHandPinky4": {
+ "rotations": {
+ "x": -5.719068774112657e-9,
+ "y": 5.142213126418937e-7,
+ "z": -0.0075718737207353115,
+ "w": 0.999971330165863
+ }
+ },
+ "RightHandRing1": {
+ "rotations": {
+ "x": -0.0013452530838549137,
+ "y": -0.017564140260219574,
+ "z": 0.0761696845293045,
+ "w": 0.9969393610954285
+ }
+ },
+ "RightHandRing2": {
+ "rotations": {
+ "x": 0.0000010375651982030831,
+ "y": -0.00002211921673733741,
+ "z": -0.04599710553884506,
+ "w": 0.9989416003227234
+ }
+ },
+ "RightHandRing3": {
+ "rotations": {
+ "x": -2.7102969868408877e-10,
+ "y": 1.9202734335976857e-7,
+ "z": 0.0016911650309339166,
+ "w": 0.9999985694885254
+ }
+ },
+ "RightHandRing4": {
+ "rotations": {
+ "x": 2.3246689018208144e-9,
+ "y": -3.364403156069784e-8,
+ "z": 0.0004951066803187132,
+ "w": 0.9999998807907104
+ }
+ },
+ "RightHandMiddle1": {
+ "rotations": {
+ "x": 0.0012630893616005778,
+ "y": -0.017612185329198837,
+ "z": -0.07168931514024734,
+ "w": 0.9972707033157349
+ }
+ },
+ "RightHandMiddle2": {
+ "rotations": {
+ "x": 2.3561028683616314e-7,
+ "y": 0.000020313073036959395,
+ "z": 0.011195243336260319,
+ "w": 0.9999373555183411
+ }
+ },
+ "RightHandMiddle3": {
+ "rotations": {
+ "x": 6.375214667286855e-8,
+ "y": 4.750924631480302e-7,
+ "z": 0.00237679248675704,
+ "w": 0.9999971985816956
+ }
+ },
+ "RightHandMiddle4": {
+ "rotations": {
+ "x": 6.717256439969788e-8,
+ "y": 3.876683507542111e-8,
+ "z": -0.005236906465142965,
+ "w": 0.9999862909317017
+ }
+ },
+ "RightHandIndex1": {
+ "rotations": {
+ "x": 0.002164300065487623,
+ "y": -0.017346171662211418,
+ "z": -0.12158434838056564,
+ "w": 0.9924271702766418
+ }
+ },
+ "RightHandIndex2": {
+ "rotations": {
+ "x": -0.00000143755482895358,
+ "y": -0.0001614764187252149,
+ "z": 0.008941099047660828,
+ "w": 0.9999601244926453
+ }
+ },
+ "RightHandIndex3": {
+ "rotations": {
+ "x": 7.458467621290765e-8,
+ "y": 5.365728839024086e-7,
+ "z": 0.03373909369111061,
+ "w": 0.999430775642395
+ }
+ },
+ "RightHandIndex4": {
+ "rotations": {
+ "x": 4.511302997833866e-10,
+ "y": -2.259726272768603e-7,
+ "z": -0.009632252156734467,
+ "w": 0.9999536275863647
+ }
+ },
+ "RightHandThumb1": {
+ "rotations": {
+ "x": -0.0783928632736206,
+ "y": -0.3033908009529114,
+ "z": -0.26653754711151123,
+ "w": 0.9114638566970825
+ }
+ },
+ "RightHandThumb2": {
+ "rotations": {
+ "x": 0.0031029442325234413,
+ "y": 0.07382386922836304,
+ "z": 0.005253761075437069,
+ "w": 0.9972526431083679
+ }
+ },
+ "RightHandThumb3": {
+ "rotations": {
+ "x": 0.0040440745651721954,
+ "y": -0.04943573474884033,
+ "z": 0.007246015593409538,
+ "w": 0.9987428188323975
+ }
+ },
+ "RightHandThumb4": {
+ "rotations": {
+ "x": -0.00009280416270485148,
+ "y": -0.01658034883439541,
+ "z": -0.00014316302258521318,
+ "w": 0.999862551689148
+ }
+ },
+ "LeftShoulder": {
+ "rotations": {
+ "x": 0.5578474402427673,
+ "y": 0.44214877486228943,
+ "z": -0.4979960322380066,
+ "w": 0.4952884614467621
+ }
+ },
+ "LeftArm": {
+ "rotations": {
+ "x": 0.626636266708374,
+ "y": 0.028824958950281143,
+ "z": 0.3233867585659027,
+ "w": 0.7084611654281616
+ }
+ },
+ "LeftForeArm": {
+ "rotations": {
+ "x": 0.00004015670492663048,
+ "y": -0.06418408453464508,
+ "z": 0.5193824768066406,
+ "w": 0.8521282076835632
+ }
+ },
+ "LeftHand": {
+ "rotations": {
+ "x": -0.21368853747844696,
+ "y": -0.02043931558728218,
+ "z": 0.02227853797376156,
+ "w": 0.9764339923858643
+ }
+ },
+ "LeftHandPinky1": {
+ "rotations": {
+ "x": -0.003497188910841942,
+ "y": 0.01721140556037426,
+ "z": -0.19736060500144958,
+ "w": 0.980173647403717
+ }
+ },
+ "LeftHandPinky2": {
+ "rotations": {
+ "x": 0.000005479304491018411,
+ "y": 0.00008023556438274682,
+ "z": 0.06867631524801254,
+ "w": 0.997639000415802
+ }
+ },
+ "LeftHandPinky3": {
+ "rotations": {
+ "x": 7.229602516645173e-8,
+ "y": 2.709063835482084e-7,
+ "z": -0.007620909716933966,
+ "w": 0.9999709725379944
+ }
+ },
+ "LeftHandPinky4": {
+ "rotations": {
+ "x": -5.52988677071653e-9,
+ "y": -5.13755651354586e-7,
+ "z": 0.007571868598461151,
+ "w": 0.999971330165863
+ }
+ },
+ "LeftHandRing1": {
+ "rotations": {
+ "x": -0.001345252152532339,
+ "y": 0.017564138397574425,
+ "z": -0.0761696845293045,
+ "w": 0.9969393610954285
+ }
+ },
+ "LeftHandRing2": {
+ "rotations": {
+ "x": 0.000001034482806971937,
+ "y": 0.000022119218556326814,
+ "z": 0.04599710553884506,
+ "w": 0.9989416003227234
+ }
+ },
+ "LeftHandRing3": {
+ "rotations": {
+ "x": -2.8012464570181805e-10,
+ "y": -1.923183816643359e-7,
+ "z": -0.0016911652637645602,
+ "w": 0.9999985694885254
+ }
+ },
+ "LeftHandRing4": {
+ "rotations": {
+ "x": -1.1168596047994583e-9,
+ "y": 3.33529932561305e-8,
+ "z": -0.0004951058072037995,
+ "w": 0.9999998807907104
+ }
+ },
+ "LeftHandMiddle1": {
+ "rotations": {
+ "x": 0.0012630895944312215,
+ "y": 0.01761218160390854,
+ "z": 0.07168931514024734,
+ "w": 0.9972707033157349
+ }
+ },
+ "LeftHandMiddle2": {
+ "rotations": {
+ "x": 2.37378529277521e-7,
+ "y": -0.00002031277836067602,
+ "z": -0.011195233091711998,
+ "w": 0.9999373555183411
+ }
+ },
+ "LeftHandMiddle3": {
+ "rotations": {
+ "x": 7.146466884933034e-8,
+ "y": -4.7555812443533796e-7,
+ "z": -0.0023767934180796146,
+ "w": 0.9999971985816956
+ }
+ },
+ "LeftHandMiddle4": {
+ "rotations": {
+ "x": 6.549178976911207e-8,
+ "y": -3.865041975359418e-8,
+ "z": 0.005236904136836529,
+ "w": 0.9999862909317017
+ }
+ },
+ "LeftHandIndex1": {
+ "rotations": {
+ "x": 0.002164299599826336,
+ "y": 0.017346171662211418,
+ "z": 0.12158433347940445,
+ "w": 0.9924271702766418
+ }
+ },
+ "LeftHandIndex2": {
+ "rotations": {
+ "x": -0.000001437780269952782,
+ "y": 0.00016147761198226362,
+ "z": -0.008941099047660828,
+ "w": 0.9999601244926453
+ }
+ },
+ "LeftHandIndex3": {
+ "rotations": {
+ "x": 7.61426193207626e-8,
+ "y": -5.373883027459669e-7,
+ "z": -0.03373908996582031,
+ "w": 0.999430775642395
+ }
+ },
+ "LeftHandIndex4": {
+ "rotations": {
+ "x": -5.311697748311417e-10,
+ "y": 2.26380109324964e-7,
+ "z": 0.009632255882024765,
+ "w": 0.9999536275863647
+ }
+ },
+ "LeftHandThumb1": {
+ "rotations": {
+ "x": -0.07839284837245941,
+ "y": 0.3033908009529114,
+ "z": 0.26653754711151123,
+ "w": 0.9114638566970825
+ }
+ },
+ "LeftHandThumb2": {
+ "rotations": {
+ "x": 0.0031029372476041317,
+ "y": -0.07382386922836304,
+ "z": -0.005253763869404793,
+ "w": 0.9972526431083679
+ }
+ },
+ "LeftHandThumb3": {
+ "rotations": {
+ "x": 0.004044072702527046,
+ "y": 0.049435727298259735,
+ "z": -0.0072460174560546875,
+ "w": 0.9987428188323975
+ }
+ },
+ "LeftHandThumb4": {
+ "rotations": {
+ "x": -0.00009280881931772456,
+ "y": 0.016580356284976006,
+ "z": 0.00014314628788270056,
+ "w": 0.999862551689148
+ }
+ }
+}
diff --git a/unpublishedScripts/marketplace/clap/animations/Clap_left.fbx b/unpublishedScripts/marketplace/clap/animations/Clap_left.fbx
new file mode 100644
index 0000000000..a3830b9838
Binary files /dev/null and b/unpublishedScripts/marketplace/clap/animations/Clap_left.fbx differ
diff --git a/unpublishedScripts/marketplace/clap/animations/Clap_right.fbx b/unpublishedScripts/marketplace/clap/animations/Clap_right.fbx
new file mode 100644
index 0000000000..1877e963f9
Binary files /dev/null and b/unpublishedScripts/marketplace/clap/animations/Clap_right.fbx differ
diff --git a/unpublishedScripts/marketplace/clap/clapApp.js b/unpublishedScripts/marketplace/clap/clapApp.js
new file mode 100644
index 0000000000..b2d8ce55db
--- /dev/null
+++ b/unpublishedScripts/marketplace/clap/clapApp.js
@@ -0,0 +1,61 @@
+"use strict";
+
+/*
+ clapApp.js
+ unpublishedScripts/marketplace/clap/clapApp.js
+
+ Created by Matti 'Menithal' Lahtinen on 9/11/2017
+ Copyright 2017 High Fidelity, Inc.
+
+ Distributed under the Apache License, Version 2.0.
+ See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+*/
+
+// Entry Script for the clap app
+
+// Load up engine
+var APP_NAME = "CLAP";
+var ClapEngine = Script.require(Script.resolvePath("scripts/ClapEngine.js?v9"));
+var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
+
+// Define Menu
+var blackIcon = Script.resolvePath("icons/tablet-icons/clap-a.svg?foxv2");
+var whiteIcon = Script.resolvePath("icons/tablet-icons/clap-i.svg?foxv2");
+
+if (Settings.getValue("clapAppEnabled") === undefined) {
+ Settings.setValue("clapAppEnabled", true);
+}
+var isActive = Settings.getValue("clapAppEnabled");
+
+var activeButton = tablet.addButton({
+ icon: whiteIcon,
+ activeIcon: blackIcon,
+ text: APP_NAME,
+ isActive: isActive,
+ sortOrder: 11
+});
+
+if (isActive) {
+ ClapEngine.connectEngine();
+}
+
+function onClick(enabled) {
+
+ isActive = !isActive;
+ Settings.setValue("clapAppEnabled", isActive);
+ activeButton.editProperties({
+ isActive: isActive
+ });
+ if (isActive) {
+ ClapEngine.connectEngine();
+ } else {
+ ClapEngine.disconnectEngine();
+ }
+}
+activeButton.clicked.connect(onClick);
+
+Script.scriptEnding.connect(function () {
+ ClapEngine.disconnectEngine();
+ activeButton.clicked.disconnect(onClick);
+ tablet.removeButton(activeButton);
+});
diff --git a/unpublishedScripts/marketplace/clap/entities/ClapParticle.json b/unpublishedScripts/marketplace/clap/entities/ClapParticle.json
new file mode 100644
index 0000000000..bf1b70665b
--- /dev/null
+++ b/unpublishedScripts/marketplace/clap/entities/ClapParticle.json
@@ -0,0 +1,58 @@
+{
+ "alpha": 0.01,
+ "alphaFinish": 0.0,
+ "alphaSpread": 1,
+ "alphaStart": 0.05,
+ "color": {
+ "blue": 200,
+ "green": 200,
+ "red": 200
+ },
+ "colorFinish": {
+ "blue": 200,
+ "green": 200,
+ "red": 200
+ },
+ "colorStart": {
+ "blue": 200,
+ "green": 200,
+ "red": 200
+ },
+ "created": "2017-09-09T16:01:38Z",
+ "dimensions": {
+ "x": 0.5,
+ "y": 0.5,
+ "z": 0.5
+ },
+ "emitAcceleration": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "emitDimensions": {
+ "x": 0.25,
+ "y": 0.25,
+ "z": 0.25
+ },
+ "emitOrientation": {
+ "w": 1,
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "emitRate": 100,
+ "emitSpeed": 0.125,
+ "lifespan": 0.5,
+ "lifetime": 2,
+ "script": "(function(){return{preload:function(id){Script.setTimeout(function(){Entities.editEntity(id,{isEmitting:false});},200);}}})",
+ "maxParticles": 100,
+ "particleRadius": 0.05,
+ "polarFinish": 1.4311699867248535,
+ "polarStart": 1.3962633609771729,
+ "radiusFinish": 0.01,
+ "radiusSpread": 0.2,
+ "radiusStart": 0.05,
+ "speedSpread": 0,
+ "emitShouldTrail": true,
+ "type": "ParticleEffect"
+}
diff --git a/unpublishedScripts/marketplace/clap/icons/clap-a.svg b/unpublishedScripts/marketplace/clap/icons/clap-a.svg
new file mode 100644
index 0000000000..60df3e0795
--- /dev/null
+++ b/unpublishedScripts/marketplace/clap/icons/clap-a.svg
@@ -0,0 +1,112 @@
+
+
+
+
diff --git a/unpublishedScripts/marketplace/clap/icons/clap-i.svg b/unpublishedScripts/marketplace/clap/icons/clap-i.svg
new file mode 100644
index 0000000000..fbd9ed0f9c
--- /dev/null
+++ b/unpublishedScripts/marketplace/clap/icons/clap-i.svg
@@ -0,0 +1,112 @@
+
+
+
+
diff --git a/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js b/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js
new file mode 100644
index 0000000000..9e7a592c01
--- /dev/null
+++ b/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js
@@ -0,0 +1,144 @@
+"use strict";
+
+/*
+ clapApp.js
+ unpublishedScripts/marketplace/clap/scripts/clapDebugger.js
+
+ Created by Matti 'Menithal' Lahtinen on 9/11/2017
+ Copyright 2017 High Fidelity, Inc.
+
+ Distributed under the Apache License, Version 2.0.
+ See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+*/
+
+var DEBUG_RIGHT_HAND;
+var DEBUG_LEFT_HAND;
+var DEBUG_CLAP_LEFT;
+var DEBUG_CLAP_RIGHT;
+var DEBUG_CLAP;
+var DEBUG_CLAP_DIRECTION;
+
+// Debug Values:
+var DEBUG_CORRECT = {
+ red: 0,
+ green: 255,
+ blue: 0
+};
+var DEBUG_WRONG = {
+ red: 255,
+ green: 0,
+ blue: 0
+};
+
+var DEBUG_VOLUME = {
+ red: 255,
+ green: 255,
+ blue: 128
+};
+
+module.exports = {
+ disableDebug: function () {
+ Overlays.deleteOverlay(DEBUG_RIGHT_HAND);
+ Overlays.deleteOverlay(DEBUG_LEFT_HAND);
+ Overlays.deleteOverlay(DEBUG_CLAP_LEFT);
+ Overlays.deleteOverlay(DEBUG_CLAP_RIGHT);
+ Overlays.deleteOverlay(DEBUG_CLAP_DIRECTION);
+ },
+
+ debugPositions: function (leftAlignmentWorld, leftHandPositionOffset, leftHandDownWorld, rightAlignmentWorld, rightHandPositionOffset, rightHandDownWorld, tolerance) {
+
+ Overlays.editOverlay(DEBUG_CLAP_LEFT, {
+ color: leftAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG,
+ position: leftHandPositionOffset
+ });
+
+ Overlays.editOverlay(DEBUG_CLAP_RIGHT, {
+ color: rightAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG,
+ position: rightHandPositionOffset
+ });
+
+ Overlays.editOverlay(DEBUG_LEFT_HAND, {
+ color: leftAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG,
+ start: leftHandPositionOffset,
+ end: Vec3.sum(leftHandPositionOffset, Vec3.multiply(leftHandDownWorld, 0.2))
+ });
+
+ Overlays.editOverlay(DEBUG_RIGHT_HAND, {
+ color: rightAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG,
+ start: rightHandPositionOffset,
+ end: Vec3.sum(rightHandPositionOffset, Vec3.multiply(rightHandDownWorld, 0.2))
+ });
+ },
+
+ debugClapLine: function (start, end, visible) {
+ Overlays.editOverlay(DEBUG_CLAP_DIRECTION, {
+ start: start,
+ end: end,
+ visible: visible
+ });
+ },
+
+ enableDebug: function () {
+ DEBUG_RIGHT_HAND = Overlays.addOverlay("line3d", {
+ color: DEBUG_WRONG,
+ start: MyAvatar.position,
+ end: Vec3.sum(MyAvatar.position, {
+ x: 0,
+ y: 1,
+ z: 0
+ }),
+ dimensions: {
+ x: 2,
+ y: 2,
+ z: 2
+ }
+ });
+
+ DEBUG_LEFT_HAND = Overlays.addOverlay("line3d", {
+ color: DEBUG_WRONG,
+ start: MyAvatar.position,
+ end: Vec3.sum(MyAvatar.position, {
+ x: 0,
+ y: 1,
+ z: 0
+ }),
+ dimensions: {
+ x: 2,
+ y: 2,
+ z: 2
+ }
+ });
+
+ DEBUG_CLAP_LEFT = Overlays.addOverlay("sphere", {
+ position: MyAvatar.position,
+ color: DEBUG_WRONG,
+ scale: {
+ x: 0.05,
+ y: 0.05,
+ z: 0.05
+ }
+ });
+
+ DEBUG_CLAP_RIGHT = Overlays.addOverlay("sphere", {
+ position: MyAvatar.position,
+ color: DEBUG_WRONG,
+ scale: {
+ x: 0.05,
+ y: 0.05,
+ z: 0.05
+ }
+ });
+
+
+ DEBUG_CLAP_DIRECTION = Overlays.addOverlay("line3d", {
+ color: DEBUG_VOLUME,
+ start: MyAvatar.position,
+ end: MyAvatar.position,
+ dimensions: {
+ x: 2,
+ y: 2,
+ z: 2
+ }
+ });
+ }
+};
diff --git a/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js b/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js
new file mode 100644
index 0000000000..1a5f6665e1
--- /dev/null
+++ b/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js
@@ -0,0 +1,313 @@
+"use strict";
+
+/*
+ clapEngine.js
+ unpublishedScripts/marketplace/clap/clapApp.js
+
+ Created by Matti 'Menithal' Lahtinen on 9/11/2017
+ Copyright 2017 High Fidelity, Inc.
+
+ Distributed under the Apache License, Version 2.0.
+ See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+
+
+ Main Heart of the clap script> Does both keyboard binding and tracking of the gear..
+
+*/
+var DEG_TO_RAD = Math.PI / 180;
+// If angle is closer to 0 from 25 degrees, then "clap" can happen;
+var COS_OF_TOLERANCE = Math.cos(25 * DEG_TO_RAD);
+var DISTANCE = 0.3;
+var CONTROL_MAP_PACKAGE = "com.highfidelity.avatar.clap.active";
+
+var CLAP_MENU = "Clap";
+var ENABLE_PARTICLE_MENU = "Enable Clap Particles";
+var ENABLE_DEBUG_MENU = "Enable Claping Helper";
+
+var sounds = [
+ "clap1.wav",
+ "clap2.wav",
+ "clap3.wav",
+ "clap4.wav",
+ "clap5.wav",
+ "clap6.wav"
+];
+
+
+var ClapParticle = Script.require(Script.resolvePath("../entities/ClapParticle.json?V3"));
+var ClapAnimation = Script.require(Script.resolvePath("../animations/ClapAnimation.json?V3"));
+var ClapDebugger = Script.require(Script.resolvePath("ClapDebugger.js?V3"));
+
+var settingDebug = false;
+var settingParticlesOn = true;
+
+function setJointRotation(map) {
+ Object.keys(map).forEach(function (key, index) {
+ MyAvatar.setJointRotation(MyAvatar.getJointIndex(key), map[key].rotations);
+ });
+}
+// Load Sounds to Cache
+var cache = [];
+for (var index in sounds) {
+ cache.push(SoundCache.getSound(Script.resolvePath("../sounds/" + sounds[index])));
+}
+
+
+var previousIndex;
+var clapOn = false;
+var animClap = false;
+var animThrottle;
+
+function clap(volume, position, rotation) {
+ var index;
+ // Make sure one does not generate consequtive sounds
+ do {
+ index = Math.floor(Math.random() * cache.length);
+ } while (index === previousIndex);
+
+ previousIndex = index;
+
+ Audio.playSound(cache[index], {
+ position: position,
+ volume: volume / 4 + Math.random() * (volume / 3)
+ });
+
+ if (settingParticlesOn) {
+ ClapParticle.orientation = Quat.multiply(MyAvatar.orientation, rotation);
+ ClapParticle.position = position;
+ ClapParticle.emitSpeed = volume > 1 ? 1 : volume;
+ Entities.addEntity(ClapParticle, true);
+ }
+
+
+}
+// Helper Functions
+function getHandFingerAnim(side) {
+ return Script.resolvePath("../animations/Clap_" + side + '.fbx');
+}
+
+// Disable all role animations related to fingers for side
+function overrideFingerRoleAnimation(side) {
+ var anim = getHandFingerAnim(side);
+ MyAvatar.overrideRoleAnimation(side + "HandGraspOpen", anim, 30, true, 0, 0);
+ MyAvatar.overrideRoleAnimation(side + "HandGraspClosed", anim, 30, true, 0, 0);
+ if (HMD.active) {
+ MyAvatar.overrideRoleAnimation(side + "HandPointIntro", anim, 30, true, 0, 0);
+ MyAvatar.overrideRoleAnimation(side + "HandPointHold", anim, 30, true, 0, 0);
+ MyAvatar.overrideRoleAnimation(side + "HandPointOutro", anim, 30, true, 0, 0);
+
+ MyAvatar.overrideRoleAnimation(side + "IndexPointOpen", anim, 30, true, 0, 0);
+ MyAvatar.overrideRoleAnimation(side + "IndexPointClosed", anim, 30, true, 0, 0);
+ MyAvatar.overrideRoleAnimation(side + "IndexPointAndThumbRaiseOpen", anim, 30, true, 0, 0);
+ MyAvatar.overrideRoleAnimation(side + "IndexPointAndThumbRaiseClosed", anim, 30, true, 0, 0);
+
+ MyAvatar.overrideRoleAnimation(side + "ThumbRaiseOpen", anim, 30, true, 0, 0);
+ MyAvatar.overrideRoleAnimation(side + "ThumbRaiseClosed", anim, 30, true, 0, 0);
+ }
+}
+// Re-enable all role animations for fingers
+function restoreFingerRoleAnimation(side) {
+ MyAvatar.restoreRoleAnimation(side + "HandGraspOpen");
+ MyAvatar.restoreRoleAnimation(side + "HandGraspClosed");
+ if (HMD.active) {
+ MyAvatar.restoreRoleAnimation(side + "HandPointIntro");
+ MyAvatar.restoreRoleAnimation(side + "HandPointHold");
+ MyAvatar.restoreRoleAnimation(side + "HandPointOutro");
+ MyAvatar.restoreRoleAnimation(side + "IndexPointOpen");
+
+ MyAvatar.restoreRoleAnimation(side + "IndexPointClosed");
+ MyAvatar.restoreRoleAnimation(side + "IndexPointAndThumbRaiseOpen");
+ MyAvatar.restoreRoleAnimation(side + "IndexPointAndThumbRaiseClosed");
+ MyAvatar.restoreRoleAnimation(side + "ThumbRaiseOpen");
+ MyAvatar.restoreRoleAnimation(side + "ThumbRaiseClosed");
+ }
+}
+
+
+function menuListener(menuItem) {
+ if (menuItem === ENABLE_PARTICLE_MENU) {
+ settingParticlesOn = Menu.isOptionChecked(ENABLE_PARTICLE_MENU);
+ } else if (menuItem === ENABLE_DEBUG_MENU) {
+ var debugOn = Menu.isOptionChecked(ENABLE_DEBUG_MENU);
+
+ if (debugOn) {
+ settingDebug = true;
+ ClapDebugger.enableDebug();
+ } else {
+ settingDebug = false;
+ ClapDebugger.disableDebug();
+ }
+ }
+}
+
+function update(dt) {
+
+ // NOTICE: Someof this stuff is unnessary for the actual: But they are done for Debug Purposes!
+ // Forexample, the controller doesnt really need to know where it is in the world, only its relation to the other controller!
+
+ var leftHand = Controller.getPoseValue(Controller.Standard.LeftHand);
+ var rightHand = Controller.getPoseValue(Controller.Standard.RightHand);
+
+ // Get Offset position for palms, not the controllers that are at the wrists (7.5 cm up)
+
+ var leftWorldRotation = Quat.multiply(MyAvatar.orientation, leftHand.rotation);
+ var rightWorldRotation = Quat.multiply(MyAvatar.orientation, rightHand.rotation);
+
+ var leftWorldUpNormal = Quat.getUp(leftWorldRotation);
+ var rightWorldUpNormal = Quat.getUp(rightWorldRotation);
+
+ var leftHandDownWorld = Vec3.multiply(-1, Quat.getForward(leftWorldRotation));
+ var rightHandDownWorld = Vec3.multiply(-1, Quat.getForward(rightWorldRotation));
+
+ //
+ var leftHandWorldPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, leftHand.translation));
+ var rightHandWorldPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, rightHand.translation));
+
+ var rightHandPositionOffset = Vec3.sum(rightHandWorldPosition, Vec3.multiply(rightWorldUpNormal, 0.035));
+ var leftHandPositionOffset = Vec3.sum(leftHandWorldPosition, Vec3.multiply(leftWorldUpNormal, 0.035));
+
+ var leftToRightWorld = Vec3.subtract(leftHandPositionOffset, rightHandPositionOffset);
+ var rightToLeftWorld = Vec3.subtract(rightHandPositionOffset, leftHandPositionOffset);
+
+ var leftAlignmentWorld = -1 * Vec3.dot(Vec3.normalize(leftToRightWorld), leftHandDownWorld);
+ var rightAlignmentWorld = -1 * Vec3.dot(Vec3.normalize(rightToLeftWorld), rightHandDownWorld);
+
+ var distance = Vec3.distance(rightHandPositionOffset, leftHandPositionOffset);
+
+ var matchTolerance = leftAlignmentWorld > COS_OF_TOLERANCE && rightAlignmentWorld > COS_OF_TOLERANCE;
+
+ if (settingDebug) {
+ ClapDebugger.debugPositions(leftAlignmentWorld,
+ leftHandPositionOffset, leftHandDownWorld,
+ rightAlignmentWorld, rightHandPositionOffset,
+ rightHandDownWorld, COS_OF_TOLERANCE);
+ }
+ // Using subtract, because these will be heading to opposite directions
+ var angularVelocity = Vec3.length(Vec3.subtract(leftHand.angularVelocity, rightHand.angularVelocity));
+ var velocity = Vec3.length(Vec3.subtract(leftHand.velocity, rightHand.velocity));
+
+ if (matchTolerance && distance < DISTANCE && !animClap) {
+ if (settingDebug) {
+ ClapDebugger.debugClapLine(leftHandPositionOffset, rightHandPositionOffset, true);
+ }
+ if (!animThrottle) {
+ overrideFingerRoleAnimation("left");
+ overrideFingerRoleAnimation("right");
+ animClap = true;
+ } else {
+ Script.clearTimeout(animThrottle);
+ animThrottle = false;
+ }
+ } else if (animClap && distance > DISTANCE) {
+ if (settingDebug) {
+ ClapDebugger.debugClapLine(leftHandPositionOffset, rightHandPositionOffset, false);
+ }
+ animThrottle = Script.setTimeout(function () {
+ restoreFingerRoleAnimation("left");
+ restoreFingerRoleAnimation("right");
+ animClap = false;
+ }, 500);
+ }
+
+ if (distance < DISTANCE && matchTolerance && !clapOn) {
+ clapOn = true;
+
+ var midClap = Vec3.mix(rightHandPositionOffset, leftHandPositionOffset, 0.5);
+ var volume = velocity / 2 + angularVelocity / 5;
+
+ clap(volume, midClap, Quat.lookAtSimple(rightHandPositionOffset, leftHandPositionOffset));
+ } else if (distance > DISTANCE && !matchTolerance) {
+ clapOn = false;
+ }
+}
+
+
+module.exports = {
+ connectEngine: function () {
+ if (!Menu.menuExists(CLAP_MENU)) {
+ Menu.addMenu(CLAP_MENU);
+ }
+
+ if (!Menu.menuItemExists(CLAP_MENU, ENABLE_PARTICLE_MENU)) {
+ Menu.addMenuItem({
+ menuName: CLAP_MENU,
+ menuItemName: ENABLE_PARTICLE_MENU,
+ isCheckable: true,
+ isChecked: settingParticlesOn
+ });
+ }
+ if (!Menu.menuItemExists(CLAP_MENU, ENABLE_DEBUG_MENU)) {
+ Menu.addMenuItem({
+ menuName: CLAP_MENU,
+ menuItemName: ENABLE_DEBUG_MENU,
+ isCheckable: true,
+ isChecked: settingDebug
+ });
+ }
+
+
+ Menu.menuItemEvent.connect(menuListener);
+
+ var controls = Controller.newMapping(CONTROL_MAP_PACKAGE);
+ var Keyboard = Controller.Hardware.Keyboard;
+
+ controls.from(Keyboard.K).to(function (down) {
+ if (down) {
+
+ setJointRotation(ClapAnimation);
+ Script.setTimeout(function () {
+ // As soon as an animation bug is fixed, this will kick and get fixed.s.
+ overrideFingerRoleAnimation("left");
+ overrideFingerRoleAnimation("right");
+
+ var lh = MyAvatar.getJointPosition("LeftHand");
+ var rh = MyAvatar.getJointPosition("RightHand");
+ var midClap = Vec3.mix(rh, lh, 0.5);
+ var volume = 0.5 + Math.random() * 0.5;
+ var position = midClap;
+
+ clap(volume, position, Quat.fromVec3Degrees(0, 0, 0));
+ }, 50); // delay is present to allow for frames to catch up.
+ } else {
+
+ restoreFingerRoleAnimation("left");
+
+ restoreFingerRoleAnimation("right");
+ MyAvatar.clearJointsData();
+ }
+ });
+ Controller.enableMapping(CONTROL_MAP_PACKAGE);
+
+ if (settingDebug) {
+ ClapDebugger.enableDebug();
+ }
+
+ Script.update.connect(update);
+
+ Script.scriptEnding.connect(this.disconnectEngine);
+ },
+ disconnectEngine: function () {
+ if (settingDebug) {
+ ClapDebugger.disableDebug();
+ }
+ if (Menu.menuItemExists(CLAP_MENU, ENABLE_PARTICLE_MENU)) {
+ Menu.removeMenuItem(CLAP_MENU, ENABLE_PARTICLE_MENU);
+ }
+ if (Menu.menuItemExists(CLAP_MENU, ENABLE_DEBUG_MENU)) {
+ Menu.removeMenuItem(CLAP_MENU, ENABLE_DEBUG_MENU);
+ }
+
+ if (Menu.menuExists(CLAP_MENU)) {
+ Menu.removeMenu(CLAP_MENU);
+ }
+ restoreFingerRoleAnimation('left');
+ restoreFingerRoleAnimation('right');
+
+ Controller.disableMapping(CONTROL_MAP_PACKAGE);
+ try {
+ Script.update.disconnect(update);
+ } catch (e) {
+ print("Script.update connection did not exist on disconnection. Skipping");
+ }
+ }
+};
diff --git a/unpublishedScripts/marketplace/clap/sounds/clap1.wav b/unpublishedScripts/marketplace/clap/sounds/clap1.wav
new file mode 100644
index 0000000000..679cb7e732
Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap1.wav differ
diff --git a/unpublishedScripts/marketplace/clap/sounds/clap2.wav b/unpublishedScripts/marketplace/clap/sounds/clap2.wav
new file mode 100644
index 0000000000..b43c745e52
Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap2.wav differ
diff --git a/unpublishedScripts/marketplace/clap/sounds/clap3.wav b/unpublishedScripts/marketplace/clap/sounds/clap3.wav
new file mode 100644
index 0000000000..47a4a8d48f
Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap3.wav differ
diff --git a/unpublishedScripts/marketplace/clap/sounds/clap4.wav b/unpublishedScripts/marketplace/clap/sounds/clap4.wav
new file mode 100644
index 0000000000..4e8dfca1a0
Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap4.wav differ
diff --git a/unpublishedScripts/marketplace/clap/sounds/clap5.wav b/unpublishedScripts/marketplace/clap/sounds/clap5.wav
new file mode 100644
index 0000000000..1f38864db4
Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap5.wav differ
diff --git a/unpublishedScripts/marketplace/clap/sounds/clap6.wav b/unpublishedScripts/marketplace/clap/sounds/clap6.wav
new file mode 100644
index 0000000000..4591f79d9d
Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap6.wav differ