Skip to content

Commit

Permalink
adds steering using hand tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
DougReeder committed Feb 5, 2025
1 parent 29574d1 commit 0986d57
Show file tree
Hide file tree
Showing 15 changed files with 80 additions and 33 deletions.
2 changes: 1 addition & 1 deletion dist/arches.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/arches.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/canyon.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/canyon.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/city.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/city.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/ginnungagap.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/ginnungagap.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/island.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/island.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/tutorial.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/tutorial.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/yggdrasil.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/yggdrasil.js.map

Large diffs are not rendered by default.

85 changes: 66 additions & 19 deletions src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import './shim/requestIdleCallback'
import {goFullscreenLandscape, isDesktop, isMagicWindow, calcPosChange, pokeEnvironmentalSound} from './elfland-utils'

const GRAVITY = 9.807; // m/s^2
const HUMAN_EYE_ELBOW_DISTANCE = 0.56; // m
const HUMAN_EYE_ELBOW_DISTANCE = 0.45; // m
const DIFFICULTY_VR = 0.75;
const DIFFICULTY_MAGIC_WINDOW = 0.6;
const DIFFICULTY_KEYBOARD = 0.5;
const POWERUP_BOOST = 16;
const BAD_CRASH_SPEED = 30;
const MAX_STICK_DISTANCE_SQ = 0.30 * 0.30; // m^2

AFRAME.registerState({
initialState: {
Expand All @@ -30,7 +31,7 @@ AFRAME.registerState({
controlStickEl: null,
controlNeutralHeight: 0.95,
controlMode: 'HEAD', // or 'HANDS'
controlSubmode: 'NONE', // or 'LEFT_CONTROLLER' or 'RIGHT_CONTROLLER' or 'LEFT_HAND' or 'RIGHT_HAND'
controlSubmode: 'NONE', // or 'LEFT_CONTROLLER', 'RIGHT_CONTROLLER', 'LEFT_HAND' or 'RIGHT_HAND'
time: 0,
difficulty: DIFFICULTY_MAGIC_WINDOW,
gliderPosition: {x:-30, y:15, z:30},
Expand All @@ -54,7 +55,8 @@ AFRAME.registerState({

nonBindedStateKeys: ['armatureEl', 'gliderEl', 'cameraEl',
'leftControllerEl', 'rightControllerEl', 'leftHandEl', 'rightHandEl',
'controllerConnections', 'isAnyPressedLeft', 'xSetting', 'zSetting', 'controlStickEl',
'controllerConnections', 'isAnyPressedLeft', 'isAnyPressedRight',
'xSetting', 'zSetting', 'controlStickEl',
'controlNeutralHeight', 'controlMode', 'controlSubmode',
'gliderPositionStart', 'gliderRotationYStart'],

Expand Down Expand Up @@ -260,18 +262,16 @@ AFRAME.registerState({
state.rightControllerEl.setAttribute('hand-controls', 'handModelStyle', 'highPoly');
}

this.leftDownHandler = this.controllerHandler.bind(this, 'LEFT', 'DOWN', state);
this.leftUpHandler = this.controllerHandler.bind(this, 'LEFT', 'UP', state);
this.rightDownHandler = this.controllerHandler.bind(this, 'RIGHT', 'DOWN', state);
this.rightUpHandler = this.controllerHandler.bind(this, 'RIGHT', 'UP', state);
this.leftDownHandler = this.buttonHandler.bind(this, 'LEFT', 'DOWN', state);
this.leftUpHandler = this.buttonHandler.bind(this, 'LEFT', 'UP', state);
this.rightDownHandler = this.buttonHandler.bind(this, 'RIGHT', 'DOWN', state);
this.rightUpHandler = this.buttonHandler.bind(this, 'RIGHT', 'UP', state);

state.leftHandEl = document.getElementById('leftHand');
state.RightHandEl = document.getElementById('rightHand');
state.rightHandEl = document.getElementById('rightHand');

this.leftPinchStartedHandler = this.pinchHandler.bind(this, 'LEFT', 'STARTED', state);
this.leftPinchEndedHandler = this.pinchHandler.bind(this, 'LEFT', 'ENDED', state);
this.rightPinchStartedHandler = this.pinchHandler.bind(this, 'RIGHT', 'STARTED', state);
this.rightPinchEndedHandler = this.pinchHandler.bind(this, 'RIGHT', 'ENDED', state);
this.leftPinchStartedHandler = this.pinchStarted.bind(this, 'LEFT_HAND', state);
this.rightPinchStartedHandler = this.pinchStarted.bind(this, 'RIGHT_HAND', state);

state.controlStickEl = document.getElementById('controlStick');
this.controlStickToNeutral(state);
Expand All @@ -287,7 +287,8 @@ AFRAME.registerState({
},
adjustControlMode: function (state) {
const oldControlMode = state.controlMode;
if (state.controllerConnections.leftController || state.controllerConnections.rightController) {
if (state.controllerConnections.leftController || state.controllerConnections.rightController ||
state.controllerConnections.leftHand || state.controllerConnections.rightHand) {
state.controlMode = 'HANDS';
} else {
state.controlMode = 'HEAD';
Expand All @@ -299,6 +300,8 @@ AFRAME.registerState({
state.leftControllerEl?.addEventListener('buttonup', this.leftUpHandler);
state.rightControllerEl?.addEventListener('buttondown', this.rightDownHandler);
state.rightControllerEl?.addEventListener('buttonup', this.rightUpHandler);
state.leftHandEl?.addEventListener('pinchstarted', this.leftPinchStartedHandler);
state.rightHandEl?.addEventListener('pinchstarted', this.rightPinchStartedHandler);

this.controlStickToNeutral(state);
state.controlStickEl.object3D.visible = true;
Expand All @@ -307,12 +310,14 @@ AFRAME.registerState({
state.leftControllerEl?.removeEventListener('buttonup', this.leftUpHandler);
state.rightControllerEl?.removeEventListener('buttondown', this.rightDownHandler);
state.rightControllerEl?.removeEventListener('buttonup', this.rightUpHandler);
state.leftHandEl?.removeEventListener('pinchstarted', this.leftPinchStartedHandler);
state.rightHandEl?.removeEventListener('pinchstarted', this.rightPinchStartedHandler);

state.controlStickEl.object3D.visible = false;
}
}
},
controllerHandler: function handHandler(handedness, upDown, state, evt) {
buttonHandler: function handHandler(handedness, upDown, state, evt) {
const wasAnyPressedLeft = state.isAnyPressedLeft;
const trackedControlsLeft = state.leftControllerEl?.components['tracked-controls'];
const buttonsLeft = trackedControlsLeft &&
Expand Down Expand Up @@ -353,6 +358,8 @@ AFRAME.registerState({
state.controlSubmode = 'NONE';
break;
case 'RIGHT_CONTROLLER':
case 'LEFT_HAND':
case 'RIGHT_HAND':
case 'NONE':
state.controlSubmode = 'LEFT_CONTROLLER';
break;
Expand All @@ -363,16 +370,29 @@ AFRAME.registerState({
state.controlSubmode = 'NONE';
break;
case 'LEFT_CONTROLLER':
case 'LEFT_HAND':
case 'RIGHT_HAND':
case 'NONE':
state.controlSubmode = 'RIGHT_CONTROLLER';
break;
}
}
console.log("controlSubmode:", state.controlSubmode);
console.log(`controllerHandler ${handedness} ${upDown} controlSubmode:`, state.controlSubmode);
},
pinchHandler: function(handedness, beginEnd, state, evt) {
pinchStarted: function (inputSource, state, evt) {
const neutral = new THREE.Vector3(0, state.controlNeutralHeight, -0.4);
if (neutral.distanceToSquared(evt.detail?.position) > MAX_STICK_DISTANCE_SQ) {
return;
}

console.log("controlSubmode:", state.controlSubmode);
state.controlMode = 'HANDS';
if (state.controlSubmode !== inputSource) {
state.controlSubmode = inputSource
} else {
this.controlStickToNeutral(state);
state.controlStickEl.object3D.visible = true;
}
console.info(`${evt.type} ${inputSource} controlSubmode is now`, state.controlSubmode);
},
controlStickToNeutral: function (state) {
state.controlSubmode = 'NONE';
Expand Down Expand Up @@ -497,29 +517,56 @@ AFRAME.registerState({
break;
case "HANDS":
let quaternion;
const leftControllerPos = state.leftControllerEl?.getAttribute("position");
const rightControllerPos = state.rightControllerEl?.getAttribute("position");
switch (state.controlSubmode) {
case "LEFT_CONTROLLER":
state.controlStickEl.object3D?.quaternion?.copy(state.leftControllerEl.object3D.quaternion);
state.controlStickEl.object3D.rotateX(-Math.PI/2);
quaternion = state.controlStickEl.object3D?.quaternion;

const leftControllerPos = state.leftControllerEl?.getAttribute("position");
state.controlStickEl.setAttribute('position', leftControllerPos);
break;
case "RIGHT_CONTROLLER":
state.controlStickEl.object3D?.quaternion?.copy(state.rightControllerEl.object3D.quaternion);
state.controlStickEl.object3D.rotateX(-Math.PI/2);
quaternion = state.controlStickEl.object3D?.quaternion;

const rightControllerPos = state.rightControllerEl?.getAttribute("position");
state.controlStickEl.setAttribute('position', rightControllerPos);
break;
case "LEFT_HAND":
case "RIGHT_HAND":
let finger, sign;
if ('LEFT_HAND' === state.controlSubmode) {
finger = state.leftHandEl?.object3D.getObjectByName('middle-finger-phalanx-proximal') ||
state.leftHandEl?.object3D.getObjectByName('index-finger-phalanx-proximal');
sign = -1;
} else {
finger = state.rightHandEl?.object3D.getObjectByName('middle-finger-phalanx-proximal') ||
state.rightHandEl?.object3D.getObjectByName('index-finger-phalanx-proximal');
sign = 1;
}
if (finger?.quaternion?.isQuaternion && Number.isFinite(finger.quaternion.x)
&& Number.isFinite(finger.quaternion.y) && Number.isFinite(finger.quaternion.z)
&& Number.isFinite(finger.quaternion.w)) {
state.controlStickEl.object3D?.quaternion?.copy(finger.quaternion);
state.controlStickEl.object3D.rotateZ(sign * Math.PI/2);
quaternion = state.controlStickEl.object3D?.quaternion;
}
if (Number.isFinite(finger?.position?.x) && Number.isFinite(finger?.position?.y)
&& Number.isFinite(finger?.position?.z)) {
state.controlStickEl.object3D?.position?.copy(finger.position);
state.controlStickEl.object3D.position.x -= sign * 0.04;
}
break;
case "NONE":
this.quaternion.identity();
quaternion = this.quaternion;
state.controlStickEl.object3D?.quaternion?.copy(quaternion)
// TODO: slow decay to neutral?
break;
default:
console.error(`unsupported controlSubmode:`, controlSubmode);
}

this.euler.setFromQuaternion(quaternion, 'XZY');
Expand Down

0 comments on commit 0986d57

Please sign in to comment.