content/hifi-content/caitlyn/dev/emoter/avatar-control.js
2022-02-13 22:19:19 +01:00

356 lines
No EOL
11 KiB
JavaScript

/* global Script */
/*
Custom Avatar Blendshape / Script UI Trigger
Released under Creative Commons Attribution, 2018 by Menithal
https://creativecommons.org/licenses/by/3.0/
*/
const APP_URL = "index.html?fggf";
const APP = Script.resolvePath(APP_URL);
var setInterval = Script.setInterval;
var setTimeout = Script.setTimeout;
// Maps the "unconventional Blendshapes" that are not used by Hifi's audio system
// To custom emoticon system, Modify to your hearts content
var emotionMap = {
"Normal": "",
"Content": "LipsUpperUp",
"Calm": "LipsLowerDown",
"Stare": "LipsLowerOpen",
"Suprised": "ChinLowerRaise",
">_<": "ChinUpperRaise", // >_<
"Λ": "JawFwd",
"o": "JawChew",
"▲": "JawLeft" // Triangle
};
function toggleEmote(target, state, booleanStates) {
return {
emote: target,
state: state,
textStates: booleanStates
};
}
var rightEyeStatic = {
"x": -1.3767878215276141e-7,
"y": 0.7071066498756409,
"z": 0.70710688829422,
"w": -1.376787679419067e-7
};
var leftEyeStatic = {
"x": -8.429372400087232e-8,
"y": 0.7071070671081543,
"z": 0.7071065902709961,
"w": -2.2476429606399506e-8
};
// These are modes that should stay on or off, instead of temporary "expressions"
var toggleMap = {
"CrossEye": toggleEmote(function (state) {
var rightEyeOverride = MyAvatar.getJointIndex("RightEyeOverride");
var leftEyeOverride = MyAvatar.getJointIndex("LeftEyeOverride");
if (state) {
MyAvatar.setJointRotation(rightEyeOverride, Quat.multiply(rightEyeStatic, Quat.fromVec3Degrees({
x: 15,
y: 0,
z: -35
})));
MyAvatar.setJointRotation(leftEyeOverride, Quat.multiply(leftEyeStatic, Quat.fromVec3Degrees({
x: -15,
y: 0,
z: 35
})));
} else {
MyAvatar.setJointRotation(rightEyeOverride, rightEyeStatic);
MyAvatar.setJointRotation(leftEyeOverride, leftEyeStatic);
// Return Joint rotations back to 0.
}
}, false, ["Off", "On"]),
"Glazed Pupil": toggleEmote("MouthDimple_L", false, ["Off", "On"]),
"Tears": toggleEmote("MouthDimple_R", false, ["Off", "On"]),
"Starry Eye": toggleEmote("LipsPucker", false, ["Off", "On"]), // Or was it pucker?
"Highlight": toggleEmote("EyeIn_R", false, ["Off", "On"]),
"Tiny Pupils": toggleEmote("EyeIn_L", false, ["Off", "On"]),
"Teeth": toggleEmote("EyeDown_L", false, ["On", "Off"]),
"Fangs": toggleEmote("EyeDown_R", false, ["Off", "On"]),
"Teeth Lower": toggleEmote("EyeOut_L", false, ["Off", "On"]),
"Serious": toggleEmote("EyeUp_L", false, ["Off", "On"]),
"Sadness": toggleEmote("EyeUp_R", false, ["Off", "On"]),
"Cheerful": toggleEmote("EyeSquint_L", false, ["Off", "On"]),
"Anger": toggleEmote("EyeSquint_R", false, ["Off", "On"]),
"Flat Brows": toggleEmote("CheekSquint_L", false, ["Off", "On"]),
"Noseline": toggleEmote("Sneer", false, ["On", "Off"]),
"Blush": toggleEmote("MouthFrown_L", false, ["Off", "On"]),
"Big Highlight": toggleEmote("MouthFrown_R", false, ["Off", "On"]),
"Clear Toggles": toggleEmote(function (state) {
var values = Object.values(toggleMap);
for (var x = 0; x < values.length; x++) {
if (typeof values[x].emote !== "function") {
MyAvatar.setBlendshape(values[x].emote, 0);
values[x].state = false;
} else {}
}
}, false, ["", ""])
};
function getCurrentStatus() {
var keys = Object.keys(toggleMap);
var values = Object.values(toggleMap);
var results = [];
for (var i = 0; i < values.length; i++) {
results[i] = [keys[i], values[i].textStates[values[i].state ? 1 : 0]];
}
return results;
}
function turnEmotesOff(list, blendshape) {
for (var x = 0; x < list.length; x++) {
if (list[x] !== blendshape) {
MyAvatar.setBlendshape(list[x], 0);
} else {
MyAvatar.setBlendshape(list[x], 1);
}
}
}
var activeApp = false;
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
// TODO: Investigate a better way to automate this
var tabletButton = tablet.addButton({
text: "AVATAR",
sortOrder: 13
});
var onWebEventReceived;
function emitWebEvent(event) {
tablet.emitScriptEvent(JSON.stringify(event));
}
MyAvatar.setForceFaceTrackerConnected(true);
var onclick = function (e) {
MyAvatar.setForceFaceTrackerConnected(true);
if (activeApp) {
tablet.gotoHomeScreen();
} else {
tablet.gotoWebScreen(APP);
}
activeApp = !activeApp;
}
// Hifi Polyfill
if (!Object.values) {
Object.prototype.values = function (val) {
return Object.keys(val).map(function (key) {
return val[key];
});
};
}
function cap(value, max) {
if (value > max) {
return max;
}
return value;
}
var onWebEventReceived = function (event) {
var data = JSON.parse(event);
if (data.type === "emote_app_load_complete") {
console.log("Build App", JSON.stringify(tablet));
emitWebEvent({
type: "emote_app_build",
emotes: Object.keys(emotionMap),
states: getCurrentStatus(toggleMap)
});
return true;
} else if (data.type === "emote_app_play") {
console.log("emote app play", data.message);
targetEmote = emotionMap[data.message];
emoteValues = Object.values(emotionMap);
if (typeof targetEmote !== "function") {
turnEmotesOff(emoteValues, targetEmote);
} else {
targetEmote();
}
} else if (data.type === "emote_app_toggle") {
console.log("emote app toggle", data.message);
targetEmote = toggleMap[data.message];
targetEmote.state = !targetEmote.state;
if (typeof targetEmote.emote !== "function") {
MyAvatar.setBlendshape(targetEmote.emote, targetEmote.state);
} else {
targetEmote.emote(targetEmote.state);
}
emitWebEvent({
type: "emote_app_state_sync",
states: getCurrentStatus(toggleMap)
});
}
return false;
};
var controllerDirections = [
"LipsUpperUp", // Content UP
"ChinLowerRaise", // Suprised DOWN
"ChinUpperRaise", // >_< LEFT
"EyeUp_R" // Sadness RIGHT
];
var currentCoordinates = {
x: 0,
y: 0
};
var DISABLED_CONTROL_GROUP = "com.menithal.avatar.emotions.disabled"
var ENABLED_CONTROL_GROUP = "com.menithal.avatar.emotions.active"
var disabledControls = Controller.newMapping(DISABLED_CONTROL_GROUP);
var enabledControls = Controller.newMapping(ENABLED_CONTROL_GROUP);
var Standard = Controller.Standard;
var Vive = Controller.Hardware.Vive;
var OculusTouch = Controller.Hardware.OculusTouch;
var GamePad = Controller.Hardware.GamePad;
function emotionControl(toggle) {
console.log("Emote ", toggle);
if (toggle) {
Controller.disableMapping(DISABLED_CONTROL_GROUP);
Controller.enableMapping(ENABLED_CONTROL_GROUP);
} else {
Controller.enableMapping(DISABLED_CONTROL_GROUP);
Controller.disableMapping(ENABLED_CONTROL_GROUP);
currentCoordinates = {
x: 0,
y: 0
};
// Clear Above
blendShapes(currentCoordinates);
var values = Object.values(toggleMap);
for (var x = 0; x < toggleMap.length; x++) {
for (var y = 0; y < controllerDirections; y++) {
if (controllerDirections[y] === toggleMap[x].emote) {
values[y].state = false;
}
}
}
}
return true;
}
var MIN_TRESHOLD = 0.05;
function blendShapes(coordinates) {
console.log("coordinates ", coordinates);
MyAvatar.setBlendshape(controllerDirections[2], cap(Math.abs(coordinates.x > MIN_TRESHOLD ? 0 : coordinates.x) * 2.5, 1.0));
MyAvatar.setBlendshape(controllerDirections[3], cap(Math.abs(coordinates.x > MIN_TRESHOLD ? coordinates.x : 0) * 2.5, 1.0));
MyAvatar.setBlendshape(controllerDirections[0], cap(Math.abs(coordinates.y > MIN_TRESHOLD ? 0 : coordinates.y) * 2.5, 1.0));
MyAvatar.setBlendshape(controllerDirections[1], cap(Math.abs(coordinates.y > MIN_TRESHOLD ? coordinates.y : 0) * 2.5, 1.0));
}
function updateX(val) {
if (Math.abs(val) > 0) {
currentCoordinates.x = val;
}
blendShapes(currentCoordinates);
}
function updateY(val) {
if (Math.abs(val) > 0) {
currentCoordinates.y = val;
}
blendShapes(currentCoordinates);
}
function clear(val) {
currentCoordinates = {
x: 0,
y: 0
};
}
function empty(val) {}
if (MyAvatar.getDominantHand() === "right") {
disabledControls.from(Standard.RightSecondaryThumb).to(emotionControl);
enabledControls.from(Standard.RightSecondaryThumb).to(emotionControl);
enabledControls.from(Standard.LX).to(updateX);
enabledControls.from(Standard.LY).to(updateY);
if (GamePad) {
disabledControls.from(GamePad.RT).to(emotionControl);
enabledControls.from(GamePad.RT).to(emotionControl);
enabledControls.from(GamePad.LX).to(updateX);
enabledControls.from(GamePad.LY).to(updateY);
}
if (Vive) {
enabledControls.from(Vive.LX).to(updateX);
enabledControls.from(Vive.LY).to(updateY);
enabledControls.from(Vive.RightApplicationMenu).to(emotionControl)
}
if (OculusTouch) {
enabledControls.from(OculusTouch.LX).to(updateX);
enabledControls.from(OculusTouch.LY).to(updateX);
enabledControls.from(OculusTouch.B).to(emotionControl)
}
enabledControls.from(Standard.LS).to(clear);
enabledControls.from(Standard.LeftPrimaryThumb).to(empty);
} else {
disabledControls.from(Standard.LeftSecondaryThumb).to(emotionControl);
enabledControls.from(Standard.LeftSecondaryThumb).to(emotionControl);
enabledControls.from(Standard.RY).to(updateX);
enabledControls.from(Standard.RX).to(updateY);
if (GamePad) {
disabledControls.from(GamePad.LT).to(emotionControl);
disabledControls.from(GamePad.LT).to(emotionControl);
enabledControls.from(GamePad.RX).to(updateX);
enabledControls.from(GamePad.RY).to(updateY);
}
if (Vive) {
enabledControls.from(Vive.LeftSecondaryThumb).to(emotionControl)
enabledControls.from(Vive.LX).to(updateX);
enabledControls.from(Vive.LY).to(updateY);
enabledControls.from(Vive.LeftApplicationMenu).to(emotionControl)
}
if (OculusTouch) {
enabledControls.from(OculusTouch.RX).to(updateX);
enabledControls.from(OculusTouch.RY).to(updateX);
enabledControls.from(OculusTouch.Y).to(emotionControl)
}
enabledControls.from(Standard.RS).to(clear);
enabledControls.from(Standard.RightPrimaryThumb).to(empty);
}
Controller.enableMapping(DISABLED_CONTROL_GROUP);
tabletButton.clicked.connect(onclick);
tablet.webEventReceived.connect(onWebEventReceived);
Script.scriptEnding.connect(function () {
if (activeApp) {
tablet.webEventReceived.disconnect(onWebEventReceived);
}
activeApp = false;
tabletButton.clicked.disconnect(onclick);
tablet.removeButton(tabletButton);
});