Skip to content

Commit be17c8f

Browse files
committed
adding autoSpeedControl
1 parent 7b381a8 commit be17c8f

File tree

3 files changed

+173
-7
lines changed

3 files changed

+173
-7
lines changed

browser/dasher/autoSpeedControl.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// (c) 2026 The ACE Centre-North, UK registered charity 1089313.
2+
// MIT licensed, see https://opensource.org/licenses/MIT
3+
4+
export default class AutoSpeedControl {
5+
constructor() {
6+
this._factor = 1;
7+
this._angles = [];
8+
this._maxSamples = 24;
9+
this._minFactor = 0.65;
10+
this._maxFactor = 1.35;
11+
this._minRadius = 55;
12+
this._sampleScale = 480;
13+
this._sampleOffset = 10;
14+
this._upRate = 0.015;
15+
this._downRate = 0.03;
16+
this._lowVariance = 0.12;
17+
this._highVariance = 0.32;
18+
}
19+
20+
get factor() {
21+
return this._factor;
22+
}
23+
24+
reset() {
25+
this._factor = 1;
26+
this._angles = [];
27+
}
28+
29+
update(rawX, rawY, baseSpeed, elapsedMillis) {
30+
if (
31+
typeof rawX !== 'number' ||
32+
typeof rawY !== 'number' ||
33+
typeof baseSpeed !== 'number'
34+
) {
35+
return this._factor;
36+
}
37+
38+
const radius = Math.hypot(rawX, rawY);
39+
if (radius < this._minRadius || baseSpeed <= 0) {
40+
this._angles = [];
41+
this._factor += (1 - this._factor) * 0.08;
42+
return this._factor;
43+
}
44+
45+
const sampleTarget = Math.max(
46+
8,
47+
Math.round(this._sampleScale / Math.max(0.01, baseSpeed) + this._sampleOffset),
48+
);
49+
this._maxSamples = Math.min(60, sampleTarget);
50+
51+
this._angles.push(Math.atan2(rawY, rawX));
52+
if (this._angles.length > this._maxSamples) {
53+
this._angles.shift();
54+
}
55+
56+
if (this._angles.length < 8) {
57+
return this._factor;
58+
}
59+
60+
let meanX = 0;
61+
let meanY = 0;
62+
this._angles.forEach((angle) => {
63+
meanX += Math.cos(angle);
64+
meanY += Math.sin(angle);
65+
});
66+
meanX /= this._angles.length;
67+
meanY /= this._angles.length;
68+
69+
const meanVectorLength = Math.hypot(meanX, meanY);
70+
const variance = 1 - Math.min(1, meanVectorLength);
71+
const stepScale = Math.max(0.4, Math.min(2.2, elapsedMillis / 33));
72+
73+
if (variance < this._lowVariance) {
74+
this._factor += this._upRate * stepScale;
75+
} else if (variance > this._highVariance) {
76+
this._factor -= this._downRate * stepScale;
77+
} else {
78+
this._factor += (1 - this._factor) * 0.02 * stepScale;
79+
}
80+
81+
this._factor = Math.max(this._minFactor, Math.min(this._maxFactor, this._factor));
82+
return this._factor;
83+
}
84+
}

browser/dasher/controlpanelspecification.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export default {
5151
'speed': {
5252
'$': {'order': 3, '$': {'html': 'div'}},
5353

54+
'auto': {$: {'order': 0, 'control': 'checkbox',
55+
'value': true, 'label': 'Auto speed'}},
5456
'horizontal': {$: {'order': 1, 'control': 'number',
5557
'value': '0.2', 'label': 'Left-Right'}},
5658
'vertical': {$: {'order': 2, 'control': 'number',

browser/dasher/userinterface.js

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import ControllerRandom from './controllerrandom.js';
1717
import ControllerPointer from './controllerpointer.js';
1818
import Viewer from './viewer.js';
1919
import ZoomBox from './zoombox.js';
20+
import AutoSpeedControl from './autoSpeedControl.js';
2021

2122
import predictor_dummy from './predictor_dummy.js';
2223
import predictor_basic from './predictor.js';
@@ -84,6 +85,10 @@ export default class UserInterface {
8485
// document.head.append(this._cssNode);
8586

8687
this._speedLeftRightInput = undefined;
88+
this._baseSpeedHorizontal = 0.1;
89+
this._baseSpeedVertical = 0.2;
90+
this._autoSpeedEnabled = true;
91+
this._autoSpeedControl = new AutoSpeedControl();
8792

8893
// Spawn and render parameters in mystery SVG units.
8994
this._limits = new Limits();
@@ -539,6 +544,14 @@ export default class UserInterface {
539544
this._quickControls.speedPlus = this._create_button(
540545
new Piece(speedGroup), '+', 'ui-step-button',
541546
);
547+
const autoSpeedLabel = speedGroup.appendChild(document.createElement('span'));
548+
autoSpeedLabel.className = 'ui-nav-label';
549+
autoSpeedLabel.textContent = 'Auto';
550+
this._quickControls.autoSpeed = speedGroup.appendChild(
551+
document.createElement('input'),
552+
);
553+
this._quickControls.autoSpeed.type = 'checkbox';
554+
this._quickControls.autoSpeed.className = 'ui-switch';
542555

543556
const groupLearning = createGroup();
544557
const learningLabel = groupLearning.appendChild(document.createElement('span'));
@@ -949,6 +962,42 @@ export default class UserInterface {
949962
return speed * (this._transitionMillis / legacyRenderIntervalMillis);
950963
}
951964

965+
_effective_speed_factor() {
966+
return this._autoSpeedEnabled ? this._autoSpeedControl.factor : 1;
967+
}
968+
969+
_apply_pointer_speeds() {
970+
if (this._pointer === undefined) {
971+
return;
972+
}
973+
const factor = this._effective_speed_factor();
974+
this._pointer.multiplierLeftRight = this._normalise_speed(
975+
this._baseSpeedHorizontal * factor,
976+
);
977+
this._pointer.multiplierUpDown = this._normalise_speed(
978+
this._baseSpeedVertical * factor,
979+
);
980+
}
981+
982+
_update_auto_speed(elapsedMillis) {
983+
if (!this._autoSpeedEnabled || this._pointer === undefined) {
984+
return;
985+
}
986+
if (!Object.is(this._controller, this._controllerPointer) || !this._pointer.going) {
987+
this._autoSpeedControl.reset();
988+
this._apply_pointer_speeds();
989+
return;
990+
}
991+
992+
this._autoSpeedControl.update(
993+
this._pointer.rawX,
994+
this._pointer.rawY,
995+
this._baseSpeedHorizontal,
996+
elapsedMillis,
997+
);
998+
this._apply_pointer_speeds();
999+
}
1000+
9521001
_longest_common_prefix(a, b) {
9531002
const max = Math.min(a.length, b.length);
9541003
let i = 0;
@@ -1027,6 +1076,9 @@ export default class UserInterface {
10271076
if (this._quickControls.learning !== undefined) {
10281077
this._quickControls.learning.checked = this._learningEnabled;
10291078
}
1079+
if (this._quickControls.autoSpeed !== undefined) {
1080+
this._quickControls.autoSpeed.checked = this._autoSpeedEnabled;
1081+
}
10301082
if (this._quickControls.messagePosition !== undefined) {
10311083
this._quickControls.messagePosition.value = this._messagePosition;
10321084
}
@@ -1072,6 +1124,11 @@ export default class UserInterface {
10721124
this._panels.speed.horizontal.set_value(next);
10731125
});
10741126
}
1127+
if (this._quickControls.autoSpeed !== undefined) {
1128+
this._quickControls.autoSpeed.addEventListener('change', (event) => {
1129+
this._panels.speed.auto.set_value(event.target.checked);
1130+
});
1131+
}
10751132

10761133
if (this._quickControls.fontSizeMinus !== undefined) {
10771134
this._quickControls.fontSizeMinus.addEventListener('click', () => {
@@ -1204,7 +1261,8 @@ export default class UserInterface {
12041261
_select_behaviour(index) {
12051262
this._limits.targetRight = (index === 0);
12061263
this._currentSpeed = (index === 0 ? 0.1 : 0.2);
1207-
this._pointer.multiplierLeftRight = this._normalise_speed(this._currentSpeed);
1264+
this._baseSpeedHorizontal = this._currentSpeed;
1265+
this._apply_pointer_speeds();
12081266
// if (this._speedLeftRightInput !== undefined) {
12091267
const speedLeftRightInput = this._panels.speed.horizontal.node;
12101268
if (speedLeftRightInput !== undefined) {
@@ -1522,26 +1580,43 @@ export default class UserInterface {
15221580
if (this._keyboardMode) {
15231581
// Can't show settings in input controls in keyboard mode. The input
15241582
// would itself require a keyboard. Set some slower default values.
1525-
this._pointer.multiplierLeftRight = this._normalise_speed(0.2);
1526-
this._pointer.multiplierUpDown = this._normalise_speed(0.2);
1583+
this._baseSpeedHorizontal = 0.2;
1584+
this._baseSpeedVertical = 0.2;
1585+
this._autoSpeedEnabled = false;
1586+
this._autoSpeedControl.reset();
1587+
this._apply_pointer_speeds();
15271588
this._select_behaviour(1);
15281589
return;
15291590
}
15301591

1592+
if (this._panels.speed.auto !== undefined) {
1593+
this._autoSpeedEnabled = !!this._panels.speed.auto.node.checked;
1594+
this._panels.speed.auto.listener = (checked) => {
1595+
this._autoSpeedEnabled = !!checked;
1596+
this._autoSpeedControl.reset();
1597+
this._apply_pointer_speeds();
1598+
};
1599+
}
1600+
15311601
this._panels.speed.horizontal.listener = (value) => {
15321602
this._currentSpeed = value;
1533-
this._pointer.multiplierLeftRight = this._normalise_speed(value);
1603+
this._baseSpeedHorizontal = value;
1604+
this._autoSpeedControl.reset();
1605+
this._apply_pointer_speeds();
15341606
this._sync_quick_controls();
15351607
};
15361608
this._select_behaviour(0);
15371609
const initialVerticalSpeed = Number.parseFloat(
15381610
this._panels.speed.vertical.node.value,
15391611
);
15401612
if (!Number.isNaN(initialVerticalSpeed)) {
1541-
this._pointer.multiplierUpDown = this._normalise_speed(initialVerticalSpeed);
1613+
this._baseSpeedVertical = initialVerticalSpeed;
1614+
this._apply_pointer_speeds();
15421615
}
15431616
this._panels.speed.vertical.listener = (value) => {
1544-
this._pointer.multiplierUpDown = this._normalise_speed(value);
1617+
this._baseSpeedVertical = value;
1618+
this._autoSpeedControl.reset();
1619+
this._apply_pointer_speeds();
15451620
};
15461621
}
15471622

@@ -1664,6 +1739,7 @@ export default class UserInterface {
16641739
}
16651740
const elapsed = timestamp - this._lastRenderTimestamp;
16661741
this._lastRenderTimestamp = timestamp;
1742+
this._update_auto_speed(elapsed);
16671743

16681744
this._renderAccumulator = Math.min(
16691745
this._renderAccumulator + elapsed,
@@ -1684,8 +1760,12 @@ export default class UserInterface {
16841760
this._intervalRender = window.requestAnimationFrame(tick);
16851761
} else {
16861762
this._renderLoopKind = 'interval';
1763+
this._update_auto_speed(this._transitionMillis);
16871764
this._intervalRender = setInterval(
1688-
render_one, this._transitionMillis);
1765+
() => {
1766+
this._update_auto_speed(this._transitionMillis);
1767+
render_one();
1768+
}, this._transitionMillis);
16891769
}
16901770
} else {
16911771
this._stop_render();

0 commit comments

Comments
 (0)