194 linhas
7.1 KiB
JavaScript
194 linhas
7.1 KiB
JavaScript
(function(exports) {
|
|
"use strict";
|
|
|
|
// lookup-tables
|
|
var BTN = {
|
|
// Face (main) buttons
|
|
FACE_1: 0, FACE_2: 1, FACE_3: 2, FACE_4: 3,
|
|
|
|
// Top/bottom shoulder buttons
|
|
LEFT_SHOULDER: 4, RIGHT_SHOULDER: 5,
|
|
LEFT_SHOULDER_BOTTOM: 6, RIGHT_SHOULDER_BOTTOM: 7,
|
|
|
|
SELECT: 8, START: 9,
|
|
|
|
// Analogue stick-buttons (if depressible)
|
|
LEFT_ANALOGUE_STICK: 10,
|
|
RIGHT_ANALOGUE_STICK: 11,
|
|
|
|
// Directional (discrete) pad
|
|
PAD_TOP: 12, PAD_BOTTOM: 13, PAD_LEFT: 14, PAD_RIGHT: 15
|
|
};
|
|
|
|
var AXES = {
|
|
LEFT_ANALOGUE_HOR: 0, LEFT_ANALOGUE_VERT: 1,
|
|
RIGHT_ANALOGUE_HOR: 2, RIGHT_ANALOGUE_VERT: 3
|
|
};
|
|
|
|
|
|
var socket = null;
|
|
|
|
/**
|
|
* initializes the gamepad-controls
|
|
*
|
|
* @param websocket the websocket control-events are sent to
|
|
* @param options TODO...
|
|
* some ideas:
|
|
* - calibration-data
|
|
* - handler-callbacks for 'gamepadState'-events
|
|
* - button-configuration
|
|
*/
|
|
exports.initGamepad = function(websocket, options) {
|
|
socket = websocket;
|
|
|
|
// kick-off control-loop
|
|
(function __controlLoop() {
|
|
requestAnimationFrame(__controlLoop);
|
|
|
|
// TODO: only works in newer versions of chrome, adapt for mozilla-API…
|
|
var gamepad = navigator.webkitGetGamepads()[0];
|
|
|
|
// TODO: emit events gamepadConnect/gamepadDisconnect
|
|
if(!gamepad) { return; }
|
|
|
|
handleGamepadState(gamepad);
|
|
} ());
|
|
};
|
|
|
|
// initialize
|
|
var lastGamepadState = {
|
|
leftX: 0.0, leftY: 0.0,
|
|
btnStart: false, btnStop: false,
|
|
btnTurnCW: false, btnTurnCCW: false,
|
|
btnUp: false, btnDown: false
|
|
};
|
|
|
|
var getCalibratedValue = exports.getCalibratedValue || function(axes,idx) { return axes[idx]; };
|
|
|
|
function handleGamepadState(gamepad) {
|
|
var leftX, leftY;
|
|
|
|
leftX = getCalibratedValue(gamepad.axes, AXES.LEFT_ANALOGUE_HOR);
|
|
leftY = getCalibratedValue(gamepad.axes, AXES.LEFT_ANALOGUE_VERT);
|
|
|
|
// this is for better readability only and might be inlined.
|
|
// (I suspect the js-engine will likely inline it anyway).
|
|
var gamepadState = {
|
|
// toFixed(1) to prevent too many useless nav-packets
|
|
leftX: leftX.toFixed(1),
|
|
leftY: leftY.toFixed(1),
|
|
|
|
// buttons are converted to boolean for easier handling
|
|
btnStart: (1==gamepad.buttons[BTN.START]),
|
|
btnStop: (1==gamepad.buttons[BTN.SELECT]),
|
|
btnTurnCW: (1==gamepad.buttons[BTN.RIGHT_SHOULDER]),
|
|
btnTurnCCW: (1==gamepad.buttons[BTN.LEFT_SHOULDER]),
|
|
btnDown: (1==gamepad.buttons[BTN.FACE_1]),
|
|
btnUp: (1==gamepad.buttons[BTN.FACE_4]),
|
|
|
|
btnFlipFwd: (1==gamepad.buttons[BTN.PAD_TOP]),
|
|
btnFlipBwd: (1==gamepad.buttons[BTN.PAD_BOTTOM]),
|
|
btnFlipLeft: (1==gamepad.buttons[BTN.PAD_LEFT]),
|
|
btnFlipRight: (1==gamepad.buttons[BTN.PAD_RIGHT])
|
|
};
|
|
|
|
// ---- logging
|
|
// TODO: emit a gamepadState-event or something
|
|
document.querySelector('.log').innerHTML = JSON.stringify(gamepadState, null, 2);
|
|
|
|
// ---- analogue-stick left/right
|
|
var horiz=gamepadState.leftX;
|
|
if(horiz != lastGamepadState.leftX) {
|
|
if(horiz<0) { // negative: left
|
|
socket.emit('control', { action: 'left', speed: -horiz });
|
|
} else if(horiz>0) {
|
|
socket.emit('control', { action: 'right', speed: horiz });
|
|
} else { // == 0
|
|
socket.emit('control', { action: 'left', speed: 0 });
|
|
socket.emit('control', { action: 'right', speed: 0 });
|
|
}
|
|
}
|
|
|
|
// ---- analogue-stick up/down
|
|
var leftY=gamepadState.leftY;
|
|
if(leftY != lastGamepadState.leftY) {
|
|
if(leftY<0) { // negative: up
|
|
socket.emit('control', { action: 'front', speed: -leftY });
|
|
} else if(leftY>0) {
|
|
socket.emit('control', { action: 'back', speed: leftY });
|
|
} else { // == 0
|
|
socket.emit('control', { action: 'front', speed: 0 });
|
|
socket.emit('control', { action: 'back', speed: 0 });
|
|
}
|
|
}
|
|
|
|
// ---- takeoff/land
|
|
if(gamepadState.btnStart && !lastGamepadState.btnStart) {
|
|
socket.emit('control', { action: 'takeoffOrLand' });
|
|
}
|
|
|
|
// ---- stop-button
|
|
if(gamepadState.btnStop && !lastGamepadState.btnStop) {
|
|
socket.emit('control', { action: 'stop' });
|
|
}
|
|
|
|
// ---- up/down/cw/ccw buttons (TODO: add accelleration for more fine-grained control)
|
|
var evMap = {
|
|
btnUp: { action: 'up', mode: 'toggleSpeed' },
|
|
btnDown: { action: 'down', mode: 'toggleSpeed' },
|
|
btnTurnCW: { action: 'clockwise', mode: 'toggleSpeed' },
|
|
btnTurnCCW: { action: 'counterClockwise', mode: 'toggleSpeed' },
|
|
|
|
btnFlipFwd: { action: 'animate', animation: 'flipAhead', mode: 'trigger' },
|
|
btnFlipBwd: { action: 'animate', animation: 'flipBehind', mode: 'trigger' },
|
|
btnFlipLeft: { action: 'animate', animation: 'flipLeft', mode: 'trigger' },
|
|
btnFlipRight: { action: 'animate', animation: 'flipRight', mode: 'trigger' }
|
|
};
|
|
|
|
Object.keys(evMap).forEach(function(btnId) {
|
|
var evData = evMap[btnId],
|
|
curr = gamepadState[btnId],
|
|
last = lastGamepadState[btnId];
|
|
|
|
if('toggleSpeed' == evData.mode) {
|
|
if(curr && !last) { // btnPress
|
|
socket.emit('control', { action: evData.action, speed: 0.5 });
|
|
} else if(!curr && last) { // btnRelease
|
|
socket.emit('control', { action: evData.action, speed: 0 });
|
|
}
|
|
} else if('trigger' == evData.mode) {
|
|
if(curr && !last) {
|
|
socket.emit('control', { action: evData.action, animation: evData.animation, duration: 15 });
|
|
}
|
|
}
|
|
});
|
|
|
|
lastGamepadState = gamepadState;
|
|
};
|
|
} ( (typeof exports === 'undefined')? (this.nodecopterGamepad = this.nodecopterGamepad||{}) : exports) );
|
|
|
|
// RAF-Polyfill
|
|
(function(window) {
|
|
var lastTime = 0;
|
|
var vendors = ['ms', 'moz', 'webkit', 'o'];
|
|
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
|
|
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
|
|
window.cancelAnimationFrame =
|
|
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
|
|
}
|
|
|
|
if (!window.requestAnimationFrame)
|
|
window.requestAnimationFrame = function(callback, element) {
|
|
var currTime = new Date().getTime();
|
|
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
|
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
|
|
timeToCall);
|
|
lastTime = currTime + timeToCall;
|
|
return id;
|
|
};
|
|
|
|
if (!window.cancelAnimationFrame)
|
|
window.cancelAnimationFrame = function(id) {
|
|
clearTimeout(id);
|
|
};
|
|
}(window||{})); |