multiple and general fixes and improvments

This commit is contained in:
e7d 2023-02-06 16:51:02 +01:00
parent 203d3bf110
commit c3067194e4
No known key found for this signature in database
GPG Key ID: F320BE007C0B8881
13 changed files with 412 additions and 473 deletions

View File

@ -38,3 +38,6 @@ Please read below or use this video tutorial: https://youtu.be/vHzf_ESseTc
- Adjust position and size of the source as you will - Adjust position and size of the source as you will
*: These gamepads work both wired and wireless via Bluetooth *: These gamepads work both wired and wireless via Bluetooth
## Credits
DualShock 4 and Xbox One skins from [gamepadviewer.com](https://gamepadviewer.com/)

View File

@ -203,6 +203,8 @@
<h3>Credits</h3> <h3>Credits</h3>
<p>All information and source code can be found on GitHub at <a target="_blank" <p>All information and source code can be found on GitHub at <a target="_blank"
href="https://github.com/e7d/gamepad-viewer">e7d/gamepad-viewer</a>.</p> href="https://github.com/e7d/gamepad-viewer">e7d/gamepad-viewer</a>.</p>
<p>DualShock 4 and Xbox One skins from <a target="_blank"
href="https://gamepadviewer.com/">gamepadviewer.com</a>.</p>
</div> </div>
<script src="js/jquery.min.js"></script> <script src="js/jquery.min.js"></script>

View File

@ -24,6 +24,10 @@ class Gamepad {
this.$helpPopout = $('#help-popout'); this.$helpPopout = $('#help-popout');
this.$gamepadList = $('#gamepad-list'); this.$gamepadList = $('#gamepad-list');
// ensure the GamePad API is available on this browser
this.assertGamepadAPI();
// overlay selectors
this.backgroundStyle = [ this.backgroundStyle = [
'transparent', 'transparent',
'checkered', 'checkered',
@ -42,10 +46,6 @@ class Gamepad {
'black', 'black',
'black', 'black',
]; ];
// ensure the GamePad API is available on this browser
this.assertGamepadAPI();
this.initOverlaySelectors(); this.initOverlaySelectors();
// gamepad collection default values // gamepad collection default values
@ -57,10 +57,11 @@ class Gamepad {
name: 'Debug', name: 'Debug',
}, },
ds4: { ds4: {
id: /054c|54c|09cc|046d|0810|2563/, // 054c = Sony vendor code, 046d,0810,2563 = PS-like controllers vendor codes id: /054c|54c|7545|09cc|0104|0ce6|046d|0810|2563/, // 054c,7545 = Sony vendor code, 09cc,0104 = DS4 controllers product codes, 0ce6 = DualSense controller product code, 046d,0810,2563 = PS-like controllers vendor codes
name: 'DualShock 4', name: 'DualShock 4',
colors: ['black', 'white', 'red', 'blue'], colors: ['black', 'white', 'red', 'blue'],
triggers: true, triggers: true,
zoom: true,
}, },
// gamecube: { // gamecube: {
// id: /0079/, // 0079 = Nintendo GameCube vendor code // id: /0079/, // 0079 = Nintendo GameCube vendor code
@ -68,7 +69,7 @@ class Gamepad {
// colors: ['black', 'purple'], // colors: ['black', 'purple'],
// }, // },
// 'joy-con': { // 'joy-con': {
// id: /200e/, // 0079 = Joy-Con specific product code // id: /200e/, // 200e = Joy-Con specific product code
// name: 'Joy-Con (L+R) Controllers', // name: 'Joy-Con (L+R) Controllers',
// colors: ['blue-red', 'grey-grey'], // colors: ['blue-red', 'grey-grey'],
// }, // },
@ -78,19 +79,21 @@ class Gamepad {
// colors: ['black'], // colors: ['black'],
// }, // },
// 'switch-pro': { // 'switch-pro': {
// id: /057e|20d6/, // 057e = Nintendo Switch vendor code, 20d6 = Switch Pro-like vendor code // id: /057e|20d6|2009/, // 057e = Nintendo Switch vendor code, 20d6,2009 = Switch Pro-like vendor code
// name: 'Switch Pro Controller', // name: 'Switch Pro Controller',
// colors: ['black'], // colors: ['black'],
// }, // },
telemetry: {
id: /telemetry/,
name: 'Telemetry',
zoom: true
},
'xbox-one': { 'xbox-one': {
id: /045e|xinput|XInput/, // 045e = Microsoft vendor code, xinput = standard Windows controller id: /045e|xinput|XInput/, // 045e = Microsoft vendor code, xinput = standard Windows controller
name: 'Xbox One', name: 'Xbox One',
colors: ['black', 'white'], colors: ['black', 'white'],
triggers: true, triggers: true,
}, zoom: true,
'telemetry': {
id: /telemetry/, // 045e = Microsoft vendor code, xinput = standard Windows controller
name: 'Telemetry'
}, },
}; };
@ -122,9 +125,6 @@ class Gamepad {
axes: [], axes: [],
}; };
// // read hash
// this.hash = this.readHash();
// listen for gamepad related events // listen for gamepad related events
this.haveEvents = 'GamepadEvent' in window; this.haveEvents = 'GamepadEvent' in window;
if (this.haveEvents) { if (this.haveEvents) {
@ -306,6 +306,16 @@ class Gamepad {
}, this.overlayDelay); }, this.overlayDelay);
} }
/**
* Extracts the name, vendor and product from a gamepad identifier
*
* @param {string} id
* @returns {object}
*/
toGamepadInfo(id) {
return /(?<name>.*) \(.*Vendor: (?<vendor>[0-9a-f]{4}) Product: (?<product>[0-9a-f]{4})\)/.exec(id).groups;
}
/** /**
* Updates the list of connected gamepads in the overlay * Updates the list of connected gamepads in the overlay
*/ */
@ -317,9 +327,9 @@ class Gamepad {
if (!gamepad) { if (!gamepad) {
continue; continue;
} }
const { name, vendor, product } = this.toGamepadInfo(gamepad.id);
$options.push( $options.push(
`<option class='entry' value='${gamepad.id}'>${gamepad.id}</option>'` `<option class='entry' value='${vendor}-${product}'>${name}</option>`
); );
} }
this.$gamepadSelect.append($options.join('')); this.$gamepadSelect.append($options.join(''));
@ -341,7 +351,7 @@ class Gamepad {
} }
const colorOptions = colors.map( const colorOptions = colors.map(
(color) => `<option value='${color}'>${color}</option>` (color) => `<option value='${color}'>${color.charAt(0).toUpperCase()}${color.slice(1)}</option>`
); );
this.$colorSelect.html(colorOptions); this.$colorSelect.html(colorOptions);
this.$colorOverlay.fadeIn(); this.$colorOverlay.fadeIn();
@ -539,7 +549,7 @@ class Gamepad {
} }
} }
return 'xbox-one'; return 'debug';
} }
/** /**
@ -571,7 +581,8 @@ class Gamepad {
// check the parameters for a selected gamepad // check the parameters for a selected gamepad
const gamepadId = this.getUrlParam('gamepad'); const gamepadId = this.getUrlParam('gamepad');
if (gamepadId) { if (gamepadId) {
if (gamepad.id === gamepadId) { const [vendor, product] = gamepadId.split('-');
if (gamepad.id.includes(vendor) && gamepad.id.includes(product)) {
this.map(gamepad.index); this.map(gamepad.index);
return; return;
} }
@ -648,7 +659,8 @@ class Gamepad {
this.identifier = this.identifiers[this.type]; this.identifier = this.identifiers[this.type];
// update the overlay selectors // update the overlay selectors
this.$gamepadSelect.val(gamepad.id); const { vendor, product } = this.toGamepadInfo(gamepad.id);
this.$gamepadSelect.val(`${vendor}-${product}`);
this.updateColors(); this.updateColors();
this.updateTriggers(); this.updateTriggers();
@ -658,16 +670,6 @@ class Gamepad {
// hide the help before displaying the template // hide the help before displaying the template
this.hideInstructions(); this.hideInstructions();
this.hidePlaceholder(); this.hidePlaceholder();
// save statistics
if (!!window.ga) {
ga('send', 'event', {
eventCategory: 'Gamepad',
eventAction: 'map',
eventLabel: 'Map',
eventValue: this.identifier,
});
}
} }
/** /**
@ -707,16 +709,6 @@ class Gamepad {
this.updateColors(); this.updateColors();
this.updateTriggers(); this.updateTriggers();
this.clearUrlParams(); this.clearUrlParams();
// save statistics
if (!!window.ga) {
ga('send', 'event', {
eventCategory: 'Gamepad',
eventAction: 'disconnect',
eventLabel: 'Disconnect',
eventValue: this.identifier,
});
}
} }
/** /**
@ -733,18 +725,31 @@ class Gamepad {
this.$gamepad.html(template); this.$gamepad.html(template);
// read for parameters to apply: // read for parameters to apply:
const identifier = this.identifiers[this.type];
// - color // - color
this.changeGamepadColor(this.getUrlParam('color')); if (identifier.colors) {
this.changeGamepadColor(this.getUrlParam('color'));
} else {
this.updateUrlParams({ color: undefined });
}
// - triggers mode // - triggers mode
this.toggleTriggersMeter(this.getUrlParam('triggers') === 'meter'); if (identifier.triggers) {
// - zoom$ this.toggleTriggersMeter(this.getUrlParam('triggers') === 'meter');
window.setTimeout(() => } else {
this.changeZoom( this.updateUrlParams({ triggers: undefined });
this.type === 'debug' }
? 'auto' // - zoom
: this.getUrlParam('zoom') || 'auto' if (identifier.zoom) {
) window.setTimeout(() =>
); this.changeZoom(
this.type === 'debug'
? 'auto'
: this.getUrlParam('zoom') || 'auto'
)
);
} else {
this.updateUrlParams({ zoom: undefined });
}
// save the buttons mapping of this template // save the buttons mapping of this template
this.mapping.buttons = []; this.mapping.buttons = [];
@ -773,7 +778,7 @@ class Gamepad {
*/ */
pollStatus(force = false) { pollStatus(force = false) {
// ensure that a gamepad is currently active // ensure that a gamepad is currently active
if (this.index === null) return; if (this.index === null || this.index === this.disconnectedIndex) return;
// enqueue the next refresh // enqueue the next refresh
window.requestAnimationFrame(this.pollStatus.bind(this)); window.requestAnimationFrame(this.pollStatus.bind(this));
@ -786,8 +791,7 @@ class Gamepad {
if ( if (
!force && !force &&
(!activeGamepad || activeGamepad.timestamp === this.lastTimestamp) (!activeGamepad || activeGamepad.timestamp === this.lastTimestamp)
) ) return;
return;
this.lastTimestamp = activeGamepad.timestamp; this.lastTimestamp = activeGamepad.timestamp;
// actually update the active gamepad graphically // actually update the active gamepad graphically
@ -863,9 +867,18 @@ class Gamepad {
} }
} }
/**
* Changes the active gamepad
*
* @param {string} gamepadId
*/
changeGamepad(gamepadId) { changeGamepad(gamepadId) {
// get the index corresponding to the identifier of the gamepad // get the index corresponding to the identifier of the gamepad
const index = this.gamepads.findIndex(g => g && g.id === gamepadId); const index = this.gamepads.findIndex(g => {
if (!g) return false;
const { vendor, product } = this.toGamepadInfo(g.id);
return `${vendor}-${product}` === gamepadId;
});
// set the selected gamepad // set the selected gamepad
this.updateUrlParams({ gamepad: gamepadId !== 'auto' ? gamepadId : undefined }); this.updateUrlParams({ gamepad: gamepadId !== 'auto' ? gamepadId : undefined });
@ -919,16 +932,6 @@ class Gamepad {
// update current settings // update current settings
this.updateUrlParams({ background: this.backgroundStyleName }); this.updateUrlParams({ background: this.backgroundStyleName });
this.$backgroundSelect.val(this.backgroundStyleName); this.$backgroundSelect.val(this.backgroundStyleName);
// save statistics
if (!!window.ga) {
ga('send', 'event', {
eventCategory: 'Gamepad',
eventAction: 'change-background-color',
eventLabel: 'Change Background Color',
eventValue: this.backgroundStyleName,
});
}
} }
/** /**
@ -975,16 +978,6 @@ class Gamepad {
// update current settings // update current settings
this.updateUrlParams({ color: this.colorName }); this.updateUrlParams({ color: this.colorName });
this.$colorSelect.val(this.colorName); this.$colorSelect.val(this.colorName);
// save statistics
if (!!window.ga) {
ga('send', 'event', {
eventCategory: 'Gamepad',
eventAction: 'change-gamepad-color',
eventLabel: 'Change Gamepad Color',
eventValue: this.colorName,
});
}
} }
/** /**
@ -1035,16 +1028,6 @@ class Gamepad {
this.updateUrlParams({ this.updateUrlParams({
zoom: this.zoomMode === 'auto' ? undefined : this.zoomLevel, zoom: this.zoomMode === 'auto' ? undefined : this.zoomLevel,
}); });
// save statistics
if (!!window.ga) {
ga('send', 'event', {
eventCategory: 'Gamepad',
eventAction: 'change-zoom',
eventLabel: 'Change Zoom',
eventValue: this.zoomLevel,
});
}
} }
/** /**
@ -1066,16 +1049,6 @@ class Gamepad {
}, 0); }, 0);
this.type = types[++typeIndex >= types.length ? 0 : typeIndex]; this.type = types[++typeIndex >= types.length ? 0 : typeIndex];
// save statistics
if (!!window.ga) {
ga('send', 'event', {
eventCategory: 'Gamepad',
eventAction: 'toggle-type',
eventLabel: 'Toggle Type',
eventValue: this.type,
});
}
// update current settings // update current settings
this.updateUrlParams({ type: this.type }); this.updateUrlParams({ type: this.type });
@ -1093,16 +1066,6 @@ class Gamepad {
// update debug value // update debug value
this.debug = debug !== null ? debug : !this.debug; this.debug = debug !== null ? debug : !this.debug;
// save statistics
if (!!window.ga) {
ga('send', 'event', {
eventCategory: 'Gamepad',
eventAction: 'toggle-debug',
eventLabel: 'Toggle Debug',
eventValue: this.debug,
});
}
// update current settings // update current settings
this.changeSkin(this.debug ? 'debug' : 'auto') this.changeSkin(this.debug ? 'debug' : 'auto')
} }
@ -1118,16 +1081,6 @@ class Gamepad {
// display the help popout // display the help popout
this.$helpPopout.toggleClass('active'); this.$helpPopout.toggleClass('active');
this.helpVisible = this.$helpPopout.is('.active'); this.helpVisible = this.$helpPopout.is('.active');
// save statistics
if (!!window.ga) {
ga('send', 'event', {
eventCategory: 'Gamepad',
eventAction: 'toggle-help',
eventLabel: 'Toggle Help',
eventValue: this.$helpPopout.is('active'),
});
}
} }
/** /**
@ -1152,7 +1105,7 @@ class Gamepad {
/** /**
* Reads an URL search parameter * Reads an URL search parameter
* *
* @param {*} name * @param {string} name
* @returns {string|boolean|null} * @returns {string|boolean|null}
*/ */
getUrlParam(name) { getUrlParam(name) {
@ -1197,7 +1150,7 @@ class Gamepad {
/** /**
* Update url hash with new settings * Update url hash with new settings
* *
* @param {*} newParams * @param {object} newParams
*/ */
updateUrlParams(newParams) { updateUrlParams(newParams) {
const params = Object.assign(this.getUrlParams(), newParams); const params = Object.assign(this.getUrlParams(), newParams);

4
js/jquery.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,4 @@
Sample: T818 Samples:
http://localhost:8081/?gamepad=Thrustmaster%20Thrustmaster%20Advance%20Racer%20(Vendor:%20044f%20Product:%20b696)&triggers=opacity&clutchIndex=6&clutchMin=1&clutchMax=-1&brakeIndex=1&brakeMin=1&brakeMax=-1&throttleIndex=5&throttleMin=1&throttleMax=-1&directionIndex=0&directionDegrees=900&type=telemetry - T818: http://localhost:8081/?gamepad=044f-b696&type=telemetry&clutchIndex=6&clutchMin=1&clutchMax=-1&brakeIndex=1&brakeMin=1&brakeMax=-1&throttleIndex=5&throttleMin=1&throttleMax=-1&directionIndex=0&directionDegrees=900
- Xbox Controller: http://localhost:8081/?gamepad=045e-0b00&type=telemetry&brakeType=button&brakeIndex=6&brakeMin=0&brakeMax=1&throttleType=button&throttleIndex=7&throttleMin=0&throttleMax=1&directionIndex=0&directionDegrees=180
Sample: Xbox Controller - Xbox Controller with clutch: http://localhost:8081/?gamepad=045e-0b00&type=telemetry&clutchType=button&clutchIndex=0&clutchMin=0&clutchMax=1&&brakeType=button&brakeIndex=6&brakeMin=0&brakeMax=1&throttleType=button&throttleIndex=7&throttleMin=0&throttleMax=1&directionIndex=0&directionDegrees=180
http://localhost:8081/?gamepad=Microsoft%20Controller%20(STANDARD%20GAMEPAD%20Vendor:%20045e%20Product:%200b00)&triggers=opacity&brakeType=button&brakeIndex=6&brakeMin=0&brakeMax=1&throttleType=button&throttleIndex=7&throttleMin=0&throttleMax=1&directionIndex=0&directionDegrees=180&type=telemetry

View File

@ -29,7 +29,7 @@
} }
.box.extra-large { .box.extra-large {
width: 100%; width: 75%;
min-width: 400px; min-width: 400px;
} }

View File

@ -2,9 +2,21 @@
<script async src="templates/debug/template.js"></script> <script async src="templates/debug/template.js"></script>
<div class="info"> <div class="info">
<div class="container"> <div class="container">
<div id="info-id" class="box extra-large"> <div id="info-name" class="box extra-large">
<div class="content"> <div class="content">
<div class="label">ID</div> <div class="label">Name</div>
<div class="value"></div>
</div>
</div>
<div id="info-vendor" class="box small">
<div class="content">
<div class="label">Vendor</div>
<div class="value"></div>
</div>
</div>
<div id="info-product" class="box small">
<div class="content">
<div class="label">Product</div>
<div class="value"></div> <div class="value"></div>
</div> </div>
</div> </div>

View File

@ -1,81 +1,83 @@
(() => { function DebugTemplate(gamepad) {
$id = $("#info-id .value"); return {
$timestamp = $("#info-timestamp .value"); $name: $('#info-name .value'),
$index = $("#info-index .value"); $vendor: $('#info-vendor .value'),
$mapping = $("#info-mapping .value"); $product: $('#info-product .value'),
$rumble = $("#info-rumble .value"); $timestamp: $('#info-timestamp .value'),
$axes = $(".axes .container"); $index: $('#info-index .value'),
$buttons = $(".buttons .container"); $mapping: $('#info-mapping .value'),
$rumble: $('#info-rumble .value'),
$axes: $('.axes .container'),
$buttons: $('.buttons .container'),
activeGamepad: gamepad.getActive(),
init: function () {
if (!this.activeGamepad) {
return;
}
const { name, vendor, product } = gamepad.toGamepadInfo(this.activeGamepad.id);
this.$name.html(name).attr('title', name);
this.$vendor.html(vendor);
this.$product.html(product);
this.updateTimestamp();
this.$index.html(this.activeGamepad.index);
this.$mapping.html(this.activeGamepad.mapping || 'N/A');
this.$rumble.html(
this.activeGamepad.vibrationActuator
? this.activeGamepad.vibrationActuator.type
: 'N/A'
);
this.initAxes();
this.initButtons();
gamepad.updateButton = ($button) => this.updateElem($button);
gamepad.updateAxis = ($axis) => this.updateElem($axis, 6);
},
initAxes: function () {
for (
let axisIndex = 0;
axisIndex < this.activeGamepad.axes.length;
axisIndex++
) {
this.$axes.append(`
<div class="box medium">
<div class="content">
<div class="label">Axis ${axisIndex}</div>
<div class="value" data-axis="${axisIndex}"></div>
</div>
</div>
`);
}
},
initButtons: function () {
for (
let buttonIndex = 0;
buttonIndex < this.activeGamepad.buttons.length;
buttonIndex++
) {
this.$buttons.append(`
<div class="box small">
<div class="content">
<div class="label">B${buttonIndex}</div>
<div class="value" data-button="${buttonIndex}"></div>
</div>
</div>
`);
}
},
updateElem: function ($elem, precision = 2) {
this.updateTimestamp();
let value = parseFloat($elem.attr('data-value'), 10).toFixed(precision);
$elem.html(value);
let color = Math.floor(255 * 0.3 + 255 * 0.7 * Math.abs(value));
$elem.css({ color: `rgb(${color}, ${color}, ${color})` });
},
updateTimestamp: function () {
this.activeGamepad = gamepad.getActive();
if (!this.activeGamepad) {
return;
}
this.$timestamp.html(parseFloat(this.activeGamepad.timestamp).toFixed(3));
},
}.init();
};
gamepad = window.gamepad; new DebugTemplate(window.gamepad);
activeGamepad = gamepad.getActive();
if (!activeGamepad) {
return;
}
$id.html(activeGamepad.id);
updateTimestamp();
$index.html(activeGamepad.index);
$mapping.html(activeGamepad.mapping || 'N/A');
$rumble.html(
activeGamepad.vibrationActuator
? activeGamepad.vibrationActuator.type
: "N/A"
);
for (
let axisIndex = 0;
axisIndex < activeGamepad.axes.length;
axisIndex++
) {
$axes.append(`
<div class="box medium">
<div class="content">
<div class="label">Axis ${axisIndex}</div>
<div class="value" data-axis="${axisIndex}"></div>
</div>
</div>
`);
}
for (
let buttonIndex = 0;
buttonIndex < activeGamepad.buttons.length;
buttonIndex++
) {
$buttons.append(`
<div class="box small">
<div class="content">
<div class="label">B${buttonIndex}</div>
<div class="value" data-button="${buttonIndex}"></div>
</div>
</div>
`);
}
gamepad.updateButton = function ($button) {
updateElem($button);
};
gamepad.updateAxis = function ($axis) {
updateElem($axis, 6);
};
function updateElem($elem, precision = 2) {
updateTimestamp();
let value = parseFloat($elem.attr("data-value"), 10).toFixed(precision);
$elem.html(value);
let color = Math.floor(255 * 0.3 + 255 * 0.7 * Math.abs(value));
$elem.css({ color: `rgb(${color}, ${color}, ${color})` });
}
function updateTimestamp() {
activeGamepad = gamepad.getActive();
if (!activeGamepad) {
return;
}
$timestamp.html(parseFloat(activeGamepad.timestamp).toFixed(3));
}
})();

View File

@ -1,33 +1,37 @@
gamepad.updateButton = function ($button) { function DualShock4Template(gamepad) {
const value = parseFloat($button.attr("data-value"), 10); return {
init: function () {
if ($button.is(".trigger")) { gamepad.updateButton = function ($button) {
$button.css( const value = parseFloat($button.attr('data-value'), 10);
gamepad.triggersMeter if ($button.is('.trigger')) {
? { $button.css(
opacity: 1, gamepad.triggersMeter
"clip-path": `inset(${(1 - value) * 100}% 0px 0px 0pc)`, ? {
} opacity: 1,
: { 'clip-path': `inset(${(1 - value) * 100}% 0px 0px 0pc)`,
opacity: `${value * 100}%`, }
"clip-path": "none", : {
} opacity: `${value * 100}%`,
); 'clip-path': 'none',
} }
}; );
}
gamepad.updateAxis = function ($axis) { };
const axisX = $axis.attr("data-value-x"); gamepad.updateAxis = function ($axis) {
const axisY = $axis.attr("data-value-y"); const axisX = $axis.attr('data-value-x');
const axisY = $axis.attr('data-value-y');
if ($axis.is(".stick")) { if ($axis.is('.stick')) {
$axis.css({ $axis.css({
"margin-top": axisY * 25, 'margin-top': axisY * 25,
"margin-left": axisX * 25, 'margin-left': axisX * 25,
transform: `rotateX(${-parseFloat( transform: `rotateX(${-parseFloat(
axisY * 30, axisY * 30,
8 8
)}deg) rotateY(${parseFloat(axisX * 30, 8)}deg)`, )}deg) rotateY(${parseFloat(axisX * 30, 8)}deg)`,
}); });
} }
};
},
}.init();
}; };
new DualShock4Template(window.gamepad);

View File

@ -3,18 +3,22 @@
display: flex; display: flex;
width: 650px; width: 650px;
height: 120px; height: 120px;
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
border-top-right-radius: 60px; border-top-right-radius: 60px;
border-bottom-right-radius: 60px; border-bottom-right-radius: 60px;
--black-color: black; --black-color: black;
--white-color: white; --white-color: white;
--grey-color: grey; --main-bg-color: black;
--main-bg-color: #444; --main-component-color: #333;
--main-component-color: grey; --clutch-color: #2D64B9;
--meter-idle-color: var(--grey-color); --brake-color: #A52725;
--clutch-color: blue; --throttle-color: #0CA818;
--brake-color: red; }
--throttle-color: lime;
#gamepad #telemetry * {
box-sizing: border-box;
} }
#gamepad #telemetry #chart { #gamepad #telemetry #chart {
@ -22,6 +26,7 @@
margin: 4px; margin: 4px;
background-color: var(--main-component-color); background-color: var(--main-component-color);
border: 1px solid var(--black-color); border: 1px solid var(--black-color);
border-radius: 4px;
} }
#gamepad #telemetry #meters { #gamepad #telemetry #meters {
@ -31,86 +36,68 @@
} }
#gamepad #telemetry #meters .meter { #gamepad #telemetry #meters .meter {
display: flex; position: relative;
flex-direction: column;
flex: 1; flex: 1;
margin: 4px 2px; margin: 4px 2px;
background-color: var(--main-component-color);
border: 1px solid var(--black-color);
border-radius: 4px;
overflow: hidden;
} }
#gamepad #telemetry #meters .meter .value { #gamepad #telemetry #meters .meter .value {
display: flex; position: absolute;
justify-content: center; bottom: 2px;
align-items: center; width: 100%;
text-align: center;
font-weight: bold; font-weight: bold;
font-size: 10pt; font-size: 10pt;
color: var(--white-color); color: var(--white-color);
} }
#gamepad #telemetry #meters .meter .bar { #gamepad #telemetry #meters .meter .bar {
display: flex; position: absolute;
align-items: flex-end; bottom: 0;
flex: 1;
background-color: var(--main-component-color);
border: 1px solid var(--black-color);
}
#gamepad #telemetry #meters .meter .bar .filler {
width: 100%; width: 100%;
height: 0%; height: 0%;
transition: height 100ms; transition: height 100ms;
} }
#gamepad #telemetry #meters #clutch.meter .bar .filler { #gamepad #telemetry #meters #clutch.meter .bar {
background-color: var(--clutch-color); background-color: var(--clutch-color);
} }
#gamepad #telemetry #meters #brake.meter .bar .filler { #gamepad #telemetry #meters #brake.meter .bar {
background-color: var(--brake-color); background-color: var(--brake-color);
} }
#gamepad #telemetry #meters #throttle.meter .bar .filler { #gamepad #telemetry #meters #throttle.meter .bar {
background-color: var(--throttle-color); background-color: var(--throttle-color);
} }
#gamepad #telemetry #direction { #gamepad #telemetry #direction {
display: flex;
justify-content: center;
align-items: center;
width: 120px;
border-radius: 50%;
}
#gamepad #telemetry #direction #wheel {
position: relative; position: relative;
display: flex; display: flex;
justify-content: center; justify-content: center;
width: 90%; width: 100px;
height: 90%; height: 100px;
border-radius: 50%; margin: 10px;
background-color: var(--black-color); border-radius: 50px;
background-color: var(--main-component-color);
} }
#gamepad #telemetry #direction #wheel #wheel--center { #gamepad #telemetry #direction .center {
display: block;
position: absolute; position: absolute;
top: 10%; top: 20%;
left: 10%; left: 20%;
width: 80%; width: 60%;
height: 80%; height: 60%;
border-radius: 50%; border-radius: 50%;
background-color: var(--main-bg-color); background-color: var(--main-bg-color);
} }
#gamepad #telemetry #direction #wheel #wheel--indicator { #gamepad #telemetry #direction .indicator {
display: block; display: block;
width: 7%; width: 5%;
height: 50%; height: 50%;
background-color: var(--white-color); background-color: var(--white-color);
transform-origin: bottom; transform-origin: bottom;
transform: rotate(0deg);
transition: transform 100ms; transition: transform 100ms;
} }
#gamepad #telemetry #direction {
display: flex;
justify-content: center;
align-items: center;
width: 120px;
border-radius: 50%;
}

View File

@ -5,28 +5,20 @@
<div id="chart"></div> <div id="chart"></div>
<div id="meters"> <div id="meters">
<div id="clutch" class="meter"> <div id="clutch" class="meter">
<div class="bar"></div>
<div class="value">0</div> <div class="value">0</div>
<div class="bar">
<div class="filler"></div>
</div>
</div> </div>
<div id="brake" class="meter"> <div id="brake" class="meter">
<div class="bar"></div>
<div class="value">0</div> <div class="value">0</div>
<div class="bar">
<div class="filler"></div>
</div>
</div> </div>
<div id="throttle" class="meter"> <div id="throttle" class="meter">
<div class="bar"></div>
<div class="value">0</div> <div class="value">0</div>
<div class="bar">
<div class="filler"></div>
</div>
</div> </div>
</div> </div>
<div id="direction"> <div id="direction">
<div id="wheel"> <div class="indicator"></div>
<div id="wheel--indicator"></div> <div class="center"></div>
<div id="wheel--center"></div>
</div>
</div> </div>
</div> </div>

View File

@ -1,143 +1,124 @@
/** function TelemetryTemplate(gamepad) {
* The Telemetry template class return {
* $clutchBar: document.querySelector('#clutch .bar'),
* @class TelemetryTemplate $clutchValue: document.querySelector('#clutch .value'),
*/ $brakeBar: document.querySelector('#brake .bar'),
class TelemetryTemplate { $brakeValue: document.querySelector('#brake .value'),
/** $throttleBar: document.querySelector('#throttle .bar'),
* Creates an instance of TelemetryTemplate. $throttleValue: document.querySelector('#throttle .value'),
*/ $directionIndicator: document.querySelector('#direction .indicator'),
constructor() { AXES: ['clutch', 'brake', 'throttle', 'direction'],
this.AXES = ['clutch', 'brake', 'throttle', 'direction']; frequency: gamepad.getUrlParam('fps') || 60,
historyLength: gamepad.getUrlParam('history') || 5000,
this.frequency = 1000 / 60; index: 0,
this.historyLength = 5000; init: function () {
this.length = this.historyLength / this.frequency; this.interval = 1000 / this.frequency;
this.index = 0; this.length = this.historyLength / this.interval;
this.loadAxes();
this.$clutchValue = document.querySelector('#clutch .value'); this.initChart();
this.$clutchBar = document.querySelector('#clutch .bar .filler'); },
this.$brakeValue = document.querySelector('#brake .value'); toPercentage: function (value, min, max) {
this.$brakeBar = document.querySelector('#brake .bar .filler'); return value !== undefined
this.$throttleValue = document.querySelector('#throttle .value'); ? Math.round((value - min) * (100 / (max - min)))
this.$throttleBar = document.querySelector('#throttle .bar .filler'); : 0;
this.$directionIndicator = document.querySelector('#direction #wheel--indicator'); },
toDegrees: function (value, min, max) {
this.init(); const percentage = this.toPercentage(value, min, max);
} return (this.directionDegrees) * (percentage - 50) / 100;
},
toPercentage(value, min, max) { toAxisValue: function (gamepad, axis) {
// debugger; const { [`${axis}Type`]: type, [`${axis}Index`]: index, [`${axis}Min`]: min, [`${axis}Max`]: max } = this[axis];
return value !== undefined const value = type === 'button' ? gamepad.buttons[index].value : gamepad.axes[index];
? Math.round((value - min) * (100 / (max - min))) return axis === 'direction' ? this.toDegrees(value, min, max) : this.toPercentage(value, min, max);
: 0; },
} loadAxes: function () {
this.AXES.forEach((axis) => {
toDegrees(value, min, max) { this[axis] = {
const percentage = this.toPercentage(value, min, max); [`${axis}Type`]: (gamepad.getUrlParam(`${axis}Type`) || 'axis').replace('axis', 'axe'),
return (this.directionDegrees) * (percentage - 50) / 100; [`${axis}Index`]: gamepad.getUrlParam(`${axis}Index`),
} [`${axis}Min`]: gamepad.getUrlParam(`${axis}Min`) || -1,
[`${axis}Max`]: gamepad.getUrlParam(`${axis}Max`) || 1,
toAxisValue(gamepad, axis) {
const { [`${axis}Type`]: type, [`${axis}Index`]: index, [`${axis}Min`]: min, [`${axis}Max`]: max } = this[axis];
const value = type === 'button' ? gamepad.buttons[index].value : gamepad.axes[index];
return axis === 'direction' ? this.toDegrees(value, min, max) : this.toPercentage(value, min, max);
}
loadAxes() {
this.AXES.forEach((axis) => {
this[axis] = {
[`${axis}Type`]: (window.gamepad.getUrlParam(`${axis}Type`) || 'axis').replace('axis', 'axe'),
[`${axis}Index`]: window.gamepad.getUrlParam(`${axis}Index`),
[`${axis}Min`]: window.gamepad.getUrlParam(`${axis}Min`) || -1,
[`${axis}Max`]: window.gamepad.getUrlParam(`${axis}Max`) || 1,
}
});
this.directionDegrees = window.gamepad.getUrlParam('directionDegrees') || 360;
}
init() {
this.loadAxes();
if (!window.google) {
window.setTimeout(this.init.bind(this), 100);
return;
}
google.charts.load('current', {
packages: ['corechart', 'line'],
});
google.charts.setOnLoadCallback(this.drawChart.bind(this));
}
drawChart() {
this.initialData = [['Time', 'Clutch', 'Brake', 'Throttle']];
for (this.index = 0; this.index < this.length; this.index++) {
this.initialData.push([this.index, 0, 0, 0]);
}
this.data = google.visualization.arrayToDataTable(this.initialData);
this.options = {
backgroundColor: 'transparent',
chartArea: {
left: 0,
top: 0,
width: '100%',
height: '100%',
backgroundColor: 'transparent',
},
hAxis: {
textPosition: 'none',
gridlines: {
color: 'transparent',
} }
}, });
vAxis: { this.directionDegrees = gamepad.getUrlParam('directionDegrees') || 360;
textPosition: 'none', },
gridlines: { initChart: function () {
color: 'transparent', if (!window.google) {
}, window.setTimeout(this.loadGoogleCharts.bind(this), 100);
minValue: 0,
maxValue: 100,
},
colors: ['blue', 'red', 'lime'],
legend: 'none'
};
this.chart = new google.visualization.LineChart(document.querySelector('#chart'));
this.chart.draw(this.data, this.options);
this.update();
}
update() {
const gamepad = window.gamepad.getActive();
if (!gamepad) return;
const [clutch, brake, throttle, direction] = this.AXES.map((axis) => this.toAxisValue(gamepad, axis));
this.updateChart(clutch, brake, throttle);
this.updateMeters(clutch, brake, throttle, direction);
window.setTimeout(this.update.bind(this), this.frequency);
}
updateChart(clutch, brake, throttle) {
if (this.data.getNumberOfRows() > this.length) {
this.data.removeRows(0, this.data.getNumberOfRows() - this.length);
}
this.data.addRow([this.index, clutch, brake, throttle]);
this.chart.draw(this.data, this.options);
this.index++;
}
updateMeters(clutch, brake, throttle, direction) {
Object.entries({ clutch, brake, throttle, direction }).forEach(([axis, value]) => {
if (axis === 'direction') {
this.$directionIndicator.style.transform = `rotate(${value}deg)`;
return; return;
} }
this[`$${axis}Value`].innerHTML = value; this.drawChart();
this[`$${axis}Bar`].style.height = `${value}%`; },
}); loadGoogleCharts: function () {
}; google.charts.load('current', { packages: ['corechart', 'line'], });
} google.charts.setOnLoadCallback(this.drawChart.bind(this));
},
drawChart: function () {
this.initialData = [['time', 'clutch', 'brake', 'throttle']];
for (this.index = 0; this.index < this.length; this.index++) {
this.initialData.push([this.index, 0, 0, 0]);
}
this.data = google.visualization.arrayToDataTable(this.initialData);
this.options = {
backgroundColor: 'transparent',
chartArea: {
left: 0,
top: 0,
width: '100%',
height: '100%',
backgroundColor: 'transparent',
},
hAxis: {
textPosition: 'none',
gridlines: {
color: 'transparent',
}
},
vAxis: {
textPosition: 'none',
gridlines: {
color: 'transparent',
},
minValue: 0,
maxValue: 100,
viewWindow: {
min: 2,
max: 102,
}
},
colors: ['#2D64B9', '#A52725', '#0CA818'],
legend: 'none'
};
this.chart = new google.visualization.LineChart(document.querySelector('#chart'));
this.chart.draw(this.data, this.options);
this.update();
},
update: function () {
const activeGamepad = gamepad.getActive();
if (!activeGamepad) return;
const [clutch, brake, throttle, direction] = this.AXES.map((axis) => this.toAxisValue(activeGamepad, axis));
this.updateChart(clutch, brake, throttle);
this.updateMeters(clutch, brake, throttle, direction);
window.setTimeout(this.update.bind(this), this.interval);
},
updateChart: function (clutch, brake, throttle) {
this.data.removeRows(0, 1);
this.data.addRow([this.index, clutch, brake, throttle]);
this.chart.draw(this.data, this.options);
this.index++;
},
updateMeters: function (clutch, brake, throttle, direction) {
Object.entries({ clutch, brake, throttle, direction }).forEach(([axis, value]) => {
if (axis === 'direction') {
this.$directionIndicator.style.transform = `rotate(${value}deg)`;
return;
}
this[`$${axis}Value`].innerHTML = value;
this[`$${axis}Value`].style.opacity = `${Math.round(33 + (value / 1.5))}%`;
this[`$${axis}Bar`].style.height = `${value}%`;
});
}
}.init();
};
window.telemetryTemplate = new TelemetryTemplate(); new TelemetryTemplate(window.gamepad);

View File

@ -1,33 +1,37 @@
gamepad.updateButton = function ($button) { function XboxOneTemplate(gamepad) {
const value = parseFloat($button.attr("data-value"), 10); return {
init: function () {
if ($button.is(".trigger")) { gamepad.updateButton = function ($button) {
$button.css( const value = parseFloat($button.attr('data-value'), 10);
gamepad.triggersMeter if ($button.is('.trigger')) {
? { $button.css(
opacity: 1, gamepad.triggersMeter
"clip-path": `inset(${(1 - value) * 100}% 0px 0px 0pc)`, ? {
} opacity: 1,
: { 'clip-path': `inset(${(1 - value) * 100}% 0px 0px 0pc)`,
opacity: `${value * 100}%`, }
"clip-path": "none", : {
} opacity: `${value * 100}%`,
); 'clip-path': 'none',
} }
}; );
}
gamepad.updateAxis = function ($axis) { };
const axisX = $axis.attr("data-value-x"); gamepad.updateAxis = function ($axis) {
const axisY = $axis.attr("data-value-y"); const axisX = $axis.attr('data-value-x');
const axisY = $axis.attr('data-value-y');
if ($axis.is(".stick")) { if ($axis.is('.stick')) {
$axis.css({ $axis.css({
"margin-top": axisY * 25, 'margin-top': axisY * 25,
"margin-left": axisX * 25, 'margin-left': axisX * 25,
transform: `rotateX(${-parseFloat( transform: `rotateX(${-parseFloat(
axisY * 30, axisY * 30,
8 8
)}deg) rotateY(${parseFloat(axisX * 30, 8)}deg)`, )}deg) rotateY(${parseFloat(axisX * 30, 8)}deg)`,
}); });
} }
};
},
}.init();
}; };
new XboxOneTemplate(window.gamepad);