-
Notifications
You must be signed in to change notification settings - Fork 69
Expand file tree
/
Copy pathMatchTransform.ts
More file actions
225 lines (184 loc) · 7.33 KB
/
MatchTransform.ts
File metadata and controls
225 lines (184 loc) · 7.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/**
* Matches the position and rotation of a target with smooth lerping.
* Maintains offset from the initial grab point.
* Works with position/rotation vectors (for hand tracking) instead of SceneObjects.
*/
@component
export class MatchTransform extends BaseScriptComponent {
@input
@hint("Speed for position lerping (higher = faster response)")
positionLerpSpeed: number = 15.0
@input
@hint("Speed for rotation lerping (higher = faster response)")
rotationLerpSpeed: number = 15.0
@input
@hint("Distance in front of finger tip to hold object (in cm)")
holdDistance: number = 5.0
// Store the offset from target when matching starts
private positionOffset: vec3 = vec3.zero()
private startingPositionOffset: vec3 = vec3.zero() // Where object actually is relative to finger
private desiredPositionOffset: vec3 = vec3.zero() // Where we want it (holdDistance in front)
private rotationOffset: quat = quat.quatIdentity()
private hasInitializedOffset: boolean = false
private isMatching: boolean = false
private updateRotation: boolean = true // Can be disabled for objects that override rotation
// Offset transition (smoothly move object from current position to hold position)
private offsetTransitionProgress: number = 0
private offsetTransitionSpeed: number = 2.0 // Takes 0.5 seconds to transition
// Current target position and rotation (updated externally)
private targetPosition: vec3 = vec3.zero()
private targetRotation: quat = quat.quatIdentity()
onAwake() {
this.createEvent("UpdateEvent").bind(this.onUpdate.bind(this))
}
/**
* Set the target position and rotation (called from GrabbableObject)
*/
setTarget(position: vec3, rotation: quat) {
this.targetPosition = position
this.targetRotation = rotation
// Initialize offset on first target set
if (!this.hasInitializedOffset) {
this.initializeOffset()
}
}
/**
* Set the target position (called from GrabbableObject)
* @deprecated Use setTarget instead
*/
setTargetPosition(position: vec3) {
this.targetPosition = position
// Initialize offset on first position set
if (!this.hasInitializedOffset) {
this.initializeOffset()
}
}
/**
* Initialize the offset between this object and the target.
* Starts with current object position, then smoothly transitions to hold position.
*/
private initializeOffset() {
const myTransform = this.getSceneObject().getTransform()
const myWorldPos = myTransform.getWorldPosition()
const myWorldRot = myTransform.getWorldRotation()
// Calculate where the object currently is relative to the finger
this.startingPositionOffset = myWorldPos.sub(this.targetPosition)
// Calculate where we want it to be (holdDistance cm in front of finger)
const indexTipForward = this.targetRotation.multiplyVec3(vec3.forward())
this.desiredPositionOffset = indexTipForward.uniformScale(this.holdDistance)
// Start with the current offset (no jump)
this.positionOffset = this.startingPositionOffset
this.offsetTransitionProgress = 0
// Calculate rotation offset (inverse relationship)
this.rotationOffset = myWorldRot.multiply(this.targetRotation.invert())
this.hasInitializedOffset = true
print(`MatchTransform (${this.getSceneObject().name}): Starting offset: ${this.startingPositionOffset}`)
print(`MatchTransform (${this.getSceneObject().name}): Desired offset: ${this.desiredPositionOffset}`)
print(
`MatchTransform (${this.getSceneObject().name}): Object world pos: ${myWorldPos}, Finger pos: ${this.targetPosition}`
)
}
/**
* Reset the offset tracking
*/
resetOffset() {
this.hasInitializedOffset = false
this.positionOffset = vec3.zero()
this.startingPositionOffset = vec3.zero()
this.desiredPositionOffset = vec3.zero()
this.rotationOffset = quat.quatIdentity()
this.offsetTransitionProgress = 0
}
private updateCounter = 0
private debugCounter = 0
/**
* Enable matching behavior
*/
enableMatching() {
this.isMatching = true
print(`MatchTransform (${this.getSceneObject().name}): Matching ENABLED`)
}
/**
* Disable matching behavior
*/
disableMatching() {
this.isMatching = false
print(`MatchTransform (${this.getSceneObject().name}): Matching DISABLED`)
}
/**
* Disable rotation updates (for objects that override rotation every frame)
*/
disableRotationUpdates() {
this.updateRotation = false
}
/**
* Enable rotation updates
*/
enableRotationUpdates() {
this.updateRotation = true
}
onUpdate() {
// Check if component itself is disabled
if (!this.enabled) {
if (this.isMatching) {
print(`MatchTransform (${this.getSceneObject().name}): WARNING - isMatching=true but component is disabled!`)
}
return
}
if (!this.isMatching) {
return
}
if (!this.hasInitializedOffset) {
print(`MatchTransform (${this.getSceneObject().name}): WARNING - isMatching=true but no offset initialized!`)
return
}
this.debugCounter++
if (this.debugCounter % 30 === 0) {
print(`MatchTransform: Actively matching (${this.getSceneObject().name})`)
}
this.updateTransform()
}
private updateTransform() {
const myTransform = this.getSceneObject().getTransform()
// Smoothly transition the offset from starting position to desired hold position
if (this.offsetTransitionProgress < 1.0) {
this.offsetTransitionProgress += this.offsetTransitionSpeed * getDeltaTime()
this.offsetTransitionProgress = Math.min(this.offsetTransitionProgress, 1.0)
// Lerp the offset itself
this.positionOffset = this.lerpVector(
this.startingPositionOffset,
this.desiredPositionOffset,
this.offsetTransitionProgress
)
if (this.offsetTransitionProgress >= 1.0) {
print(`MatchTransform (${this.getSceneObject().name}): Offset transition complete - object at hold distance`)
}
}
// Calculate target position and rotation with current offset
const targetPosWithOffset = this.targetPosition.add(this.positionOffset)
const targetRotWithOffset = this.targetRotation.multiply(this.rotationOffset)
const currentPosition = myTransform.getWorldPosition()
const currentRotation = myTransform.getWorldRotation()
// Debug logging every 10 frames
this.updateCounter++
if (this.updateCounter % 10 === 0) {
const dist = currentPosition.distance(targetPosWithOffset)
print(
`MatchTransform (${this.getSceneObject().name}): dist=${dist.toFixed(2)}cm, transition=${(this.offsetTransitionProgress * 100).toFixed(0)}%`
)
}
// Lerp to target position
const newPosition = this.lerpVector(currentPosition, targetPosWithOffset, this.positionLerpSpeed * getDeltaTime())
myTransform.setWorldPosition(newPosition)
// Only update rotation if enabled (disabled for objects that override rotation)
if (this.updateRotation) {
// Slerp to target rotation
const newRotation = quat.slerp(currentRotation, targetRotWithOffset, this.rotationLerpSpeed * getDeltaTime())
myTransform.setWorldRotation(newRotation)
}
}
private lerpVector(a: vec3, b: vec3, t: number): vec3 {
const clampedT = Math.max(0, Math.min(1, t))
return new vec3(a.x + (b.x - a.x) * clampedT, a.y + (b.y - a.y) * clampedT, a.z + (b.z - a.z) * clampedT)
}
}