mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-07 03:32:42 +02:00
Merge pull request #579 from Armored-Dragon/FBT-Smoothing-Fix
Avatar Smoothing
This commit is contained in:
commit
57a2ebb800
10 changed files with 426 additions and 2 deletions
|
@ -219,4 +219,3 @@ Script.scriptEnding.connect(function() {
|
|||
}
|
||||
tablet.removeButton(tabletButton);
|
||||
});
|
||||
|
|
@ -237,4 +237,3 @@ Script.scriptEnding.connect(function() {
|
|||
}
|
||||
tablet.removeButton(tabletButton);
|
||||
});
|
||||
|
115
scripts/developer/characterSmoothing/characterSmoothing.js
Normal file
115
scripts/developer/characterSmoothing/characterSmoothing.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
//
|
||||
// Copyright 2023 Overte e.V.
|
||||
|
||||
let smoothing_settings = {
|
||||
enabled: true,
|
||||
targets: {
|
||||
LeftHand: { transform: 1, rotation: 1 },
|
||||
RightHand: { transform: 1, rotation: 1 },
|
||||
LeftFoot: { transform: 1, rotation: 1 },
|
||||
RightFoot: { transform: 1, rotation: 1 },
|
||||
Hips: { transform: 1, rotation: 1 },
|
||||
Spine2: { transform: 1, rotation: 1 },
|
||||
},
|
||||
};
|
||||
|
||||
let mapping;
|
||||
let mapping_settings = {
|
||||
name: "org.overte.controllers.smoothing",
|
||||
channels: [],
|
||||
};
|
||||
|
||||
const html_url = Script.resolvePath("./ui/index.html");
|
||||
let tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
let shown = false;
|
||||
|
||||
tablet.screenChanged.connect(onScreenChanged);
|
||||
Script.scriptEnding.connect(shutdownTabletApp);
|
||||
|
||||
let tabletButton = tablet.addButton({
|
||||
text: "AviSmooth",
|
||||
icon: Script.resolvePath("./img/icon.png"),
|
||||
activeIcon: Script.resolvePath("./img/icon-a.png"),
|
||||
});
|
||||
|
||||
tabletButton.clicked.connect(() => {
|
||||
tablet.gotoWebScreen(html_url);
|
||||
});
|
||||
|
||||
function onScreenChanged(type, url) {
|
||||
if (type !== "Web" || url !== html_url) {
|
||||
tablet.webEventReceived.disconnect(onWebEventReceived);
|
||||
tabletButton.editProperties({ isActive: false });
|
||||
shown = false;
|
||||
} else {
|
||||
tabletButton.editProperties({ isActive: true });
|
||||
tablet.webEventReceived.connect(onWebEventReceived);
|
||||
shown = true;
|
||||
smoothing_settings = Settings.getValue(
|
||||
"smoothing_settings",
|
||||
smoothing_settings
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function shutdownTabletApp() {
|
||||
if (mapping) mapping.disable(); // Disable custom mapping
|
||||
tablet.removeButton(tabletButton); // Remove the app
|
||||
tablet.screenChanged.disconnect(onScreenChanged);
|
||||
tablet.webEventReceived.disconnect(onWebEventReceived);
|
||||
}
|
||||
|
||||
const _sendMessage = (message) =>
|
||||
tablet.emitScriptEvent(JSON.stringify(message));
|
||||
|
||||
function onWebEventReceived(message) {
|
||||
message = JSON.parse(message);
|
||||
|
||||
if (message.action === "ready") {
|
||||
_sendMessage({
|
||||
action: "initialize",
|
||||
data: smoothing_settings,
|
||||
});
|
||||
}
|
||||
|
||||
if (message.action === "new_settings") {
|
||||
smoothing_settings = message.data;
|
||||
mappingChanged();
|
||||
}
|
||||
|
||||
if (message.action === "set_state") {
|
||||
smoothing_settings.enabled = message.data;
|
||||
mappingChanged();
|
||||
}
|
||||
}
|
||||
|
||||
function mappingChanged() {
|
||||
Settings.setValue("smoothing_settings", smoothing_settings);
|
||||
if (mapping) mapping.disable();
|
||||
|
||||
if (smoothing_settings.enabled) {
|
||||
// Build mapping_settings
|
||||
mapping_settings.channels = [];
|
||||
|
||||
Object.keys(smoothing_settings.targets).forEach((target) =>
|
||||
mapping_settings.channels.push(_generateChannel(target))
|
||||
);
|
||||
|
||||
function _generateChannel(name) {
|
||||
return {
|
||||
from: `Standard.${name}`,
|
||||
to: `Actions.${name}`,
|
||||
filters: [
|
||||
{
|
||||
type: "exponentialSmoothing",
|
||||
translation: smoothing_settings.targets[name].transform,
|
||||
rotation: smoothing_settings.targets[name].rotation,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
mapping = Controller.parseMapping(JSON.stringify(mapping_settings));
|
||||
mapping.enable();
|
||||
}
|
||||
}
|
BIN
scripts/developer/characterSmoothing/img/icon-a.png
Normal file
BIN
scripts/developer/characterSmoothing/img/icon-a.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 598 B |
BIN
scripts/developer/characterSmoothing/img/icon.png
Normal file
BIN
scripts/developer/characterSmoothing/img/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
39
scripts/developer/characterSmoothing/ui/index.css
Normal file
39
scripts/developer/characterSmoothing/ui/index.css
Normal file
|
@ -0,0 +1,39 @@
|
|||
body {
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.container .content .type {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
width: 100%;
|
||||
}
|
||||
.container .content .type span {
|
||||
margin: 0 10px;
|
||||
}
|
||||
.container .content .type .header {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 3;
|
||||
}
|
||||
.container .content .type .type-value {
|
||||
text-align: center;
|
||||
}
|
||||
.container .content .type input {
|
||||
width: 100%;
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 3;
|
||||
}
|
||||
|
||||
.container:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.horizontal-button-container {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.horizontal-button-container button:focus {
|
||||
outline: 0;
|
||||
}
|
43
scripts/developer/characterSmoothing/ui/index.html
Normal file
43
scripts/developer/characterSmoothing/ui/index.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link type="text/css" rel="stylesheet" href="theme.css" />
|
||||
<link type="text/css" rel="stylesheet" href="index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="target-list"></div>
|
||||
|
||||
<div class="horizontal-button-container">
|
||||
<button class="generic" onclick="applySettings()">Apply</button>
|
||||
<button id="toggle-button" class="bad" onclick="toggleSmoothing()">
|
||||
Disable
|
||||
</button>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<template id="target-template">
|
||||
<div data-name="[BONE/TARGET NAME]" class="container color-primary">
|
||||
<div class="container-header color-secondary">
|
||||
[BONE/TARGET NAME PRETTY]
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="type" data-value="rotation">
|
||||
<span class="header">Rotation</span>
|
||||
|
||||
<input type="range" min="1" max="100" value="100" />
|
||||
<span class="type-value">[VALUE % PRETTY]</span>
|
||||
</div>
|
||||
|
||||
<div class="type" data-value="transform">
|
||||
<span class="header">Transform</span>
|
||||
<input type="range" min="1" max="100" value="100" />
|
||||
<span class="type-value">[VALUE % PRETTY]</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./index.js"></script>
|
||||
</html>
|
107
scripts/developer/characterSmoothing/ui/index.js
Normal file
107
scripts/developer/characterSmoothing/ui/index.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
// Helper functions
|
||||
const qs = (target) => document.querySelector(target);
|
||||
const qsa = (target) => document.querySelectorAll(target);
|
||||
|
||||
// Message listeners
|
||||
const _sendMessage = (message) =>
|
||||
EventBridge.emitWebEvent(JSON.stringify(message));
|
||||
EventBridge.scriptEventReceived.connect((message) =>
|
||||
newMessage(JSON.parse(message))
|
||||
);
|
||||
|
||||
// Settings & data
|
||||
let smoothing_settings = {};
|
||||
|
||||
function newMessage(message) {
|
||||
if (message.action === "initialize") {
|
||||
initialize(message.data);
|
||||
}
|
||||
}
|
||||
|
||||
function initialize(data) {
|
||||
smoothing_settings = data;
|
||||
|
||||
// Clear all existing listings (if any)
|
||||
qsa("body .target-list").forEach((item) => item.remove());
|
||||
|
||||
// Set state
|
||||
if (smoothing_settings.enabled === false) _toggleEnabledFalse();
|
||||
if (smoothing_settings.enabled === true) _toggleEnabledTrue();
|
||||
|
||||
// For each target point
|
||||
Object.keys(smoothing_settings.targets).forEach((target) => {
|
||||
// Use the target data to build a listing
|
||||
let template = qs("#target-template").content.cloneNode(true);
|
||||
|
||||
template.querySelector(".container").dataset.name = target;
|
||||
template.querySelector(".container-header").innerText = target;
|
||||
|
||||
const rotation_area = template.querySelector('[data-value="rotation"]');
|
||||
const transform_area = template.querySelector(
|
||||
'[data-value="transform"]'
|
||||
);
|
||||
|
||||
rotation_area.querySelector("input").value = _fromDecimal(
|
||||
smoothing_settings.targets[target].rotation
|
||||
);
|
||||
transform_area.querySelector("input").value = _fromDecimal(
|
||||
smoothing_settings.targets[target].transform
|
||||
);
|
||||
|
||||
rotation_area.querySelector(".type-value").innerText = _formatPercent(
|
||||
_fromDecimal(smoothing_settings.targets[target].rotation)
|
||||
);
|
||||
transform_area.querySelector(".type-value").innerText = _formatPercent(
|
||||
_fromDecimal(smoothing_settings.targets[target].transform)
|
||||
);
|
||||
|
||||
rotation_area.querySelector("input").addEventListener("change", () => {
|
||||
rotation_area.querySelector(".type-value").innerText =
|
||||
_formatPercent(rotation_area.querySelector("input").value);
|
||||
smoothing_settings.targets[target].rotation = _toDecimal(
|
||||
rotation_area.querySelector("input").value
|
||||
);
|
||||
});
|
||||
transform_area.querySelector("input").addEventListener("change", () => {
|
||||
transform_area.querySelector(".type-value").innerText =
|
||||
_formatPercent(transform_area.querySelector("input").value);
|
||||
smoothing_settings.targets[target].transform = _toDecimal(
|
||||
transform_area.querySelector("input").value
|
||||
);
|
||||
});
|
||||
|
||||
// // Append our newly created child
|
||||
qs("#target-list").appendChild(template);
|
||||
});
|
||||
}
|
||||
|
||||
qsa("input").forEach((button) =>
|
||||
button.addEventListener("click", (event) => event.target.blur())
|
||||
);
|
||||
|
||||
function toggleSmoothing() {
|
||||
if (smoothing_settings.enabled) _toggleEnabledFalse();
|
||||
else _toggleEnabledTrue();
|
||||
}
|
||||
|
||||
function _toggleEnabledFalse() {
|
||||
_sendMessage({ action: "set_state", data: false });
|
||||
qs("#toggle-button").classList.remove("bad");
|
||||
qs("#toggle-button").classList.add("good");
|
||||
qs("#toggle-button").innerText = "Enable";
|
||||
smoothing_settings.enabled = false;
|
||||
}
|
||||
function _toggleEnabledTrue() {
|
||||
_sendMessage({ action: "set_state", data: true });
|
||||
qs("#toggle-button").classList.remove("good");
|
||||
qs("#toggle-button").classList.add("bad");
|
||||
qs("#toggle-button").innerText = "Disable";
|
||||
smoothing_settings.enabled = true;
|
||||
}
|
||||
|
||||
_sendMessage({ action: "ready" });
|
||||
const applySettings = () =>
|
||||
_sendMessage({ action: "new_settings", data: smoothing_settings });
|
||||
const _formatPercent = (value) => parseInt(value).toString() + " %";
|
||||
const _toDecimal = (value) => value / 100;
|
||||
const _fromDecimal = (value) => value * 100;
|
47
scripts/developer/characterSmoothing/ui/index.scss
Normal file
47
scripts/developer/characterSmoothing/ui/index.scss
Normal file
|
@ -0,0 +1,47 @@
|
|||
body {
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.content {
|
||||
.type {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
width: 100%;
|
||||
|
||||
span {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 3;
|
||||
}
|
||||
|
||||
.type-value {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.horizontal-button-container {
|
||||
margin-top: 10px;
|
||||
|
||||
button:focus{
|
||||
outline: 0;
|
||||
}
|
||||
}
|
75
scripts/developer/characterSmoothing/ui/theme.css
Normal file
75
scripts/developer/characterSmoothing/ui/theme.css
Normal file
|
@ -0,0 +1,75 @@
|
|||
body {
|
||||
margin: 2px;
|
||||
box-sizing: border-box;
|
||||
background-color: #101010;
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.color-primary {
|
||||
background-color: #212121;
|
||||
}
|
||||
|
||||
.color-secondary {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
.color-tertiary {
|
||||
background-color: #454545;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
min-height: 2px;
|
||||
}
|
||||
.container .container-header {
|
||||
font-size: 18px;
|
||||
padding: 0.5rem 1rem;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.container .content {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.horizontal-button-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.horizontal-button-container button {
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.horizontal-button-container button:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
padding: 1rem;
|
||||
box-sizing: border-box;
|
||||
background-color: #696e6f;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
filter: brightness(70%);
|
||||
}
|
||||
|
||||
button:active {
|
||||
filter: brightness(50%);
|
||||
}
|
||||
|
||||
button.good {
|
||||
background-color: #077e30;
|
||||
}
|
||||
|
||||
button.bad {
|
||||
background-color: #771d1d;
|
||||
}
|
||||
|
||||
button.generic {
|
||||
background-color: #505186;
|
||||
}
|
Loading…
Reference in a new issue