From 0eee6dfe200b5ab79249d304cd38034baf367522 Mon Sep 17 00:00:00 2001 From: Medape <47781797+medape@users.noreply.github.com> Date: Fri, 19 Jul 2019 01:17:47 +0200 Subject: [PATCH 1/7] Not expect extra bytes from the APK --- devel/app-debug.apk | Bin 86741 -> 44352 bytes yoke/service.py | 7 +------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/devel/app-debug.apk b/devel/app-debug.apk index 7a1e696351e833d03d16b6045bfc4790ac671702..45fc93c00a53afb1a16b3c8c887cade9b1b4da92 100755 GIT binary patch delta 14274 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 CB%v?> delta 20805 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 diff --git a/yoke/service.py b/yoke/service.py index 2b27c01..e63e2a9 100644 --- a/yoke/service.py +++ b/yoke/service.py @@ -52,8 +52,6 @@ def get_ip_address(): ) - - ABS_EVENTS = [getattr(EVENTS, n) for n in dir(EVENTS) if n.startswith("ABS_")] class Device: @@ -181,10 +179,7 @@ def make_events(self, values): raise NotImplementedError() def preprocess(self, message): - _, *v, _ = message.split(b',') # first and last value is nothing - _, *v = v # first real value (from accelerometer) is not important yet - _, _, *v = v # ignore 2 values from accelerometer in Android code - # TODO: remove when removing corresponding Android code + v = message.split(b',') v = [float(m) for m in v] v = ( # TODO: normalize from [0, 1] to [-1, 1] on JS side v[0] * 2 - 1, From 8dff68ecf1b658e658e56a65b62a8d94cba847da Mon Sep 17 00:00:00 2001 From: Medape <47781797+medape@users.noreply.github.com> Date: Fri, 19 Jul 2019 01:52:29 +0200 Subject: [PATCH 2/7] Clarify the JavaScript code Control.prototype.getBoundingClientRect() calculates every coordinate for every control now. This deletes repeated lines of code and makes it clearer. --- yoke/assets/joypad/base.js | 56 ++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/yoke/assets/joypad/base.js b/yoke/assets/joypad/base.js index b15b1b5..7d71deb 100644 --- a/yoke/assets/joypad/base.js +++ b/yoke/assets/joypad/base.js @@ -7,7 +7,7 @@ var VIBRATE_ON_PAD_BOUNDARY = true; // Code: // These 2 options are recommended for testing in non-kiosk/non-embedded browsers: -var WAIT_FOR_FULLSCREEN = true; +var WAIT_FOR_FULLSCREEN = false; // FIXME true; var DEBUG_NO_CONSOLE_SPAM = true; var VIBRATION_MILLISECONDS_IN = 40; @@ -206,11 +206,7 @@ function truncate(f, id, pattern) { else { return val; } }); if (VIBRATE_ON_PAD_BOUNDARY && pattern) { - if (truncated) { - queueForVibration(id, pattern); - } else { - unqueueForVibration(id); - } + truncated ? queueForVibration(id, pattern) : unqueueForVibration(id); } return f; } @@ -252,6 +248,13 @@ function Control(type, id, updateStateCallback) { this._state = 0; this.kernelEvent = ''; } +Control.prototype.getBoundingClientRect = function() { + this._offset = this.element.getBoundingClientRect(); + this._offset.semiwidth = this._offset.width / 2; + this._offset.semiheight = this._offset.height / 2; + this._offset.xCenter = this._offset.x + this._offset.semiwidth; + this._offset.yCenter = this._offset.y + this._offset.semiheight; +}; Control.prototype.onAttached = function() {}; Control.prototype.state = function() { return this._state.toString(); @@ -262,7 +265,6 @@ function Joystick(id, updateStateCallback) { this._state = [0.5, 0.5]; this.quadrant = -2; this._locking = (id[0] == 's'); - this._offset = {}; this._circle = document.createElement('div'); this._circle.className = 'circle'; this.element.appendChild(this._circle); @@ -270,7 +272,6 @@ function Joystick(id, updateStateCallback) { } Joystick.prototype = Object.create(Control.prototype); Joystick.prototype.onAttached = function() { - this._offset = this.element.getBoundingClientRect(); this._updateCircle(); this.element.addEventListener('touchmove', this.onTouch.bind(this), false); this.element.addEventListener('touchstart', this.onTouchStart.bind(this), false); @@ -311,7 +312,9 @@ Joystick.prototype.onTouchEnd = function() { window.navigator.vibrate(VIBRATION_MILLISECONDS_OUT); }; Joystick.prototype._updateCircle = function() { - this._circle.style.transform = 'translate(-50%, -50%) translate(' + (this._offset.x + this._offset.width * this._state[0]) + 'px, ' + (this._offset.y + this._offset.height * this._state[1]) + 'px)'; + this._circle.style.transform = 'translate(-50%, -50%) translate(' + + (this._offset.x + this._offset.width * this._state[0]) + 'px, ' + + (this._offset.y + this._offset.height * this._state[1]) + 'px)'; }; function Motion(id, updateStateCallback) { @@ -371,12 +374,10 @@ Motion.prototype.state = function() { function Pedal(id, updateStateCallback) { Control.call(this, 'button', id, updateStateCallback); this._state = 0; - this._offset = {}; axes += 1; } Pedal.prototype = Object.create(Control.prototype); Pedal.prototype.onAttached = function() { - this._offset = this.element.getBoundingClientRect(); this.element.addEventListener('touchstart', this.onTouchStart.bind(this), false); this.element.addEventListener('touchmove', this.onTouchMove.bind(this), false); this.element.addEventListener('touchend', this.onTouchEnd.bind(this), false); @@ -415,30 +416,22 @@ function AnalogButton(id, updateStateCallback) { this.onTouchMoveParticular = function() {}; Control.call(this, 'button', id, updateStateCallback); this._state = 0; - this._offset = {}; axes += 1; } AnalogButton.prototype = Object.create(Control.prototype); AnalogButton.prototype.onAttached = function() { - this._offset = this.element.getBoundingClientRect(); this.element.addEventListener('touchstart', this.onTouchStart.bind(this), false); this.element.addEventListener('touchmove', this.onTouchMove.bind(this), false); this.element.addEventListener('touchend', this.onTouchEnd.bind(this), false); if (this._offset.width > this._offset.height) { - this._offset.width /= 2; - this._offset.x += this._offset.width; this.onTouchMoveParticular = function(ev) { var pos = ev.targetTouches[0]; - return truncate([1 - Math.abs(this._offset.x - pos.pageX) / this._offset.width]); - + return truncate([1 - Math.abs(this._offset.xCenter - pos.pageX) / this._offset.semiwidth]); }; } else { - this._offset.height /= 2; - this._offset.y += this._offset.height; this.onTouchMoveParticular = function(ev) { var pos = ev.targetTouches[0]; - return truncate([1 - Math.abs(this._offset.y - pos.pageY) / this._offset.height]); - + return truncate([1 - Math.abs(this._offset.yCenter - pos.pageY) / this._offset.semiheight]); }; } }; @@ -470,7 +463,6 @@ AnalogButton.prototype.onTouchEnd = function() { function Knob(id, updateStateCallback) { Control.call(this, 'knob', id, updateStateCallback); this._state = 0; - this._offset = {}; this._knobcircle = document.createElement('div'); this._knobcircle.className = 'knobcircle'; this.element.appendChild(this._knobcircle); @@ -481,23 +473,20 @@ function Knob(id, updateStateCallback) { } Knob.prototype = Object.create(Control.prototype); Knob.prototype.onAttached = function() { - // First approximation to the knob coordinates. - this._offset = this.element.getBoundingClientRect(); // Centering the knob within the boundary. - var minDimension = Math.min(this._offset.width, this._offset.height); - if (minDimension == this._offset.width) { - this._knobcircle.style.top = this._offset.y + (this._offset.height - this._offset.width) / 2 + 'px'; + if (this._offset.width < this._offset.height) { + this._offset.y += this._offset.semiheight - this._offset.semiwidth; this._offset.height = this._offset.width; + this._offset.semiheight = this._offset.semiwidth; } else { - this._knobcircle.style.left = this._offset.x + (this._offset.width - this._offset.height) / 2 + 'px'; + this._offset.x += this._offset.semiwidth - this._offset.semiheight; this._offset.width = this._offset.height; + this._offset.semiwidth = this._offset.semiheight; } + this._knobcircle.style.top = this._offset.y + 'px'; + this._knobcircle.style.left = this._offset.x + 'px'; this._knobcircle.style.height = this._offset.width + 'px'; this._knobcircle.style.width = this._offset.height + 'px'; - // Calculating the exact center. - this._offset = this._knobcircle.getBoundingClientRect(); - this._offset.x += this._offset.width / 2; - this._offset.y += this._offset.height / 2; this._updateCircles(); this.quadrant = 0; this.element.addEventListener('touchmove', this.onTouch.bind(this), false); @@ -506,7 +495,7 @@ Knob.prototype.onAttached = function() { }; Knob.prototype.onTouch = function(ev) { var pos = ev.targetTouches[0]; - this._state = Math.atan2(pos.pageY - this._offset.y, pos.pageX - this._offset.x) / 2 / Math.PI + 0.5; + this._state = Math.atan2(pos.pageY - this._offset.yCenter, pos.pageX - this._offset.xCenter) / 2 / Math.PI + 0.5; this.updateStateCallback(); var currentQuadrant = Math.floor(this._state * 16); if (VIBRATE_ON_QUADRANT_BOUNDARY && this.quadrant != currentQuadrant) { @@ -583,6 +572,7 @@ function Joypad() { }, this); this._controls.forEach(function(control) { this.element.appendChild(control.element); + control.getBoundingClientRect(); control.onAttached(); }, this); if (axes == 0 && buttons == 0) { From 9ae9167c813c9769a5e2c01dbecc04e46498c685 Mon Sep 17 00:00:00 2001 From: Medape <47781797+medape@users.noreply.github.com> Date: Fri, 19 Jul 2019 02:42:02 +0200 Subject: [PATCH 3/7] Sending int:[0, 255] from JavaScript state() maps float:[0, 1] to int:[0, 255] right before joining every state into a string. --- yoke/assets/joypad/base.js | 21 +++++++++++---------- yoke/service.py | 15 ++------------- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/yoke/assets/joypad/base.js b/yoke/assets/joypad/base.js index 7d71deb..932bff5 100644 --- a/yoke/assets/joypad/base.js +++ b/yoke/assets/joypad/base.js @@ -257,7 +257,7 @@ Control.prototype.getBoundingClientRect = function() { }; Control.prototype.onAttached = function() {}; Control.prototype.state = function() { - return this._state.toString(); + return Math.round(255 * this._state); }; function Joystick(id, updateStateCallback) { @@ -316,6 +316,9 @@ Joystick.prototype._updateCircle = function() { (this._offset.x + this._offset.width * this._state[0]) + 'px, ' + (this._offset.y + this._offset.height * this._state[1]) + 'px)'; }; +Joystick.prototype.state = function() { + return this._state.map(function(val) {return Math.round(255 * val);}) +} function Motion(id, updateStateCallback) { // Motion reads the letters in id to decide which coordinates should it send to the Yoke server. @@ -368,7 +371,7 @@ Motion.prototype.onDeviceOrientation = function(ev) { motionSensor.updateStateCallback(); }; Motion.prototype.state = function() { - return motionState[this._mask].toString(); + return Math.round(255 * motionState[this._mask]); }; function Pedal(id, updateStateCallback) { @@ -520,7 +523,7 @@ Knob.prototype._updateCircles = function() { function Button(id, updateStateCallback) { Control.call(this, 'button', id, updateStateCallback); - this._state = 0; + this._state = false; buttons += 1; } Button.prototype = Object.create(Control.prototype); @@ -530,17 +533,20 @@ Button.prototype.onAttached = function() { }; Button.prototype.onTouchStart = function(ev) { ev.preventDefault(); // Android Webview delays the vibration without this. - this._state = 1; + this._state = true; this.updateStateCallback(); this.element.classList.add('pressed'); window.navigator.vibrate(VIBRATION_MILLISECONDS_IN); }; Button.prototype.onTouchEnd = function() { - this._state = 0; + this._state = false; this.updateStateCallback(); this.element.classList.remove('pressed'); window.navigator.vibrate(VIBRATION_MILLISECONDS_OUT); }; +Button.prototype.state = function() { + return (this._state ? 1 : 0); +}; function Dummy(id, updateStateCallback) { Control.call(this, 'dummy', 'dum', updateStateCallback); @@ -604,11 +610,6 @@ function Joypad() { Joypad.prototype.updateState = function() { var state = this._controls.map(function(control) { return control.state(); }).join(','); - // We are reducing float precision to avoid getting UDP messages cut in half. - // We are re-splitting the string since some control.state() above return strings - // (e.g. because [0.5, 0.5].toString() == '0.5,0.5') - state = state.split(',').map(function(x) { return x.substr(0, 6); }).join(','); - // Within the Yoke webview, sends the joypad state. // Outside the Yoke webview, window.Yoke.update_vals() is redefined to have no effect. // This prevents JavaScript exceptions, and wastes less CPU time when in Yoke: diff --git a/yoke/service.py b/yoke/service.py index e63e2a9..f001496 100644 --- a/yoke/service.py +++ b/yoke/service.py @@ -75,8 +75,6 @@ def __init__(self, id=1, name="Yoke", events=GAMEPAD_EVENTS): def emit(self, d, v): if d not in self.events: raise AttributeError("Event {} has not been registered.".format(d)) - if d in ABS_EVENTS: - v = (v+1)/2 * 255 self.device.emit(d, int(v), syn=False) def flush(self): @@ -107,7 +105,7 @@ def emit(self, d, v): if d in range(1, 8+1): self.device.set_button(d, v) else: - self.device.set_axis(d, int((v+1)/2 * 32768)) + self.device.set_axis(d, int(v * 32767 / 255)) def flush(self): pass def close(self): @@ -173,20 +171,11 @@ def __init__(self, dev, iface='auto', port=0, client_path=DEFAULT_CLIENT_PATH): self.client_path = client_path def make_events(self, values): - """returns a (event_code, value) tuple for each value in values - values are in (-1, 1) and should be returned in (-1, 1) - """ raise NotImplementedError() def preprocess(self, message): v = message.split(b',') - v = [float(m) for m in v] - v = ( # TODO: normalize from [0, 1] to [-1, 1] on JS side - v[0] * 2 - 1, - v[1] * 2 - 1, - v[2] * 2 - 1, - v[3] * 2 - 1, - ) + tuple(v[4:]) + v = tuple([int(m) for m in v]) if len(v) < len(GAMEPAD_EVENTS): # Before reducing float precision, sometimes UDP messages were getting cut in half. # Keeping the code just in case. From fac84690c21505943dc44a57e600f92d02c054d0 Mon Sep 17 00:00:00 2001 From: Medape <47781797+medape@users.noreply.github.com> Date: Fri, 19 Jul 2019 03:00:12 +0200 Subject: [PATCH 4/7] Simplifying CSS styling and adding buttons. Placeholders for select, start and mode buttons. --- yoke/assets/joypad/base.css | 41 +++++++++++++++++++++++++++++----- yoke/assets/joypad/gamepad.css | 26 ++------------------- yoke/assets/joypad/img/g.svg | 4 ++++ yoke/assets/joypad/img/m.svg | 4 ++++ yoke/assets/joypad/img/s.svg | 4 ++++ yoke/assets/joypad/racing.css | 26 ++------------------- yoke/assets/joypad/testing.css | 30 +------------------------ 7 files changed, 53 insertions(+), 82 deletions(-) create mode 100644 yoke/assets/joypad/img/g.svg create mode 100644 yoke/assets/joypad/img/m.svg create mode 100644 yoke/assets/joypad/img/s.svg diff --git a/yoke/assets/joypad/base.css b/yoke/assets/joypad/base.css index 66cae88..8df5948 100644 --- a/yoke/assets/joypad/base.css +++ b/yoke/assets/joypad/base.css @@ -110,11 +110,24 @@ div { background-size: 100% 100%; } +/* Joysticks */ +.joystick { background-image: url("img/joystick.svg"); background-color: #bbb; } +.circle { + background-color: black; + width: 10px; + height: 10px; + border-radius: 100%; +} + +/* Buttons */ +.button { background-color: #bbb; } +.pressed { filter: brightness(70%); } /* seq 1 16 | xargs -I xx echo "#bxx { background-image: url('img/xx.svg'); }" */ -#b1 { background-image: url('img/1.svg'); } -#b2 { background-image: url('img/2.svg'); } -#b3 { background-image: url('img/3.svg'); } -#b4 { background-image: url('img/4.svg'); } +/* Some edits done by hand */ +#b1 { background-image: url('img/1.svg'); background-color: #00d; } +#b2 { background-image: url('img/2.svg'); background-color: #e00; } +#b3 { background-image: url('img/3.svg'); background-color: #dd0; } +#b4 { background-image: url('img/4.svg'); background-color: #0d0; } #b5 { background-image: url('img/5.svg'); } #b6 { background-image: url('img/6.svg'); } #b7 { background-image: url('img/7.svg'); } @@ -127,9 +140,27 @@ div { #b14 { background-image: url('img/14.svg'); } #b15 { background-image: url('img/15.svg'); } #b16 { background-image: url('img/16.svg'); } - +#bg { background-image: url('img/g.svg'); background-color: #444; } +#bs { background-image: url('img/s.svg'); background-color: #444; } +#bm { background-image: url('img/m.svg'); background-color: #444; } /* printf "du\ndl\ndd\ndr" | xargs -I xx echo "#xx { background-image: url('img/xx.svg'); }" */ #du { background-image: url('img/du.svg'); } #dl { background-image: url('img/dl.svg'); } #dd { background-image: url('img/dd.svg'); } #dr { background-image: url('img/dr.svg'); } + +/* Analog buttons */ +#a1 {background-color: #66f;} +#a2 {background-color: #f33;} +#a3 {background-color: #ff2;} +#a4 {background-color: #2f2;} + +/* Motion controls */ +.motion {background-color: #ddd;} + +/* Pedals */ +.pedal {background-color: #444;} + +/* Knobs */ +.knob { } +.knobcircle {background-color: #888;} diff --git a/yoke/assets/joypad/gamepad.css b/yoke/assets/joypad/gamepad.css index 5218e0e..c12bc61 100644 --- a/yoke/assets/joypad/gamepad.css +++ b/yoke/assets/joypad/gamepad.css @@ -32,7 +32,8 @@ * g for SELECT button, * m for the branded button (HOME or equivalent), * 1, 2, 3, 4, 5, 6, 7, 8, 9, 10... for the rest. - * d_ for D-PAD, where _ is one of these letters: + * a_ for analog button/trigger, where _ is a number; + * d_ for D-pad, where _ is one of these letters: * u, for the key UP, * d, for the key DOWN, * l, for the key LEFT, @@ -40,26 +41,3 @@ * dbg for debug messages; * a period for empty space. */ } - -.joystick { background-image: url("img/joystick.svg"); background-color: #bbb; } -.circle { - background-color: black; - width: 10px; - height: 10px; - border-radius: 100%; -} - -.motion {background-color: #ddd;} - -.pedal {background-color: #444;} - -.knob { } -.knobcircle {background-color: #888;} - -.button { background-color: #bbb; } -.pressed { filter: brightness(70%); } -#b1 {background-color: #22e;} -#b2 {background-color: #e00;} -#b3 {background-color: #dd0;} -#b4 {background-color: #0d0;} -#bg, #bs, #bm {background-color: #444;} diff --git a/yoke/assets/joypad/img/g.svg b/yoke/assets/joypad/img/g.svg new file mode 100644 index 0000000..b641c1e --- /dev/null +++ b/yoke/assets/joypad/img/g.svg @@ -0,0 +1,4 @@ + + + G + diff --git a/yoke/assets/joypad/img/m.svg b/yoke/assets/joypad/img/m.svg new file mode 100644 index 0000000..23a4f7e --- /dev/null +++ b/yoke/assets/joypad/img/m.svg @@ -0,0 +1,4 @@ + + + M + diff --git a/yoke/assets/joypad/img/s.svg b/yoke/assets/joypad/img/s.svg new file mode 100644 index 0000000..7d6904b --- /dev/null +++ b/yoke/assets/joypad/img/s.svg @@ -0,0 +1,4 @@ + + + + diff --git a/yoke/assets/joypad/racing.css b/yoke/assets/joypad/racing.css index 8c32be0..3769916 100644 --- a/yoke/assets/joypad/racing.css +++ b/yoke/assets/joypad/racing.css @@ -32,7 +32,8 @@ * g for SELECT button, * m for the branded button (HOME or equivalent), * 1, 2, 3, 4, 5, 6, 7, 8, 9, 10... for the rest. - * d_ for D-PAD, where _ is one of these letters: + * a_ for analog button/trigger, where _ is a number; + * d_ for D-pad, where _ is one of these letters: * u, for the key UP, * d, for the key DOWN, * l, for the key LEFT, @@ -40,26 +41,3 @@ * dbg for debug messages; * a period for empty space. */ } - -.joystick { background-image: url("img/joystick.svg"); background-color: #bbb; } -.circle { - background-color: black; - width: 10px; - height: 10px; - border-radius: 100%; -} - -.motion {background-color: #ddd;} - -.pedal {background-color: #444;} - -.knob { } -.knobcircle {background-color: #888;} - -.button { background-color: #bbb; } -.pressed { filter: brightness(70%); } -#b1 {background-color: #22e;} -#b2 {background-color: #e00;} -#b3 {background-color: #dd0;} -#b4 {background-color: #0d0;} -#bg, #bs, #bm {background-color: #444;} diff --git a/yoke/assets/joypad/testing.css b/yoke/assets/joypad/testing.css index e5b93c3..3c0e623 100644 --- a/yoke/assets/joypad/testing.css +++ b/yoke/assets/joypad/testing.css @@ -33,7 +33,7 @@ * m for the branded button (HOME or equivalent), * 1, 2, 3, 4, 5, 6, 7, 8, 9, 10... for the rest; * a_ for analog button/trigger, where _ is a number; - * d_ for D-PAD, where _ is one of these letters: + * d_ for D-pad, where _ is one of these letters: * u, for the key UP, * d, for the key DOWN, * l, for the key LEFT, @@ -41,31 +41,3 @@ * dbg for debug messages; * a period for empty space. */ } - -.joystick { background-image: url("img/joystick.svg"); background-color: #bbb; } -.circle { - background-color: black; - width: 10px; - height: 10px; - border-radius: 100%; -} - -.motion {background-color: #ddd;} - -.pedal {background-color: #444;} - -.knob { } -.knobcircle {background-color: #888;} - -.button { background-color: #bbb; } -.pressed { filter: brightness(70%); } -#b1 {background-color: #22e;} -#b2 {background-color: #e00;} -#b3 {background-color: #dd0;} -#b4 {background-color: #0d0;} -#bg, #bs, #bm {background-color: #444;} - -#a1 {background-color: #66f;} -#a2 {background-color: #f33;} -#a3 {background-color: #ff2;} -#a4 {background-color: #2f2;} From 464eda10c7e53a056e8a753ed7036dcbb54a317e Mon Sep 17 00:00:00 2001 From: Medape <47781797+medape@users.noreply.github.com> Date: Fri, 19 Jul 2019 03:14:58 +0200 Subject: [PATCH 5/7] Reenable fullscreen (I disabled it by mistake) Also, run eslint. --- yoke/assets/joypad/base.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yoke/assets/joypad/base.js b/yoke/assets/joypad/base.js index 932bff5..23a9110 100644 --- a/yoke/assets/joypad/base.js +++ b/yoke/assets/joypad/base.js @@ -7,7 +7,7 @@ var VIBRATE_ON_PAD_BOUNDARY = true; // Code: // These 2 options are recommended for testing in non-kiosk/non-embedded browsers: -var WAIT_FOR_FULLSCREEN = false; // FIXME true; +var WAIT_FOR_FULLSCREEN = true; var DEBUG_NO_CONSOLE_SPAM = true; var VIBRATION_MILLISECONDS_IN = 40; @@ -317,8 +317,8 @@ Joystick.prototype._updateCircle = function() { (this._offset.y + this._offset.height * this._state[1]) + 'px)'; }; Joystick.prototype.state = function() { - return this._state.map(function(val) {return Math.round(255 * val);}) -} + return this._state.map(function(val) {return Math.round(255 * val);}); +}; function Motion(id, updateStateCallback) { // Motion reads the letters in id to decide which coordinates should it send to the Yoke server. From ebe6640d4d9378034fea2e9c0b9ff3fd1a2ea60c Mon Sep 17 00:00:00 2001 From: Medape <47781797+medape@users.noreply.github.com> Date: Fri, 19 Jul 2019 14:58:15 +0200 Subject: [PATCH 6/7] Map from float to int more quickly and accurately. float:[0.000001, 0.999999] is, in practice, equal to float:[0.0, 1.0], but allows me to use floor() or ceil() instead of round(). This makes every interval in float that maps to a different integer the same size. --- yoke/assets/joypad/base.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/yoke/assets/joypad/base.js b/yoke/assets/joypad/base.js index 23a9110..7d510da 100644 --- a/yoke/assets/joypad/base.js +++ b/yoke/assets/joypad/base.js @@ -201,8 +201,8 @@ function mnemonics(a, b) { function truncate(f, id, pattern) { var truncated = false; f = f.map(function(val) { - if (val < 0) { truncated = true; return 0; } - else if (val > 1) { truncated = true; return 1; } + if (val < 0.000001) { truncated = true; return 0.000001; } + else if (val > 0.999999) { truncated = true; return 0.999999; } else { return val; } }); if (VIBRATE_ON_PAD_BOUNDARY && pattern) { @@ -257,7 +257,7 @@ Control.prototype.getBoundingClientRect = function() { }; Control.prototype.onAttached = function() {}; Control.prototype.state = function() { - return Math.round(255 * this._state); + return Math.floor(256 * this._state); }; function Joystick(id, updateStateCallback) { @@ -317,7 +317,7 @@ Joystick.prototype._updateCircle = function() { (this._offset.y + this._offset.height * this._state[1]) + 'px)'; }; Joystick.prototype.state = function() { - return this._state.map(function(val) {return Math.round(255 * val);}); + return this._state.map(function(val) {return Math.floor(256 * val);}); }; function Motion(id, updateStateCallback) { @@ -353,8 +353,8 @@ function Motion(id, updateStateCallback) { Motion.prototype = Object.create(Control.prototype); Motion.prototype._normalize = function(f) { f *= ACCELERATION_CONSTANT; - if (f < -0.5) { f = -0.5; } - if (f > 0.5) { f = 0.5; } + if (f < -0.499999) { f = -0.499999; } + if (f > 0.499999) { f = 0.499999; } return f + 0.5; }; Motion.prototype.onAttached = function() {}; @@ -371,7 +371,7 @@ Motion.prototype.onDeviceOrientation = function(ev) { motionSensor.updateStateCallback(); }; Motion.prototype.state = function() { - return Math.round(255 * motionState[this._mask]); + return Math.floor(256 * motionState[this._mask]); }; function Pedal(id, updateStateCallback) { From e79aba9ec932301fd59042225d05733a706e04c5 Mon Sep 17 00:00:00 2001 From: Medape <47781797+medape@users.noreply.github.com> Date: Fri, 19 Jul 2019 15:08:19 +0200 Subject: [PATCH 7/7] Fix typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6479886..b61ed9f 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,6 @@ The communication between the Linux client and the Android app are unencrypted U ## Tweaking -Many aspects of Yoke behavior can be changed easily - ave a look at `yoke/assets/joypad`, `bin/yoke` and `yoke/service.py`. +Many aspects of Yoke behavior can be changed easily - have a look at `yoke/assets/joypad`, `bin/yoke` and `yoke/service.py`. ![Thumbstick](media/thumbstick.gif)