{"id":15019020,"url":"https://github.com/adarosecannon/handy-work","last_synced_at":"2025-05-05T22:09:53.612Z","repository":{"id":39630531,"uuid":"378145621","full_name":"AdaRoseCannon/handy-work","owner":"AdaRoseCannon","description":"Framework Agnostic Hand tracking for WebXR","archived":false,"fork":false,"pushed_at":"2024-12-07T11:35:55.000Z","size":3155,"stargazers_count":99,"open_issues_count":7,"forks_count":11,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-05-05T22:09:46.034Z","etag":null,"topics":["aframe","hand-tracking","threejs","webxr"],"latest_commit_sha":null,"homepage":"https://aframe-xr-starterkit.glitch.me/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AdaRoseCannon.png","metadata":{"files":{"readme":"README-AFRAME.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-06-18T12:37:14.000Z","updated_at":"2025-04-29T23:41:39.000Z","dependencies_parsed_at":"2025-05-04T03:30:39.995Z","dependency_job_id":null,"html_url":"https://github.com/AdaRoseCannon/handy-work","commit_stats":{"total_commits":166,"total_committers":6,"mean_commits":"27.666666666666668","dds":0.1987951807228916,"last_synced_commit":"b5c37d5b24edf8461f3130127d2cf3d3c7586ecb"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AdaRoseCannon%2Fhandy-work","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AdaRoseCannon%2Fhandy-work/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AdaRoseCannon%2Fhandy-work/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AdaRoseCannon%2Fhandy-work/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AdaRoseCannon","download_url":"https://codeload.github.com/AdaRoseCannon/handy-work/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252584329,"owners_count":21771945,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aframe","hand-tracking","threejs","webxr"],"created_at":"2024-09-24T19:52:46.865Z","updated_at":"2025-05-05T22:09:53.595Z","avatar_url":"https://github.com/AdaRoseCannon.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AFrame Handy Controls\n\nThe controls provide the ability to render hands and attach objects to individual joints.\n\nIt also fires events for when poses have been held for certain lengths of time.\n\nThe following are exposed on the component itself so you can hook into the library\n\n* handyWorkUpdate\n* dumpHands\n* loadPose\n* setPose \n* getPose \n\nUse the following properties to customise the component\n\n\u003c!--SCHEMA--\u003e\nVector3\nVector3\nQuaternion\nQuaternion\nQuaternion\n| Property         | Type   | Description                                                                                                                                                                             | Default                                                                                          |\n| :--------------- | :----- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------- |\n| renderGamepad    | string | Whether to render a gamepad model when it's not doing hand tracking, right, none and left are the names of controller handedness, any is all of them, and never is to not draw gamepads | \"any\"                                                                                            |\n| left             | model  | URL for left controller                                                                                                                                                                 | \"https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets/dist/profiles/generic-hand/left.glb\"  |\n| right            | model  | URL for right controller                                                                                                                                                                | \"https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets/dist/profiles/generic-hand/right.glb\" |\n| materialOverride | string | Which hand to use the `material` component for                                                                                                                                          | \"both\"                                                                                           |\n| fuseVShort       | number | Time for a pose to trigger a pose event (ms)                                                                                                                                            | 48                                                                                               |\n| fuseShort        | number | Time for a pose to trigger a pose_fuseShort event (ms)                                                                                                                                  | 480                                                                                              |\n| fuseLong         | number | Time for a pose to trigger a pose_fuseLong event (ms)                                                                                                                                   | 1440                                                                                             |\n\n\u003c!--SCHEMA_END--\u003e\n\nChild entities with the `data-left`, `data-right` or `data-none` properties have their position and \nrotation set to match the tracked points from the WebXR API:\n\n`data-left` and `data-right` are used for tracked hands or controllers where the hardware has controllers\nwhich are explicity handed. i.e. Oculus Quest. Some hardware has a single ambiguously handed controller\nthis will be exposed as `data-none` because it has no handedness.  Screen based transient inputs will also\nbe exposed under `data-none`.\n\n* grip (where someone would hold an object)\n* ray (the target ray space from WebXR)\n* screen-0 (1st transient input)\n* screen-1 (2nd transient input)\n* screen-2 (3rd transient input)\n* screen-n ({n+1}th transient input)\n\n* wrist\n* thumb-metacarpal\n* thumb-phalanx-proximal\n* thumb-phalanx-distal\n* thumb-tip\n* index-finger-metacarpal\n* index-finger-phalanx-proximal\n* index-finger-phalanx-intermediate\n* index-finger-phalanx-distal\n* index-finger-tip\n* middle-finger-metacarpal\n* middle-finger-phalanx-proximal\n* middle-finger-phalanx-intermediate\n* middle-finger-phalanx-distal\n* middle-finger-tip\n* ring-finger-metacarpal\n* ring-finger-phalanx-proximal\n* ring-finger-phalanx-intermediate\n* ring-finger-phalanx-distal\n* ring-finger-tip\n* pinky-finger-metacarpal\n* pinky-finger-phalanx-proximal\n* pinky-finger-phalanx-intermediate\n* pinky-finger-phalanx-distal\n* pinky-finger-tip\n\n### Events\n\nThe component and each child emit events when they happen these events are\n\n#### Standard WebXR API Events\n\n* \"select\"\n* \"selectstart\"\n* \"selectend\"\n* \"squeeze\"\n* \"squeezeend\"\n* \"squeezestart\"\n\n#### Poses\n\n* pose_[name]\n* pose_[name]_fuseShort\n* pose_[name]_fuseLong\n\nWhere name is one of the poses in `/poses`\n\nYou can see what the currently detected pose is by listening for `\"pose\"` events and inspecting `\"event.detail.pose\"`\n\n#### Gamepad Events\n\nThese are very hardware dependent. If it is able to get access to the input profile data from the WebXR\ninput profiles repo then this will fire off events as named in that. Such as thumbstickmoved or a-buttondown x-buttonup. \n\nOtherwise it will just fire events like button0down button0up or axes0moved\n\nTo find out what a particular piece of hardware is using listen for `\"gamepad\"` events and inspect the `event.detail.event`.\n\n### Magnetic Actions\n\nAdd data-magnet is set to the classname for elements it's to be drawn to in a magnetic fashion so that when the joint approaches that element the whole hand will get pulled towards it and aligned with the element.\n\nOnly set 1 `data-magnet` per hand. You can configure the magnetic elements by setting their data-magnet range e.g. `data-magnet-range=\"0.2,0.1,120,80\"`. Where the first number is where the magnetism starts and the second the range at which the hand is totally moved to the destination location. The second set of numbers are how aligned the hand needs to be before magnetism starts (degrees between 0 and 360) , where the second number is what counts as fully aligned. In that example, which is the default it will start approaching from 0.2m and if the hand is within 0.1m it will be placed on the `#sword` handle.\n\nFor physics systems it probably won't work well when you use magnet elements you probably want to use the real joint location, do declare an additional that a joint should ignore magnet effects add `data-no-magnet` to it.\n\nThe object currently attracting the controller will have it's ID noted on the `data-magnet` element as a `data-magnet-target` which you can read in JavaScript through the object's dataset.\n\nThe magnet finding function always picks the first element it detects in it's range. Not the closest element. You can\nsort the order magnets are tested by setting the `data-magnet-priority` property on elements. The default value is `1` if you want it to be low priority set `data-magnet-priority=\"0\"` or lower like `\"-1\"` fractions are fine too. If you want it to be higher priority set it to a higher number such as `data-magnet-priority=\"10\"`. \n\n```html\n\u003c!-- inside the handy-controls --\u003e\n\u003ca-entity data-right=\"grip\" data-magnet=\"magnet\"\u003e\u003c/a-entity\u003e\n\u003ca-entity data-left=\"grip\" data-magnet=\"magnet\"\u003e\u003c/a-entity\u003e\n\n\u003c!-- Elsewhere in the scene --\u003e\n\u003ca-gltf-model class=\"magnet\" id=\"sword\" src=\"#sword-gltf\" data-magnet-range=\"0.2,0.1,120,80\"\u003e\u003c/a-gltf-model\u003e\n```\n\n### Example use case:\n\n```html\n\u003c!-- After the AFrame script --\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/handy-work/build/handy-controls.min.js\"\u003e\u003c/script\u003e\n\n\u003c!-- In your camera rig --\u003e\n\u003ca-entity handy-controls=\"right:#right-gltf;materialOverride:right;\" material=\"color:gold;metalness:1;roughness:0;\"\u003e\n\n  \u003c!-- Screen space inputs like mobile AR --\u003e\n  \u003ca-torus radius=\"0.008\" radius-tubular=\"0.001\" material=\"shader:flat;color:blue\" data-none=\"screen-0\"\u003e\u003c/a-torus\u003e\n  \u003ca-torus radius=\"0.008\" radius-tubular=\"0.001\" material=\"shader:flat;color:green\" data-none=\"screen-1\"\u003e\u003c/a-torus\u003e\n  \u003ca-torus radius=\"0.008\" radius-tubular=\"0.001\" material=\"shader:flat;color:red\" data-none=\"screen-2\"\u003e\u003c/a-torus\u003e\n  \n  \u003c!-- Objects attached to tracked hand joints --\u003e\n  \u003ca-gltf-model src=\"#watch-gltf\" data-left=\"wrist\" position=\"-1000 0 0\"\u003e\n    \u003ca-sphere radius=\"0.02\" position=\"0 0.02 0\" sphere-collider=\"radius:0.02;objects:[data-right$=-tip];\" exit-on=\"hitend\" visible=\"false\"\u003e\u003c/a-sphere\u003e\n  \u003c/a-gltf-model\u003e\n  \u003ca-entity data-left=\"ring-finger-phalanx-proximal\"\u003e\n    \u003ca-torus position=\"0 0 -0.03\" radius=\"0.008\" radius-tubular=\"0.001\" scale=\"1 1 1.5\" material=\"color:gold;metalness:1;roughness:0;\"\u003e\u003c/a-torus\u003e\n  \u003c/a-entity\u003e\n  \n  \u003ca-entity data-right=\"index-finger-tip\" mixin=\"blink\" blink-controls=\"rotateOnTeleport:false;startEvents:pose_point_fuseShort;endEvents:pose_point_fuseLong;\"\u003e\u003c/a-entity\u003e\n  \u003ca-entity data-left=\"index-finger-tip\"  mixin=\"blink\" blink-controls=\"rotateOnTeleport:false;startEvents:pose_point_fuseShort;endEvents:pose_point_fuseLong;\"\u003e\u003c/a-entity\u003e\n  \n  \u003c!-- Ray and Grip are Available on Hands or Tracked Inputs --\u003e\n  \u003ca-entity data-right=\"ray\" mixin=\"blink\" blink-controls\u003e\n    \u003ca-entity position=\"0 0 -0.22\" class=\"pose-label\" text=\"value: Hello World; align: center;\"\u003e\u003c/a-entity\u003e\n  \u003c/a-entity\u003e\n  \u003ca-entity data-left=\"ray\" mixin=\"blink\" blink-controls\u003e\n    \u003ca-entity position=\"0 0 -0.22\" class=\"pose-label\" text=\"value: Hello World; align: center;\"\u003e\u003c/a-entity\u003e\n  \u003c/a-entity\u003e\n\n  \u003c!-- These act like anchors pulling both hands and controllers towards grabable objects, moving the whole hand and the attached elements--\u003e\n  \u003ca-entity id=\"right-magnet\" data-right=\"grip\" data-magnet=\".magnet-right:not([data-no-magnet]),.magnet:not([data-no-magnet])\" grab-magnet-target=\"startEvents:squeezestart,pose_fist;stopEvents:pose_flat_fuseShort,squeezeend;\"\u003e\u003c/a-entity\u003e\n  \u003ca-entity id=\"left-magnet\" data-left=\"grip\"  data-magnet=\".magnet-left:not([data-no-magnet]),.magnet:not([data-no-magnet])\"  grab-magnet-target=\"startEvents:squeezestart,pose_fist;stopEvents:pose_flat_fuseShort,squeezeend;\"\u003e\u003c/a-entity\u003e\n\n  \u003c!-- Markers to let us know the real location of the hands --\u003e\n  \u003ca-sphere id=\"right-no-magnet\" data-right=\"grip\" data-no-magnet radius=\"0.01\" color=\"red\"\u003e\u003c/a-sphere\u003e\n  \u003ca-sphere id=\"left-no-magnet\" data-left=\"grip\" data-no-magnet radius=\"0.01\" color=\"red\"\u003e\u003c/a-sphere\u003e\n  \n  \u003c!-- Invisible objects at the tips of each finger for physics or intersections --\u003e\n  \u003ca-sphere data-right=\"index-finger-tip\" radius=\"0.01\" visible=\"false\"\u003e\u003c/a-sphere\u003e\n  \u003ca-sphere data-right=\"middle-finger-tip\" radius=\"0.01\" visible=\"false\"\u003e\u003c/a-sphere\u003e\n  \u003ca-sphere data-right=\"ring-finger-tip\" radius=\"0.01\" visible=\"false\"\u003e\u003c/a-sphere\u003e\n  \u003ca-sphere data-right=\"pinky-finger-tip\" radius=\"0.01\" visible=\"false\"\u003e\u003c/a-sphere\u003e\n  \u003ca-sphere data-right=\"thumb-tip\" radius=\"0.01\" visible=\"false\"\u003e\u003c/a-sphere\u003e\n  \u003ca-sphere data-left=\"index-finger-tip\" radius=\"0.01\" visible=\"false\"\u003e\u003c/a-sphere\u003e\n  \u003ca-sphere data-left=\"middle-finger-tip\" radius=\"0.01\" visible=\"false\"\u003e\u003c/a-sphere\u003e\n  \u003ca-sphere data-left=\"ring-finger-tip\" radius=\"0.01\" visible=\"false\"\u003e\u003c/a-sphere\u003e\n  \u003ca-sphere data-left=\"pinky-finger-tip\" radius=\"0.01\" visible=\"false\"\u003e\u003c/a-sphere\u003e\n  \u003ca-sphere data-left=\"thumb-tip\" radius=\"0.01\" visible=\"false\"\u003e\u003c/a-sphere\u003e\n\u003c/a-entity\u003e\n```\n\n## Magnet Helpers\n\nIn `magnet-helpers.js` you can find a few helpful components for dealing with the magnetic behaviour for advanced hand interactions.\n\n\n\u003c!--SCHEMA2--\u003e\n### linear-constraint\n\nlinear-constraint is designed to place the element it's attached to \nplace elements as close as possible to the target element whilst being restrained along a line\ndefined by the `axis`, between the `min` and `max` on either side of the original position\nof the object when this component is first run.\n\nThis is useful for creating magnetic lines. Put linear-constraint on a magnetic element and set it's target\nto the **non-magnet** version of the hand element with the data-magnet property. i.e. the same part but with `data-no-magnet`\n\nVector3\nVector3\n| Property                  | Type        | Description                                                                        | Default              |\n| :------------------------ | :---------- | :--------------------------------------------------------------------------------- | :------------------- |\n| axis                      | vec3        | Axis upon which the element is constrained, does not need to be normalized.        | {\"x\":0,\"y\":0,\"z\":-1} |\n| max                       | number      | How far can it travel along the axis                                               | Infinity             |\n| min                       | number      | How far can it travel opposite to the axis                                         | -Infinity            |\n| radius                    | number      | Outside this distance it will not work                                             | Infinity             |\n| useFixedValueIfOutOfRange | boolean     | Should the object remain at a fixed position if out of the radius.                 | false                |\n| valueIfOutOfRange         | number      | Value the object should be set to if out of the radius                             | 0                    |\n| step                      | number      | Steps it should take from the origin.                                              | 0                    |\n| target                    | selectorAll | Element it should try to follow                                                    |                      |\n| part                      | string      | If applied to a 3D model this is the name of the part that should be used instead. | \"\"                   |\n| enabled                   | boolean     | Whether it should currently run or not                                             | true                 |\n| upEventName               | string      | Name of event to trigger when t is increasing                                      | \"\"                   |\n| upEventThreshold          | number      | Threshold to trigger up event                                                      | 0                    |\n| downEventName             | string      | Name of event to trigger when t is decreasing                                      | \"\"                   |\n| downEventThreshold        | number      | Threshold to trigger up event                                                      | 0                    |\n\n### attach-to-model\n\nEach frame attach-to-model will move an object to the same position as part of the 3D model of it's parent element.\n\nThis is useful for attaching magnetic elements to moving elements of a 3D model so it can be grabbed in different ways.\n\n| Type   | Description            | Default |\n| :----- | :--------------------- | :------ |\n| string | Name of part to follow | \"\"      |\n\n### grab-magnet-target\n\nThis should be added to the hand elements with the `data-magnet`.\n\nWhen one of the `startEvents` events is fired it will start a grab action where it will\nconsider itself grabbing whatever magnetic item it is currently being attracted to and fires the \"grabbed\" event\non the object.\n\nIf the object has `data-pick-up` set then the object will be reparented to the hand element that fired\nthe grab event. The \"pickup\" event will be fired on the object.\n\nIf the object has `data-pick-up=\"parent\"` set then the object's parent will be reparented to the hand element that fired\nthe grab event. The \"pickup\" event will be fired on the object's parent.\n\nWhen either the release event is fired or the object stops being magnet target then it will consider itself released.\nit will reparent objects back to where they were originally and fire the \"putdown\" event on what was held if it had been picked up.\nIf the held object has `data-reset-transform` set then it will also restore it's oriingal position. Otherwise the world position and rotation of the object will remain the same.\n\nFinally the \"released\" event is fired on whatever was being held.\n\nQuaternion\nVector3\n| Property    | Type     | Description                                                               | Default |\n| :---------- | :------- | :------------------------------------------------------------------------ | :------ |\n| startEvents | array    | Event to start grabbing                                                   |         |\n| stopEvents  | array    | Event to stop grabbing                                                    |         |\n| noMagnetEl  | selector | The version of the grip with no magnet providing it helps physics things. |         |\n\n\u003c!--SCHEMA2_END--\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadarosecannon%2Fhandy-work","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadarosecannon%2Fhandy-work","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadarosecannon%2Fhandy-work/lists"}