Skip to content

Commit 3eea7a8

Browse files
zjm-metameta-codesync[bot]
authored andcommitted
feat(core) Support target position and quaternion offset in DistanceGrabbable.
Summary: Added the support of target position and quaternion offset configuration in DistanceGrabbable. Support the target pos and quat calculation in Distance Handle and use lerp and slerp for the movement control. Rev2: Add snap support when the object arrives at the input source and have it rest when the pointer is up. Reviewed By: felixtrz Differential Revision: D89073193 fbshipit-source-id: ea3867aee125d43a093ed250dd65ef538c554ad0
1 parent 7317fdb commit 3eea7a8

File tree

5 files changed

+108
-27
lines changed

5 files changed

+108
-27
lines changed

examples/grab/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
name="viewport"
1414
content="width=device-width, initial-scale=1, shrink-to-fit=no"
1515
/>
16-
<title>IWSDK Boilerplate</title>
16+
<title>IWSDK Grab Example</title>
1717
</head>
1818
<body style="margin: 0">
1919
<div id="scene-container"></div>

examples/grab/metaspatial/Composition/Main.scene

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,15 @@ entities:
8181
tag: earth
8282
- components:
8383
com.iwsdk.components.DistanceGrabbable:
84-
{}
84+
targetPositionOffset:
85+
- 0.100000001
86+
- 0
87+
- 0
88+
targetQuaternionOffset:
89+
- 0
90+
- 120
91+
- 0
92+
- 1
8593
com.iwsdk.components.Interactable:
8694
{}
8795
com.meta.components.Animatable:
@@ -264,7 +272,16 @@ entities:
264272
tag: opera_house
265273
- components:
266274
com.iwsdk.components.DistanceGrabbable:
267-
movementMode: 0
275+
targetPositionOffset:
276+
- 0
277+
- 0.300000012
278+
- 0
279+
moveSpeedFactor: 0.5
280+
targetQuaternionOffset:
281+
- 12
282+
- 0
283+
- 0
284+
- 1
268285
com.iwsdk.components.Interactable:
269286
{}
270287
com.meta.components.Animatable:

packages/core/src/grab/distance-grabbable.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { MovementMode } from './handles.js';
1919
* - **RotateAtSource**: Object rotates in place without translation or scaling.
2020
* - Optional `returnToOrigin` makes objects snap back to their original position when released.
2121
* - Supports rotation, translation, and scaling with per‑axis constraints.
22+
* - Configure target position and rotation offsets relative to the input source for MoveTowardsTarget mode.
2223
* - Works with the {@link GrabSystem} to create specialized distance grab handles.
2324
* - Perfect for telekinetic‑style interactions and remote object manipulation.
2425
*
@@ -47,6 +48,15 @@ import { MovementMode } from './handles.js';
4748
* })
4849
* ```
4950
*
51+
* @example Distance grabbable with offset (object stays 0.3m in front of controller)
52+
* ```ts
53+
* entity.addComponent(DistanceGrabbable, {
54+
* movementMode: MovementMode.MoveTowardsTarget,
55+
* targetPositionOffset: [0, 0, -0.3], // 0.3m forward in controller space
56+
* targetQuaternionOffset: [0, 0, 0, 1], // no rotation offset
57+
* })
58+
* ```
59+
*
5060
* @category Grab
5161
* @see {@link GrabSystem}
5262
* @see {@link OneHandGrabbable}
@@ -100,8 +110,18 @@ export const DistanceGrabbable = createComponent(
100110
},
101111
/** A boolean value to set whether the object snap back to its original position and rotation. */
102112
returnToOrigin: { type: Types.Boolean, default: false },
103-
/** Object movement speed for the MoveTowardsTarget movement mode. */
104-
moveSpeed: { type: Types.Float32, default: 0.1 },
113+
/** Object movement and rotation speed factor for the MoveTowardsTarget movement mode with a scale from 0 to 1. */
114+
moveSpeedFactor: { type: Types.Float32, default: 0.1 },
115+
/** Target position offset relative to the input source for MoveTowardsTarget mode in [x, y, z]. */
116+
targetPositionOffset: {
117+
type: Types.Vec3,
118+
default: [0, 0, 0],
119+
},
120+
/** Target rotation offset as a quaternion relative to the input source for MoveTowardsTarget mode in [x, y, z, w]. */
121+
targetQuaternionOffset: {
122+
type: Types.Vec4,
123+
default: [0, 0, 0, 1],
124+
},
105125
},
106126
'Component for distance-based object grabbing and manipulation',
107127
);

packages/core/src/grab/grab-system.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
import { HandleStore } from '@pmndrs/handle';
99
import { PointerEventsMap } from '@pmndrs/pointer-events';
1010
import { createSystem, Entity, Types } from '../ecs/index.js';
11-
import { Object3D, Object3DEventMap } from '../runtime/index.js';
11+
import {
12+
Object3D,
13+
Object3DEventMap,
14+
Vector3,
15+
Quaternion,
16+
} from '../runtime/index.js';
1217
import { DistanceGrabbable } from './distance-grabbable.js';
1318
import { DistanceGrabHandle, MovementMode, Handle } from './handles.js';
1419
import { OneHandGrabbable } from './one-hand-grabbable.js';
@@ -252,6 +257,14 @@ export class GrabSystem extends createSystem(
252257
const returnToOrigin = Boolean(
253258
entity.getValue(DistanceGrabbable, 'returnToOrigin'),
254259
);
260+
const targetPosOffset = entity.getVectorView(
261+
DistanceGrabbable,
262+
'targetPositionOffset',
263+
);
264+
const targetQuatOffset = entity.getVectorView(
265+
DistanceGrabbable,
266+
'targetQuaternionOffset',
267+
);
255268
const opts = () => ({
256269
rotate: entity.getValue(DistanceGrabbable, 'rotate')
257270
? {
@@ -287,7 +300,14 @@ export class GrabSystem extends createSystem(
287300
opts,
288301
movementMode!,
289302
returnToOrigin,
290-
entity.getValue(DistanceGrabbable, 'moveSpeed') ?? 0.1,
303+
entity.getValue(DistanceGrabbable, 'moveSpeedFactor') ?? 0.1,
304+
new Vector3(targetPosOffset[0], targetPosOffset[1], targetPosOffset[2]),
305+
new Quaternion(
306+
targetQuatOffset[0],
307+
targetQuatOffset[1],
308+
targetQuatOffset[2],
309+
targetQuatOffset[3],
310+
),
291311
);
292312
handle.bind(obj);
293313
obj.pointerEventsType = { deny: 'grab' };

packages/core/src/grab/handles.ts

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,32 @@ export const MovementMode = {
3131

3232
export class DistanceGrabHandle<T> extends HandleStore<T> {
3333
private previousPointerOrigin: Vector3 | undefined;
34+
private isSnapped: boolean = false;
35+
private static SNAP_THRESHOLD = 0.005;
36+
private static MOVE_SPEED_SCALE = 100;
3437
private static _tmp = new Vector3();
38+
private static _tmpQuat = new Quaternion();
3539
private static _posHelper = new Vector3();
3640
private static _quatHelper = new Quaternion();
41+
private static _offsetPosHelper = new Vector3();
42+
private static _offsetQuatHelper = new Quaternion();
3743

3844
constructor(
3945
readonly target_: Object3D | { current?: Object3D | null },
4046
public readonly getOptions: () => HandleOptions<T> = () => ({}),
4147
public readonly movementMode: string,
4248
public readonly returnToOrigin: Boolean,
43-
public readonly moveSpeed: number = 0.1,
49+
public readonly moveSpeedFactor: number = 0.1,
50+
public readonly targetPosOffset: Vector3 = new Vector3(0, 0, 0),
51+
public readonly targetQuatOffset: Quaternion = new Quaternion(0, 0, 0, 1),
4452
) {
4553
super(target_, getOptions);
54+
55+
this.targetQuatOffset.normalize();
56+
57+
(target_ as Object3D).addEventListener('pointerup', () => {
58+
this.isSnapped = false;
59+
});
4660
}
4761

4862
update(time: number) {
@@ -95,30 +109,40 @@ export class DistanceGrabHandle<T> extends HandleStore<T> {
95109
case MovementMode.MoveTowardsTarget: {
96110
const [p1] = this.inputState.values();
97111
const pointerOrigin = p1.pointerWorldOrigin;
98-
const distance = pointerOrigin.distanceTo(
112+
const pointerQuaternion = p1.pointerWorldQuaternion;
113+
114+
// Calculate target position by applying position offset in pointer's local space
115+
const targetPosition = DistanceGrabHandle._offsetPosHelper
116+
.copy(this.targetPosOffset)
117+
.applyQuaternion(pointerQuaternion)
118+
.add(pointerOrigin);
119+
120+
// Calculate target rotation by multiplying pointer rotation with rotation offset
121+
const targetQuaternion = DistanceGrabHandle._offsetQuatHelper
122+
.copy(pointerQuaternion)
123+
.multiply(this.targetQuatOffset);
124+
125+
const distance = targetPosition.distanceTo(
99126
DistanceGrabHandle._posHelper,
100127
);
101128

102-
if (distance > this.moveSpeed) {
103-
const step = DistanceGrabHandle._tmp
104-
.copy(pointerOrigin)
105-
.sub(DistanceGrabHandle._posHelper)
106-
.normalize()
107-
.multiplyScalar(this.moveSpeed);
108-
DistanceGrabHandle._posHelper.add(step);
109-
} else {
110-
DistanceGrabHandle._posHelper.set(
111-
pointerOrigin.x,
112-
pointerOrigin.y,
113-
pointerOrigin.z,
129+
if (!this.isSnapped && distance > DistanceGrabHandle.SNAP_THRESHOLD) {
130+
DistanceGrabHandle._posHelper.lerp(
131+
targetPosition,
132+
this.moveSpeedFactor * time * DistanceGrabHandle.MOVE_SPEED_SCALE,
114133
);
115-
DistanceGrabHandle._quatHelper.set(
116-
p1.pointerWorldQuaternion.x,
117-
p1.pointerWorldQuaternion.y,
118-
p1.pointerWorldQuaternion.z,
119-
p1.pointerWorldQuaternion.w,
134+
DistanceGrabHandle._quatHelper.slerp(
135+
targetQuaternion,
136+
this.moveSpeedFactor * time * DistanceGrabHandle.MOVE_SPEED_SCALE,
120137
);
138+
} else {
139+
if (!this.isSnapped) {
140+
this.isSnapped = true;
141+
}
142+
DistanceGrabHandle._posHelper.copy(targetPosition);
143+
DistanceGrabHandle._quatHelper.copy(targetQuaternion);
121144
}
145+
122146
break;
123147
}
124148
}
@@ -137,7 +161,7 @@ export class DistanceGrabHandle<T> extends HandleStore<T> {
137161

138162
// Transform desired world quaternion to local space
139163
const parentWorldQuaternionInverse = target.parent
140-
.getWorldQuaternion(new Quaternion())
164+
.getWorldQuaternion(DistanceGrabHandle._tmpQuat)
141165
.invert();
142166
quaternion
143167
.copy(DistanceGrabHandle._quatHelper)

0 commit comments

Comments
 (0)