Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avatar Smoothing #579

Merged
merged 4 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,3 @@ Script.scriptEnding.connect(function() {
}
tablet.removeButton(tabletButton);
});

Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,3 @@ Script.scriptEnding.connect(function() {
}
tablet.removeButton(tabletButton);
});

115 changes: 115 additions & 0 deletions scripts/developer/characterSmoothing/characterSmoothing.js
Original file line number Diff line number Diff line change
@@ -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();
}
}
Binary file added scripts/developer/characterSmoothing/img/icon-a.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added scripts/developer/characterSmoothing/img/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 39 additions & 0 deletions scripts/developer/characterSmoothing/ui/index.css
Original file line number Diff line number Diff line change
@@ -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 changes: 43 additions & 0 deletions scripts/developer/characterSmoothing/ui/index.html
Original file line number Diff line number Diff line change
@@ -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 changes: 107 additions & 0 deletions scripts/developer/characterSmoothing/ui/index.js
Original file line number Diff line number Diff line change
@@ -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 changes: 47 additions & 0 deletions scripts/developer/characterSmoothing/ui/index.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading
Loading