"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"); } } };