urdf-loader-babylonjs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,385 @@
1
+ import { Vector3, Ray, Matrix } from '@babylonjs/core';
2
+
3
+ // Find the nearest parent that is a joint
4
+ function isJoint(j) {
5
+
6
+ return j.isURDFJoint && j.jointType !== 'fixed';
7
+
8
+ }
9
+
10
+ function findNearestJoint(child) {
11
+
12
+ let curr = child;
13
+ while (curr) {
14
+
15
+ if (isJoint(curr)) {
16
+
17
+ return curr;
18
+
19
+ }
20
+
21
+ curr = curr.parent;
22
+
23
+ }
24
+
25
+ return curr;
26
+
27
+ }
28
+
29
+ // Project a point onto a plane defined by normal and d (ax + by + cz + d = 0)
30
+ function projectPointOnPlane(planeNormal, planeD, point) {
31
+
32
+ const dist = Vector3.Dot(planeNormal, point) + planeD;
33
+ return point.subtract(planeNormal.scale(dist));
34
+
35
+ }
36
+
37
+ const prevHitPoint = new Vector3();
38
+ const newHitPoint = new Vector3();
39
+ const pivotPoint = new Vector3();
40
+ const tempVector = new Vector3();
41
+ const tempVector2 = new Vector3();
42
+ const projectedStartPoint = new Vector3();
43
+ const projectedEndPoint = new Vector3();
44
+
45
+ // Simple plane representation: normal + d (ax + by + cz + d = 0)
46
+ let planeNormal = new Vector3();
47
+ let planeD = 0;
48
+
49
+ function setPlaneFromNormalAndPoint(normal, point) {
50
+
51
+ planeNormal = normal.clone();
52
+ planeD = -Vector3.Dot(normal, point);
53
+
54
+ }
55
+
56
+ export class URDFDragControls {
57
+
58
+ constructor(scene) {
59
+
60
+ this.enabled = true;
61
+ this.scene = scene;
62
+ this.ray = new Ray(Vector3.Zero(), Vector3.Forward());
63
+ this.initialGrabPoint = new Vector3();
64
+
65
+ this.hitDistance = -1;
66
+ this.hovered = null;
67
+ this.manipulating = null;
68
+
69
+ // Babylon scene for picking
70
+ this.babylonScene = null;
71
+
72
+ }
73
+
74
+ update() {
75
+
76
+ const {
77
+ ray,
78
+ hovered,
79
+ manipulating,
80
+ scene,
81
+ } = this;
82
+
83
+ if (manipulating) {
84
+
85
+ return;
86
+
87
+ }
88
+
89
+ let hoveredJoint = null;
90
+
91
+ // Use Babylon.js scene picking with the ray
92
+ if (this.babylonScene) {
93
+
94
+ const pickResult = this.babylonScene.pickWithRay(ray, (mesh) => {
95
+
96
+ // Check if this mesh is a descendant of the scene (URDF robot)
97
+ let parent = mesh;
98
+ while (parent) {
99
+
100
+ if (parent === scene) return true;
101
+ parent = parent.parent;
102
+
103
+ }
104
+ return false;
105
+
106
+ });
107
+
108
+ if (pickResult && pickResult.hit) {
109
+
110
+ this.hitDistance = pickResult.distance;
111
+ hoveredJoint = findNearestJoint(pickResult.pickedMesh);
112
+ this.initialGrabPoint.copyFrom(pickResult.pickedPoint);
113
+
114
+ }
115
+
116
+ }
117
+
118
+ if (hoveredJoint !== hovered) {
119
+
120
+ if (hovered) {
121
+
122
+ this.onUnhover(hovered);
123
+
124
+ }
125
+
126
+ this.hovered = hoveredJoint;
127
+
128
+ if (hoveredJoint) {
129
+
130
+ this.onHover(hoveredJoint);
131
+
132
+ }
133
+
134
+ }
135
+
136
+ }
137
+
138
+ updateJoint(joint, angle) {
139
+
140
+ joint.setJointValue(angle);
141
+
142
+ }
143
+
144
+ onDragStart(joint) {
145
+
146
+ }
147
+
148
+ onDragEnd(joint) {
149
+
150
+ }
151
+
152
+ onHover(joint) {
153
+
154
+ }
155
+
156
+ onUnhover(joint) {
157
+
158
+ }
159
+
160
+ getRevoluteDelta(joint, startPoint, endPoint) {
161
+
162
+ // set up the plane
163
+ // Transform joint axis to world space
164
+ const worldMatrix = joint.getWorldMatrix();
165
+ Vector3.TransformNormalToRef(joint.axis, worldMatrix, tempVector);
166
+ tempVector.normalize();
167
+
168
+ // Get pivot point (joint origin in world space)
169
+ Vector3.TransformCoordinatesToRef(Vector3.Zero(), worldMatrix, pivotPoint);
170
+
171
+ setPlaneFromNormalAndPoint(tempVector, pivotPoint);
172
+
173
+ // project the drag points onto the plane
174
+ const pStart = projectPointOnPlane(planeNormal, planeD, startPoint);
175
+ projectedStartPoint.copyFrom(pStart);
176
+ const pEnd = projectPointOnPlane(planeNormal, planeD, endPoint);
177
+ projectedEndPoint.copyFrom(pEnd);
178
+
179
+ // get the directions relative to the pivot
180
+ projectedStartPoint.subtractInPlace(pivotPoint);
181
+ projectedEndPoint.subtractInPlace(pivotPoint);
182
+
183
+ const cross = Vector3.Cross(projectedStartPoint, projectedEndPoint);
184
+
185
+ const direction = Math.sign(Vector3.Dot(cross, planeNormal));
186
+ const startNorm = projectedStartPoint.length();
187
+ const endNorm = projectedEndPoint.length();
188
+ if (startNorm === 0 || endNorm === 0) return 0;
189
+
190
+ const cosAngle = Vector3.Dot(projectedStartPoint.normalize(), projectedEndPoint.normalize());
191
+ return direction * Math.acos(Math.max(-1, Math.min(1, cosAngle)));
192
+
193
+ }
194
+
195
+ getPrismaticDelta(joint, startPoint, endPoint) {
196
+
197
+ tempVector.copyFrom(endPoint).subtractInPlace(startPoint);
198
+
199
+ // Transform joint axis to parent world space
200
+ const parentWorldMatrix = joint.parent ? joint.parent.getWorldMatrix() : Matrix.Identity();
201
+ const axisWorld = new Vector3();
202
+ Vector3.TransformNormalToRef(joint.axis, parentWorldMatrix, axisWorld);
203
+ axisWorld.normalize();
204
+
205
+ return Vector3.Dot(tempVector, axisWorld);
206
+
207
+ }
208
+
209
+ moveRay(toRay) {
210
+
211
+ const { ray, hitDistance, manipulating } = this;
212
+
213
+ if (manipulating) {
214
+
215
+ // ray.at(hitDistance) = ray.origin + ray.direction * hitDistance
216
+ ray.origin.addToRef(ray.direction.scale(hitDistance), prevHitPoint);
217
+ toRay.origin.addToRef(toRay.direction.scale(hitDistance), newHitPoint);
218
+
219
+ let delta = 0;
220
+ if (manipulating.jointType === 'revolute' || manipulating.jointType === 'continuous') {
221
+
222
+ delta = this.getRevoluteDelta(manipulating, prevHitPoint, newHitPoint);
223
+
224
+ } else if (manipulating.jointType === 'prismatic') {
225
+
226
+ delta = this.getPrismaticDelta(manipulating, prevHitPoint, newHitPoint);
227
+
228
+ }
229
+
230
+ if (delta) {
231
+
232
+ this.updateJoint(manipulating, manipulating.angle + delta);
233
+
234
+ }
235
+
236
+ }
237
+
238
+ this.ray.origin.copyFrom(toRay.origin);
239
+ this.ray.direction.copyFrom(toRay.direction);
240
+ this.update();
241
+
242
+ }
243
+
244
+ setGrabbed(grabbed) {
245
+
246
+ const { hovered, manipulating } = this;
247
+
248
+ if (grabbed) {
249
+
250
+ if (manipulating !== null || hovered === null) {
251
+
252
+ return;
253
+
254
+ }
255
+
256
+ this.manipulating = hovered;
257
+ this.onDragStart(hovered);
258
+
259
+ } else {
260
+
261
+ if (this.manipulating === null) {
262
+ return;
263
+ }
264
+
265
+ this.onDragEnd(this.manipulating);
266
+ this.manipulating = null;
267
+ this.update();
268
+
269
+ }
270
+
271
+ }
272
+
273
+ }
274
+
275
+ export class PointerURDFDragControls extends URDFDragControls {
276
+
277
+ constructor(scene, babylonScene, camera, domElement) {
278
+
279
+ super(scene);
280
+ this.babylonScene = babylonScene;
281
+ this.camera = camera;
282
+ this.domElement = domElement;
283
+
284
+ const self = this;
285
+
286
+ function updateRayFromMouse(e) {
287
+
288
+ const rect = domElement.getBoundingClientRect();
289
+ const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
290
+ const y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
291
+
292
+ // Convert to Babylon.js viewport coordinates (0 to width, 0 to height)
293
+ const viewportX = (x + 1) / 2 * rect.width;
294
+ const viewportY = (1 - y) / 2 * rect.height;
295
+
296
+ // Create picking ray from camera through the screen point
297
+ const pickRay = babylonScene.createPickingRay(
298
+ viewportX, viewportY,
299
+ Matrix.Identity(),
300
+ camera,
301
+ );
302
+
303
+ return pickRay;
304
+
305
+ }
306
+
307
+ this._mouseDown = e => {
308
+
309
+ const pickRay = updateRayFromMouse(e);
310
+ self.moveRay(pickRay);
311
+ self.setGrabbed(true);
312
+
313
+ };
314
+
315
+ this._mouseMove = e => {
316
+
317
+ const pickRay = updateRayFromMouse(e);
318
+ self.moveRay(pickRay);
319
+
320
+ };
321
+
322
+ this._mouseUp = e => {
323
+
324
+ const pickRay = updateRayFromMouse(e);
325
+ self.moveRay(pickRay);
326
+ self.setGrabbed(false);
327
+
328
+ };
329
+
330
+ domElement.addEventListener('mousedown', this._mouseDown);
331
+ domElement.addEventListener('mousemove', this._mouseMove);
332
+ domElement.addEventListener('mouseup', this._mouseUp);
333
+
334
+ }
335
+
336
+ getRevoluteDelta(joint, startPoint, endPoint) {
337
+
338
+ const { camera, initialGrabPoint } = this;
339
+
340
+ // set up the plane
341
+ const worldMatrix = joint.getWorldMatrix();
342
+ Vector3.TransformNormalToRef(joint.axis, worldMatrix, tempVector);
343
+ tempVector.normalize();
344
+
345
+ Vector3.TransformCoordinatesToRef(Vector3.Zero(), worldMatrix, pivotPoint);
346
+
347
+ setPlaneFromNormalAndPoint(tempVector, pivotPoint);
348
+
349
+ const cameraDir = camera.position.subtract(initialGrabPoint).normalize();
350
+
351
+ // if looking into the plane of rotation
352
+ if (Math.abs(Vector3.Dot(cameraDir, planeNormal)) > 0.3) {
353
+
354
+ return super.getRevoluteDelta(joint, startPoint, endPoint);
355
+
356
+ } else {
357
+
358
+ // get the up direction from camera world matrix
359
+ const cameraWorldMatrix = camera.getWorldMatrix();
360
+ Vector3.TransformNormalToRef(new Vector3(0, 1, 0), cameraWorldMatrix, tempVector);
361
+
362
+ // get points projected onto the plane of rotation
363
+ projectedStartPoint.copyFrom(projectPointOnPlane(planeNormal, planeD, startPoint));
364
+ projectedEndPoint.copyFrom(projectPointOnPlane(planeNormal, planeD, endPoint));
365
+
366
+ Vector3.TransformNormalToRef(new Vector3(0, 0, -1), cameraWorldMatrix, tempVector);
367
+ const cross = Vector3.Cross(tempVector, planeNormal);
368
+ tempVector2.copyFrom(endPoint).subtractInPlace(startPoint);
369
+
370
+ return Vector3.Dot(cross, tempVector2);
371
+
372
+ }
373
+
374
+ }
375
+
376
+ dispose() {
377
+
378
+ const { domElement } = this;
379
+ domElement.removeEventListener('mousedown', this._mouseDown);
380
+ domElement.removeEventListener('mousemove', this._mouseMove);
381
+ domElement.removeEventListener('mouseup', this._mouseUp);
382
+
383
+ }
384
+
385
+ }
@@ -0,0 +1,45 @@
1
+ import { Scene, TransformNode } from '@babylonjs/core';
2
+ import { URDFRobot } from './URDFClasses';
3
+
4
+ interface MeshLoadDoneFunc {
5
+ (mesh: TransformNode | null, err?: Error): void;
6
+ }
7
+
8
+ interface MeshLoadFunc {
9
+ (url: string, scene: Scene | null, onLoad: MeshLoadDoneFunc): void;
10
+ }
11
+
12
+ interface LoadTracker {
13
+ onLoad: (() => void) | null;
14
+ itemStart(): void;
15
+ itemEnd(): void;
16
+ itemError(url: string): void;
17
+ }
18
+
19
+ export default class URDFLoader {
20
+
21
+ scene: Scene | null;
22
+ manager: LoadTracker;
23
+ defaultMeshLoader: MeshLoadFunc;
24
+
25
+ // options
26
+ fetchOptions: RequestInit;
27
+ workingPath: string;
28
+ parseVisual: boolean;
29
+ parseCollision: boolean;
30
+ packages: string | { [key: string]: string } | ((targetPkg: string) => string);
31
+ loadMeshCb: MeshLoadFunc;
32
+
33
+ constructor(scene?: Scene | null);
34
+ loadAsync(urdf: string): Promise<URDFRobot>;
35
+ load(
36
+ url: string,
37
+ onLoad: (robot: URDFRobot) => void,
38
+ onProgress?: ((progress?: any) => void) | null,
39
+ onError?: ((err?: any) => void) | null
40
+ ): void;
41
+ parse(content: string | Element | Document): URDFRobot;
42
+
43
+ }
44
+
45
+ export * from './URDFClasses';