diff --git a/scripts/developer/accelerationFilterApp.js b/script-archive/accelerationFilterApp.js similarity index 99% rename from scripts/developer/accelerationFilterApp.js rename to script-archive/accelerationFilterApp.js index a05e7bacf0..7bde13ad28 100644 --- a/scripts/developer/accelerationFilterApp.js +++ b/script-archive/accelerationFilterApp.js @@ -219,4 +219,3 @@ Script.scriptEnding.connect(function() { } tablet.removeButton(tabletButton); }); - diff --git a/scripts/developer/exponentialFilterApp.js b/script-archive/exponentialFilterApp.js similarity index 99% rename from scripts/developer/exponentialFilterApp.js rename to script-archive/exponentialFilterApp.js index 58c0e49e31..86b4f3032d 100644 --- a/scripts/developer/exponentialFilterApp.js +++ b/script-archive/exponentialFilterApp.js @@ -237,4 +237,3 @@ Script.scriptEnding.connect(function() { } tablet.removeButton(tabletButton); }); - diff --git a/scripts/developer/characterSmoothing/characterSmoothing.js b/scripts/developer/characterSmoothing/characterSmoothing.js new file mode 100644 index 0000000000..c2321b9275 --- /dev/null +++ b/scripts/developer/characterSmoothing/characterSmoothing.js @@ -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(); + } +} 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/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