mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
463 lines
No EOL
14 KiB
JavaScript
463 lines
No EOL
14 KiB
JavaScript
/*
|
|
|
|
Avimoji
|
|
avimoji_app.js
|
|
Created by Milad Nazeri on 2019-04-25
|
|
Copyright 2019 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
|
|
|
|
*/
|
|
|
|
(function () {
|
|
|
|
// *************************************
|
|
// START dependencies
|
|
// *************************************
|
|
// #region dependencies
|
|
|
|
// Custom module for handling entities
|
|
var EntityMaker = Script.require("./resources/modules/entityMaker.js?" + Date.now());
|
|
// Add nice smoothing functions to the animations
|
|
var EasingFunctions = Script.require("./resources/modules/easing.js");
|
|
|
|
// The information needed to properly use the sprite sheets and get the general information
|
|
// about the emojis
|
|
var emojiList = Script.require("./resources/node/emojiList.json?" + Date.now());
|
|
// The location to find where the individual emoji icons are
|
|
var CONFIG = Script.require("./resources/config.json?" + Date.now());
|
|
// Where to load the images from taken from the Config above
|
|
var imageURLBase = CONFIG.baseImagesURL;
|
|
|
|
// #endregion
|
|
// *************************************
|
|
// END dependencies
|
|
// *************************************
|
|
|
|
// *************************************
|
|
// START utility
|
|
// *************************************
|
|
// #region utility
|
|
|
|
|
|
// CONSTS
|
|
// UTF-Codes are stored on the first index of one of the keys on a returned emoji object.
|
|
// Just makes the code a little easier to read
|
|
var UTF_CODE = 0;
|
|
|
|
// Make the emoji groups
|
|
var MAX_PER_GROUP = 250;
|
|
var emojiChunks = [];
|
|
function makeEmojiChunks() {
|
|
for (var i = 0, len = emojiList.length; i < len; i += MAX_PER_GROUP) {
|
|
emojiChunks.push(emojiList.slice(i, i + MAX_PER_GROUP));
|
|
}
|
|
}
|
|
|
|
|
|
// Play sound borrowed from bingo app:
|
|
// Plays the specified sound at the specified position, volume, and localOnly
|
|
// Only plays a sound if it is downloaded.
|
|
// Only plays one sound at a time.
|
|
var soundUrl = Script.resolvePath('./resources/sounds/emojiPopSound.wav');
|
|
var popSound = SoundCache.getSound(soundUrl);
|
|
var injector;
|
|
var DEFAULT_VOLUME = 0.0003;
|
|
var local = false;
|
|
function playSound(sound, volume, position, localOnly) {
|
|
sound = sound || popSound;
|
|
volume = volume || DEFAULT_VOLUME;
|
|
position = position || MyAvatar.position;
|
|
localOnly = localOnly || local;
|
|
if (sound.downloaded) {
|
|
if (injector) {
|
|
injector.stop();
|
|
}
|
|
injector = Audio.playSound(sound, {
|
|
position: position,
|
|
volume: volume,
|
|
localOnly: localOnly
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
// prune old emojis on you
|
|
function pruneOldAvimojis() {
|
|
MyAvatar.getAvatarEntitiesVariant()
|
|
.forEach(function (avatarEntity) {
|
|
if (avatarEntity && avatarEntity.properties.name.toLowerCase().indexOf("avimoji") > -1) {
|
|
Entities.deleteEntity(avatarEntity.id);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// help send an emoji chunk to load up faster in the tablet
|
|
var INTERVAL = 200;
|
|
var currentChunk = 0;
|
|
function sendEmojiChunks() {
|
|
if (currentChunk >= emojiChunks.length) {
|
|
currentChunk = 0;
|
|
return;
|
|
} else {
|
|
var chunk = emojiChunks[currentChunk];
|
|
ui.sendMessage({
|
|
app: "avimoji",
|
|
method: "sendChunk",
|
|
chunkNumber: currentChunk,
|
|
totalChunks: emojiChunks.length,
|
|
chunk: chunk
|
|
});
|
|
currentChunk++;
|
|
Script.setTimeout(function () {
|
|
sendEmojiChunks();
|
|
}, INTERVAL);
|
|
}
|
|
}
|
|
|
|
|
|
// #endregion
|
|
// *************************************
|
|
// END utility
|
|
// *************************************
|
|
|
|
// *************************************
|
|
// START ui_handlers
|
|
// *************************************
|
|
// #region ui_handlers
|
|
|
|
|
|
// what to do when someone selects an emoji in the tablet
|
|
var selectedEmoji = null;
|
|
var lastEmoji = null;
|
|
function handleSelectedEmoji(data) {
|
|
var emoji = data.emoji;
|
|
if (selectedEmoji && selectedEmoji.code[UTF_CODE] === emoji.code[UTF_CODE]) {
|
|
maybePlayPop("off");
|
|
selectedEmoji = null;
|
|
return;
|
|
} else {
|
|
selectedEmoji = emoji;
|
|
lastEmoji = emoji;
|
|
addEmoji(selectedEmoji);
|
|
}
|
|
ui.sendMessage({
|
|
app: "avimoji",
|
|
method: "updateEmojiPicks",
|
|
selectedEmoji: selectedEmoji
|
|
});
|
|
}
|
|
|
|
|
|
// handle what happens when unselect an emoji
|
|
function handleSelectedRemoved() {
|
|
maybePlayPop("off");
|
|
selectedEmoji = null;
|
|
ui.sendMessage({
|
|
app: "avimoji",
|
|
method: "updateEmojiPicks",
|
|
selectedEmoji: selectedEmoji
|
|
});
|
|
}
|
|
|
|
function onDomainChanged() {
|
|
pruneOldAvimojis();
|
|
maybeClearPop();
|
|
if (currentEmoji && currentEmoji.id) {
|
|
currentEmoji.destroy();
|
|
selectedEmoji = null;
|
|
}
|
|
maybeClearPop();
|
|
if (ui.isOpen) {
|
|
ui.close();
|
|
}
|
|
}
|
|
|
|
|
|
// #endregion
|
|
// *************************************
|
|
// END ui_handlers
|
|
// *************************************
|
|
|
|
|
|
|
|
// *************************************
|
|
// START avimoji
|
|
// *************************************
|
|
// #region avimoji
|
|
|
|
|
|
// what happens when we need to add an emoji over a user
|
|
var billboardMode = "full";
|
|
var currentSelectedDimensions = null;
|
|
function addEmoji(emoji) {
|
|
if (currentEmoji && currentEmoji.id) {
|
|
maybePlayPop("offThenOn");
|
|
} else {
|
|
createEmoji(emoji);
|
|
}
|
|
}
|
|
|
|
|
|
// creating the actual emoji that isn't an animation
|
|
var ABOVE_HEAD = 0.60;
|
|
var EMOJI_CONST_SCALER = 0.27;
|
|
var currentEmoji = new EntityMaker("avatar");
|
|
var EMOJI_X_OFFSET = -0.01;
|
|
var DEFAULT_EMOJI_SIZE = 0.27;
|
|
var emojiSize = Settings.getValue("avimoji/emojiSize", DEFAULT_EMOJI_SIZE);
|
|
function createEmoji(emoji) {
|
|
var neckPosition, avatarScale, aboveNeck, emojiPosition;
|
|
avatarScale = MyAvatar.scale;
|
|
aboveNeck = ABOVE_HEAD;
|
|
neckPosition = Vec3.subtract(MyAvatar.getNeckPosition(), MyAvatar.position);
|
|
emojiPosition = Vec3.sum(
|
|
neckPosition,
|
|
[
|
|
EMOJI_X_OFFSET,
|
|
avatarScale * aboveNeck * (1 + emojiSize * EMOJI_CONST_SCALER),
|
|
0
|
|
]);
|
|
var IMAGE_SIZE = avatarScale * emojiSize;
|
|
var dimensions = { x: IMAGE_SIZE, y: IMAGE_SIZE, z: IMAGE_SIZE };
|
|
currentSelectedDimensions = dimensions;
|
|
|
|
var parentID = MyAvatar.sessionUUID;
|
|
var imageURL = imageURLBase + emoji.code[UTF_CODE] + ".png";
|
|
currentEmoji
|
|
.add('type', "Image")
|
|
.add('name', 'AVIMOJI')
|
|
.add('localPosition', emojiPosition)
|
|
.add('dimensions', [0, 0, 0])
|
|
.add('parentID', parentID)
|
|
.add('emissive', true)
|
|
.add('collisionless', true)
|
|
.add('imageURL', imageURL)
|
|
.add('billboardMode', billboardMode)
|
|
.add('ignorePickIntersection', true)
|
|
.add('alpha', 1)
|
|
.add('grab', {grabbable: false})
|
|
.create();
|
|
maybePlayPop("on");
|
|
}
|
|
|
|
|
|
// #endregion
|
|
// *************************************
|
|
// END avimoji
|
|
// *************************************
|
|
|
|
// *************************************
|
|
// START animation
|
|
// *************************************
|
|
// #region animation
|
|
|
|
|
|
// see what we need to do when an emoji gets clicked
|
|
var DURATION = POP_ANIMATION_DURATION_MS + 100;
|
|
function maybePlayPop(type) {
|
|
maybeClearPop();
|
|
popType = type;
|
|
playPopInterval = Script.setInterval(playPopAnimation, POP_DURATION_PER_STEP);
|
|
}
|
|
|
|
|
|
// maybe clear a pop up animation not in animation mode
|
|
function maybeClearPop() {
|
|
if (playPopInterval) {
|
|
Script.clearTimeout(playPopInterval);
|
|
playPopInterval = null;
|
|
currentPopScale = null;
|
|
currentPopStep = 1;
|
|
isPopPlaying = false;
|
|
popType = null;
|
|
}
|
|
}
|
|
|
|
|
|
// play an animation pop in
|
|
var currentPopStep = 1;
|
|
var POP_ANIMATION_DURATION_MS = 170;
|
|
var POP_ANIMATION_STEPS = 10;
|
|
var POP_DURATION_PER_STEP = POP_ANIMATION_DURATION_MS / POP_ANIMATION_STEPS;
|
|
var currentPopScale = null;
|
|
var MAX_POP_SCALE = 1;
|
|
var MIN_POP_SCALE = 0.0;
|
|
var POP_SCALE_DISTANCE = MAX_POP_SCALE - MIN_POP_SCALE;
|
|
var POP_PER_STEP = POP_SCALE_DISTANCE / POP_ANIMATION_STEPS;
|
|
var isPopPlaying = false;
|
|
var popType = null;
|
|
var playPopInterval = null;
|
|
function playPopAnimation() {
|
|
var dimensions;
|
|
|
|
// Handle if this is the first step of the animation
|
|
|
|
if (currentPopStep === 1) {
|
|
isPopPlaying = true;
|
|
if (popType === "in") {
|
|
currentPopScale = MIN_POP_SCALE;
|
|
} else {
|
|
currentPopScale = MAX_POP_SCALE;
|
|
playSound();
|
|
}
|
|
}
|
|
|
|
// Setup the animation step
|
|
|
|
if (popType === "in") {
|
|
currentPopScale += POP_PER_STEP;
|
|
dimensions = Vec3.multiply(currentSelectedDimensions, EasingFunctions.easeInCubic(currentPopScale));
|
|
} else {
|
|
currentPopScale -= POP_PER_STEP;
|
|
dimensions = Vec3.multiply(currentSelectedDimensions, EasingFunctions.easeOutCubic(currentPopScale));
|
|
}
|
|
currentPopStep++;
|
|
currentEmoji.edit("dimensions", dimensions);
|
|
|
|
// Handle if it's the end of the animation step
|
|
|
|
if (currentPopStep === POP_ANIMATION_STEPS) {
|
|
if (popType === "in") {
|
|
playSound();
|
|
} else {
|
|
if (currentEmoji && currentEmoji.id) {
|
|
currentEmoji.destroy();
|
|
currentEmoji = new EntityMaker("avatar");
|
|
if (popType === "out") {
|
|
selectedEmoji = null;
|
|
ui.sendMessage({
|
|
app: "avimoji",
|
|
method: "updateEmojiPicks",
|
|
selectedEmoji: selectedEmoji
|
|
});
|
|
}
|
|
if (popType === "offThenOn") {
|
|
Script.setTimeout(function () {
|
|
createEmoji(selectedEmoji);
|
|
}, DURATION);
|
|
}
|
|
}
|
|
}
|
|
maybeClearPop();
|
|
}
|
|
}
|
|
|
|
|
|
// #endregion
|
|
// *************************************
|
|
// END animation
|
|
// *************************************
|
|
|
|
// *************************************
|
|
// START messages
|
|
// *************************************
|
|
// #region messages
|
|
|
|
|
|
// handle getting a message from the tablet
|
|
function onMessage(message) {
|
|
if (message.app !== "avimoji") {
|
|
return;
|
|
}
|
|
|
|
switch (message.method) {
|
|
case "eventBridgeReady":
|
|
ui.sendMessage({
|
|
app: "avimoji",
|
|
method: "updateUI",
|
|
selectedEmoji: selectedEmoji
|
|
});
|
|
sendEmojiChunks();
|
|
break;
|
|
|
|
case "handleSelectedEmoji":
|
|
handleSelectedEmoji(message.data);
|
|
break;
|
|
|
|
case "handleSelectedRemoved":
|
|
handleSelectedRemoved();
|
|
break;
|
|
|
|
default:
|
|
console.log("Unhandled message from avimoji_ui.js", message);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// #endregion
|
|
// *************************************
|
|
// END messages
|
|
// *************************************
|
|
|
|
// *************************************
|
|
// START main
|
|
// *************************************
|
|
// #region main
|
|
|
|
|
|
// startup the app
|
|
var BUTTON_NAME = "AVIMOJI";
|
|
var APP_UI_URL = Script.resolvePath('./resources/avimoji_ui.html');
|
|
var AppUI = Script.require('./resources/modules/appUi.js?' + Date.now());
|
|
var ui;
|
|
var emojiCodeMap;
|
|
function startup() {
|
|
// make a map of just the utf codes to help with accesing
|
|
emojiCodeMap = emojiList.reduce(function (previous, current, index) {
|
|
if (current && current.code && current.code.length > 0 && current.code[UTF_CODE]) {
|
|
previous[current.code[UTF_CODE]] = index;
|
|
return previous;
|
|
}
|
|
}, {});
|
|
ui = new AppUI({
|
|
buttonName: BUTTON_NAME,
|
|
home: APP_UI_URL,
|
|
onMessage: onMessage,
|
|
graphicsDirectory: Script.resolvePath("./resources/images/icons/")
|
|
});
|
|
|
|
pruneOldAvimojis();
|
|
makeEmojiChunks();
|
|
|
|
|
|
Script.scriptEnding.connect(scriptEnding);
|
|
}
|
|
|
|
|
|
startup();
|
|
|
|
|
|
// #endregion
|
|
// *************************************
|
|
// END main
|
|
// *************************************
|
|
|
|
// *************************************
|
|
// START cleanup
|
|
// *************************************
|
|
// #region cleanup
|
|
|
|
|
|
function scriptEnding() {
|
|
if (ui.isOpen) {
|
|
ui.onClosed();
|
|
}
|
|
pruneOldAvimojis();
|
|
Window.domainChanged.disconnect(onDomainChanged);
|
|
if (currentEmoji && currentEmoji.id) {
|
|
currentEmoji.destroy();
|
|
}
|
|
maybeClearPop();
|
|
}
|
|
|
|
|
|
// #endregion
|
|
// *************************************
|
|
// END cleanup
|
|
// *************************************
|
|
|
|
})(); |