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
*: 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>
<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>
<p>DualShock 4 and Xbox One skins from <a target="_blank"
href="https://gamepadviewer.com/">gamepadviewer.com</a>.</p>
</div>
<script src="js/jquery.min.js"></script>

View File

@ -24,6 +24,10 @@ class Gamepad {
this.$helpPopout = $('#help-popout');
this.$gamepadList = $('#gamepad-list');
// ensure the GamePad API is available on this browser
this.assertGamepadAPI();
// overlay selectors
this.backgroundStyle = [
'transparent',
'checkered',
@ -42,10 +46,6 @@ class Gamepad {
'black',
'black',
];
// ensure the GamePad API is available on this browser
this.assertGamepadAPI();
this.initOverlaySelectors();
// gamepad collection default values
@ -57,10 +57,11 @@ class Gamepad {
name: 'Debug',
},
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',
colors: ['black', 'white', 'red', 'blue'],
triggers: true,
zoom: true,
},
// gamecube: {
// id: /0079/, // 0079 = Nintendo GameCube vendor code
@ -68,7 +69,7 @@ class Gamepad {
// colors: ['black', 'purple'],
// },
// 'joy-con': {
// id: /200e/, // 0079 = Joy-Con specific product code
// id: /200e/, // 200e = Joy-Con specific product code
// name: 'Joy-Con (L+R) Controllers',
// colors: ['blue-red', 'grey-grey'],
// },
@ -78,19 +79,21 @@ class Gamepad {
// colors: ['black'],
// },
// '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',
// colors: ['black'],
// },
telemetry: {
id: /telemetry/,
name: 'Telemetry',
zoom: true
},
'xbox-one': {
id: /045e|xinput|XInput/, // 045e = Microsoft vendor code, xinput = standard Windows controller
name: 'Xbox One',
colors: ['black', 'white'],
triggers: true,
},
'telemetry': {
id: /telemetry/, // 045e = Microsoft vendor code, xinput = standard Windows controller
name: 'Telemetry'
zoom: true,
},
};
@ -122,9 +125,6 @@ class Gamepad {
axes: [],
};
// // read hash
// this.hash = this.readHash();
// listen for gamepad related events
this.haveEvents = 'GamepadEvent' in window;
if (this.haveEvents) {
@ -306,6 +306,16 @@ class Gamepad {
}, 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
*/
@ -317,9 +327,9 @@ class Gamepad {
if (!gamepad) {
continue;
}
const { name, vendor, product } = this.toGamepadInfo(gamepad.id);
$options.push(
`<option class='entry' value='${gamepad.id}'>${gamepad.id}</option>'`
`<option class='entry' value='${vendor}-${product}'>${name}</option>`
);
}
this.$gamepadSelect.append($options.join(''));
@ -341,7 +351,7 @@ class Gamepad {
}
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.$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
const gamepadId = this.getUrlParam('gamepad');
if (gamepadId) {
if (gamepad.id === gamepadId) {
const [vendor, product] = gamepadId.split('-');
if (gamepad.id.includes(vendor) && gamepad.id.includes(product)) {
this.map(gamepad.index);
return;
}
@ -648,7 +659,8 @@ class Gamepad {
this.identifier = this.identifiers[this.type];
// update the overlay selectors
this.$gamepadSelect.val(gamepad.id);
const { vendor, product } = this.toGamepadInfo(gamepad.id);
this.$gamepadSelect.val(`${vendor}-${product}`);
this.updateColors();
this.updateTriggers();
@ -658,16 +670,6 @@ class Gamepad {
// hide the help before displaying the template
this.hideInstructions();
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.updateTriggers();
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);
// read for parameters to apply:
const identifier = this.identifiers[this.type];
// - color
this.changeGamepadColor(this.getUrlParam('color'));
if (identifier.colors) {
this.changeGamepadColor(this.getUrlParam('color'));
} else {
this.updateUrlParams({ color: undefined });
}
// - triggers mode
this.toggleTriggersMeter(this.getUrlParam('triggers') === 'meter');
// - zoom$
window.setTimeout(() =>
this.changeZoom(
this.type === 'debug'
? 'auto'
: this.getUrlParam('zoom') || 'auto'
)
);
if (identifier.triggers) {
this.toggleTriggersMeter(this.getUrlParam('triggers') === 'meter');
} else {
this.updateUrlParams({ triggers: undefined });
}
// - zoom
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
this.mapping.buttons = [];
@ -773,7 +778,7 @@ class Gamepad {
*/
pollStatus(force = false) {
// 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
window.requestAnimationFrame(this.pollStatus.bind(this));
@ -786,8 +791,7 @@ class Gamepad {
if (
!force &&
(!activeGamepad || activeGamepad.timestamp === this.lastTimestamp)
)
return;
) return;
this.lastTimestamp = activeGamepad.timestamp;
// actually update the active gamepad graphically
@ -863,9 +867,18 @@ class Gamepad {
}
}
/**
* Changes the active gamepad
*
* @param {string} gamepadId
*/
changeGamepad(gamepadId) {
// 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
this.updateUrlParams({ gamepad: gamepadId !== 'auto' ? gamepadId : undefined });
@ -919,16 +932,6 @@ class Gamepad {
// update current settings
this.updateUrlParams({ background: 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
this.updateUrlParams({ color: 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({
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);
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
this.updateUrlParams({ type: this.type });
@ -1093,16 +1066,6 @@ class Gamepad {
// update debug value
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
this.changeSkin(this.debug ? 'debug' : 'auto')
}
@ -1118,16 +1081,6 @@ class Gamepad {
// display the help popout
this.$helpPopout.toggleClass('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
*
* @param {*} name
* @param {string} name
* @returns {string|boolean|null}
*/
getUrlParam(name) {
@ -1197,7 +1150,7 @@ class Gamepad {
/**
* Update url hash with new settings
*
* @param {*} newParams
* @param {object} newParams
*/
updateUrlParams(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
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
Sample: Xbox Controller
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
Samples:
- 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
- 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

View File

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

View File

@ -2,9 +2,21 @@
<script async src="templates/debug/template.js"></script>
<div class="info">
<div class="container">
<div id="info-id" class="box extra-large">
<div id="info-name" class="box extra-large">
<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>
</div>

View File

@ -1,81 +1,83 @@
(() => {
$id = $("#info-id .value");
$timestamp = $("#info-timestamp .value");
$index = $("#info-index .value");
$mapping = $("#info-mapping .value");
$rumble = $("#info-rumble .value");
$axes = $(".axes .container");
$buttons = $(".buttons .container");
function DebugTemplate(gamepad) {
return {
$name: $('#info-name .value'),
$vendor: $('#info-vendor .value'),
$product: $('#info-product .value'),
$timestamp: $('#info-timestamp .value'),
$index: $('#info-index .value'),
$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;
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));
}
})();
new DebugTemplate(window.gamepad);

View File

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

View File

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

View File

@ -1,143 +1,124 @@
/**
* The Telemetry template class
*
* @class TelemetryTemplate
*/
class TelemetryTemplate {
/**
* Creates an instance of TelemetryTemplate.
*/
constructor() {
this.AXES = ['clutch', 'brake', 'throttle', 'direction'];
this.frequency = 1000 / 60;
this.historyLength = 5000;
this.length = this.historyLength / this.frequency;
this.index = 0;
this.$clutchValue = document.querySelector('#clutch .value');
this.$clutchBar = document.querySelector('#clutch .bar .filler');
this.$brakeValue = document.querySelector('#brake .value');
this.$brakeBar = document.querySelector('#brake .bar .filler');
this.$throttleValue = document.querySelector('#throttle .value');
this.$throttleBar = document.querySelector('#throttle .bar .filler');
this.$directionIndicator = document.querySelector('#direction #wheel--indicator');
this.init();
}
toPercentage(value, min, max) {
// debugger;
return value !== undefined
? Math.round((value - min) * (100 / (max - min)))
: 0;
}
toDegrees(value, min, max) {
const percentage = this.toPercentage(value, min, max);
return (this.directionDegrees) * (percentage - 50) / 100;
}
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',
function TelemetryTemplate(gamepad) {
return {
$clutchBar: document.querySelector('#clutch .bar'),
$clutchValue: document.querySelector('#clutch .value'),
$brakeBar: document.querySelector('#brake .bar'),
$brakeValue: document.querySelector('#brake .value'),
$throttleBar: document.querySelector('#throttle .bar'),
$throttleValue: document.querySelector('#throttle .value'),
$directionIndicator: document.querySelector('#direction .indicator'),
AXES: ['clutch', 'brake', 'throttle', 'direction'],
frequency: gamepad.getUrlParam('fps') || 60,
historyLength: gamepad.getUrlParam('history') || 5000,
index: 0,
init: function () {
this.interval = 1000 / this.frequency;
this.length = this.historyLength / this.interval;
this.loadAxes();
this.initChart();
},
toPercentage: function (value, min, max) {
return value !== undefined
? Math.round((value - min) * (100 / (max - min)))
: 0;
},
toDegrees: function (value, min, max) {
const percentage = this.toPercentage(value, min, max);
return (this.directionDegrees) * (percentage - 50) / 100;
},
toAxisValue: function (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: function () {
this.AXES.forEach((axis) => {
this[axis] = {
[`${axis}Type`]: (gamepad.getUrlParam(`${axis}Type`) || 'axis').replace('axis', 'axe'),
[`${axis}Index`]: gamepad.getUrlParam(`${axis}Index`),
[`${axis}Min`]: gamepad.getUrlParam(`${axis}Min`) || -1,
[`${axis}Max`]: gamepad.getUrlParam(`${axis}Max`) || 1,
}
},
vAxis: {
textPosition: 'none',
gridlines: {
color: 'transparent',
},
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)`;
});
this.directionDegrees = gamepad.getUrlParam('directionDegrees') || 360;
},
initChart: function () {
if (!window.google) {
window.setTimeout(this.loadGoogleCharts.bind(this), 100);
return;
}
this[`$${axis}Value`].innerHTML = value;
this[`$${axis}Bar`].style.height = `${value}%`;
});
};
}
this.drawChart();
},
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) {
const value = parseFloat($button.attr("data-value"), 10);
if ($button.is(".trigger")) {
$button.css(
gamepad.triggersMeter
? {
opacity: 1,
"clip-path": `inset(${(1 - value) * 100}% 0px 0px 0pc)`,
}
: {
opacity: `${value * 100}%`,
"clip-path": "none",
}
);
}
};
gamepad.updateAxis = function ($axis) {
const axisX = $axis.attr("data-value-x");
const axisY = $axis.attr("data-value-y");
if ($axis.is(".stick")) {
$axis.css({
"margin-top": axisY * 25,
"margin-left": axisX * 25,
transform: `rotateX(${-parseFloat(
axisY * 30,
8
)}deg) rotateY(${parseFloat(axisX * 30, 8)}deg)`,
});
}
function XboxOneTemplate(gamepad) {
return {
init: function () {
gamepad.updateButton = function ($button) {
const value = parseFloat($button.attr('data-value'), 10);
if ($button.is('.trigger')) {
$button.css(
gamepad.triggersMeter
? {
opacity: 1,
'clip-path': `inset(${(1 - value) * 100}% 0px 0px 0pc)`,
}
: {
opacity: `${value * 100}%`,
'clip-path': 'none',
}
);
}
};
gamepad.updateAxis = function ($axis) {
const axisX = $axis.attr('data-value-x');
const axisY = $axis.attr('data-value-y');
if ($axis.is('.stick')) {
$axis.css({
'margin-top': axisY * 25,
'margin-left': axisX * 25,
transform: `rotateX(${-parseFloat(
axisY * 30,
8
)}deg) rotateY(${parseFloat(axisX * 30, 8)}deg)`,
});
}
};
},
}.init();
};
new XboxOneTemplate(window.gamepad);