Giter Club home page Giter Club logo

handy.js's Introduction

👉 Handy.js 👈

Want to add hand pose recognition to your WebXR project? Handy makes defining and recognizing custom hand poses a snap! Why use hand-held controllers when you can use your bare hands? 👋 Built on Three.js and tested on the Oculus Quest, Handy recognizes over 100 hand poses right out of the box—including the American Sign Language (ASL) alphabet.

UPDATE. May 2021. While a recent overhaul of the WebXR Hands API had temporarily broken Handy, we are once again 100% functional 👍 Curious what happened? See this issue for more details.

Explore the demo. 👉 Make your hand into a “finger gun” pose, then tap your thumb down onto your middle finger to shoot lasers from your hand. ✊ Make a fist to cycle through different hand model visual styles. ✌️ Make a “peace sign” to toggle the hand-specific colors—red for right, green for left. This demo is live at https://stewartsmith.io/handy and the open-source code repository is available at https://github.com/stewdio/handy.js.

Pose vs. Gesture. You may notice we use the term “hand pose” rather than “hand gesture” and that’s because “gesture” implies movement over time and a spatial relationship to the body or other objects. Right now Handy is primarily concerned with the basic pose of a hand’s configuration. But it’s still early days—and yes, full hand gesture recognition is on the roadmap 👍

How to: Make it handy

Three.js already does the heavy lifting by interacting with the WebXR hand tracking API, creating a THREE.Group per each joint within a hand model, updating those joint positions / rotations within its own update loop, and even creating multiple visual models to use. (See Three’s VR hand input profiles example and its source code for details.) But Three doesn’t include an easy way to define and listen for hand poses. Here’s how easy it is to “Handify” an existing Three.js hand input example:

//  Use Three.js to hookup hand inputs:

hand0 = renderer.xr.getHand( 0 )
hand1 = renderer.xr.getHand( 1 )


//  Now use Handy to “Handify” them:

Handy.makeHandy( hand0 )
Handy.makeHandy( hand1 )


//  Which one is which?
// (We won’t know until the browser returns tracking data.)

handLeft = Handy.hands.getLeft()

Put this one command in your animation loop to update all handified objects.

Handy.update()

And that’s it. You’re good to go 👍

How to: Listen for hand poses

Handy provides an isPose method on each hand which expects a pose name as an argument. It returns false if the live hand data does not most resemble that pose, or returns a search result object (which includes a reference to the pose definition data as well a distance property representing the Euclidean distance) if the live hand data does indeed most resemble the indicated pose.

const isPeace = handLeft.isPose( 'peace' )
if( isPeace ){

	//  Do something the entire time
	//  that our hand most resembles
	//  a “peace” pose.

	//  For example, let’s log
	//  the Euclidean distance
	//  of that search result.

	console.log( isPeace.distance )
}

An optional second argument, a Euclidean distance threshold, follows a similar pattern as above, but does not require that the pose be the top search result. Instead the pose must be closer than the supplied distance threshold.

if( handLeft.isPose( 'peace', 3000 )){

	//  Do something the entire time
	//  that our hand resembles
	//  a “peace” pose
	//  within a Euclidean distance of
	//  less than or equal to 3,000 millimeters.
}

Handy also fires events on the handified object itself to inform you the moment a pose appears or vanishes.

handLeft.addEventListener( 

	'peace pose began', 
	 function( event ){

		//  Do something when the
		// “peace” pose appears.
	}
)
handLeft.addEventListener(

	'peace pose ended',
	 function( event ){

		//  Do something when the
		// “peace” pose vanishes.
	}
)

The content of the passed event argument is:

{
	type,    //  Event name, eg. “peace pose began”.
	hand,    //  Hand object itself.
	pose,    //  Pose definition data.
	distance,//  Euclidean distance between live hand pose and this pose.
	message  //  A human-readable description of the event, 
	         //  eg. “LEFT hand peace pose began”.
}

How to: Record your own hand poses

  1. First, you will need to enable remote debugging for your Oculus Quest. See Oculus’ guide to enabling remote debugging.

  2. To make Handy accessible from your JavaScript console you must create a globally accessible reference to it in your code. For example, this demo declares window.Handy = Handy.

  3. Now from your JavaScript console you can record a new pose with something similar to the following: Handy.hands.getLeft().recordLivePose( 'my pose name' ). This will output a pose definition as a JSON string to your console that you can then paste into your pose definitions file. For this demo the appropriate location would be ./scripts/Handy-poses-left.js. This command will also add the pose into that hand’s pose library immediately—so your code can make use of that new hand pose in the very next hand.search() call.

Example

//  Pose your left hand into the Vulcan
// “Live long and prosper” salute,
//  then hit enter on the following in your console:

Handy.hands.getLeft().recordLivePose( 'vulcan' )


//  Handy will return a snapshot of the pose.
//  Note that the position measurements are in millimeters
//  relative to the wrist joint position.

{"names":["vulcan"],"handedness":"left","handyRevision":4,"time":1598280104572,"headPosition":[-1156,5370,-2368],"headRotation":[-3.700743415417188e-17,0,0,1],"jointPositions":[[0,0,0],[32,10,-32],[51,14,-58],[77,18,-79],[90,18,-100],[41,9,-16],[96,7,-24],[132,20,-24],[154,29,-24],[176,34,-22],[41,9,-3],[96,3,-2],[134,21,-5],[159,33,-6],[183,39,-7],[38,9,8],[89,7,17],[123,23,27],[146,35,33],[168,43,39],[34,9,23],[78,14,35],[107,16,44],[123,29,46],[141,40,47]],"digitTipPositions":[[90,18,-100],[176,34,-22],[183,39,-7],[168,43,39],[141,40,47]]},

Note that the names property of a pose definition is an Array. One pose can have many names. Multiple poses can share the same name. Handy is quite flexible and will automatically create all of the necessary began, ended, and changed events for whatever poses you provide it.

Known issues

Oculus Quest has trouble with digit overlaps as illustrated by the American Sign Language (ASL) poses for M, N, R, T, X, and so on. This is a limitation of the tracking on Oculus’ side so there’s not much we can do about that for the moment. Take heart though: the folks over at Oculus have been making huge strides in what is a difficult technology to wrangle, and I imagine stuff like this is on their to-do list already.

A note on repetition

It’s cleaner, more legible, and easier to debug when you write logic once and reuse it rather than copy and paste it multiple times. This quickly comes into play when dealing with hands; we often must apply the same logic to two things at once. One clean way of doing this is using an Array literal to house the two hands, then use the Array’s forEach method to operate on each hand in turn. Note the semicolon prefix before the Array literal. JavaScript does not require semicolons as line endings and I do not believe in using them as such because they are merely typographic clutter. Because of this it’s somtimes necessary to include a semicolon ahead of an expression beginning with an Array literal to ensure the interpreter does not mistake the literal for a property accessor for a preceding object.

;[ handLeft, handRight ]
.forEach( function( hand ){

	hand.addEventListener( 

		'peace pose began', 
		 onPeacePoseBegan
	)
	hand.addEventListener(

		'peace pose ended',
		 onPeacePoseEnded
	)
})

Requirements

This code has been designed for the Oculus Quest headset, though device support will expand as more devices implement the WebXR hand tracking API. Be sure your Oculus Quest has all of the latest software updates installed, including Oculus Browser 15.3.0 or later, then follow these steps to experience the demo for yourself:

  1. In the main Settings menu navigate to Device → Hands and Controllers. Enable both Hand Tracking and Auto Enable Hands or Controllers.

  2. In Oculus Browser visit chrome://flags/ and search for “hands” in the flags search box.

  3. Within this flags page, set the “WebXR experiences with hands tracking” flag (#webxr-hands-tracking) to Hands and pointers.

  4. Again, within this flags page, set the “WebXR experiences with joints tracking” flag (#webxr-hands) to Enabled.

  5. Restart Oculus Browser and visit this demo again. Use your hand controllers to click the “Enter VR” button. Once you are inside the experience put your controllers down, hold your hands out in front of you so that the headset can see them, and enjoy! 👍

Colophon

I’m Stewart. These days I make WebXR things and quantum computing things from my home in Brooklyn, New York. Black lives matter.

handy.js's People

Contributors

adarosecannon avatar gsimone avatar stewdio avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

handy.js's Issues

Changes to WebXR Hands API broke pose mapping and hand models.

Recently the Oculus Browser updated its implementation of the WebXR Hands API from this October 2020 draft to this March 2021 draft which changed some fundamental aspects of how hand joints are referenced and how they are oriented in space. For example, joints are no longer referenced via an Array (eg. INDEX_PHALANX_TIP = 9; joints[ 9 ]), but by named Object (eg. joints[ 'index-finger-tip' ]). The joint naming convention itself has also changed. While an evolving API can sometimes be frustrating to support I’m nonetheless very happy to see folks pushing this API forward. It’s still early days, after all.

Handy has been updated to handle the joint referencing changes, and so on. But currently it does not support the joint orientation changes that the API has implemented—this means Handy is temporarily broken. The orientation changes are being addressed within Three.js itself: mrdoob/three.js#21712 Once these updates to Three are complete I’ll incorporate them into Handy and it should work again as intended 👍

Rounding of joint positions causing pose search to fail

@stewdio
Heya, I'm using handy.js for a machine learning project and you have saved me a lot of time! Thanks for the amazing library. However I think I might have found a bug but I'm not sure if it's a problem with my code or with the library.

The problem that I encountered was that handy.js was not properly searching up distances to the predefined poses, and I traced the problem to this:

handy.js/src/Handy.js

Lines 772 to 774 in 85f22ad

hand.livePoseData.jointPositions[ 0 ][ 0 ] === 0 &&
hand.livePoseData.jointPositions[ 0 ][ 1 ] === 0 &&
hand.livePoseData.jointPositions[ 0 ][ 2 ] === 0

where there was an optimization to terminate the search early if the first joint had its coordinates be all zero. I double checked with your live demo but your demo seemed to work fine.

However, when I ran it in my code, even with all the non-essential parts cut out, the first joint position was always at [0, 0 ,0] which causes the pose search to always fail. The other joint positions were however not all 0s, so the termination should not have happened.

If I remove the rounding from here:

handy.js/src/Handy.js

Lines 522 to 524 in 85f22ad

Math.round( jointMatrix.elements[ 12 ] * 1000 ),
Math.round( jointMatrix.elements[ 13 ] * 1000 ),
Math.round( jointMatrix.elements[ 14 ] * 1000 )

the first joint position is still some ridiculously small number (e-13!!) so probably its just some numeric errors from the matrix operations, so honestly I'm not sure what I'm doing wrong in my code as in your demo, the first joint does not return a position so close to all zeros.

BUT, upon closer inspection after I removed the rounding from handy.js, the position of the first joint also are quite often in the >0.5 range in your live demo, which perhaps might have cause some skipped searches. While I'm still figuring out why the coordinates of the first joint of my hands are almost all zero, which may as well be specific to my code, perhaps the rounding may also be causing issues for other people?

Website demonstration doesn't work with latest Oculus Browser

The demonstration at https://stewartsmith.io/handy/ doesn't work on Oculus Quest (v1), latest Oculus Browser (v14). The cosmic background shows up, but nothing else (hands or other UI).

An error occurs inside Three.js init code:

WebGL context must be marked as XR compatible in order to use with an immersive XRSession
three.module.js:23581

See here: mrdoob/three.js#21126 - it seems to be a recent problem, presumably on all Chromium-based browsers, and should be fixed by updating to latest Three.

Search method optimization

Currently, our search method calculates the Euclidean distance between live hand pose data and each recorded hand pose for that handedness. In one respect this is excellent because it yields a full list of search results which can then be sorted by distance and every single pose has a calculated (potentially useful) result. However, as the pose library grows the search routine becomes more cumbersome and time consuming. Array-clutching coupled with the fact that we don’t require a full search results list per frame for a good user experience has mostly ameliorated the problem thus far—but I think I’m starting to feel the detection lag and I think we can do better.

Proposal:
Let’s say Handy requires that our pose library contain at least one specific pose—for example, the “at rest” pose—and that all other pose objects in the library include the Euclidean distance between themselves and this “at rest” pose. We then take the live hand pose data and get the distance, d, between it and the “at rest” pose. We can now immediately see (without calculating further distances) what poses in the library might be similar to the live pose by just looking at each recorded pose’s d property. If the live pose is very distant from the “at rest” pose, then we can eliminate poses that are similar to the “at rest” pose from the actual search; we only need to search through recorded poses with a d similar to the live pose’s d.

Perhaps we can further compound this efficiency by requiring two poses in the library instead of one, and pre-calculating the distances between these required poses and all the others? (There must be diminishing returns here, however.)

documenting usage with a-frame

A-Frame is the de-facto webxr library, and it has solid built-in hand tracking. out of the box, it recognizes the pinch gesture. A quick guide for implementing Handy to extend a-rame would be super helpful, and likely expand the reach of this library.

Build and mirror poses from one hand to another automatically

Having to record the same poses for each hand is time consuming and opens the door to accidental inconsistent labeling, etc. We need to look into the most efficient means of reflecting the recorded poses of one hand onto the other. This also has the potential to reduce download size as only one hand’s pose library would need to be downloaded. (It can then be cloned / reflected on the user’s end to act as the pose library for the other hand.)

My personal preference (and bias), as a right-handed person is to build the library for left hands only, then have that cloned and reflected for right hands. The reason is it’s easier for me to pose my left hand into a shape for recording and then use my right hand to raise / lower the headset and also work the keyboard to take that snapshot.

Bonus: Is it more efficient to never clone and reflect one hand’s pose library and instead bake reflection detection into the search mechanism itself? Or is that just asking for trouble?

Using Handy.js with computer camera and MediaPipe

Hi,

I'm currently working on a project that interprets ASL into text, using a computer camera and MediaPipe to get the hand model.
I was wondering if there would be a way to use Handy.js for the hand model interpretation. Would there be a way for me to use Handy.js with the hand model from MediaPipe, instead of using the hand models from the Oculus Quest? (Potentially using the WebXR hand tracking API)
If so, could you point me in the right direction on how I would be able to do that?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.