Browse Source

virtual joystick

main
Hendrik Langer 4 years ago
parent
commit
5fcdb4753f
  1. 6
      raspberry/roberto/views/frontend/routes.py
  2. 62
      raspberry/roberto/views/frontend/templates/gamepad.js
  3. 59
      raspberry/roberto/views/frontend/templates/index.html
  4. 476
      raspberry/roberto/views/frontend/templates/virtualjoystick.js

6
raspberry/roberto/views/frontend/routes.py

@ -32,4 +32,8 @@ def index():
@frontend_blueprint.route('/gamepad.js') @frontend_blueprint.route('/gamepad.js')
def gamepad_js(): def gamepad_js():
return render_template('gamepad.js') return render_template('gamepad.js'), {'Content-Type': 'text/javascript'}
@frontend_blueprint.route('/virtualjoystick.js')
def virtualjoystick_js():
return render_template('virtualjoystick.js'), {'Content-Type': 'text/javascript'}

62
raspberry/roberto/views/frontend/templates/gamepad.js

@ -109,6 +109,7 @@ function updateStatus() {
axesChanged = true; axesChanged = true;
} }
} }
if (axesChanged) { if (axesChanged) {
gp_socket.emit('axes', {0: controller.axes[0], 1: controller.axes[1], 3: controller.axes[3], 4: controller.axes[4]}); gp_socket.emit('axes', {0: controller.axes[0], 1: controller.axes[1], 3: controller.axes[3], 4: controller.axes[4]});
} }
@ -134,3 +135,64 @@ if (haveEvents) {
} else { } else {
setInterval(scangamepads, 500); setInterval(scangamepads, 500);
} }
// Virtual Joystick
var joystick = new VirtualJoystick({
container: document.getElementById('controls'),
mouseSupport: true,
stationaryBase: true,
baseX: 250,
baseY: window.innerHeight-300,
limitStickTravel: true,
stickRadius: 120,
strokeStyle: 'cyan'
});
var joystick2 = new VirtualJoystick({
container: document.getElementById('controls'),
mouseSupport: true,
stationaryBase: true,
baseX: window.innerWidth-250,
baseY: window.innerHeight-300,
limitStickTravel: true,
stickRadius: 120,
strokeStyle: 'orange'
});
joystick.addEventListener('touchStartValidation', function(event){
var touch = event.changedTouches[0];
if( touch.pageX >= window.innerWidth/2 ) return false;
return true
});
joystick.addEventListener('mouseStartValidation', function(event){
if( event.clientX >= window.innerWidth/2 ) return false;
return true
});
joystick2.addEventListener('touchStartValidation', function(event){
var touch = event.changedTouches[0];
if( touch.pageX < window.innerWidth/2 ) return false;
return true
});
joystick2.addEventListener('mouseStartValidation', function(event){
if( event.clientX < window.innerWidth/2 ) return false;
return true
});
/*
joystick2.addEventListener('touchStart', function(){
console.log('fire')
})
*/
setInterval(function(){
if (joystick && joystick2) {
if (joystick._pressed === true || joystick2._pressed === true) {
var dx = joystick.deltaX() / joystick._stickRadius;
var dy = joystick.deltaY() / joystick._stickRadius;
var dx2 = joystick2.deltaX() / joystick2._stickRadius;
var dy2 = joystick2.deltaY() / joystick2._stickRadius;
gp_socket.emit('axes', {0: dx, 1: dy, 3: dx2, 4: dy2});
}
}
}, 50);

59
raspberry/roberto/views/frontend/templates/index.html

@ -3,9 +3,27 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Video Streaming Demonstration</title> <title>Roberto</title>
<!-- Bootstrap --> <!-- Bootstrap -->
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet"> <link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
<style>
body {
background: black;
}
#stream, #textoverlay, #controls {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
}
p {
position: relative;
z-index: 1;
}
</style>
</head> </head>
<body> <body>
<h1>Video Streaming Demonstration</h1> <h1>Video Streaming Demonstration</h1>
@ -13,21 +31,12 @@
<div id="video"> <div id="video">
<video id="stream" autoplay playsinline muted>Your browser does not support video</video> <video id="stream" autoplay playsinline muted>Your browser does not support video</video>
<canvas id="textoverlay"></canvas>
<div id="controls"></div>
</div> </div>
<h2>Send:</h2>
<form id="emit" method="POST" action='#'>
<input type="text" name="emit_data" id="emit_data" placeholder="Message">
<input type="submit" value="Echo">
</form>
<form id="broadcast" method="POST" action='#'>
<input type="text" name="broadcast_data" id="broadcast_data" placeholder="Message">
<input type="submit" value="Broadcast">
</form>
<h2>Receive:</h2>
<div id="log"></div>
<h2 id="start">Press a button on your controller to start</h2> <!--<h2 id="start">Press a button on your controller to start</h2>-->
<!-- jQuery and Bootstrap --> <!-- jQuery and Bootstrap -->
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script> <script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
@ -133,27 +142,17 @@ socket.on('data', (data) => {
var vidstream = document.getElementById("stream"); var vidstream = document.getElementById("stream");
var config = { 'iceServers': [{ 'urls': 'stun:stun.l.google.com:19302' }] }; var config = { 'iceServers': [{ 'urls': 'stun:stun.l.google.com:19302' }] };
playStream(vidstream, null, null, null, config, function (errmsg) { console.error(errmsg); }); playStream(vidstream, null, null, null, config, function (errmsg) { console.error(errmsg); });
var canvas = document.getElementById("textoverlay");
var ctx = canvas.getContext("2d");
ctx.font = "24px Arial";
ctx.fillStyle = "blue";
ctx.fillText("Hello World", canvas.width/3, 30);
}; };
</script> </script>
<script type="text/javascript" charset="utf-8"> <script src="{{ url_for('frontend.virtualjoystick_js') }}"></script>
$(document).ready(function(){
var test_socket = io.connect('wss://' + document.domain + ':' + location.port + '/test', { transports: [ 'websocket' ] });
test_socket.on('my response', function(msg) {
$('#log').append('<p>Received: ' + msg.data + '</p>');
});
$('form#emit').submit(function(event) {
test_socket.emit('my event', {data: $('#emit_data').val()});
return false;
});
$('form#broadcast').submit(function(event) {
test_socket.emit('my broadcast event', {data: $('#broadcast_data').val()});
return false;
});
});
</script>
<script src="{{ url_for('frontend.gamepad_js') }}"></script> <script src="{{ url_for('frontend.gamepad_js') }}"></script>
</body> </body>

476
raspberry/roberto/views/frontend/templates/virtualjoystick.js

@ -0,0 +1,476 @@
var VirtualJoystick = function(opts)
{
opts = opts || {};
this._container = opts.container || document.body;
this._strokeStyle = opts.strokeStyle || 'cyan';
this._stickEl = opts.stickElement || this._buildJoystickStick();
this._baseEl = opts.baseElement || this._buildJoystickBase();
this._mouseSupport = opts.mouseSupport !== undefined ? opts.mouseSupport : false;
this._stationaryBase = opts.stationaryBase || false;
this._baseX = this._stickX = opts.baseX || 0;
this._baseY = this._stickY = opts.baseY || 0;
this._limitStickTravel = opts.limitStickTravel || false;
this._stickRadius = opts.stickRadius !== undefined ? opts.stickRadius : 100;
this._useCssTransform = opts.useCssTransform !== undefined ? opts.useCssTransform : false;
this._show = opts.show || false;
this._releaseX = opts.releaseX !== undefined ? opts.releaseX : null;
this._releaseY = opts.releaseY !== undefined ? opts.releaseY : null;
this._container.style.position = "relative";
this._container.appendChild(this._baseEl);
this._baseEl.style.position = "absolute";
this._baseEl.style.display = "none";
this._container.appendChild(this._stickEl);
this._stickEl.style.position = "absolute";
this._stickEl.style.display = "none";
this._pressed = false;
this._touchIdx = null;
if(this._stationaryBase === true){
this._baseEl.style.display = "";
this._baseEl.style.left = (this._baseX - this._baseEl.width /2)+"px";
this._baseEl.style.top = (this._baseY - this._baseEl.height/2)+"px";
}
this._transform = this._useCssTransform ? this._getTransformProperty() : false;
this._has3d = this._check3D();
this._updatePositionOfContainer();
window.onresize = (function (joystick) {
return function () {
return joystick._updatePositionOfContainer();
}
})(this);
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
this._$onTouchStart = __bind(this._onTouchStart , this);
this._$onTouchEnd = __bind(this._onTouchEnd , this);
this._$onTouchMove = __bind(this._onTouchMove , this);
this._container.addEventListener( 'touchstart' , this._$onTouchStart , false );
this._container.addEventListener( 'touchend' , this._$onTouchEnd , false );
this._container.addEventListener( 'touchmove' , this._$onTouchMove , false );
if( this._mouseSupport ){
this._$onMouseDown = __bind(this._onMouseDown , this);
this._$onMouseUp = __bind(this._onMouseUp , this);
this._$onMouseMove = __bind(this._onMouseMove , this);
this._container.addEventListener( 'mousedown' , this._$onMouseDown , false );
this._container.addEventListener( 'mouseup' , this._$onMouseUp , false );
this._container.addEventListener( 'mousemove' , this._$onMouseMove , false );
}
}
//////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////////////////////////////////////////
VirtualJoystick.prototype.destroy = function()
{
this._container.removeChild(this._baseEl);
this._container.removeChild(this._stickEl);
this._container.removeEventListener( 'touchstart' , this._$onTouchStart , false );
this._container.removeEventListener( 'touchend' , this._$onTouchEnd , false );
this._container.removeEventListener( 'touchmove' , this._$onTouchMove , false );
if( this._mouseSupport ){
this._container.removeEventListener( 'mouseup' , this._$onMouseUp , false );
this._container.removeEventListener( 'mousedown' , this._$onMouseDown , false );
this._container.removeEventListener( 'mousemove' , this._$onMouseMove , false );
}
}
/**
* @returns {Boolean} true if touchscreen is currently available, false otherwise
*/
VirtualJoystick.touchScreenAvailable = function()
{
return 'createTouch' in document ? true : false;
}
/**
* microevents.js - https://github.com/jeromeetienne/microevent.js
*/
;(function(destObj){
destObj.addEventListener = function(event, fct){
if(this._events === undefined) this._events = {};
this._events[event] = this._events[event] || [];
this._events[event].push(fct);
return fct;
};
destObj.removeEventListener = function(event, fct){
if(this._events === undefined) this._events = {};
if( event in this._events === false ) return;
this._events[event].splice(this._events[event].indexOf(fct), 1);
};
destObj.dispatchEvent = function(event /* , args... */){
if(this._events === undefined) this._events = {};
if( this._events[event] === undefined ) return;
var tmpArray = this._events[event].slice();
for(var i = 0; i < tmpArray.length; i++){
var result = tmpArray[i].apply(this, Array.prototype.slice.call(arguments, 1))
if( result !== undefined ) return result;
}
return undefined
};
})(VirtualJoystick.prototype);
//////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////////////////////////////////////////
VirtualJoystick.prototype.deltaX = function(){ return this._stickX - this._baseX; }
VirtualJoystick.prototype.deltaY = function(){ return this._stickY - this._baseY; }
VirtualJoystick.prototype.up = function(){
if( this._pressed === false ) return false;
var deltaX = this.deltaX();
var deltaY = this.deltaY();
if( deltaY >= 0 ) return false;
if( Math.abs(deltaX) > 2*Math.abs(deltaY) ) return false;
return true;
}
VirtualJoystick.prototype.down = function(){
if( this._pressed === false ) return false;
var deltaX = this.deltaX();
var deltaY = this.deltaY();
if( deltaY <= 0 ) return false;
if( Math.abs(deltaX) > 2*Math.abs(deltaY) ) return false;
return true;
}
VirtualJoystick.prototype.right = function(){
if( this._pressed === false ) return false;
var deltaX = this.deltaX();
var deltaY = this.deltaY();
if( deltaX <= 0 ) return false;
if( Math.abs(deltaY) > 2*Math.abs(deltaX) ) return false;
return true;
}
VirtualJoystick.prototype.left = function(){
if( this._pressed === false ) return false;
var deltaX = this.deltaX();
var deltaY = this.deltaY();
if( deltaX >= 0 ) return false;
if( Math.abs(deltaY) > 2*Math.abs(deltaX) ) return false;
return true;
}
//////////////////////////////////////////////////////////////////////////////////
// Dealing with touches/mouse movents
//////////////////////////////////////////////////////////////////////////////////
VirtualJoystick.prototype._UpdateBase = function(){
this._move(this._baseEl.style, (this._baseX - this._baseEl.width /2), (this._baseY - this._baseEl.height/2));
}
VirtualJoystick.prototype._UpdateStick = function(){
this._move(this._stickEl.style, (this._stickX - this._stickEl.width /2), (this._stickY - this._stickEl.height/2));
}
VirtualJoystick.prototype._onUp = function()
{
this._pressed = false;
this._show ? this._stickEl.style.display = "" : this._stickEl.style.display = "none";
if(!(this._stationaryBase || this._show)) { this._baseEl.style.display = "none"; }
if(this._releaseX !=null || this._releaseY !=null){
if (this._releaseX !=null){
this._stickX= this._baseX + this._releaseX;
}
if (this._releaseY !=null){
this._stickY= this._baseY + this._releaseY;
}
}
else {
this._stickX = this._baseX;
this._stickY = this._baseY;
}
this._UpdateStick()
}
VirtualJoystick.prototype._onDown = function(x, y)
{
this._pressed = true;
if(this._stationaryBase == false){
this._baseX = x;
this._baseY = y;
this._baseEl.style.display = "";
this._UpdateBase()
}
this._stickX = x;
this._stickY = y;
if(this._limitStickTravel === true){
var deltaX = this.deltaX();
var deltaY = this.deltaY();
var stickDistance = Math.sqrt( (deltaX * deltaX) + (deltaY * deltaY) );
if(stickDistance > this._stickRadius){
var stickNormalizedX = deltaX / stickDistance;
var stickNormalizedY = deltaY / stickDistance;
this._stickX = stickNormalizedX * this._stickRadius + this._baseX;
this._stickY = stickNormalizedY * this._stickRadius + this._baseY;
}
}
this._stickEl.style.display = "";
this._UpdateStick()
}
VirtualJoystick.prototype._onMove = function(x, y)
{
if( this._pressed === true ){
this._stickX = x;
this._stickY = y;
if(this._limitStickTravel === true){
var deltaX = this.deltaX();
var deltaY = this.deltaY();
var stickDistance = Math.sqrt( (deltaX * deltaX) + (deltaY * deltaY) );
if(stickDistance > this._stickRadius){
var stickNormalizedX = deltaX / stickDistance;
var stickNormalizedY = deltaY / stickDistance;
this._stickX = stickNormalizedX * this._stickRadius + this._baseX;
this._stickY = stickNormalizedY * this._stickRadius + this._baseY;
}
}
this._UpdateStick()
}
}
//////////////////////////////////////////////////////////////////////////////////
// bind touch events (and mouse events for debug) //
// mouse events
//////////////////////////////////////////////////////////////////////////////////
// container offset.
VirtualJoystick.prototype._updatePositionOfContainer = function () {
var x = 0, y = 0;
var element = this._container;
if(this._stationaryBase === true){
this._UpdateBase()
while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) {
x += element.offsetLeft;
y += element.offsetTop;
element = element.offsetParent;
}
}
else {
while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) {
x += element.offsetLeft - element.scrollLeft;
y += element.offsetTop - element.scrollTop;
element = element.offsetParent;
}
}
this._containerX = x;
this._containerY = y;
}
VirtualJoystick.prototype._onMouseUp = function(event)
{
return this._onUp();
}
VirtualJoystick.prototype._onMouseDown = function(event)
{
// notify event for validation
var isValid = this.dispatchEvent('mouseStartValidation', event);
if( isValid === false ) return;
event.preventDefault();
var x = event.clientX - this._containerX;
var y = event.clientY - this._containerY;
return this._onDown(x, y);
}
VirtualJoystick.prototype._onMouseMove = function(event)
{
var x = event.clientX - this._containerX;
var y = event.clientY - this._containerY;
return this._onMove(x, y);
}
//////////////////////////////////////////////////////////////////////////////////
// touch events //
//////////////////////////////////////////////////////////////////////////////////
VirtualJoystick.prototype._onTouchStart = function(event)
{
// if there is already a touch inprogress do nothing
if( this._touchIdx !== null ) return;
// notify event for validation
var isValid = this.dispatchEvent('touchStartValidation', event);
if( isValid === false ) return;
// dispatch touchStart
this.dispatchEvent('touchStart', event);
event.preventDefault();
// get the first who changed
var touch = event.changedTouches[0];
// set the touchIdx of this joystick
this._touchIdx = touch.identifier;
// forward the action
var x = touch.pageX - this._containerX;
var y = touch.pageY - this._containerY;
return this._onDown(x, y)
}
VirtualJoystick.prototype._onTouchEnd = function(event)
{
// if there is no touch in progress, do nothing
if( this._touchIdx === null ) return;
// dispatch touchEnd
this.dispatchEvent('touchEnd', event);
// try to find our touch event
var touchList = event.changedTouches;
for(var i = 0; i < touchList.length && touchList[i].identifier !== this._touchIdx; i++);
// if touch event isnt found,
if( i === touchList.length) return;
// reset touchIdx - mark it as no-touch-in-progress
this._touchIdx = null;
//??????
// no preventDefault to get click event on ios
event.preventDefault();
return this._onUp()
}
VirtualJoystick.prototype._onTouchMove = function(event)
{
// if there is no touch in progress, do nothing
if( this._touchIdx === null ) return;
// try to find our touch event
var touchList = event.changedTouches;
for(var i = 0; i < touchList.length && touchList[i].identifier !== this._touchIdx; i++ );
// if touch event with the proper identifier isnt found, do nothing
if( i === touchList.length) return;
var touch = touchList[i];
event.preventDefault();
var x = touch.pageX - this._containerX;
var y = touch.pageY - this._containerY;
return this._onMove(x, y)
}
//////////////////////////////////////////////////////////////////////////////////
// build default stickEl and baseEl //
//////////////////////////////////////////////////////////////////////////////////
/**
* build the canvas for joystick base
*/
VirtualJoystick.prototype._buildJoystickBase = function()
{
var canvas = document.createElement( 'canvas' );
canvas.width = 126;
canvas.height = 126;
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.strokeStyle = this._strokeStyle;
ctx.lineWidth = 6;
ctx.arc( canvas.width/2, canvas.width/2, 40, 0, Math.PI*2, true);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = this._strokeStyle;
ctx.lineWidth = 2;
ctx.arc( canvas.width/2, canvas.width/2, 60, 0, Math.PI*2, true);
ctx.stroke();
return canvas;
}
/**
* build the canvas for joystick stick
*/
VirtualJoystick.prototype._buildJoystickStick = function()
{
var canvas = document.createElement( 'canvas' );
canvas.width = 86;
canvas.height = 86;
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.strokeStyle = this._strokeStyle;
ctx.lineWidth = 6;
ctx.arc( canvas.width/2, canvas.width/2, 40, 0, Math.PI*2, true);
ctx.stroke();
return canvas;
}
//////////////////////////////////////////////////////////////////////////////////
// move using translate3d method with fallback to translate > 'top' and 'left'
// modified from https://github.com/component/translate and dependents
//////////////////////////////////////////////////////////////////////////////////
VirtualJoystick.prototype._move = function(style, x, y) // moves the top left of cavas to (x,y).
{
if (this._transform) {
if (this._has3d) {
style[this._transform] = 'translate3d(' + x + 'px,' + y + 'px, 0)';
} else {
style[this._transform] = 'translate(' + x + 'px,' + y + 'px)';
}
} else {
style.left = x + 'px';
style.top = y + 'px';
}
}
VirtualJoystick.prototype._getTransformProperty = function()
{
var styles = [
'webkitTransform',
'MozTransform',
'msTransform',
'OTransform',
'transform'
];
var el = document.createElement('p');
var style;
for (var i = 0; i < styles.length; i++) {
style = styles[i];
if (null != el.style[style]) {
return style;
}
}
}
VirtualJoystick.prototype._check3D = function()
{
var prop = this._getTransformProperty();
// IE8<= doesn't have `getComputedStyle`
if (!prop || !window.getComputedStyle) return module.exports = false;
var map = {
webkitTransform: '-webkit-transform',
OTransform: '-o-transform',
msTransform: '-ms-transform',
MozTransform: '-moz-transform',
transform: 'transform'
};
// from: https://gist.github.com/lorenzopolidori/3794226
var el = document.createElement('div');
el.style[prop] = 'translate3d(1px,1px,1px)';
document.body.insertBefore(el, null);
var val = getComputedStyle(el).getPropertyValue(map[prop]);
document.body.removeChild(el);
var exports = null != val && val.length && 'none' != val;
return exports;
}
Loading…
Cancel
Save