content/hifi-content/wadewatts/applause-app.js
2022-02-14 02:04:11 +01:00

420 lines
No EOL
16 KiB
JavaScript

// applause-app.js
//
// Created by Liv Erickson on 6/4/18
// Copyright 2018 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
//
// App Icon made by Freepick from www.flaticon.com
//
(function() {
var INDIVIDUAL_CLAP_URLS = [
Script.resolvePath("sounds/Claps/clap-3.wav"),
Script.resolvePath("sounds/Claps/clap-4.wav"),
Script.resolvePath("sounds/Claps/clap-5.wav"),
Script.resolvePath("sounds/Claps/clap-6.wav"),
Script.resolvePath("sounds/Claps/clap-7.wav"),
Script.resolvePath("sounds/Claps/clap-8.wav"),
Script.resolvePath("sounds/Claps/clap-9.wav"),
Script.resolvePath("sounds/Claps/clap-10.wav")
];
var CAN_APPLAUD_SETTING = 'io.highfidelity.applauseEnabled';
var SHOULD_DISPLAY_PARTICLES_APPLAUSE = 'io.highfidelity.applauseEnabled.useParticles';
var HAS_APPLAUSE_APP_SETTING = 'io.highfidelity.applauseEnabled.appPresent';
var APPLAUSE_BUTTON_IMAGE = Script.resolvePath('./resources/button.png');
var APPLAUSE_BUTTON_PRESSED = Script.resolvePath('./resources/button-pressed.png');
var WINDOW_Y_OFFSET = 24;
var BUTTON_DIMENSIONS = {x: 221, y: 69};
var BUTTON_PRESS_TIMEOUT = 150; //ms
var HAND_PROXIMITY_SCALE = 6;
var handProximityDistance = MyAvatar.getEyeHeight() / HAND_PROXIMITY_SCALE;
var APP_ICON = Script.resolvePath('./resources/hand-icon-by-freepik.png');
var APPLAUSE_KEY = 't';
var APPLAUSE_VOLUME = 0.25;
var HAPTICS = {
strength: 0.75,
duration: 25,
hands: 2
};
var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system');
var appPage = Script.resolvePath('applause.html');
var button = tablet.addButton({
text: 'Clap',
icon: APP_ICON
});
var open = false;
var shouldUseParticles = true;
var applauseOverlay = "";
var previousHandLocations = [];
var previousHandOrientations = [];
var individualClapSound;
var handsStretched = false;
var applauseInjector;
var applauseEnabled = false;
var dataOpen = {
left: {
pinky: [{ x: -0.0066, y: -0.0224, z: -0.2174, w: 0.9758 }, { x: 0.0112, y: 0.0001, z: 0.0093, w: 0.9999 }, { x: -0.0346, y: 0.0003, z: -0.0073, w: 0.9994 }],
ring: [{ x: -0.0029, y: -0.0094, z: -0.1413, w: 0.9899 }, { x: 0.0112, y: 0.0001, z: 0.0059, w: 0.9999 }, { x: -0.0346, y: 0.0002, z: -0.006, w: 0.9994 }],
middle: [{ x: -0.0016, y: 0, z: -0.0286, w: 0.9996 }, { x: 0.0112, y: -0.0001, z: -0.0063, w: 0.9999 }, { x: -0.0346, y: -0.0003, z: 0.0073, w: 0.9994 }],
index: [{ x: -0.0016, y: 0.0001, z: 0.0199, w: 0.9998 }, { x: 0.0112, y: 0, z: 0.0081, w: 0.9999 }, { x: -0.0346, y: 0.0008, z: -0.023, w: 0.9991 }],
thumb: [{ x: 0.0354, y: 0.0363, z: 0.3275, w: 0.9435 }, { x: -0.0945, y: 0.0938, z: 0.0995, w: 0.9861 }, { x: -0.0952, y: 0.0718, z: 0.1382, w: 0.9832 }]
},
right: {
pinky: [{ x: -0.0034, y: 0.023, z: 0.1051, w: 0.9942 }, { x: 0.0106, y: -0.0001, z: -0.0091, w: 0.9999 }, { x: -0.0346, y: -0.0003, z: 0.0075, w: 0.9994 }],
ring: [{ x: -0.0013, y: 0.0097, z: 0.0311, w: 0.9995 }, { x: 0.0106, y: -0.0001, z: -0.0056, w: 0.9999 }, { x: -0.0346, y: -0.0002, z: 0.0061, w: 0.9994 }],
middle: [{ x: -0.001, y: 0, z: 0.0285, w: 0.9996 }, { x: 0.0106, y: 0.0001, z: 0.0062, w: 0.9999 }, { x: -0.0346, y: 0.0003, z: -0.0074, w: 0.9994 }],
index: [{ x: -0.001, y: 0, z: -0.0199, w: 0.9998 }, { x: 0.0106, y: -0.0001, z: -0.0079, w: 0.9999 }, { x: -0.0346, y: -0.0008, z: 0.0229, w: 0.9991 }],
thumb: [{ x: 0.0355, y: -0.0363, z: -0.3263, w: 0.9439 }, { x: -0.0946, y: -0.0938, z: -0.0996, w: 0.9861 }, { x: -0.0952, y: -0.0719, z: -0.1376, w: 0.9833 }]
}
};
var fingerKeys = ['pinky', 'ring', 'middle', 'index', 'thumb'];
// Position Helper Functions
function midpoint( a, b ) {
return {x: (a.x + b.x) / 2,
y: (a.y + b.y) / 2,
z: (a.z + b.z) / 2};
}
function createPosition() {
if (!HMD.active) {
var DISTANCE = 0.25;
var direction = Quat.getFront(MyAvatar.orientation);
var distance = DISTANCE;
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, distance));
position.y += 3 * DISTANCE * MyAvatar.scale;
return position;
} else {
return midpoint(MyAvatar.getJointPosition("RightHand"),
MyAvatar.getJointPosition("LeftHand"));
}
}
function getRotation() {
return Quat.fromVec3Degrees({x: 0, y: MyAvatar.bodyYaw - 180, z: 0});
}
// Avatar Helper Functions
function adjustApplauseScale() {
handProximityDistance = MyAvatar.getEyeHeight() / HAND_PROXIMITY_SCALE;
}
MyAvatar.scaleChanged.connect(adjustApplauseScale);
function getJointNames(side, finger, count) {
var names = [];
for (var i = 1; i < count + 1; i++) {
var name = side[0].toUpperCase() + side.substring(1) + 'Hand' + finger[0].toUpperCase() + finger.substring(1) + (i);
names.push(name);
}
return names;
}
function makeOpenPalm() {
['right', 'left'].forEach(function(side) {
for (var i = 0; i < fingerKeys.length; i++) {
var finger = fingerKeys[i];
var jointSuffixes = 3; // We need to update rotation of the 0, 1 and 2 joints
var names = getJointNames(side, finger, jointSuffixes);
// update every finger joint
for (var j = 0; j < names.length; j++) {
var index = MyAvatar.getJointIndex(names[j]);
// if no finger is touching restate the default poses
var quatRot = dataOpen[side][finger][j];
MyAvatar.setJointRotation(index, quatRot);
}
}
});
}
function clearJoints() {
['right', 'left'].forEach(function(side) {
for (var i = 0; i < fingerKeys.length; i++) {
var finger = fingerKeys[i];
var jointSuffixes = 3; // We need to update rotation of the 0, 1 and 2 joints
var names = getJointNames(side, finger, jointSuffixes);
// update every finger joint
for (var j = 0; j < names.length; j++) {
var index = MyAvatar.getJointIndex(names[j]);
// if no finger is touching restate the default poses
MyAvatar.clearJointData(index);
}
}
});
}
function compareRotations(q1, q2) {
var threshold = 0.85;
return (Quat.dot(q1, q2) <= threshold);
}
function getRandomApplauseSound() {
return INDIVIDUAL_CLAP_URLS[Math.round(Math.random() * INDIVIDUAL_CLAP_URLS.length - 1)];
}
function checkHandsDistance() {
var handPositionR = MyAvatar.getJointPosition('RightHand');
var handPositionL = MyAvatar.getJointPosition('LeftHand');
var handRotationR = MyAvatar.getRightPalmRotation();
var handRotationL = MyAvatar.getLeftPalmRotation();
var oldRotationR = previousHandOrientations[0];
var oldRotationL = previousHandOrientations[1];
if (Vec3.distance(handPositionL, handPositionR) <= handProximityDistance) {
if (!handsStretched) {
makeOpenPalm();
handsStretched = true;
}
} else {
if (handsStretched) {
clearJoints();
handsStretched = false;
}
}
if ((Vec3.distance(handPositionL, handPositionR) <= handProximityDistance)
&& (compareRotations(oldRotationR, handRotationR)
|| compareRotations(oldRotationL, handRotationL)) && applauseEnabled) {
playSingleClap();
previousHandOrientations = [];
previousHandOrientations.push(handRotationR);
previousHandOrientations.push(handRotationL);
}
}
// Tablet Handlers
function onClicked() {
if (!tablet) {
print("Warning in onClicked(): 'tablet' is undefined");
return;
}
if (open) {
tablet.gotoHomeScreen();
} else {
tablet.gotoWebScreen(appPage);
}
}
function onWebEventReceived(event) {
if (typeof event === 'string') {
event = JSON.parse(event);
}
if (!tablet) {
print("Warning in onWebEventReceived(): 'tablet' is undefined");
return;
}
switch (event.type) {
case 'applause-app-ready':
var webEvent = {
type: 'setup',
enabled : Settings.getValue(CAN_APPLAUD_SETTING),
particles: Settings.getValue(SHOULD_DISPLAY_PARTICLES_APPLAUSE, true),
HMD : HMD.active
};
tablet.emitScriptEvent(JSON.stringify(webEvent));
break;
case 'clap':
if (applauseEnabled) {
playSingleClap();
}
break;
case 'applause-enabled':
applauseEnabled = event.value;
Settings.setValue(CAN_APPLAUD_SETTING, applauseEnabled);
if (applauseEnabled) {
setup();
} else {
disableApplause();
}
break;
case 'change-particles':
shouldUseParticles = event.value;
Settings.setValue(SHOULD_DISPLAY_PARTICLES_APPLAUSE, event.value);
break;
default:
break;
}
}
var setup = function() {
if (HMD.active) {
removeDesktopOverlay();
Script.update.connect(checkHandsDistance);
previousHandLocations.push(MyAvatar.getJointPosition('RightHand'));
previousHandLocations.push(MyAvatar.getJointPosition('LeftHand'));
previousHandOrientations.push(MyAvatar.getRightPalmRotation());
previousHandOrientations.push(MyAvatar.getLeftPalmRotation());
} else {
addDesktopOverlay();
Controller.mousePressEvent.connect(mousePressEvent);
Script.update.disconnect(checkHandsDistance);
}
};
var disableApplause = function() {
if (HMD.active) {
Script.update.disconnect(checkHandsDistance);
clearJoints();
handsStretched = false;
} else {
Controller.mousePressEvent.disconnect(mousePressEvent);
removeDesktopOverlay();
}
};
var toggleOnHMDSwap = function() {
if (Settings.getValue(CAN_APPLAUD_SETTING)) {
setup();
}
};
var mousePressEvent = function(event) {
if (!event.isLeftButton) {
return;
}
var selectedResult = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (selectedResult === applauseOverlay) {
Overlays.editOverlay(applauseOverlay, { imageURL : APPLAUSE_BUTTON_PRESSED});
Script.setTimeout(function(){
Overlays.editOverlay(applauseOverlay, { imageURL: APPLAUSE_BUTTON_IMAGE});
}, BUTTON_PRESS_TIMEOUT);
playSingleClap();
}
};
function keyPressEvent(event) {
if (event.text === APPLAUSE_KEY && applauseEnabled) {
playSingleClap();
}
}
function appEnding() {
button.clicked.disconnect(onClicked);
if (tablet) {
tablet.removeButton(button);
tablet.webEventReceived.disconnect(onWebEventReceived);
}
Controller.mousePressEvent.disconnect(mousePressEvent);
Controller.keyPressEvent.disconnect(keyPressEvent);
removeDesktopOverlay();
Settings.setValue(CAN_APPLAUD_SETTING, false);
Settings.setValue(HAS_APPLAUSE_APP_SETTING, false);
HMD.displayModeChanged.disconnect(toggleOnHMDSwap);
MyAvatar.disconnect(adjustApplauseScale);
}
button.clicked.connect(onClicked);
Settings.setValue(HAS_APPLAUSE_APP_SETTING, true);
if (tablet) {
tablet.webEventReceived.connect(onWebEventReceived);
}
Script.scriptEnding.connect(appEnding);
HMD.displayModeChanged.connect(toggleOnHMDSwap);
Controller.keyPressEvent.connect(keyPressEvent);
prefetchAudio();
function prefetchAudio() {
for (var i = 0; i < INDIVIDUAL_CLAP_URLS.length; i++) {
SoundCache.prefetch(INDIVIDUAL_CLAP_URLS[i]);
}
}
function playSoundAndTriggerHaptics() {
if (applauseInjector !== undefined && applauseInjector.isPlaying()) {
return;
}
individualClapSound = SoundCache.getSound(getRandomApplauseSound());
if (individualClapSound.downloaded) {
applauseInjector = Audio.playSound(
individualClapSound,
{
volume: APPLAUSE_VOLUME,
localOnly: false,
position: MyAvatar.position
}
);
}
if (HMD.active) {
Controller.triggerHapticPulse(HAPTICS.strength, HAPTICS.duration, HAPTICS.hands);
}
}
// Applause Functions
function playParticleEffect() {
var properties = {
type: 'ParticleEffect',
position: createPosition(),
rotation : getRotation(),
isEmitting:true,
lifespan:10,
maxParticles:1,
textures:'http://hifi-content.s3.amazonaws.com/alan/dev/Particles/heart-2.png',
emitRate:1,
emitSpeed:3,
emitOrientation: Quat.fromVec3Degrees({x: 0, y: 0, z: 0}),
particleRadius:0.25,
radiusSpread:0.25,
radiusStart:0.01,
radiusFinish:0.15,
color:{red:0,blue:255,green:0},
colorSpread:{red:0,blue:0,green:0},
colorStart:{red:0,blue:255,green:0},
colorFinish:{red:0,blue:255,green:85},
emitAcceleration: {x: 0, y: 0, z: 0},
accelerationSpread: {x:0.01, y: 0.01, z: 0.01},
alpha:1.0,
alphaSpread:0.25,
alphaStart:1,
alphaFinish:0,
lifetime: 1,
polarStart: 0,
polarFinish: 0.0523599
};
Entities.addEntity(properties, true);
}
function playSingleClap() {
playSoundAndTriggerHaptics();
if (shouldUseParticles) {
playParticleEffect();
}
}
function removeDesktopOverlay(){
Overlays.deleteOverlay(applauseOverlay);
}
function addDesktopOverlay() {
removeDesktopOverlay();
var windowHeight = Controller.getViewportDimensions().y;
applauseOverlay = Overlays.addOverlay('image', {
imageURL: APPLAUSE_BUTTON_IMAGE,
x: 0,
y: windowHeight - BUTTON_DIMENSIONS.y - WINDOW_Y_OFFSET,
width: BUTTON_DIMENSIONS.x,
height: BUTTON_DIMENSIONS.y,
alpha: 1.0,
visible: true
});
}
}());