From deb46d55421356ae6c94a72331ed90349473e4ae Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Sun, 26 Jul 2020 18:33:04 +0300 Subject: [PATCH 1/7] add XrHand, XrFinger, XrJoint --- src/xr/xr-hand.js | 329 ++++++++++++++++++++++++++++++++++++++ src/xr/xr-input-source.js | 68 ++++---- src/xr/xr-manager.js | 3 + 3 files changed, 373 insertions(+), 27 deletions(-) create mode 100644 src/xr/xr-hand.js diff --git a/src/xr/xr-hand.js b/src/xr/xr-hand.js new file mode 100644 index 00000000000..cdccfa7a631 --- /dev/null +++ b/src/xr/xr-hand.js @@ -0,0 +1,329 @@ +import { EventHandler } from '../core/event-handler.js'; + +import { Mat4 } from '../math/mat4.js'; +import { Quat } from '../math/quat.js'; +import { Vec3 } from '../math/vec3.js'; + +var fingerJointIds = [ ]; +var tipJointIds = [ ]; +var tipJointIdsIndex = { }; + +if (window.XRHand) { + fingerJointIds = [ + [ XRHand.THUMB_METACARPAL, XRHand.THUMB_PHALANX_PROXIMAL, XRHand.THUMB_PHALANX_DISTAL, XRHand.THUMB_PHALANX_TIP ], + [ XRHand.INDEX_METACARPAL, XRHand.INDEX_PHALANX_PROXIMAL, XRHand.INDEX_PHALANX_INTERMEDIATE, XRHand.INDEX_PHALANX_DISTAL, XRHand.INDEX_PHALANX_TIP ], + [ XRHand.MIDDLE_METACARPAL, XRHand.MIDDLE_PHALANX_PROXIMAL, XRHand.MIDDLE_PHALANX_INTERMEDIATE, XRHand.MIDDLE_PHALANX_DISTAL, XRHand.MIDDLE_PHALANX_TIP ], + [ XRHand.RING_METACARPAL, XRHand.RING_PHALANX_PROXIMAL, XRHand.RING_PHALANX_INTERMEDIATE, XRHand.RING_PHALANX_DISTAL, XRHand.RING_PHALANX_TIP ], + [ XRHand.LITTLE_METACARPAL, XRHand.LITTLE_PHALANX_PROXIMAL, XRHand.LITTLE_PHALANX_INTERMEDIATE, XRHand.LITTLE_PHALANX_DISTAL, XRHand.LITTLE_PHALANX_TIP ] + ]; + tipJointIds = [ XRHand.THUMB_PHALANX_TIP, XRHand.INDEX_PHALANX_TIP, XRHand.MIDDLE_PHALANX_TIP, XRHand.RING_PHALANX_TIP, XRHand.LITTLE_PHALANX_TIP ]; +} + +for(var i = 0; i < tipJointIds.length; i++) { + tipJointIdsIndex[tipJointIds[i]] = true; +} + +/** + * @class + * @name pc.XrFinger + * @classdesc Represents finger with related joints and index + * @description Represents finger with related joints and index + * @param {number} index - Index of a finger + * @param {pc.XrHand} hand - Hand that finger relates to + * @property {number} index Index of a finger, numeration is: thumb, index, middle, ring, little + * @property {pc.XrHand} hand Hand that finger relates to + * @property {pc.XrJoint[]} joints List of joints that relates to this finger, starting from joint closest to wrist all the way to the tip of a finger + * @property {pc.XrJoint|null} tip Tip of a finger, or null if not available + */ +function XrFinger(index, hand) { + this._index = index; + this._hand = hand; + this._hand._fingers.push(this); + this._joints = [ ]; + this._tip = null; +} + +Object.defineProperty(XrFinger.prototype, 'index', { + get: function () { + return this._index; + } +}); + +Object.defineProperty(XrFinger.prototype, 'hand', { + get: function () { + return this._hand; + } +}); + +Object.defineProperty(XrFinger.prototype, 'joints', { + get: function () { + return this._joints; + } +}); + +Object.defineProperty(XrFinger.prototype, 'tip', { + get: function () { + return this._tip; + } +}); + + +/** + * @class + * @name pc.XrJoint + * @classdesc Represents joint of a finger + * @description Represents joint of a finger + * @param {number} index - Index of a joint within a finger + * @param {number} id - Id of a joint based on XRHand specs + * @param {pc.XrHand} hand - Hand that joint relates to + * @param {pc.XrFinger} [finger] - Finger that joint is related to, can be null in case of wrist joint + * @property {number} index Index of a joint within a finger, starting from 0 (root of a finger) all the way to tip of the finger + * @property {pc.XrHand} hand Hand that joint relates to + * @property {pc.XrFinger|null} finger Finger that joint relates to + * @property {boolean} wrist True if joint is a wrist + * @property {boolean} tip True if joint is a tip of a finger + */ +function XrJoint(index, id, hand, finger) { + this._index = index; + this._id = id; + + this._hand = hand; + this._hand._joints.push(this); + + this._finger = finger || null; + if (this._finger) this._finger._joints.push(this); + + this._wrist = id == XRHand.WRIST; + if (this._wrist) this._hand._wrist = this; + + this._tip = this._finger && !! tipJointIdsIndex[id]; + if (this._tip) { + this._hand._tips.push(this); + if (this._finger) this._finger._tip = this; + } + + this._radius = null; + + this._localTransform = new Mat4(); + this._localTransform.setIdentity(); + + this._worldTransform = new Mat4(); + this._worldTransform.setIdentity(); + + this._localPosition = new pc.Vec3(); + this._localRotation = new pc.Quat(); + + this._position = new pc.Vec3(); + this._rotation = new pc.Quat(); + + this._dirtyLocal = true; +} + +XrJoint.prototype.update = function(pose) { + this._dirtyLocal = true; + this._radius = pose.radius; + this._localPosition.copy(pose.transform.position); + this._localRotation.copy(pose.transform.orientation); +}; + +XrJoint.prototype._updateTransforms = function() { + var dirty; + + if (this._dirtyLocal) { + dirty = true; + this._dirtyLocal = false; + this._localTransform.setTRS(this._localPosition, this._localRotation, Vec3.ONE); + } + + var manager = this._hand._manager; + var parent = manager.camera.parent; + + if (parent) { + dirty = dirty || parent._dirtyLocal || parent._dirtyWorld; + if (dirty) this._worldTransform.mul2(parent.getWorldTransform(), this._localTransform); + } else { + this._worldTransform.copy(this._localTransform); + } +}; + +/** + * @function + * @name pc.XrJoint#getPosition + * @description Get the world space position of a joint + * @returns {pc.Vec3} The world space position of a joint + */ +XrJoint.prototype.getPosition = function() { + this._updateTransforms(); + this._worldTransform.getTranslation(this._position); + return this._position; +}; + +/** + * @function + * @name pc.XrJoint#getRotation + * @description Get the world space rotation of a joint + * @returns {pc.Quat} The world space rotation of a joint + */ +XrJoint.prototype.getRotation = function() { + this._updateTransforms(); + this._rotation.setFromMat4(this._worldTransform); + return this._rotation; +}; + +/** + * @function + * @name pc.XrJoint#getRadius + * @description Get the radius of a joint, which is a distance from joint to the edge of a skin + * @returns {number} Radius of a joint + */ +XrJoint.prototype.getRadius = function() { + return this._radius || 0.005; +}; + +Object.defineProperty(XrJoint.prototype, 'index', { + get: function () { + return this._index; + } +}); + +Object.defineProperty(XrJoint.prototype, 'hand', { + get: function () { + return this._hand; + } +}); + +Object.defineProperty(XrJoint.prototype, 'finger', { + get: function () { + return this._finger; + } +}); + +Object.defineProperty(XrJoint.prototype, 'wrist', { + get: function () { + return this._wrist; + } +}); + +Object.defineProperty(XrJoint.prototype, 'tip', { + get: function () { + return this._tip; + } +}); + + +/** + * @class + * @name pc.XrHand + * @classdesc Represents a hand with fingers and joints + * @description Represents a hand with fingers and joints + * @param {pc.XrInputSource} inputSource - Input Source that hand is related to + * @property {pc.XrFinger[]} fingers List of fingers of a hand + * @property {pc.XrJoint[]} joints List of joints of hand + * @property {pc.XrJoint[]} tips List of joints that are tips of a fingers + * @property {pc.XrJoint|null} wrist Wrist of a hand, or null if it is not available by WebXR underlying system + * @property {boolean} tracking True if tracking is available, otherwise tracking might be lost + */ +function XrHand(inputSource) { + EventHandler.call(this); + + var xrHand = inputSource._xrInputSource.hand; + + this._manager = inputSource._manager; + this._inputSource = inputSource; + + this._tracking = false; + + this._fingers = [ ]; + this._joints = [ ]; + this._tips = [ ]; + + this._wrist = null; + if (xrHand[XRHand.WRIST]) { + this._wrist = new XrJoint(0, XRHand.WRIST, this, null); + this._joints.push(this._wrist); + } + + for(var f = 0; f < fingerJointIds.length; f++) { + var finger = new XrFinger(f, this); + + for(var j = 0; j < fingerJointIds[f].length; j++) { + var jointId = fingerJointIds[f][j]; + if (! xrHand[jointId]) continue; + new XrJoint(j, jointId, this, finger); + } + } +} +XrHand.prototype = Object.create(EventHandler.prototype); +XrHand.prototype.constructor = XrHand; + +/** + * @event + * @name pc.XrHand#tracking + * @description Fired when tracking becomes available. + */ + + /** + * @event + * @name pc.XrHand#trackinglost + * @description Fired when tracking is lost. + */ + +XrHand.prototype.update = function(frame) { + var xrInputSource = this._inputSource._xrInputSource; + + for(var j = 0; j < this._joints.length; j++) { + var joint = this._joints[j]; + var jointSpace = xrInputSource.hand[joint._id]; + if (jointSpace) { + var pose = frame.getJointPose(jointSpace, this._manager._referenceSpace); + if (pose) { + joint.update(pose); + + if (joint.wrist && ! this._tracking) { + this._tracking = true; + this.fire('tracking'); + } + } else if (joint.wrist) { + // lost tracking + + if (this._tracking) { + this._tracking = false; + this.fire('trackinglost'); + } + break; + } + } + } +}; + +Object.defineProperty(XrHand.prototype, 'fingers', { + get: function () { + return this._fingers; + } +}); + +Object.defineProperty(XrHand.prototype, 'joints', { + get: function () { + return this._joints; + } +}); + +Object.defineProperty(XrHand.prototype, 'tips', { + get: function () { + return this._tips; + } +}); + +Object.defineProperty(XrHand.prototype, 'wrist', { + get: function () { + return this._wrist; + } +}); + +Object.defineProperty(XrHand.prototype, 'tracking', { + get: function () { + return this._tracking; + } +}); + +export { XrHand }; diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js index e3d6376d8ec..4fecbbdeb1e 100644 --- a/src/xr/xr-input-source.js +++ b/src/xr/xr-input-source.js @@ -6,6 +6,8 @@ import { Vec3 } from '../math/vec3.js'; import { Ray } from '../shape/ray.js'; +import { XrHand } from './xr-hand.js'; + var quat = new Quat(); var ids = 0; @@ -33,6 +35,7 @@ var ids = 0; * * @property {string[]} profiles List of input profile names indicating both the prefered visual representation and behavior of the input source. * @property {boolean} grip If input source can be held, then it will have node with its world transformation, that can be used to position and rotate virtual joystics based on it. + * @property {pc.XrHand|null} hand If input source is a tracked hand, then it will point to {@link pc.XrHand} otherwise it is null. Then ray related to input source should be ignored. * @property {Gamepad|null} gamepad If input source has buttons, triggers, thumbstick or touchpad, then this object provides access to its states. * @property {boolean} selecting True if input source is in active primary action between selectstart and selectend events. * @property {boolean} elementInput Set to true to allow input source to interact with Element components. Defaults to true. @@ -50,6 +53,10 @@ function XrInputSource(manager, xrInputSource) { this._ray = new Ray(); this._rayLocal = new Ray(); this._grip = false; + this._hand = null; + + if (xrInputSource.hand) + this._hand = new XrHand(this); this._localTransform = null; this._worldTransform = null; @@ -148,31 +155,36 @@ XrInputSource.prototype.update = function (frame) { var targetRayPose = frame.getPose(this._xrInputSource.targetRaySpace, this._manager._referenceSpace); if (! targetRayPose) return; - // grip - if (this._xrInputSource.gripSpace) { - var gripPose = frame.getPose(this._xrInputSource.gripSpace, this._manager._referenceSpace); - if (gripPose) { - if (! this._grip) { - this._grip = true; - - this._localTransform = new Mat4(); - this._worldTransform = new Mat4(); - - this._localPosition = new Vec3(); - this._localRotation = new Quat(); + // hand + if (this._hand) { + this._hand.update(frame); + } else { + // grip + if (this._xrInputSource.gripSpace) { + var gripPose = frame.getPose(this._xrInputSource.gripSpace, this._manager._referenceSpace); + if (gripPose) { + if (! this._grip) { + this._grip = true; + + this._localTransform = new Mat4(); + this._worldTransform = new Mat4(); + + this._localPosition = new Vec3(); + this._localRotation = new Quat(); + } + this._dirtyLocal = true; + this._localPosition.copy(gripPose.transform.position); + this._localRotation.copy(gripPose.transform.orientation); } - this._dirtyLocal = true; - this._localPosition.copy(gripPose.transform.position); - this._localRotation.copy(gripPose.transform.orientation); } - } - // ray - this._dirtyRay = true; - this._rayLocal.origin.copy(targetRayPose.transform.position); - this._rayLocal.direction.set(0, 0, -1); - quat.copy(targetRayPose.transform.orientation); - quat.transformVector(this._rayLocal.direction, this._rayLocal.direction); + // ray + this._dirtyRay = true; + this._rayLocal.origin.copy(targetRayPose.transform.position); + this._rayLocal.direction.set(0, 0, -1); + quat.copy(targetRayPose.transform.orientation); + quat.transformVector(this._rayLocal.direction, this._rayLocal.direction); + } }; XrInputSource.prototype._updateTransforms = function () { @@ -187,11 +199,7 @@ XrInputSource.prototype._updateTransforms = function () { var parent = this._manager.camera.parent; if (parent) { dirty = dirty || parent._dirtyLocal || parent._dirtyWorld; - - if (dirty) { - var parentTransform = this._manager.camera.parent.getWorldTransform(); - this._worldTransform.mul2(parentTransform, this._localTransform); - } + if (dirty) this._worldTransform.mul2(parent.getWorldTransform(), this._localTransform); } else { this._worldTransform.copy(this._localTransform); } @@ -398,6 +406,12 @@ Object.defineProperty(XrInputSource.prototype, 'grip', { } }); +Object.defineProperty(XrInputSource.prototype, 'hand', { + get: function () { + return this._hand; + } +}); + Object.defineProperty(XrInputSource.prototype, 'gamepad', { get: function () { return this._xrInputSource.gamepad || null; diff --git a/src/xr/xr-manager.js b/src/xr/xr-manager.js index fd0af7717f3..d286a06a698 100644 --- a/src/xr/xr-manager.js +++ b/src/xr/xr-manager.js @@ -193,6 +193,9 @@ XrManager.prototype.start = function (camera, type, spaceType, callback) { if (type === XRTYPE_AR) optionalFeatures.push('light-estimation'); + if (type === XRTYPE_VR) + optionalFeatures.push('hand-tracking'); + navigator.xr.requestSession(type, { requiredFeatures: [spaceType], optionalFeatures: optionalFeatures From 2078a7094419b8439c4a1d8f1a2dc7498efbc028 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Mon, 27 Jul 2020 01:55:40 +0300 Subject: [PATCH 2/7] lint fixes --- src/xr/xr-hand.js | 54 +++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/xr/xr-hand.js b/src/xr/xr-hand.js index cdccfa7a631..6d0bccf16e1 100644 --- a/src/xr/xr-hand.js +++ b/src/xr/xr-hand.js @@ -4,22 +4,22 @@ import { Mat4 } from '../math/mat4.js'; import { Quat } from '../math/quat.js'; import { Vec3 } from '../math/vec3.js'; -var fingerJointIds = [ ]; -var tipJointIds = [ ]; -var tipJointIdsIndex = { }; +var fingerJointIds = []; +var tipJointIds = []; +var tipJointIdsIndex = {}; if (window.XRHand) { fingerJointIds = [ - [ XRHand.THUMB_METACARPAL, XRHand.THUMB_PHALANX_PROXIMAL, XRHand.THUMB_PHALANX_DISTAL, XRHand.THUMB_PHALANX_TIP ], - [ XRHand.INDEX_METACARPAL, XRHand.INDEX_PHALANX_PROXIMAL, XRHand.INDEX_PHALANX_INTERMEDIATE, XRHand.INDEX_PHALANX_DISTAL, XRHand.INDEX_PHALANX_TIP ], - [ XRHand.MIDDLE_METACARPAL, XRHand.MIDDLE_PHALANX_PROXIMAL, XRHand.MIDDLE_PHALANX_INTERMEDIATE, XRHand.MIDDLE_PHALANX_DISTAL, XRHand.MIDDLE_PHALANX_TIP ], - [ XRHand.RING_METACARPAL, XRHand.RING_PHALANX_PROXIMAL, XRHand.RING_PHALANX_INTERMEDIATE, XRHand.RING_PHALANX_DISTAL, XRHand.RING_PHALANX_TIP ], - [ XRHand.LITTLE_METACARPAL, XRHand.LITTLE_PHALANX_PROXIMAL, XRHand.LITTLE_PHALANX_INTERMEDIATE, XRHand.LITTLE_PHALANX_DISTAL, XRHand.LITTLE_PHALANX_TIP ] + [XRHand.THUMB_METACARPAL, XRHand.THUMB_PHALANX_PROXIMAL, XRHand.THUMB_PHALANX_DISTAL, XRHand.THUMB_PHALANX_TIP], + [XRHand.INDEX_METACARPAL, XRHand.INDEX_PHALANX_PROXIMAL, XRHand.INDEX_PHALANX_INTERMEDIATE, XRHand.INDEX_PHALANX_DISTAL, XRHand.INDEX_PHALANX_TIP], + [XRHand.MIDDLE_METACARPAL, XRHand.MIDDLE_PHALANX_PROXIMAL, XRHand.MIDDLE_PHALANX_INTERMEDIATE, XRHand.MIDDLE_PHALANX_DISTAL, XRHand.MIDDLE_PHALANX_TIP], + [XRHand.RING_METACARPAL, XRHand.RING_PHALANX_PROXIMAL, XRHand.RING_PHALANX_INTERMEDIATE, XRHand.RING_PHALANX_DISTAL, XRHand.RING_PHALANX_TIP], + [XRHand.LITTLE_METACARPAL, XRHand.LITTLE_PHALANX_PROXIMAL, XRHand.LITTLE_PHALANX_INTERMEDIATE, XRHand.LITTLE_PHALANX_DISTAL, XRHand.LITTLE_PHALANX_TIP] ]; - tipJointIds = [ XRHand.THUMB_PHALANX_TIP, XRHand.INDEX_PHALANX_TIP, XRHand.MIDDLE_PHALANX_TIP, XRHand.RING_PHALANX_TIP, XRHand.LITTLE_PHALANX_TIP ]; + tipJointIds = [XRHand.THUMB_PHALANX_TIP, XRHand.INDEX_PHALANX_TIP, XRHand.MIDDLE_PHALANX_TIP, XRHand.RING_PHALANX_TIP, XRHand.LITTLE_PHALANX_TIP]; } -for(var i = 0; i < tipJointIds.length; i++) { +for (var i = 0; i < tipJointIds.length; i++) { tipJointIdsIndex[tipJointIds[i]] = true; } @@ -39,7 +39,7 @@ function XrFinger(index, hand) { this._index = index; this._hand = hand; this._hand._fingers.push(this); - this._joints = [ ]; + this._joints = []; this._tip = null; } @@ -110,23 +110,23 @@ function XrJoint(index, id, hand, finger) { this._worldTransform = new Mat4(); this._worldTransform.setIdentity(); - this._localPosition = new pc.Vec3(); - this._localRotation = new pc.Quat(); + this._localPosition = new Vec3(); + this._localRotation = new Quat(); - this._position = new pc.Vec3(); - this._rotation = new pc.Quat(); + this._position = new Vec3(); + this._rotation = new Quat(); this._dirtyLocal = true; } -XrJoint.prototype.update = function(pose) { +XrJoint.prototype.update = function (pose) { this._dirtyLocal = true; this._radius = pose.radius; this._localPosition.copy(pose.transform.position); this._localRotation.copy(pose.transform.orientation); }; -XrJoint.prototype._updateTransforms = function() { +XrJoint.prototype._updateTransforms = function () { var dirty; if (this._dirtyLocal) { @@ -152,7 +152,7 @@ XrJoint.prototype._updateTransforms = function() { * @description Get the world space position of a joint * @returns {pc.Vec3} The world space position of a joint */ -XrJoint.prototype.getPosition = function() { +XrJoint.prototype.getPosition = function () { this._updateTransforms(); this._worldTransform.getTranslation(this._position); return this._position; @@ -164,7 +164,7 @@ XrJoint.prototype.getPosition = function() { * @description Get the world space rotation of a joint * @returns {pc.Quat} The world space rotation of a joint */ -XrJoint.prototype.getRotation = function() { +XrJoint.prototype.getRotation = function () { this._updateTransforms(); this._rotation.setFromMat4(this._worldTransform); return this._rotation; @@ -176,7 +176,7 @@ XrJoint.prototype.getRotation = function() { * @description Get the radius of a joint, which is a distance from joint to the edge of a skin * @returns {number} Radius of a joint */ -XrJoint.prototype.getRadius = function() { +XrJoint.prototype.getRadius = function () { return this._radius || 0.005; }; @@ -233,9 +233,9 @@ function XrHand(inputSource) { this._tracking = false; - this._fingers = [ ]; - this._joints = [ ]; - this._tips = [ ]; + this._fingers = []; + this._joints = []; + this._tips = []; this._wrist = null; if (xrHand[XRHand.WRIST]) { @@ -243,10 +243,10 @@ function XrHand(inputSource) { this._joints.push(this._wrist); } - for(var f = 0; f < fingerJointIds.length; f++) { + for (var f = 0; f < fingerJointIds.length; f++) { var finger = new XrFinger(f, this); - for(var j = 0; j < fingerJointIds[f].length; j++) { + for (var j = 0; j < fingerJointIds[f].length; j++) { var jointId = fingerJointIds[f][j]; if (! xrHand[jointId]) continue; new XrJoint(j, jointId, this, finger); @@ -268,10 +268,10 @@ XrHand.prototype.constructor = XrHand; * @description Fired when tracking is lost. */ -XrHand.prototype.update = function(frame) { +XrHand.prototype.update = function (frame) { var xrInputSource = this._inputSource._xrInputSource; - for(var j = 0; j < this._joints.length; j++) { + for (var j = 0; j < this._joints.length; j++) { var joint = this._joints[j]; var jointSpace = xrInputSource.hand[joint._id]; if (jointSpace) { From f566ac20c739623fc612501a71768dca0ce27457 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Tue, 28 Jul 2020 13:40:19 +0300 Subject: [PATCH 3/7] webxr hands example --- examples/examples.js | 1 + examples/xr/vr-hands.html | 229 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 examples/xr/vr-hands.html diff --git a/examples/examples.js b/examples/examples.js index 2a94fef6fca..ad70300ba83 100644 --- a/examples/examples.js +++ b/examples/examples.js @@ -101,6 +101,7 @@ var categories = [ 'ar-hit-test', 'vr-basic', 'vr-controllers', + 'vr-hands', 'vr-movement', 'xr-picking' ] diff --git a/examples/xr/vr-hands.html b/examples/xr/vr-hands.html new file mode 100644 index 00000000000..c24a9266629 --- /dev/null +++ b/examples/xr/vr-hands.html @@ -0,0 +1,229 @@ + + + + PlayCanvas VR Hands + + + + + + + + + +
+ + + From 7f8c73a74b1b9cf6ef77cfa79e70d62a79624be6 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Tue, 28 Jul 2020 16:09:12 +0300 Subject: [PATCH 4/7] implement ray for hands tracking, and select events --- src/xr/xr-hand.js | 88 +++++++++++++++++++++++++++++++++++++-- src/xr/xr-input-source.js | 18 ++++---- 2 files changed, 94 insertions(+), 12 deletions(-) diff --git a/src/xr/xr-hand.js b/src/xr/xr-hand.js index 6d0bccf16e1..1e16bb858c4 100644 --- a/src/xr/xr-hand.js +++ b/src/xr/xr-hand.js @@ -1,5 +1,7 @@ import { EventHandler } from '../core/event-handler.js'; +import { XRHAND_LEFT, XRHAND_RIGHT } from './constants.js'; + import { Mat4 } from '../math/mat4.js'; import { Quat } from '../math/quat.js'; import { Vec3 } from '../math/vec3.js'; @@ -8,6 +10,10 @@ var fingerJointIds = []; var tipJointIds = []; var tipJointIdsIndex = {}; +var vecA = new Vec3(); +var vecB = new Vec3(); +var vecC = new Vec3(); + if (window.XRHand) { fingerJointIds = [ [XRHand.THUMB_METACARPAL, XRHand.THUMB_PHALANX_PROXIMAL, XRHand.THUMB_PHALANX_DISTAL, XRHand.THUMB_PHALANX_TIP], @@ -89,6 +95,7 @@ function XrJoint(index, id, hand, finger) { this._hand = hand; this._hand._joints.push(this); + this._hand._jointsById[id] = this; this._finger = finger || null; if (this._finger) this._finger._joints.push(this); @@ -235,13 +242,13 @@ function XrHand(inputSource) { this._fingers = []; this._joints = []; + this._jointsById = {}; this._tips = []; this._wrist = null; - if (xrHand[XRHand.WRIST]) { + + if (xrHand[XRHand.WRIST]) this._wrist = new XrJoint(0, XRHand.WRIST, this, null); - this._joints.push(this._wrist); - } for (var f = 0; f < fingerJointIds.length; f++) { var finger = new XrFinger(f, this); @@ -271,6 +278,7 @@ XrHand.prototype.constructor = XrHand; XrHand.prototype.update = function (frame) { var xrInputSource = this._inputSource._xrInputSource; + // joints for (var j = 0; j < this._joints.length; j++) { var joint = this._joints[j]; var jointSpace = xrInputSource.hand[joint._id]; @@ -294,6 +302,80 @@ XrHand.prototype.update = function (frame) { } } } + + var j1 = this._jointsById[XRHand.THUMB_METACARPAL]; + var j4 = this._jointsById[XRHand.THUMB_PHALANX_TIP]; + var j6 = this._jointsById[XRHand.INDEX_PHALANX_PROXIMAL]; + var j9 = this._jointsById[XRHand.INDEX_PHALANX_TIP]; + var j16 = this._jointsById[XRHand.RING_PHALANX_PROXIMAL]; + var j21 = this._jointsById[XRHand.LITTLE_PHALANX_PROXIMAL];; + + // ray + if (j1 && j4 && j6 && j9 && j16 && j21) { + this._inputSource._dirtyRay = true; + + // ray origin + // get point between thumb tip and index tip + this._inputSource._rayLocal.origin.lerp(j4._localPosition, j9._localPosition, 0.5); + + // ray direction + var jointL = j1; + var jointR = j21; + + if (this._inputSource.handedness === XRHAND_LEFT) { + var t = jointL; + jointL = jointR; + jointR = t; + } + + // (A) calculate normal vector between 3 joints: wrist, thumb metacarpal, little phalanx proximal + vecA.sub2(jointL._localPosition, this._wrist._localPosition); + vecB.sub2(jointR._localPosition, this._wrist._localPosition); + vecC.cross(vecA, vecB).normalize(); + + // get point between: index phalanx proximal and right phalanx proximal + vecA.lerp(j6._localPosition, j16._localPosition, 0.5); + // (B) get vector between that point and a wrist + vecA.sub(this._wrist._localPosition).normalize(); + + // mix normal vector (A) with hand directional vector (B) + this._inputSource._rayLocal.direction.lerp(vecC, vecA, 0.5).normalize(); + } + + // emulate select events by touching thumb tip and index tips + if (j4 && j9) { + vecA.copy(j4._localPosition); + var d = vecA.distance(j9._localPosition); + + if (d < 0.015) { // 15 mm + if (! this._inputSource._selecting) { + this._inputSource._selecting = true; + this._inputSource.fire('selectstart'); + this._manager.input.fire('selectstart', this._inputSource); + } + } else { + if (this._inputSource._selecting) { + this._inputSource._selecting = false; + + this._inputSource.fire('select'); + this._manager.input.fire('select', this._inputSource); + + this._inputSource.fire('selectend'); + this._manager.input.fire('selectend', this._inputSource); + } + } + } +}; + +/** + * @function + * @name pc.XrHand#getJointById + * @description Returns joint by XRHand id from list in specs: https://immersive-web.github.io/webxr-hand-input/ + * @param {number} id - id of a joint based on specs ID's in XRHand: https://immersive-web.github.io/webxr-hand-input/ + * @returns {pc.XrJoint|null} Joint or null if not available + */ +XrHand.prototype.getJointById = function (id) { + return this._jointsById[id] || null; }; Object.defineProperty(XrHand.prototype, 'fingers', { diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js index 4fecbbdeb1e..7aa6fb5ad96 100644 --- a/src/xr/xr-input-source.js +++ b/src/xr/xr-input-source.js @@ -35,7 +35,7 @@ var ids = 0; * * @property {string[]} profiles List of input profile names indicating both the prefered visual representation and behavior of the input source. * @property {boolean} grip If input source can be held, then it will have node with its world transformation, that can be used to position and rotate virtual joystics based on it. - * @property {pc.XrHand|null} hand If input source is a tracked hand, then it will point to {@link pc.XrHand} otherwise it is null. Then ray related to input source should be ignored. + * @property {pc.XrHand|null} hand If input source is a tracked hand, then it will point to {@link pc.XrHand} otherwise it is null. * @property {Gamepad|null} gamepad If input source has buttons, triggers, thumbstick or touchpad, then this object provides access to its states. * @property {boolean} selecting True if input source is in active primary action between selectstart and selectend events. * @property {boolean} elementInput Set to true to allow input source to interact with Element components. Defaults to true. @@ -152,9 +152,6 @@ XrInputSource.prototype.constructor = XrInputSource; */ XrInputSource.prototype.update = function (frame) { - var targetRayPose = frame.getPose(this._xrInputSource.targetRaySpace, this._manager._referenceSpace); - if (! targetRayPose) return; - // hand if (this._hand) { this._hand.update(frame); @@ -179,11 +176,14 @@ XrInputSource.prototype.update = function (frame) { } // ray - this._dirtyRay = true; - this._rayLocal.origin.copy(targetRayPose.transform.position); - this._rayLocal.direction.set(0, 0, -1); - quat.copy(targetRayPose.transform.orientation); - quat.transformVector(this._rayLocal.direction, this._rayLocal.direction); + var targetRayPose = frame.getPose(this._xrInputSource.targetRaySpace, this._manager._referenceSpace); + if (targetRayPose) { + this._dirtyRay = true; + this._rayLocal.origin.copy(targetRayPose.transform.position); + this._rayLocal.direction.set(0, 0, -1); + quat.copy(targetRayPose.transform.orientation); + quat.transformVector(this._rayLocal.direction, this._rayLocal.direction); + } } }; From 3e54969c11d1b351ea9f67040faa69b0f05ab95b Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Tue, 28 Jul 2020 16:24:13 +0300 Subject: [PATCH 5/7] fix lint --- src/xr/xr-hand.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xr/xr-hand.js b/src/xr/xr-hand.js index 1e16bb858c4..cba0bb7be35 100644 --- a/src/xr/xr-hand.js +++ b/src/xr/xr-hand.js @@ -308,7 +308,7 @@ XrHand.prototype.update = function (frame) { var j6 = this._jointsById[XRHand.INDEX_PHALANX_PROXIMAL]; var j9 = this._jointsById[XRHand.INDEX_PHALANX_TIP]; var j16 = this._jointsById[XRHand.RING_PHALANX_PROXIMAL]; - var j21 = this._jointsById[XRHand.LITTLE_PHALANX_PROXIMAL];; + var j21 = this._jointsById[XRHand.LITTLE_PHALANX_PROXIMAL]; // ray if (j1 && j4 && j6 && j9 && j16 && j21) { From e72f09d4ce739771efffa95840a3efd65be3d55b Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Tue, 4 Aug 2020 01:09:20 +0300 Subject: [PATCH 6/7] XrHand.getRadius > XrHand.radius, moved XrFinger and XrJoint to own files --- examples/xr/vr-hands.html | 2 +- src/xr/xr-finger.js | 45 +++++++++ src/xr/xr-hand.js | 203 +------------------------------------- src/xr/xr-joint.js | 156 +++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 200 deletions(-) create mode 100644 src/xr/xr-finger.js create mode 100644 src/xr/xr-joint.js diff --git a/examples/xr/vr-hands.html b/examples/xr/vr-hands.html index c24a9266629..fc42b604068 100644 --- a/examples/xr/vr-hands.html +++ b/examples/xr/vr-hands.html @@ -205,7 +205,7 @@ // update each hand joint for(var j = 0; j < controllers[i].joints.length; j++) { var joint = controllers[i].joints[j].joint; - var r = joint.getRadius() * 2; + var r = joint.radius * 2; controllers[i].joints[j].setLocalScale(r, r, r); controllers[i].joints[j].setPosition(joint.getPosition()); controllers[i].joints[j].setRotation(joint.getRotation()); diff --git a/src/xr/xr-finger.js b/src/xr/xr-finger.js new file mode 100644 index 00000000000..0ce7d0ef7f6 --- /dev/null +++ b/src/xr/xr-finger.js @@ -0,0 +1,45 @@ +/** + * @class + * @name pc.XrFinger + * @classdesc Represents finger with related joints and index + * @description Represents finger with related joints and index + * @param {number} index - Index of a finger + * @param {pc.XrHand} hand - Hand that finger relates to + * @property {number} index Index of a finger, numeration is: thumb, index, middle, ring, little + * @property {pc.XrHand} hand Hand that finger relates to + * @property {pc.XrJoint[]} joints List of joints that relates to this finger, starting from joint closest to wrist all the way to the tip of a finger + * @property {pc.XrJoint|null} tip Tip of a finger, or null if not available + */ +function XrFinger(index, hand) { + this._index = index; + this._hand = hand; + this._hand._fingers.push(this); + this._joints = []; + this._tip = null; +} + +Object.defineProperty(XrFinger.prototype, 'index', { + get: function () { + return this._index; + } +}); + +Object.defineProperty(XrFinger.prototype, 'hand', { + get: function () { + return this._hand; + } +}); + +Object.defineProperty(XrFinger.prototype, 'joints', { + get: function () { + return this._joints; + } +}); + +Object.defineProperty(XrFinger.prototype, 'tip', { + get: function () { + return this._tip; + } +}); + +export { XrFinger }; diff --git a/src/xr/xr-hand.js b/src/xr/xr-hand.js index cba0bb7be35..9f58bf94104 100644 --- a/src/xr/xr-hand.js +++ b/src/xr/xr-hand.js @@ -1,14 +1,13 @@ import { EventHandler } from '../core/event-handler.js'; -import { XRHAND_LEFT, XRHAND_RIGHT } from './constants.js'; +import { XRHAND_LEFT } from './constants.js'; + +import { XrFinger } from './xr-finger.js'; +import { XrJoint } from './xr-joint.js'; -import { Mat4 } from '../math/mat4.js'; -import { Quat } from '../math/quat.js'; import { Vec3 } from '../math/vec3.js'; var fingerJointIds = []; -var tipJointIds = []; -var tipJointIdsIndex = {}; var vecA = new Vec3(); var vecB = new Vec3(); @@ -22,202 +21,8 @@ if (window.XRHand) { [XRHand.RING_METACARPAL, XRHand.RING_PHALANX_PROXIMAL, XRHand.RING_PHALANX_INTERMEDIATE, XRHand.RING_PHALANX_DISTAL, XRHand.RING_PHALANX_TIP], [XRHand.LITTLE_METACARPAL, XRHand.LITTLE_PHALANX_PROXIMAL, XRHand.LITTLE_PHALANX_INTERMEDIATE, XRHand.LITTLE_PHALANX_DISTAL, XRHand.LITTLE_PHALANX_TIP] ]; - tipJointIds = [XRHand.THUMB_PHALANX_TIP, XRHand.INDEX_PHALANX_TIP, XRHand.MIDDLE_PHALANX_TIP, XRHand.RING_PHALANX_TIP, XRHand.LITTLE_PHALANX_TIP]; -} - -for (var i = 0; i < tipJointIds.length; i++) { - tipJointIdsIndex[tipJointIds[i]] = true; -} - -/** - * @class - * @name pc.XrFinger - * @classdesc Represents finger with related joints and index - * @description Represents finger with related joints and index - * @param {number} index - Index of a finger - * @param {pc.XrHand} hand - Hand that finger relates to - * @property {number} index Index of a finger, numeration is: thumb, index, middle, ring, little - * @property {pc.XrHand} hand Hand that finger relates to - * @property {pc.XrJoint[]} joints List of joints that relates to this finger, starting from joint closest to wrist all the way to the tip of a finger - * @property {pc.XrJoint|null} tip Tip of a finger, or null if not available - */ -function XrFinger(index, hand) { - this._index = index; - this._hand = hand; - this._hand._fingers.push(this); - this._joints = []; - this._tip = null; } -Object.defineProperty(XrFinger.prototype, 'index', { - get: function () { - return this._index; - } -}); - -Object.defineProperty(XrFinger.prototype, 'hand', { - get: function () { - return this._hand; - } -}); - -Object.defineProperty(XrFinger.prototype, 'joints', { - get: function () { - return this._joints; - } -}); - -Object.defineProperty(XrFinger.prototype, 'tip', { - get: function () { - return this._tip; - } -}); - - -/** - * @class - * @name pc.XrJoint - * @classdesc Represents joint of a finger - * @description Represents joint of a finger - * @param {number} index - Index of a joint within a finger - * @param {number} id - Id of a joint based on XRHand specs - * @param {pc.XrHand} hand - Hand that joint relates to - * @param {pc.XrFinger} [finger] - Finger that joint is related to, can be null in case of wrist joint - * @property {number} index Index of a joint within a finger, starting from 0 (root of a finger) all the way to tip of the finger - * @property {pc.XrHand} hand Hand that joint relates to - * @property {pc.XrFinger|null} finger Finger that joint relates to - * @property {boolean} wrist True if joint is a wrist - * @property {boolean} tip True if joint is a tip of a finger - */ -function XrJoint(index, id, hand, finger) { - this._index = index; - this._id = id; - - this._hand = hand; - this._hand._joints.push(this); - this._hand._jointsById[id] = this; - - this._finger = finger || null; - if (this._finger) this._finger._joints.push(this); - - this._wrist = id == XRHand.WRIST; - if (this._wrist) this._hand._wrist = this; - - this._tip = this._finger && !! tipJointIdsIndex[id]; - if (this._tip) { - this._hand._tips.push(this); - if (this._finger) this._finger._tip = this; - } - - this._radius = null; - - this._localTransform = new Mat4(); - this._localTransform.setIdentity(); - - this._worldTransform = new Mat4(); - this._worldTransform.setIdentity(); - - this._localPosition = new Vec3(); - this._localRotation = new Quat(); - - this._position = new Vec3(); - this._rotation = new Quat(); - - this._dirtyLocal = true; -} - -XrJoint.prototype.update = function (pose) { - this._dirtyLocal = true; - this._radius = pose.radius; - this._localPosition.copy(pose.transform.position); - this._localRotation.copy(pose.transform.orientation); -}; - -XrJoint.prototype._updateTransforms = function () { - var dirty; - - if (this._dirtyLocal) { - dirty = true; - this._dirtyLocal = false; - this._localTransform.setTRS(this._localPosition, this._localRotation, Vec3.ONE); - } - - var manager = this._hand._manager; - var parent = manager.camera.parent; - - if (parent) { - dirty = dirty || parent._dirtyLocal || parent._dirtyWorld; - if (dirty) this._worldTransform.mul2(parent.getWorldTransform(), this._localTransform); - } else { - this._worldTransform.copy(this._localTransform); - } -}; - -/** - * @function - * @name pc.XrJoint#getPosition - * @description Get the world space position of a joint - * @returns {pc.Vec3} The world space position of a joint - */ -XrJoint.prototype.getPosition = function () { - this._updateTransforms(); - this._worldTransform.getTranslation(this._position); - return this._position; -}; - -/** - * @function - * @name pc.XrJoint#getRotation - * @description Get the world space rotation of a joint - * @returns {pc.Quat} The world space rotation of a joint - */ -XrJoint.prototype.getRotation = function () { - this._updateTransforms(); - this._rotation.setFromMat4(this._worldTransform); - return this._rotation; -}; - -/** - * @function - * @name pc.XrJoint#getRadius - * @description Get the radius of a joint, which is a distance from joint to the edge of a skin - * @returns {number} Radius of a joint - */ -XrJoint.prototype.getRadius = function () { - return this._radius || 0.005; -}; - -Object.defineProperty(XrJoint.prototype, 'index', { - get: function () { - return this._index; - } -}); - -Object.defineProperty(XrJoint.prototype, 'hand', { - get: function () { - return this._hand; - } -}); - -Object.defineProperty(XrJoint.prototype, 'finger', { - get: function () { - return this._finger; - } -}); - -Object.defineProperty(XrJoint.prototype, 'wrist', { - get: function () { - return this._wrist; - } -}); - -Object.defineProperty(XrJoint.prototype, 'tip', { - get: function () { - return this._tip; - } -}); - - /** * @class * @name pc.XrHand diff --git a/src/xr/xr-joint.js b/src/xr/xr-joint.js new file mode 100644 index 00000000000..d84c46006c3 --- /dev/null +++ b/src/xr/xr-joint.js @@ -0,0 +1,156 @@ +import { Mat4 } from '../math/mat4.js'; +import { Quat } from '../math/quat.js'; +import { Vec3 } from '../math/vec3.js'; + +var tipJointIds = []; +var tipJointIdsIndex = {}; + +if (window.XRHand) { + tipJointIds = [XRHand.THUMB_PHALANX_TIP, XRHand.INDEX_PHALANX_TIP, XRHand.MIDDLE_PHALANX_TIP, XRHand.RING_PHALANX_TIP, XRHand.LITTLE_PHALANX_TIP]; +} + +for (var i = 0; i < tipJointIds.length; i++) { + tipJointIdsIndex[tipJointIds[i]] = true; +} + +/** + * @class + * @name pc.XrJoint + * @classdesc Represents joint of a finger + * @description Represents joint of a finger + * @param {number} index - Index of a joint within a finger + * @param {number} id - Id of a joint based on XRHand specs + * @param {pc.XrHand} hand - Hand that joint relates to + * @param {pc.XrFinger} [finger] - Finger that joint is related to, can be null in case of wrist joint + * @property {number} index Index of a joint within a finger, starting from 0 (root of a finger) all the way to tip of the finger + * @property {pc.XrHand} hand Hand that joint relates to + * @property {pc.XrFinger|null} finger Finger that joint relates to + * @property {boolean} wrist True if joint is a wrist + * @property {boolean} tip True if joint is a tip of a finger + * @property {number} radius The radius of a joint, which is a distance from joint to the edge of a skin + */ +function XrJoint(index, id, hand, finger) { + this._index = index; + this._id = id; + + this._hand = hand; + this._hand._joints.push(this); + this._hand._jointsById[id] = this; + + this._finger = finger || null; + if (this._finger) this._finger._joints.push(this); + + this._wrist = id == XRHand.WRIST; + if (this._wrist) this._hand._wrist = this; + + this._tip = this._finger && !! tipJointIdsIndex[id]; + if (this._tip) { + this._hand._tips.push(this); + if (this._finger) this._finger._tip = this; + } + + this._radius = null; + + this._localTransform = new Mat4(); + this._localTransform.setIdentity(); + + this._worldTransform = new Mat4(); + this._worldTransform.setIdentity(); + + this._localPosition = new Vec3(); + this._localRotation = new Quat(); + + this._position = new Vec3(); + this._rotation = new Quat(); + + this._dirtyLocal = true; +} + +XrJoint.prototype.update = function (pose) { + this._dirtyLocal = true; + this._radius = pose.radius; + this._localPosition.copy(pose.transform.position); + this._localRotation.copy(pose.transform.orientation); +}; + +XrJoint.prototype._updateTransforms = function () { + var dirty; + + if (this._dirtyLocal) { + dirty = true; + this._dirtyLocal = false; + this._localTransform.setTRS(this._localPosition, this._localRotation, Vec3.ONE); + } + + var manager = this._hand._manager; + var parent = manager.camera.parent; + + if (parent) { + dirty = dirty || parent._dirtyLocal || parent._dirtyWorld; + if (dirty) this._worldTransform.mul2(parent.getWorldTransform(), this._localTransform); + } else { + this._worldTransform.copy(this._localTransform); + } +}; + +/** + * @function + * @name pc.XrJoint#getPosition + * @description Get the world space position of a joint + * @returns {pc.Vec3} The world space position of a joint + */ +XrJoint.prototype.getPosition = function () { + this._updateTransforms(); + this._worldTransform.getTranslation(this._position); + return this._position; +}; + +/** + * @function + * @name pc.XrJoint#getRotation + * @description Get the world space rotation of a joint + * @returns {pc.Quat} The world space rotation of a joint + */ +XrJoint.prototype.getRotation = function () { + this._updateTransforms(); + this._rotation.setFromMat4(this._worldTransform); + return this._rotation; +}; + +Object.defineProperty(XrJoint.prototype, 'index', { + get: function () { + return this._index; + } +}); + +Object.defineProperty(XrJoint.prototype, 'hand', { + get: function () { + return this._hand; + } +}); + +Object.defineProperty(XrJoint.prototype, 'finger', { + get: function () { + return this._finger; + } +}); + +Object.defineProperty(XrJoint.prototype, 'wrist', { + get: function () { + return this._wrist; + } +}); + +Object.defineProperty(XrJoint.prototype, 'tip', { + get: function () { + return this._tip; + } +}); + +Object.defineProperty(XrJoint.prototype, 'radius', { + get: function () { + return this._radius || 0.005; + } +}); + +export { XrJoint }; From 73fdd92cecf942cce26bc7f2730ef54e01cce231 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Thu, 6 Aug 2020 13:59:07 +0300 Subject: [PATCH 7/7] corrections --- src/xr/xr-joint.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/xr/xr-joint.js b/src/xr/xr-joint.js index d84c46006c3..a8989368745 100644 --- a/src/xr/xr-joint.js +++ b/src/xr/xr-joint.js @@ -2,12 +2,15 @@ import { Mat4 } from '../math/mat4.js'; import { Quat } from '../math/quat.js'; import { Vec3 } from '../math/vec3.js'; -var tipJointIds = []; -var tipJointIdsIndex = {}; +var tipJointIds = window.XRHand ? [ + XRHand.THUMB_PHALANX_TIP, + XRHand.INDEX_PHALANX_TIP, + XRHand.MIDDLE_PHALANX_TIP, + XRHand.RING_PHALANX_TIP, + XRHand.LITTLE_PHALANX_TIP +] : []; -if (window.XRHand) { - tipJointIds = [XRHand.THUMB_PHALANX_TIP, XRHand.INDEX_PHALANX_TIP, XRHand.MIDDLE_PHALANX_TIP, XRHand.RING_PHALANX_TIP, XRHand.LITTLE_PHALANX_TIP]; -} +var tipJointIdsIndex = {}; for (var i = 0; i < tipJointIds.length; i++) { tipJointIdsIndex[tipJointIds[i]] = true; @@ -40,7 +43,7 @@ function XrJoint(index, id, hand, finger) { this._finger = finger || null; if (this._finger) this._finger._joints.push(this); - this._wrist = id == XRHand.WRIST; + this._wrist = id === XRHand.WRIST; if (this._wrist) this._hand._wrist = this; this._tip = this._finger && !! tipJointIdsIndex[id]; @@ -52,10 +55,7 @@ function XrJoint(index, id, hand, finger) { this._radius = null; this._localTransform = new Mat4(); - this._localTransform.setIdentity(); - this._worldTransform = new Mat4(); - this._worldTransform.setIdentity(); this._localPosition = new Vec3(); this._localRotation = new Quat();