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.
- package/README.md +592 -0
- package/package.json +53 -0
- package/src/URDFClasses.d.ts +80 -0
- package/src/URDFClasses.js +566 -0
- package/src/URDFDragControls.js +385 -0
- package/src/URDFLoader.d.ts +45 -0
- package/src/URDFLoader.js +769 -0
- package/src/urdf-manipulator-element.js +158 -0
- package/src/urdf-viewer-element.js +607 -0
- package/umd/URDFLoader.js +1337 -0
- package/umd/URDFLoader.js.map +1 -0
- package/umd/urdf-manipulator-element.js +548 -0
- package/umd/urdf-manipulator-element.js.map +1 -0
- package/umd/urdf-viewer-element.js +614 -0
- package/umd/urdf-viewer-element.js.map +1 -0
|
@@ -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';
|