diff --git a/scripts/developer/characterSmoothing/characterSmoothing.js b/scripts/developer/characterSmoothing/characterSmoothing.js old mode 100755 new mode 100644 index 069bf191f1..c2321b9275 --- a/scripts/developer/characterSmoothing/characterSmoothing.js +++ b/scripts/developer/characterSmoothing/characterSmoothing.js @@ -1,254 +1,115 @@ -// TODO: Good UI -// TODO: Script optimization -// TODO: AccelerationLimiter choice? -// TODO: Force limit values on smoothing_settings.targets to 0 though 1 - // // Copyright 2023 Overte e.V. -// Start everything at no smoothing. -// Ideally the miniscule about of smoothing that is actually still there should be sufficient. let smoothing_settings = { - enabled: false, + enabled: true, targets: { - left_hand: { - transform: 1, - rotation: 1, - }, - right_hand: { - transform: 1, - rotation: 1, - }, - left_foot: { - transform: 1, - rotation: 1, - }, - right_foot: { - transform: 1, - rotation: 1, - }, - hips: { - transform: 1, - rotation: 1, - }, - spine2: { - transform: 1, - rotation: 1, - }, + 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 mappingJson = { - name: "org.overte.controllers.smoothing", - channels: [ - { - from: "Standard.LeftHand", - to: "Actions.LeftHand", - filters: [ - { - type: "exponentialSmoothing", - translation: smoothing_settings.targets.left_hand.transform, - rotation: smoothing_settings.targets.left_hand.rotation, - }, - ], - }, - { - from: "Standard.RightHand", - to: "Actions.RightHand", - filters: [ - { - type: "exponentialSmoothing", - translation: - smoothing_settings.targets.right_hand.transform, - rotation: smoothing_settings.targets.right_hand.rotation, - }, - ], - }, - { - from: "Standard.LeftFoot", - to: "Actions.LeftFoot", - filters: [ - { - type: "exponentialSmoothing", - translation: smoothing_settings.targets.left_foot.transform, - rotation: smoothing_settings.targets.left_foot.rotation, - }, - ], - }, - { - from: "Standard.RightFoot", - to: "Actions.RightFoot", - filters: [ - { - type: "exponentialSmoothing", - translation: - smoothing_settings.targets.right_foot.transform, - rotation: smoothing_settings.targets.right_foot.rotation, - }, - ], - }, - { - from: "Standard.Hips", - to: "Actions.Hips", - filters: [ - { - type: "exponentialSmoothing", - translation: smoothing_settings.targets.hips.transform, - rotation: smoothing_settings.targets.hips.rotation, - }, - ], - }, - { - from: "Standard.Spine2", - to: "Actions.Spine2", - filters: [ - { - type: "exponentialSmoothing", - translation: smoothing_settings.targets.spine2.transform, - rotation: smoothing_settings.targets.spine2.rotation, - }, - ], - }, - ], -}; let mapping; +let mapping_settings = { + name: "org.overte.controllers.smoothing", + channels: [], +}; -// Build tablet -const HTML_URL = Script.resolvePath("./index.html"); -let shown = false; +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: "CHR", - // TODO: Icon + text: "AviSmooth", icon: Script.resolvePath("./img/icon.png"), + activeIcon: Script.resolvePath("./img/icon-a.png"), }); + tabletButton.clicked.connect(() => { - if (shown) tablet.gotoHomeScreen(); - else tablet.gotoWebScreen(HTML_URL); + tablet.gotoWebScreen(html_url); }); function onScreenChanged(type, url) { - if (type === "Web" && url === HTML_URL) { - tabletButton.editProperties({ isActive: true }); - - if (!shown) { - // hook up to event bridge - tablet.webEventReceived.connect(onWebEventReceived); - shownChanged(true); - } - // FIXME: Works, just need to wait for response before we send data - Script.setTimeout(() => { - _sendMessage({ - action: "load_listings", - data: smoothing_settings, - }); - }, 1000); - - shown = true; - } else { + if (type !== "Web" || url !== html_url) { + tablet.webEventReceived.disconnect(onWebEventReceived); tabletButton.editProperties({ isActive: false }); - - if (shown) { - // disconnect from event bridge - tablet.webEventReceived.disconnect(onWebEventReceived); - shownChanged(false); - } shown = false; + } else { + tabletButton.editProperties({ isActive: true }); + tablet.webEventReceived.connect(onWebEventReceived); + shown = true; + smoothing_settings = Settings.getValue( + "smoothing_settings", + smoothing_settings + ); } } -tablet.screenChanged.connect(onScreenChanged); function shutdownTabletApp() { - tablet.removeButton(tabletButton); - if (shown) { - tablet.webEventReceived.disconnect(onWebEventReceived); - tablet.gotoHomeScreen(); - } + if (mapping) mapping.disable(); // Disable custom mapping + tablet.removeButton(tabletButton); // Remove the app tablet.screenChanged.disconnect(onScreenChanged); + tablet.webEventReceived.disconnect(onWebEventReceived); } -function onWebEventReceived(msg) { - msg = JSON.parse(msg); +const _sendMessage = (message) => + tablet.emitScriptEvent(JSON.stringify(message)); - // TODO - // Toggle smoothing - // if (msg.action === "set_state") { - // smoothing_settings.enabled = msg.value ? true : false; - // mappingChanged(); - // } +function onWebEventReceived(message) { + message = JSON.parse(message); - // Adjust a target's rotation and transform values - if (msg.action === "new_settings") { + if (message.action === "ready") { + _sendMessage({ + action: "initialize", + data: smoothing_settings, + }); + } - smoothing_settings = msg.data + 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) { - mapping = Controller.parseMapping(JSON.stringify(mappingJson)); + // 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(); } } - -function shownChanged(newShown) { - if (newShown) mappingChanged(); - else if (mapping) mapping.disable(); -} - -Script.scriptEnding.connect(function () { - if (mapping) mapping.disable(); - tablet.removeButton(tabletButton); -}); - -function _sendMessage(message) { - message = JSON.stringify(message); - tablet.emitScriptEvent(message); -} - -// Load settings -smoothing_settings = Settings.getValue( - "smoothing_settings", - smoothing_settings -); - -// TODO: Does script init work? -// Settings.setValue( -// "smoothing_settings", -// { -// enabled: false, -// targets: { -// left_hand: { -// transform: 1, -// rotation: 1, -// }, -// right_hand: { -// transform: 1, -// rotation: 1, -// }, -// left_foot: { -// transform: 1, -// rotation: 1, -// }, -// right_foot: { -// transform: 1, -// rotation: 1, -// }, -// hips: { -// transform: 1, -// rotation: 1, -// }, -// spine2: { -// transform: 1, -// rotation: 1, -// }, -// }, -// } -// ); - -mappingChanged(); diff --git a/scripts/developer/characterSmoothing/img/icon-a.png b/scripts/developer/characterSmoothing/img/icon-a.png new file mode 100644 index 0000000000..aa603c2a45 Binary files /dev/null and b/scripts/developer/characterSmoothing/img/icon-a.png differ diff --git a/scripts/developer/characterSmoothing/img/icon.png b/scripts/developer/characterSmoothing/img/icon.png new file mode 100644 index 0000000000..ac25aeb647 Binary files /dev/null and b/scripts/developer/characterSmoothing/img/icon.png differ diff --git a/scripts/developer/characterSmoothing/index.css b/scripts/developer/characterSmoothing/index.css deleted file mode 100644 index b43782988b..0000000000 --- a/scripts/developer/characterSmoothing/index.css +++ /dev/null @@ -1,80 +0,0 @@ -body { - background-color: black; - color: white; - margin: 0; - padding: 0.5rem; - box-sizing: border-box; - height: 100vh; - width: 100vw; - display: flex; - flex-direction: column; - width: 100%; -} -body .container-1 { - margin-bottom: 1rem; - display: flex; - flex-direction: column; - width: 100%; -} -body .container-1 .title { - background-color: #262626; - font-size: 1.2rem; - padding: 0.2rem; - box-sizing: border-box; -} -body .container-1 .list { - display: grid; - grid-template-columns: 1fr 1fr; - padding: 1rem; - box-sizing: border-box; - background-color: #131313; -} -body .container-1 .list .option { - display: flex; - flex-direction: row; -} -body .container-1 .list .option * { - margin: auto; -} -body .container-1 .list .option div { - margin-right: 0.5rem; -} -body .container-1 .list .option input { - margin-left: 0.5rem; - text-align: center; - font-size: 1.1rem; - height: 35px; -} -body .button-container { - width: 100%; - display: flex; - flex-direction: row; - background-color: #131313; - padding: 0.5rem; - box-sizing: border-box; - display: grid; - grid-gap: 1rem; - grid-template-columns: 1fr 1fr; -} -body .button-container button { - width: 100%; - height: 35px; - background-color: #505186; - border: 0; - color: white; - border-radius: 5px; - cursor: pointer; -} -body .button-container button:hover, -body .button-container button:focus { - filter: brightness(60%); -} -body .button-container button:active { - filter: brightness(40%); -} -body .button-container .active { - background-color: #277727; -} -body .button-container .unactive { - background-color: #771d1d; -} \ No newline at end of file diff --git a/scripts/developer/characterSmoothing/index.html b/scripts/developer/characterSmoothing/index.html deleted file mode 100755 index 677971baea..0000000000 --- a/scripts/developer/characterSmoothing/index.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - Character Smoothing - - - -
- -
- - -
- - - - - - diff --git a/scripts/developer/characterSmoothing/index.scss b/scripts/developer/characterSmoothing/index.scss deleted file mode 100644 index 8464f24597..0000000000 --- a/scripts/developer/characterSmoothing/index.scss +++ /dev/null @@ -1,98 +0,0 @@ -body { - background-color: black; - color: white; - margin: 0; - padding: 0.5rem; - box-sizing: border-box; - - height: 100vh; - width: 100vw; - - display: flex; - flex-direction: column; - width: 100%; - - .container-1 { - margin-bottom: 1rem; - display: flex; - flex-direction: column; - width: 100%; - - .title { - background-color: #262626; - font-size: 1.2rem; - - padding: 0.2rem; - box-sizing: border-box; - } - - .list { - display: grid; - grid-template-columns: 1fr 1fr; - padding: 1rem; - box-sizing: border-box; - - background-color: #131313; - - .option { - display: flex; - flex-direction: row; - - * { - margin: auto; - } - - div { - margin-right: 0.5rem; - } - - input { - margin-left: 0.5rem; - text-align: center; - font-size: 1.1rem; - height: 35px; - } - } - } - } - - .button-container { - width: 100%; - display: flex; - flex-direction: row; - background-color: #131313; - padding: 0.5rem; - box-sizing: border-box; - - display: grid; - grid-gap: 1rem; - grid-template-columns: 1fr 1fr; - - button { - width: 100%; - height: 35px; - background-color: #505186; - border: 0; - color: white; - border-radius: 5px; - cursor: pointer; - } - - button:hover, - button:focus { - filter: brightness(60%); - } - - button:active { - filter: brightness(40%); - } - - .active { - background-color: #277727; - } - - .unactive { - background-color: #771d1d; - } - } -} diff --git a/scripts/developer/characterSmoothing/ui/index.css b/scripts/developer/characterSmoothing/ui/index.css new file mode 100644 index 0000000000..641d00c6a8 --- /dev/null +++ b/scripts/developer/characterSmoothing/ui/index.css @@ -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; +} \ No newline at end of file diff --git a/scripts/developer/characterSmoothing/ui/index.html b/scripts/developer/characterSmoothing/ui/index.html new file mode 100644 index 0000000000..6d745b177a --- /dev/null +++ b/scripts/developer/characterSmoothing/ui/index.html @@ -0,0 +1,43 @@ + + + + + + + + + +
+ +
+ + +
+ + + + + + diff --git a/scripts/developer/characterSmoothing/ui/index.js b/scripts/developer/characterSmoothing/ui/index.js new file mode 100644 index 0000000000..87010164bf --- /dev/null +++ b/scripts/developer/characterSmoothing/ui/index.js @@ -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; diff --git a/scripts/developer/characterSmoothing/ui/index.scss b/scripts/developer/characterSmoothing/ui/index.scss new file mode 100644 index 0000000000..acfd97f7a5 --- /dev/null +++ b/scripts/developer/characterSmoothing/ui/index.scss @@ -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; + } +} diff --git a/scripts/developer/characterSmoothing/ui/theme.css b/scripts/developer/characterSmoothing/ui/theme.css new file mode 100644 index 0000000000..f45b4bdf8b --- /dev/null +++ b/scripts/developer/characterSmoothing/ui/theme.css @@ -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; +} \ No newline at end of file