urdf-loader 0.10.3 → 0.10.5

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.
@@ -29,551 +29,615 @@
29
29
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
30
30
  var URDFLoader__default = /*#__PURE__*/_interopDefaultLegacy(URDFLoader);
31
31
 
32
- const tempVec2 = new THREE__namespace.Vector2();
33
-
34
- // urdf-viewer element
35
- // Loads and displays a 3D view of a URDF-formatted robot
36
-
37
- // Events
38
- // urdf-change: Fires when the URDF has finished loading and getting processed
39
- // urdf-processed: Fires when the URDF has finished loading and getting processed
40
- // geometry-loaded: Fires when all the geometry has been fully loaded
41
- // ignore-limits-change: Fires when the 'ignore-limits' attribute changes
42
- // angle-change: Fires when an angle changes
43
- class URDFViewer extends HTMLElement {
44
-
45
- static get observedAttributes() {
46
-
47
- return ['package', 'urdf', 'up', 'display-shadow', 'ambient-color', 'ignore-limits'];
48
-
49
- }
50
-
51
- get package() { return this.getAttribute('package') || ''; }
52
- set package(val) { this.setAttribute('package', val); }
53
-
54
- get urdf() { return this.getAttribute('urdf') || ''; }
55
- set urdf(val) { this.setAttribute('urdf', val); }
56
-
57
- get ignoreLimits() { return this.hasAttribute('ignore-limits') || false; }
58
- set ignoreLimits(val) { val ? this.setAttribute('ignore-limits', val) : this.removeAttribute('ignore-limits'); }
59
-
60
- get up() { return this.getAttribute('up') || '+Z'; }
61
- set up(val) { this.setAttribute('up', val); }
62
-
63
- get displayShadow() { return this.hasAttribute('display-shadow') || false; }
64
- set displayShadow(val) { val ? this.setAttribute('display-shadow', '') : this.removeAttribute('display-shadow'); }
65
-
66
- get ambientColor() { return this.getAttribute('ambient-color') || '#455A64'; }
67
- set ambientColor(val) { val ? this.setAttribute('ambient-color', val) : this.removeAttribute('ambient-color'); }
68
-
69
- get autoRedraw() { return this.hasAttribute('auto-redraw') || false; }
70
- set autoRedraw(val) { val ? this.setAttribute('auto-redraw', true) : this.removeAttribute('auto-redraw'); }
71
-
72
- get noAutoRecenter() { return this.hasAttribute('no-auto-recenter') || false; }
73
- set noAutoRecenter(val) { val ? this.setAttribute('no-auto-recenter', true) : this.removeAttribute('no-auto-recenter'); }
74
-
75
- get jointValues() {
76
-
77
- const values = {};
78
- if (this.robot) {
79
-
80
- for (const name in this.robot.joints) {
81
-
82
- const joint = this.robot.joints[name];
83
- values[name] = joint.jointValue.length === 1 ? joint.angle : [...joint.jointValue];
84
-
85
- }
86
-
87
- }
88
-
89
- return values;
90
-
91
- }
92
- set jointValues(val) { this.setJointValues(val); }
93
-
94
- get angles() {
95
-
96
- return this.jointValues;
97
-
98
- }
99
- set angles(v) {
100
-
101
- this.jointValues = v;
102
-
103
- }
104
-
105
- /* Lifecycle Functions */
106
- constructor() {
107
-
108
- super();
109
-
110
- this._requestId = 0;
111
- this._dirty = false;
112
- this._loadScheduled = false;
113
- this.robot = null;
114
- this.loadMeshFunc = null;
115
- this.urlModifierFunc = null;
116
-
117
- // Scene setup
118
- const scene = new THREE__namespace.Scene();
119
-
120
- const ambientLight = new THREE__namespace.HemisphereLight(this.ambientColor, '#000');
121
- ambientLight.groundColor.lerp(ambientLight.color, 0.5);
122
- ambientLight.intensity = 0.5;
123
- ambientLight.position.set(0, 1, 0);
124
- scene.add(ambientLight);
125
-
126
- // Light setup
127
- const dirLight = new THREE__namespace.DirectionalLight(0xffffff);
128
- dirLight.position.set(4, 10, 1);
129
- dirLight.shadow.mapSize.width = 2048;
130
- dirLight.shadow.mapSize.height = 2048;
131
- dirLight.shadow.normalBias = 0.001;
132
- dirLight.castShadow = true;
133
- scene.add(dirLight);
134
- scene.add(dirLight.target);
135
-
136
- // Renderer setup
137
- const renderer = new THREE__namespace.WebGLRenderer({ antialias: true, alpha: true });
138
- renderer.setClearColor(0xffffff);
139
- renderer.setClearAlpha(0);
140
- renderer.shadowMap.enabled = true;
141
- renderer.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
142
- renderer.outputEncoding = THREE__namespace.sRGBEncoding;
143
-
144
- // Camera setup
145
- const camera = new THREE__namespace.PerspectiveCamera(75, 1, 0.1, 1000);
146
- camera.position.z = -10;
147
-
148
- // World setup
149
- const world = new THREE__namespace.Object3D();
150
- scene.add(world);
151
-
152
- const plane = new THREE__namespace.Mesh(
153
- new THREE__namespace.PlaneBufferGeometry(40, 40),
154
- new THREE__namespace.ShadowMaterial({ side: THREE__namespace.DoubleSide, transparent: true, opacity: 0.5 }),
155
- );
156
- plane.rotation.x = -Math.PI / 2;
157
- plane.position.y = -0.5;
158
- plane.receiveShadow = true;
159
- plane.scale.set(10, 10, 10);
160
- scene.add(plane);
161
-
162
- // Controls setup
163
- const controls = new OrbitControls_js.OrbitControls(camera, renderer.domElement);
164
- controls.rotateSpeed = 2.0;
165
- controls.zoomSpeed = 5;
166
- controls.panSpeed = 2;
167
- controls.enableZoom = true;
168
- controls.enableDamping = false;
169
- controls.maxDistance = 50;
170
- controls.minDistance = 0.25;
171
- controls.addEventListener('change', () => this.recenter());
172
-
173
- this.scene = scene;
174
- this.world = world;
175
- this.renderer = renderer;
176
- this.camera = camera;
177
- this.controls = controls;
178
- this.plane = plane;
179
- this.directionalLight = dirLight;
180
- this.ambientLight = ambientLight;
181
-
182
- this._setUp(this.up);
183
-
184
- const _renderLoop = () => {
185
-
186
- if (this.parentNode) {
187
-
188
- this.updateSize();
189
-
190
- if (this._dirty || this.autoRedraw) {
191
-
192
- if (!this.noAutoRecenter) {
193
-
194
- this._updateEnvironment();
195
- }
196
-
197
- this.renderer.render(scene, camera);
198
- this._dirty = false;
199
-
200
- }
201
-
202
- // update controls after the environment in
203
- // case the controls are retargeted
204
- this.controls.update();
205
-
206
- }
207
- this._renderLoopId = requestAnimationFrame(_renderLoop);
208
-
209
- };
210
- _renderLoop();
211
-
212
- }
213
-
214
- connectedCallback() {
215
-
216
- // Add our initialize styles for the element if they haven't
217
- // been added yet
218
- if (!this.constructor._styletag) {
219
-
220
- const styletag = document.createElement('style');
221
- styletag.innerHTML =
222
- `
223
- ${ this.tagName } { display: block; }
224
- ${ this.tagName } canvas {
225
- width: 100%;
226
- height: 100%;
227
- }
228
- `;
229
- document.head.appendChild(styletag);
230
- this.constructor._styletag = styletag;
231
-
232
- }
233
-
234
- // add the renderer
235
- if (this.childElementCount === 0) {
236
-
237
- this.appendChild(this.renderer.domElement);
238
-
239
- }
240
-
241
- this.updateSize();
242
- requestAnimationFrame(() => this.updateSize());
243
-
244
- }
245
-
246
- disconnectedCallback() {
247
-
248
- cancelAnimationFrame(this._renderLoopId);
249
-
250
- }
251
-
252
- attributeChangedCallback(attr, oldval, newval) {
253
-
254
- this.recenter();
255
-
256
- switch (attr) {
257
-
258
- case 'package':
259
- case 'urdf': {
260
-
261
- this._scheduleLoad();
262
- break;
263
-
264
- }
265
-
266
- case 'up': {
267
-
268
- this._setUp(this.up);
269
- break;
270
-
271
- }
272
-
273
- case 'ambient-color': {
274
-
275
- this.ambientLight.color.set(this.ambientColor);
276
- this.ambientLight.groundColor.set('#000').lerp(this.ambientLight.color, 0.5);
277
- break;
278
-
279
- }
280
-
281
- case 'ignore-limits': {
282
-
283
- this._setIgnoreLimits(this.ignoreLimits, true);
284
- break;
285
-
286
- }
287
-
288
- }
289
-
290
- }
291
-
292
- /* Public API */
293
- updateSize() {
294
-
295
- const r = this.renderer;
296
- const w = this.clientWidth;
297
- const h = this.clientHeight;
298
- const currsize = r.getSize(tempVec2);
299
-
300
- if (currsize.width !== w || currsize.height !== h) {
301
-
302
- this.recenter();
303
-
304
- }
305
-
306
- r.setPixelRatio(window.devicePixelRatio);
307
- r.setSize(w, h, false);
308
-
309
- this.camera.aspect = w / h;
310
- this.camera.updateProjectionMatrix();
311
-
312
- }
313
-
314
- redraw() {
315
-
316
- this._dirty = true;
317
- }
318
-
319
- recenter() {
320
-
321
- this._updateEnvironment();
322
- this.redraw();
323
-
324
- }
325
-
326
- // Set the joint with jointName to
327
- // angle in degrees
328
- setJointValue(jointName, ...values) {
329
-
330
- if (!this.robot) return;
331
- if (!this.robot.joints[jointName]) return;
332
-
333
- if (this.robot.joints[jointName].setJointValue(...values)) {
334
-
335
- this.redraw();
336
- this.dispatchEvent(new CustomEvent('angle-change', { bubbles: true, cancelable: true, detail: jointName }));
337
-
338
- }
339
-
340
- }
341
-
342
- setJointValues(values) {
343
-
344
- for (const name in values) this.setJointValue(name, values[name]);
345
-
346
- }
347
-
348
- /* Private Functions */
349
- // Updates the position of the plane to be at the
350
- // lowest point below the robot and focuses the
351
- // camera on the center of the scene
352
- _updateEnvironment() {
353
-
354
- if (!this.robot) return;
355
-
356
- this.world.updateMatrixWorld();
357
-
358
- const bbox = new THREE__namespace.Box3();
359
- bbox.setFromObject(this.robot);
360
-
361
- const center = bbox.getCenter(new THREE__namespace.Vector3());
362
- this.controls.target.y = center.y;
363
- this.plane.position.y = bbox.min.y - 1e-3;
364
-
365
- const dirLight = this.directionalLight;
366
- dirLight.castShadow = this.displayShadow;
367
-
368
- if (this.displayShadow) {
369
-
370
- // Update the shadow camera rendering bounds to encapsulate the
371
- // model. We use the bounding sphere of the bounding box for
372
- // simplicity -- this could be a tighter fit.
373
- const sphere = bbox.getBoundingSphere(new THREE__namespace.Sphere());
374
- const minmax = sphere.radius;
375
- const cam = dirLight.shadow.camera;
376
- cam.left = cam.bottom = -minmax;
377
- cam.right = cam.top = minmax;
378
-
379
- // Update the camera to focus on the center of the model so the
380
- // shadow can encapsulate it
381
- const offset = dirLight.position.clone().sub(dirLight.target.position);
382
- dirLight.target.position.copy(center);
383
- dirLight.position.copy(center).add(offset);
384
-
385
- cam.updateProjectionMatrix();
386
-
387
- }
388
-
389
- }
390
-
391
- _scheduleLoad() {
392
-
393
- // if our current model is already what's being requested
394
- // or has been loaded then early out
395
- if (this._prevload === `${ this.package }|${ this.urdf }`) return;
396
- this._prevload = `${ this.package }|${ this.urdf }`;
397
-
398
- // if we're already waiting on a load then early out
399
- if (this._loadScheduled) return;
400
- this._loadScheduled = true;
401
-
402
- if (this.robot) {
403
-
404
- this.robot.traverse(c => c.dispose && c.dispose());
405
- this.robot.parent.remove(this.robot);
406
- this.robot = null;
407
-
408
- }
409
-
410
- requestAnimationFrame(() => {
411
-
412
- this._loadUrdf(this.package, this.urdf);
413
- this._loadScheduled = false;
414
-
415
- });
416
-
417
- }
418
-
419
- // Watch the package and urdf field and load the robot model.
420
- // This should _only_ be called from _scheduleLoad because that
421
- // ensures the that current robot has been removed
422
- _loadUrdf(pkg, urdf) {
423
-
424
- this.dispatchEvent(new CustomEvent('urdf-change', { bubbles: true, cancelable: true, composed: true }));
425
-
426
- if (urdf) {
427
-
428
- // Keep track of this request and make
429
- // sure it doesn't get overwritten by
430
- // a subsequent one
431
- this._requestId++;
432
- const requestId = this._requestId;
433
-
434
- const updateMaterials = mesh => {
435
-
436
- mesh.traverse(c => {
437
-
438
- if (c.isMesh) {
439
-
440
- c.castShadow = true;
441
- c.receiveShadow = true;
442
-
443
- if (c.material) {
444
-
445
- const mats =
446
- (Array.isArray(c.material) ? c.material : [c.material])
447
- .map(m => {
448
-
449
- if (m instanceof THREE__namespace.MeshBasicMaterial) {
450
-
451
- m = new THREE__namespace.MeshPhongMaterial();
452
-
453
- }
454
-
455
- if (m.map) {
456
-
457
- m.map.encoding = THREE__namespace.GammaEncoding;
458
-
459
- }
460
-
461
- return m;
462
-
463
- });
464
- c.material = mats.length === 1 ? mats[0] : mats;
465
-
466
- }
467
-
468
- }
469
-
470
- });
471
-
472
- };
473
-
474
- if (pkg.includes(':') && (pkg.split(':')[1].substring(0, 2)) !== '//') {
475
- // E.g. pkg = "pkg_name: path/to/pkg_name, pk2: path2/to/pk2"}
476
-
477
- // Convert pkg(s) into a map. E.g.
478
- // { "pkg_name": "path/to/pkg_name",
479
- // "pk2": "path2/to/pk2" }
480
-
481
- pkg = pkg.split(',').reduce((map, value) => {
482
-
483
- const split = value.split(/:/).filter(x => !!x);
484
- const pkgName = split.shift().trim();
485
- const pkgPath = split.join(':').trim();
486
- map[pkgName] = pkgPath;
487
-
488
- return map;
489
-
490
- }, {});
491
- }
492
-
493
- let robot = null;
494
- const manager = new THREE__namespace.LoadingManager();
495
- manager.onLoad = () => {
496
-
497
- // If another request has come in to load a new
498
- // robot, then ignore this one
499
- if (this._requestId !== requestId) {
500
-
501
- robot.traverse(c => c.dispose && c.dispose());
502
- return;
503
-
504
- }
505
-
506
- this.robot = robot;
507
- this.world.add(robot);
508
- updateMaterials(robot);
509
-
510
- this._setIgnoreLimits(this.ignoreLimits);
511
-
512
- this.dispatchEvent(new CustomEvent('urdf-processed', { bubbles: true, cancelable: true, composed: true }));
513
- this.dispatchEvent(new CustomEvent('geometry-loaded', { bubbles: true, cancelable: true, composed: true }));
514
-
515
- this.recenter();
516
-
517
- };
518
-
519
- if (this.urlModifierFunc) {
520
-
521
- manager.setURLModifier(this.urlModifierFunc);
522
-
523
- }
524
-
525
- const loader = new URDFLoader__default['default'](manager);
526
- loader.packages = pkg;
527
- loader.loadMeshCb = this.loadMeshFunc;
528
- loader.fetchOptions = { mode: 'cors', credentials: 'same-origin' };
529
- loader.load(urdf, model => robot = model);
530
-
531
- }
532
-
533
- }
534
-
535
- // Watch the coordinate frame and update the
536
- // rotation of the scene to match
537
- _setUp(up) {
538
-
539
- if (!up) up = '+Z';
540
- up = up.toUpperCase();
541
- const sign = up.replace(/[^-+]/g, '')[0] || '+';
542
- const axis = up.replace(/[^XYZ]/gi, '')[0] || 'Z';
543
-
544
- const PI = Math.PI;
545
- const HALFPI = PI / 2;
546
- if (axis === 'X') this.world.rotation.set(0, 0, sign === '+' ? HALFPI : -HALFPI);
547
- if (axis === 'Z') this.world.rotation.set(sign === '+' ? -HALFPI : HALFPI, 0, 0);
548
- if (axis === 'Y') this.world.rotation.set(sign === '+' ? 0 : PI, 0, 0);
549
-
550
- }
551
-
552
- // Updates the current robot's angles to ignore
553
- // joint limits or not
554
- _setIgnoreLimits(ignore, dispatch = false) {
555
-
556
- if (this.robot) {
557
-
558
- Object
559
- .values(this.robot.joints)
560
- .forEach(joint => {
561
-
562
- joint.ignoreLimits = ignore;
563
- joint.setJointValue(...joint.jointValue);
564
-
565
- });
566
-
567
- }
568
-
569
- if (dispatch) {
570
-
571
- this.dispatchEvent(new CustomEvent('ignore-limits-change', { bubbles: true, cancelable: true, composed: true }));
572
-
573
- }
574
-
575
- }
576
-
32
+ const tempVec2 = new THREE__namespace.Vector2();
33
+ const emptyRaycast = () => {};
34
+
35
+ // urdf-viewer element
36
+ // Loads and displays a 3D view of a URDF-formatted robot
37
+
38
+ // Events
39
+ // urdf-change: Fires when the URDF has finished loading and getting processed
40
+ // urdf-processed: Fires when the URDF has finished loading and getting processed
41
+ // geometry-loaded: Fires when all the geometry has been fully loaded
42
+ // ignore-limits-change: Fires when the 'ignore-limits' attribute changes
43
+ // angle-change: Fires when an angle changes
44
+ class URDFViewer extends HTMLElement {
45
+
46
+ static get observedAttributes() {
47
+
48
+ return ['package', 'urdf', 'up', 'display-shadow', 'ambient-color', 'ignore-limits', 'show-collision'];
49
+
50
+ }
51
+
52
+ get package() { return this.getAttribute('package') || ''; }
53
+ set package(val) { this.setAttribute('package', val); }
54
+
55
+ get urdf() { return this.getAttribute('urdf') || ''; }
56
+ set urdf(val) { this.setAttribute('urdf', val); }
57
+
58
+ get ignoreLimits() { return this.hasAttribute('ignore-limits') || false; }
59
+ set ignoreLimits(val) { val ? this.setAttribute('ignore-limits', val) : this.removeAttribute('ignore-limits'); }
60
+
61
+ get up() { return this.getAttribute('up') || '+Z'; }
62
+ set up(val) { this.setAttribute('up', val); }
63
+
64
+ get displayShadow() { return this.hasAttribute('display-shadow') || false; }
65
+ set displayShadow(val) { val ? this.setAttribute('display-shadow', '') : this.removeAttribute('display-shadow'); }
66
+
67
+ get ambientColor() { return this.getAttribute('ambient-color') || '#455A64'; }
68
+ set ambientColor(val) { val ? this.setAttribute('ambient-color', val) : this.removeAttribute('ambient-color'); }
69
+
70
+ get autoRedraw() { return this.hasAttribute('auto-redraw') || false; }
71
+ set autoRedraw(val) { val ? this.setAttribute('auto-redraw', true) : this.removeAttribute('auto-redraw'); }
72
+
73
+ get noAutoRecenter() { return this.hasAttribute('no-auto-recenter') || false; }
74
+ set noAutoRecenter(val) { val ? this.setAttribute('no-auto-recenter', true) : this.removeAttribute('no-auto-recenter'); }
75
+
76
+ get showCollision() { return this.hasAttribute('show-collision') || false; }
77
+ set showCollision(val) { val ? this.setAttribute('show-collision', true) : this.removeAttribute('show-collision'); }
78
+
79
+ get jointValues() {
80
+
81
+ const values = {};
82
+ if (this.robot) {
83
+
84
+ for (const name in this.robot.joints) {
85
+
86
+ const joint = this.robot.joints[name];
87
+ values[name] = joint.jointValue.length === 1 ? joint.angle : [...joint.jointValue];
88
+
89
+ }
90
+
91
+ }
92
+
93
+ return values;
94
+
95
+ }
96
+ set jointValues(val) { this.setJointValues(val); }
97
+
98
+ get angles() {
99
+
100
+ return this.jointValues;
101
+
102
+ }
103
+ set angles(v) {
104
+
105
+ this.jointValues = v;
106
+
107
+ }
108
+
109
+ /* Lifecycle Functions */
110
+ constructor() {
111
+
112
+ super();
113
+
114
+ this._requestId = 0;
115
+ this._dirty = false;
116
+ this._loadScheduled = false;
117
+ this.robot = null;
118
+ this.loadMeshFunc = null;
119
+ this.urlModifierFunc = null;
120
+
121
+ // Scene setup
122
+ const scene = new THREE__namespace.Scene();
123
+
124
+ const ambientLight = new THREE__namespace.HemisphereLight(this.ambientColor, '#000');
125
+ ambientLight.groundColor.lerp(ambientLight.color, 0.5);
126
+ ambientLight.intensity = 0.5;
127
+ ambientLight.position.set(0, 1, 0);
128
+ scene.add(ambientLight);
129
+
130
+ // Light setup
131
+ const dirLight = new THREE__namespace.DirectionalLight(0xffffff);
132
+ dirLight.position.set(4, 10, 1);
133
+ dirLight.shadow.mapSize.width = 2048;
134
+ dirLight.shadow.mapSize.height = 2048;
135
+ dirLight.shadow.normalBias = 0.001;
136
+ dirLight.castShadow = true;
137
+ scene.add(dirLight);
138
+ scene.add(dirLight.target);
139
+
140
+ // Renderer setup
141
+ const renderer = new THREE__namespace.WebGLRenderer({ antialias: true, alpha: true });
142
+ renderer.setClearColor(0xffffff);
143
+ renderer.setClearAlpha(0);
144
+ renderer.shadowMap.enabled = true;
145
+ renderer.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
146
+ renderer.outputEncoding = THREE__namespace.sRGBEncoding;
147
+
148
+ // Camera setup
149
+ const camera = new THREE__namespace.PerspectiveCamera(75, 1, 0.1, 1000);
150
+ camera.position.z = -10;
151
+
152
+ // World setup
153
+ const world = new THREE__namespace.Object3D();
154
+ scene.add(world);
155
+
156
+ const plane = new THREE__namespace.Mesh(
157
+ new THREE__namespace.PlaneBufferGeometry(40, 40),
158
+ new THREE__namespace.ShadowMaterial({ side: THREE__namespace.DoubleSide, transparent: true, opacity: 0.5 }),
159
+ );
160
+ plane.rotation.x = -Math.PI / 2;
161
+ plane.position.y = -0.5;
162
+ plane.receiveShadow = true;
163
+ plane.scale.set(10, 10, 10);
164
+ scene.add(plane);
165
+
166
+ // Controls setup
167
+ const controls = new OrbitControls_js.OrbitControls(camera, renderer.domElement);
168
+ controls.rotateSpeed = 2.0;
169
+ controls.zoomSpeed = 5;
170
+ controls.panSpeed = 2;
171
+ controls.enableZoom = true;
172
+ controls.enableDamping = false;
173
+ controls.maxDistance = 50;
174
+ controls.minDistance = 0.25;
175
+ controls.addEventListener('change', () => this.recenter());
176
+
177
+ this.scene = scene;
178
+ this.world = world;
179
+ this.renderer = renderer;
180
+ this.camera = camera;
181
+ this.controls = controls;
182
+ this.plane = plane;
183
+ this.directionalLight = dirLight;
184
+ this.ambientLight = ambientLight;
185
+
186
+ this._setUp(this.up);
187
+
188
+ this._collisionMaterial = new THREE.MeshPhongMaterial({
189
+ transparent: true,
190
+ opacity: 0.35,
191
+ shininess: 2.5,
192
+ premultipliedAlpha: true,
193
+ color: 0xffbe38,
194
+ polygonOffset: true,
195
+ polygonOffsetFactor: -1,
196
+ polygonOffsetUnits: -1,
197
+ });
198
+
199
+ const _renderLoop = () => {
200
+
201
+ if (this.parentNode) {
202
+
203
+ this.updateSize();
204
+
205
+ if (this._dirty || this.autoRedraw) {
206
+
207
+ if (!this.noAutoRecenter) {
208
+
209
+ this._updateEnvironment();
210
+ }
211
+
212
+ this.renderer.render(scene, camera);
213
+ this._dirty = false;
214
+
215
+ }
216
+
217
+ // update controls after the environment in
218
+ // case the controls are retargeted
219
+ this.controls.update();
220
+
221
+ }
222
+ this._renderLoopId = requestAnimationFrame(_renderLoop);
223
+
224
+ };
225
+ _renderLoop();
226
+
227
+ }
228
+
229
+ connectedCallback() {
230
+
231
+ // Add our initialize styles for the element if they haven't
232
+ // been added yet
233
+ if (!this.constructor._styletag) {
234
+
235
+ const styletag = document.createElement('style');
236
+ styletag.innerHTML =
237
+ `
238
+ ${ this.tagName } { display: block; }
239
+ ${ this.tagName } canvas {
240
+ width: 100%;
241
+ height: 100%;
242
+ }
243
+ `;
244
+ document.head.appendChild(styletag);
245
+ this.constructor._styletag = styletag;
246
+
247
+ }
248
+
249
+ // add the renderer
250
+ if (this.childElementCount === 0) {
251
+
252
+ this.appendChild(this.renderer.domElement);
253
+
254
+ }
255
+
256
+ this.updateSize();
257
+ requestAnimationFrame(() => this.updateSize());
258
+
259
+ }
260
+
261
+ disconnectedCallback() {
262
+
263
+ cancelAnimationFrame(this._renderLoopId);
264
+
265
+ }
266
+
267
+ attributeChangedCallback(attr, oldval, newval) {
268
+
269
+ this._updateCollisionVisibility();
270
+ if (!this.noAutoRecenter) {
271
+ this.recenter();
272
+ }
273
+
274
+ switch (attr) {
275
+
276
+ case 'package':
277
+ case 'urdf': {
278
+
279
+ this._scheduleLoad();
280
+ break;
281
+
282
+ }
283
+
284
+ case 'up': {
285
+
286
+ this._setUp(this.up);
287
+ break;
288
+
289
+ }
290
+
291
+ case 'ambient-color': {
292
+
293
+ this.ambientLight.color.set(this.ambientColor);
294
+ this.ambientLight.groundColor.set('#000').lerp(this.ambientLight.color, 0.5);
295
+ break;
296
+
297
+ }
298
+
299
+ case 'ignore-limits': {
300
+
301
+ this._setIgnoreLimits(this.ignoreLimits, true);
302
+ break;
303
+
304
+ }
305
+
306
+ }
307
+
308
+ }
309
+
310
+ /* Public API */
311
+ updateSize() {
312
+
313
+ const r = this.renderer;
314
+ const w = this.clientWidth;
315
+ const h = this.clientHeight;
316
+ const currSize = r.getSize(tempVec2);
317
+
318
+ if (currSize.width !== w || currSize.height !== h) {
319
+
320
+ this.recenter();
321
+
322
+ }
323
+
324
+ r.setPixelRatio(window.devicePixelRatio);
325
+ r.setSize(w, h, false);
326
+
327
+ this.camera.aspect = w / h;
328
+ this.camera.updateProjectionMatrix();
329
+
330
+ }
331
+
332
+ redraw() {
333
+
334
+ this._dirty = true;
335
+ }
336
+
337
+ recenter() {
338
+
339
+ this._updateEnvironment();
340
+ this.redraw();
341
+
342
+ }
343
+
344
+ // Set the joint with jointName to
345
+ // angle in degrees
346
+ setJointValue(jointName, ...values) {
347
+
348
+ if (!this.robot) return;
349
+ if (!this.robot.joints[jointName]) return;
350
+
351
+ if (this.robot.joints[jointName].setJointValue(...values)) {
352
+
353
+ this.redraw();
354
+ this.dispatchEvent(new CustomEvent('angle-change', { bubbles: true, cancelable: true, detail: jointName }));
355
+
356
+ }
357
+
358
+ }
359
+
360
+ setJointValues(values) {
361
+
362
+ for (const name in values) this.setJointValue(name, values[name]);
363
+
364
+ }
365
+
366
+ /* Private Functions */
367
+ // Updates the position of the plane to be at the
368
+ // lowest point below the robot and focuses the
369
+ // camera on the center of the scene
370
+ _updateEnvironment() {
371
+
372
+ const robot = this.robot;
373
+ if (!robot) return;
374
+
375
+ this.world.updateMatrixWorld();
376
+
377
+ const bbox = new THREE__namespace.Box3();
378
+ bbox.makeEmpty();
379
+ robot.traverse(c => {
380
+ if (c.isURDFVisual) {
381
+ bbox.expandByObject(c);
382
+ }
383
+ });
384
+
385
+ const center = bbox.getCenter(new THREE__namespace.Vector3());
386
+ this.controls.target.y = center.y;
387
+ this.plane.position.y = bbox.min.y - 1e-3;
388
+
389
+ const dirLight = this.directionalLight;
390
+ dirLight.castShadow = this.displayShadow;
391
+
392
+ if (this.displayShadow) {
393
+
394
+ // Update the shadow camera rendering bounds to encapsulate the
395
+ // model. We use the bounding sphere of the bounding box for
396
+ // simplicity -- this could be a tighter fit.
397
+ const sphere = bbox.getBoundingSphere(new THREE__namespace.Sphere());
398
+ const minmax = sphere.radius;
399
+ const cam = dirLight.shadow.camera;
400
+ cam.left = cam.bottom = -minmax;
401
+ cam.right = cam.top = minmax;
402
+
403
+ // Update the camera to focus on the center of the model so the
404
+ // shadow can encapsulate it
405
+ const offset = dirLight.position.clone().sub(dirLight.target.position);
406
+ dirLight.target.position.copy(center);
407
+ dirLight.position.copy(center).add(offset);
408
+
409
+ cam.updateProjectionMatrix();
410
+
411
+ }
412
+
413
+ }
414
+
415
+ _scheduleLoad() {
416
+
417
+ // if our current model is already what's being requested
418
+ // or has been loaded then early out
419
+ if (this._prevload === `${ this.package }|${ this.urdf }`) return;
420
+ this._prevload = `${ this.package }|${ this.urdf }`;
421
+
422
+ // if we're already waiting on a load then early out
423
+ if (this._loadScheduled) return;
424
+ this._loadScheduled = true;
425
+
426
+ if (this.robot) {
427
+
428
+ this.robot.traverse(c => c.dispose && c.dispose());
429
+ this.robot.parent.remove(this.robot);
430
+ this.robot = null;
431
+
432
+ }
433
+
434
+ requestAnimationFrame(() => {
435
+
436
+ this._loadUrdf(this.package, this.urdf);
437
+ this._loadScheduled = false;
438
+
439
+ });
440
+
441
+ }
442
+
443
+ // Watch the package and urdf field and load the robot model.
444
+ // This should _only_ be called from _scheduleLoad because that
445
+ // ensures the that current robot has been removed
446
+ _loadUrdf(pkg, urdf) {
447
+
448
+ this.dispatchEvent(new CustomEvent('urdf-change', { bubbles: true, cancelable: true, composed: true }));
449
+
450
+ if (urdf) {
451
+
452
+ // Keep track of this request and make
453
+ // sure it doesn't get overwritten by
454
+ // a subsequent one
455
+ this._requestId++;
456
+ const requestId = this._requestId;
457
+
458
+ const updateMaterials = mesh => {
459
+
460
+ mesh.traverse(c => {
461
+
462
+ if (c.isMesh) {
463
+
464
+ c.castShadow = true;
465
+ c.receiveShadow = true;
466
+
467
+ if (c.material) {
468
+
469
+ const mats =
470
+ (Array.isArray(c.material) ? c.material : [c.material])
471
+ .map(m => {
472
+
473
+ if (m instanceof THREE__namespace.MeshBasicMaterial) {
474
+
475
+ m = new THREE__namespace.MeshPhongMaterial();
476
+
477
+ }
478
+
479
+ if (m.map) {
480
+
481
+ m.map.encoding = THREE__namespace.GammaEncoding;
482
+
483
+ }
484
+
485
+ return m;
486
+
487
+ });
488
+ c.material = mats.length === 1 ? mats[0] : mats;
489
+
490
+ }
491
+
492
+ }
493
+
494
+ });
495
+
496
+ };
497
+
498
+ if (pkg.includes(':') && (pkg.split(':')[1].substring(0, 2)) !== '//') {
499
+ // E.g. pkg = "pkg_name: path/to/pkg_name, pk2: path2/to/pk2"}
500
+
501
+ // Convert pkg(s) into a map. E.g.
502
+ // { "pkg_name": "path/to/pkg_name",
503
+ // "pk2": "path2/to/pk2" }
504
+
505
+ pkg = pkg.split(',').reduce((map, value) => {
506
+
507
+ const split = value.split(/:/).filter(x => !!x);
508
+ const pkgName = split.shift().trim();
509
+ const pkgPath = split.join(':').trim();
510
+ map[pkgName] = pkgPath;
511
+
512
+ return map;
513
+
514
+ }, {});
515
+ }
516
+
517
+ let robot = null;
518
+ const manager = new THREE__namespace.LoadingManager();
519
+ manager.onLoad = () => {
520
+
521
+ // If another request has come in to load a new
522
+ // robot, then ignore this one
523
+ if (this._requestId !== requestId) {
524
+
525
+ robot.traverse(c => c.dispose && c.dispose());
526
+ return;
527
+
528
+ }
529
+
530
+ this.robot = robot;
531
+ this.world.add(robot);
532
+ updateMaterials(robot);
533
+
534
+ this._setIgnoreLimits(this.ignoreLimits);
535
+ this._updateCollisionVisibility();
536
+
537
+ this.dispatchEvent(new CustomEvent('urdf-processed', { bubbles: true, cancelable: true, composed: true }));
538
+ this.dispatchEvent(new CustomEvent('geometry-loaded', { bubbles: true, cancelable: true, composed: true }));
539
+
540
+ this.recenter();
541
+
542
+ };
543
+
544
+ if (this.urlModifierFunc) {
545
+
546
+ manager.setURLModifier(this.urlModifierFunc);
547
+
548
+ }
549
+
550
+ const loader = new URDFLoader__default['default'](manager);
551
+ loader.packages = pkg;
552
+ loader.loadMeshCb = this.loadMeshFunc;
553
+ loader.fetchOptions = { mode: 'cors', credentials: 'same-origin' };
554
+ loader.parseCollision = true;
555
+ loader.load(urdf, model => robot = model);
556
+
557
+ }
558
+
559
+ }
560
+
561
+ _updateCollisionVisibility() {
562
+
563
+ const showCollision = this.showCollision;
564
+ const collisionMaterial = this._collisionMaterial;
565
+ const robot = this.robot;
566
+
567
+ if (robot === null) return;
568
+
569
+ const colliders = [];
570
+ robot.traverse(c => {
571
+
572
+ if (c.isURDFCollider) {
573
+
574
+ c.visible = showCollision;
575
+ colliders.push(c);
576
+
577
+ }
578
+
579
+ });
580
+
581
+ colliders.forEach(coll => {
582
+
583
+ coll.traverse(c => {
584
+
585
+ if (c.isMesh) {
586
+
587
+ c.raycast = emptyRaycast;
588
+ c.material = collisionMaterial;
589
+ c.castShadow = false;
590
+
591
+ }
592
+
593
+ });
594
+
595
+ });
596
+
597
+ }
598
+
599
+ // Watch the coordinate frame and update the
600
+ // rotation of the scene to match
601
+ _setUp(up) {
602
+
603
+ if (!up) up = '+Z';
604
+ up = up.toUpperCase();
605
+ const sign = up.replace(/[^-+]/g, '')[0] || '+';
606
+ const axis = up.replace(/[^XYZ]/gi, '')[0] || 'Z';
607
+
608
+ const PI = Math.PI;
609
+ const HALFPI = PI / 2;
610
+ if (axis === 'X') this.world.rotation.set(0, 0, sign === '+' ? HALFPI : -HALFPI);
611
+ if (axis === 'Z') this.world.rotation.set(sign === '+' ? -HALFPI : HALFPI, 0, 0);
612
+ if (axis === 'Y') this.world.rotation.set(sign === '+' ? 0 : PI, 0, 0);
613
+
614
+ }
615
+
616
+ // Updates the current robot's angles to ignore
617
+ // joint limits or not
618
+ _setIgnoreLimits(ignore, dispatch = false) {
619
+
620
+ if (this.robot) {
621
+
622
+ Object
623
+ .values(this.robot.joints)
624
+ .forEach(joint => {
625
+
626
+ joint.ignoreLimits = ignore;
627
+ joint.setJointValue(...joint.jointValue);
628
+
629
+ });
630
+
631
+ }
632
+
633
+ if (dispatch) {
634
+
635
+ this.dispatchEvent(new CustomEvent('ignore-limits-change', { bubbles: true, cancelable: true, composed: true }));
636
+
637
+ }
638
+
639
+ }
640
+
577
641
  };
578
642
 
579
643
  return URDFViewer;