Skip to content

Commit 0e9d049

Browse files
Update XR input source to better work with world/local space (playcanvas#1991)
* improve xr input source world/local space * remove euler angles functions Co-authored-by: Will Eastcott <will@playcanvas.com>
1 parent b396e97 commit 0e9d049

File tree

8 files changed

+215
-66
lines changed

8 files changed

+215
-66
lines changed

examples/xr/vr-controllers.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@
163163
if (inputSource.grip) {
164164
// some controllers can be gripped
165165
controllers[i].enabled = true;
166-
controllers[i].setPosition(inputSource.position);
167-
controllers[i].setRotation(inputSource.rotation);
166+
controllers[i].setLocalPosition(inputSource.getLocalPosition());
167+
controllers[i].setLocalRotation(inputSource.getLocalRotation());
168168
} else {
169169
// some controllers cannot be gripped
170170
controllers[i].enabled = false;

examples/xr/vr-movement.html

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -239,34 +239,22 @@
239239
}
240240

241241
// after movement and rotation is done
242-
// we can get camera parent position and rotation
243-
// to offset controller ray and model
244-
var parentPosition = cameraParent.getPosition();
245-
var parentRotation = cameraParent.getRotation();
246-
247-
// then we update/render controllers
242+
// we update/render controllers
248243
for(var i = 0; i < controllers.length; i++) {
249244
var inputSource = controllers[i].inputSource;
250245

251-
// transform ray into parent space
252-
// position
253-
tmpVec3A.copy(inputSource.ray.origin);
254-
parentRotation.transformVector(tmpVec3A, tmpVec3A);
255-
tmpVec3A.add(parentPosition);
256-
// rotation
257-
tmpVec3B.copy(inputSource.ray.direction);
258-
parentRotation.transformVector(tmpVec3B, tmpVec3B);
259-
260246
// render controller ray
247+
tmpVec3A.copy(inputSource.getOrigin());
248+
tmpVec3B.copy(inputSource.getDirection());
261249
tmpVec3B.scale(100).add(tmpVec3A);
262250
app.renderLine(tmpVec3A, tmpVec3B, lineColor);
263251

264252
// render controller
265253
if (inputSource.grip) {
266254
// some controllers can be gripped
267255
controllers[i].model.enabled = true;
268-
controllers[i].setLocalPosition(inputSource.position);
269-
controllers[i].setLocalRotation(inputSource.rotation);
256+
controllers[i].setLocalPosition(inputSource.getLocalPosition);
257+
controllers[i].setLocalRotation(inputSource.getLocalRotation);
270258
} else {
271259
// some controllers cannot be gripped
272260
controllers[i].model.enabled = false;

examples/xr/xr-picking.html

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@
135135

136136
// when input source is triggers select
137137
// pick closest box and change its color
138+
var ray = new pc.Ray();
138139
app.xr.input.on('select', function(inputSource) {
139140
var candidate = null;
140141
var candidateDist = Infinity;
@@ -143,7 +144,8 @@
143144
var mesh = cubes[i].model.meshInstances[0];
144145

145146
// check if mesh bounding box intersects with input source ray
146-
if (mesh.aabb.intersectsRay(inputSource.ray)) {
147+
ray.set(inputSource.getOrigin(), inputSource.getDirection());
148+
if (mesh.aabb.intersectsRay(ray)) {
147149
// check distance to camera
148150
var dist = mesh.aabb.center.distance(c.getPosition());
149151

@@ -174,10 +176,10 @@
174176
for(var i = 0; i < app.xr.input.inputSources.length; i++) {
175177
var inputSource = app.xr.input.inputSources[i];
176178

177-
tmpVec.copy(inputSource.ray.direction);
178-
tmpVec.scale(100).add(inputSource.ray.origin);
179+
tmpVec.copy(inputSource.getDirection());
180+
tmpVec.scale(100).add(inputSource.getOrigin());
179181

180-
app.renderLine(inputSource.ray.origin, tmpVec, lineColor);
182+
app.renderLine(inputSource.getOrigin(), tmpVec, lineColor);
181183
}
182184
});
183185
} else {

src/backwards-compatibility.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,3 +526,30 @@ Object.defineProperty(pc.MouseEvent.prototype, 'wheel', {
526526
return this.wheelDelta * -2;
527527
}
528528
});
529+
530+
Object.defineProperty(pc.XrInputSource.prototype, 'ray', {
531+
get: function () {
532+
// #ifdef DEBUG
533+
console.warn('DEPRECATED: pc.XrInputSource#ray is deprecated. Use pc.XrInputSource#getOrigin and pc.XrInputSource#getDirection instead.');
534+
// #endif
535+
return this._rayLocal;
536+
}
537+
});
538+
539+
Object.defineProperty(pc.XrInputSource.prototype, 'position', {
540+
get: function () {
541+
// #ifdef DEBUG
542+
console.warn('DEPRECATED: pc.XrInputSource#position is deprecated. Use pc.XrInputSource#getLocalPosition instead.');
543+
// #endif
544+
return this._localPosition;
545+
}
546+
});
547+
548+
Object.defineProperty(pc.XrInputSource.prototype, 'rotation', {
549+
get: function () {
550+
// #ifdef DEBUG
551+
console.warn('DEPRECATED: pc.XrInputSource#rotation is deprecated. Use pc.XrInputSource#getLocalRotation instead.');
552+
// #endif
553+
return this._localRotation;
554+
}
555+
});

src/shape/ray.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@ Object.assign(pc, function () {
2020
this.direction = direction || new pc.Vec3(0, 0, -1);
2121
};
2222

23+
/**
24+
* @function
25+
* @name pc.Ray#set
26+
* @description Sets origin and direction to the supplied vector values.
27+
* @param {pc.Vec3} origin - The starting point of the ray.
28+
* @param {pc.Vec3} direction - The direction of the ray.
29+
* @returns {pc.Ray} Self for chaining.
30+
*/
31+
Ray.prototype.set = function (origin, direction) {
32+
this.origin.copy(origin);
33+
this.direction.copy(direction);
34+
return this;
35+
};
36+
2337
return {
2438
Ray: Ray
2539
};

src/xr/xr-input-source.js

Lines changed: 152 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,7 @@ Object.assign(pc, function () {
7676
* * {@link pc.XRHAND_RIGHT}: Right - indicates that input source is meant to be held in right hand.
7777
*
7878
* @property {string[]} profiles List of input profile names indicating both the prefered visual representation and behavior of the input source.
79-
* @property {pc.Ray} ray Ray that is calculated based on {@link pc.XrInputSource#targetRayMode} that can be used for interacting with virtual objects. Its origin and direction are in local space of XR session.
8079
* @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.
81-
* @property {pc.Vec3|null} position If {@link pc.XrInputSource#grip} is true, then position will represent position of handheld input source in local space of XR session.
82-
* @property {pc.Quat|null} rotation If {@link pc.XrInputSource#grip} is true, then rotation will represent rotation of handheld input source in local space of XR session.
8380
* @property {Gamepad|null} gamepad If input source has buttons, triggers, thumbstick or touchpad, then this object provides access to its states.
8481
* @property {boolean} selecting True if input source is in active primary action between selectstart and selectend events.
8582
* @property {pc.XrHitTestSource[]} hitTestSources list of active {@link pc.XrHitTestSource} created by this input source.
@@ -91,9 +88,17 @@ Object.assign(pc, function () {
9188
this._xrInputSource = xrInputSource;
9289

9390
this._ray = new pc.Ray();
91+
this._rayLocal = new pc.Ray();
9492
this._grip = false;
95-
this._position = null;
96-
this._rotation = null;
93+
94+
this._localTransform = null;
95+
this._worldTransform = null;
96+
this._position = new pc.Vec3();
97+
this._rotation = new pc.Quat();
98+
this._localPosition = null;
99+
this._localRotation = null;
100+
this._dirtyLocal = true;
101+
97102
this._selecting = false;
98103

99104
this._hitTestSources = [];
@@ -117,8 +122,10 @@ Object.assign(pc, function () {
117122
* @description Fired when input source has triggered primary action. This could be pressing a trigger button, or touching a screen.
118123
* @param {object} evt - XRInputSourceEvent event data from WebXR API
119124
* @example
120-
* app.xr.input.on('select', function (evt) {
121-
* if (obj.intersectsRay(inputSource.ray)) {
125+
* var ray = new pc.Ray();
126+
* inputSource.on('select', function (evt) {
127+
* ray.set(inputSource.getOrigin(), inputSource.getDirection());
128+
* if (obj.intersectsRay(ray)) {
122129
* // selected an object with input source
123130
* }
124131
* });
@@ -178,26 +185,153 @@ Object.assign(pc, function () {
178185
var targetRayPose = frame.getPose(this._xrInputSource.targetRaySpace, this._manager._referenceSpace);
179186
if (! targetRayPose) return;
180187

181-
// ray
182-
this._ray.origin.copy(targetRayPose.transform.position);
183-
184-
this._ray.direction.set(0, 0, -1);
185-
quat.copy(targetRayPose.transform.orientation);
186-
quat.transformVector(this._ray.direction, this._ray.direction);
187-
188188
// grip
189189
if (this._xrInputSource.gripSpace) {
190190
var gripPose = frame.getPose(this._xrInputSource.gripSpace, this._manager._referenceSpace);
191191
if (gripPose) {
192192
if (! this._grip) {
193193
this._grip = true;
194-
this._position = new pc.Vec3();
195-
this._rotation = new pc.Quat();
194+
195+
this._localTransform = new pc.Mat4();
196+
this._worldTransform = new pc.Mat4();
197+
198+
this._localPosition = new pc.Vec3();
199+
this._localRotation = new pc.Quat();
196200
}
197-
this._position.copy(gripPose.transform.position);
198-
this._rotation.copy(gripPose.transform.orientation);
201+
this._dirtyLocal = true;
202+
this._localPosition.copy(gripPose.transform.position);
203+
this._localRotation.copy(gripPose.transform.orientation);
204+
}
205+
}
206+
207+
// ray
208+
this._dirtyRay = true;
209+
this._rayLocal.origin.copy(targetRayPose.transform.position);
210+
this._rayLocal.direction.set(0, 0, -1);
211+
quat.copy(targetRayPose.transform.orientation);
212+
quat.transformVector(this._rayLocal.direction, this._rayLocal.direction);
213+
};
214+
215+
XrInputSource.prototype._updateTransforms = function () {
216+
var dirty;
217+
218+
if (this._dirtyLocal) {
219+
dirty = true;
220+
this._dirtyLocal = false;
221+
this._localTransform.setTRS(this._localPosition, this._localRotation, pc.Vec3.ONE);
222+
}
223+
224+
var parent = this._manager.camera.parent;
225+
if (parent) {
226+
dirty = dirty || parent._dirtyLocal || parent._dirtyWorld;
227+
228+
if (dirty) {
229+
var parentTransform = this._manager.camera.parent.getWorldTransform();
230+
this._worldTransform.mul2(parentTransform, this._localTransform);
199231
}
232+
} else {
233+
this._worldTransform.copy(this._localTransform);
200234
}
235+
236+
return dirty;
237+
};
238+
239+
XrInputSource.prototype._updateRayTransforms = function () {
240+
var dirty = this._dirtyRay;
241+
this._dirtyRay = false;
242+
243+
var parent = this._manager.camera.parent;
244+
if (parent) {
245+
dirty = dirty || parent._dirtyLocal || parent._dirtyWorld;
246+
247+
if (dirty) {
248+
var parentTransform = this._manager.camera.parent.getWorldTransform();
249+
250+
parentTransform.getTranslation(this._position);
251+
this._rotation.setFromMat4(parentTransform);
252+
253+
this._rotation.transformVector(this._rayLocal.origin, this._ray.origin);
254+
this._ray.origin.add(this._position);
255+
this._rotation.transformVector(this._rayLocal.direction, this._ray.direction);
256+
}
257+
} else if (dirty) {
258+
this._ray.origin.copy(this._rayLocal.origin);
259+
this._ray.direction.copy(this._rayLocal.direction);
260+
}
261+
262+
return dirty;
263+
};
264+
265+
/**
266+
* @function
267+
* @name pc.XrInputSource#getPosition
268+
* @description Get the world space position of input source if it is handheld ({@link pc.XrInputSource#grip} is true). Otherwise it will return null.
269+
* @returns {pc.Vec3|null} The world space position of handheld input source.
270+
*/
271+
XrInputSource.prototype.getPosition = function () {
272+
if (! this._position) return null;
273+
274+
this._updateTransforms();
275+
this._worldTransform.getTranslation(this._position);
276+
277+
return this._position;
278+
};
279+
280+
/**
281+
* @function
282+
* @name pc.XrInputSource#getLocalPosition
283+
* @description Get the local space position of input source if it is handheld ({@link pc.XrInputSource#grip} is true). Local space is relative to parent of the XR camera. Otherwise it will return null.
284+
* @returns {pc.Vec3|null} The world space position of handheld input source.
285+
*/
286+
XrInputSource.prototype.getLocalPosition = function () {
287+
return this._localPosition;
288+
};
289+
290+
/**
291+
* @function
292+
* @name pc.XrInputSource#getRotation
293+
* @description Get the world space rotation of input source if it is handheld ({@link pc.XrInputSource#grip} is true). Otherwise it will return null.
294+
* @returns {pc.Vec3|null} The world space rotation of handheld input source.
295+
*/
296+
XrInputSource.prototype.getRotation = function () {
297+
if (! this._rotation) return null;
298+
299+
this._updateTransforms();
300+
this._rotation.setFromMat4(this._worldTransform);
301+
302+
return this._rotation;
303+
};
304+
305+
/**
306+
* @function
307+
* @name pc.XrInputSource#getLocalRotation
308+
* @description Get the local space rotation of input source if it is handheld ({@link pc.XrInputSource#grip} is true). Local space is relative to parent of the XR camera. Otherwise it will return null.
309+
* @returns {pc.Vec3|null} The world space rotation of handheld input source.
310+
*/
311+
XrInputSource.prototype.getLocalRotation = function () {
312+
return this._localRotation;
313+
};
314+
315+
/**
316+
* @function
317+
* @name pc.XrInputSource#getOrigin
318+
* @description Get the world space origin of input source ray.
319+
* @returns {pc.Vec3} The world space origin of input source ray.
320+
*/
321+
XrInputSource.prototype.getOrigin = function () {
322+
this._updateRayTransforms();
323+
return this._ray.origin;
324+
};
325+
326+
/**
327+
* @function
328+
* @name pc.XrInputSource#getDirection
329+
* @description Get the world space direction of input source ray.
330+
* @returns {pc.Vec3} The world space direction of input source ray.
331+
*/
332+
XrInputSource.prototype.getDirection = function () {
333+
this._updateRayTransforms();
334+
return this._ray.direction;
201335
};
202336

203337
/**
@@ -293,30 +427,12 @@ Object.assign(pc, function () {
293427
}
294428
});
295429

296-
Object.defineProperty(XrInputSource.prototype, 'ray', {
297-
get: function () {
298-
return this._ray;
299-
}
300-
});
301-
302430
Object.defineProperty(XrInputSource.prototype, 'grip', {
303431
get: function () {
304432
return this._grip;
305433
}
306434
});
307435

308-
Object.defineProperty(XrInputSource.prototype, 'position', {
309-
get: function () {
310-
return this._position;
311-
}
312-
});
313-
314-
Object.defineProperty(XrInputSource.prototype, 'rotation', {
315-
get: function () {
316-
return this._rotation;
317-
}
318-
});
319-
320436
Object.defineProperty(XrInputSource.prototype, 'gamepad', {
321437
get: function () {
322438
return this._xrInputSource.gamepad || null;

src/xr/xr-input.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ Object.assign(pc, function () {
5656
* @param {pc.XrInputSource} inputSource - Input source that triggered select event
5757
* @param {object} evt - XRInputSourceEvent event data from WebXR API
5858
* @example
59+
* var ray = new pc.Ray();
5960
* app.xr.input.on('select', function (inputSource, evt) {
60-
* if (obj.intersectsRay(inputSource.ray)) {
61+
* ray.set(inputSource.getOrigin(), inputSource.getDirection());
62+
* if (obj.intersectsRay(ray)) {
6163
* // selected an object with input source
6264
* }
6365
* });

0 commit comments

Comments
 (0)