@operato/scene-urdf 1.2.2

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +41 -0
  3. package/assets/images/spinner.png +0 -0
  4. package/demo/index.html +109 -0
  5. package/dist/editors/index.d.ts +0 -0
  6. package/dist/editors/index.js +2 -0
  7. package/dist/editors/index.js.map +1 -0
  8. package/dist/elements/drag-n-drop.d.ts +2 -0
  9. package/dist/elements/drag-n-drop.js +126 -0
  10. package/dist/elements/drag-n-drop.js.map +1 -0
  11. package/dist/elements/urdf-controller-element.d.ts +12 -0
  12. package/dist/elements/urdf-controller-element.js +283 -0
  13. package/dist/elements/urdf-controller-element.js.map +1 -0
  14. package/dist/elements/urdf-drag-controls.d.ts +32 -0
  15. package/dist/elements/urdf-drag-controls.js +185 -0
  16. package/dist/elements/urdf-drag-controls.js.map +1 -0
  17. package/dist/elements/urdf-manipulator-element.d.ts +15 -0
  18. package/dist/elements/urdf-manipulator-element.js +110 -0
  19. package/dist/elements/urdf-manipulator-element.js.map +1 -0
  20. package/dist/elements/urdf-viewer-element.d.ts +53 -0
  21. package/dist/elements/urdf-viewer-element.js +401 -0
  22. package/dist/elements/urdf-viewer-element.js.map +1 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.js +3 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/templates/index.d.ts +14 -0
  27. package/dist/templates/index.js +4 -0
  28. package/dist/templates/index.js.map +1 -0
  29. package/dist/templates/urdf-controller.d.ts +14 -0
  30. package/dist/templates/urdf-controller.js +16 -0
  31. package/dist/templates/urdf-controller.js.map +1 -0
  32. package/dist/templates/urdf-viewer.d.ts +14 -0
  33. package/dist/templates/urdf-viewer.js +16 -0
  34. package/dist/templates/urdf-viewer.js.map +1 -0
  35. package/dist/urdf-controller.d.ts +27 -0
  36. package/dist/urdf-controller.js +65 -0
  37. package/dist/urdf-controller.js.map +1 -0
  38. package/dist/urdf-viewer.d.ts +50 -0
  39. package/dist/urdf-viewer.js +198 -0
  40. package/dist/urdf-viewer.js.map +1 -0
  41. package/icons/urdf-controller.png +0 -0
  42. package/icons/urdf-viewer.png +0 -0
  43. package/package.json +66 -0
  44. package/src/editors/index.ts +0 -0
  45. package/src/elements/drag-n-drop.ts +142 -0
  46. package/src/elements/urdf-controller-element.ts +297 -0
  47. package/src/elements/urdf-drag-controls.ts +248 -0
  48. package/src/elements/urdf-manipulator-element.ts +133 -0
  49. package/src/elements/urdf-viewer-element.ts +495 -0
  50. package/src/index.ts +2 -0
  51. package/src/templates/index.ts +4 -0
  52. package/src/templates/urdf-controller.ts +16 -0
  53. package/src/templates/urdf-viewer.ts +16 -0
  54. package/src/urdf-controller.ts +82 -0
  55. package/src/urdf-viewer.ts +249 -0
  56. package/things-scene.config.js +7 -0
  57. package/translations/en.json +16 -0
  58. package/translations/ko.json +16 -0
  59. package/translations/ms.json +16 -0
  60. package/translations/zh.json +16 -0
  61. package/tsconfig.json +22 -0
  62. package/tsconfig.tsbuildinfo +1 -0
  63. package/web-dev-server.config.mjs +27 -0
@@ -0,0 +1,297 @@
1
+ import { LitElement, css, html, PropertyValues } from 'lit'
2
+ import { customElement, property } from 'lit/decorators.js'
3
+ import * as THREE from 'three'
4
+ import { registerDragEvents } from './drag-n-drop'
5
+ import { ScrollbarStyles } from '@operato/styles'
6
+ import { URDFRobot } from 'urdf-loader'
7
+ import URDFManipulatorElement from './urdf-manipulator-element'
8
+
9
+ const DEG2RAD = Math.PI / 180
10
+ const RAD2DEG = 1 / DEG2RAD
11
+
12
+ @customElement('urdf-controller')
13
+ export default class URDFControllerElement extends LitElement {
14
+ static styles = [
15
+ ScrollbarStyles,
16
+ css`
17
+ :host {
18
+ display: flex;
19
+ flex-direction: column;
20
+ width: 100%;
21
+ margin: 15px 0;
22
+ overflow: hidden;
23
+ user-select: none;
24
+ }
25
+
26
+ :host > * {
27
+ margin: 5px 0;
28
+ }
29
+
30
+ input[type='number'] {
31
+ color: white;
32
+ border: none;
33
+ font-weight: 300;
34
+ background: rgba(255, 255, 255, 0.25);
35
+ padding: 1px 2px;
36
+ }
37
+
38
+ ul {
39
+ list-style: none;
40
+ padding: 0;
41
+ margin: 0;
42
+ }
43
+
44
+ input[type='range'] {
45
+ -webkit-appearance: none;
46
+ border: none;
47
+ outline: none;
48
+ width: 100%;
49
+ flex: 1;
50
+ height: 16px;
51
+ background-color: transparent;
52
+ }
53
+ input[type='range']::-webkit-slider-runnable-track {
54
+ width: 100%;
55
+ height: 1px;
56
+ background: white;
57
+ border: none;
58
+ border-radius: 5px;
59
+ }
60
+ input[type='range']::-webkit-slider-thumb {
61
+ -webkit-appearance: none;
62
+ border: none;
63
+ height: 10px;
64
+ width: 10px;
65
+ border-radius: 50%;
66
+ background: white;
67
+ margin-top: -5px;
68
+ }
69
+ input[type='range']:focus {
70
+ outline: none;
71
+ }
72
+ input[type='range']:focus::-webkit-slider-runnable-track {
73
+ background: white;
74
+ }
75
+
76
+ input[type='range']::-moz-range-track {
77
+ width: 100%;
78
+ height: 1px;
79
+ background: white;
80
+ border: none;
81
+ border-radius: 5px;
82
+ }
83
+ input[type='range']::-moz-range-thumb {
84
+ border: none;
85
+ height: 10px;
86
+ width: 10px;
87
+ border-radius: 50%;
88
+ background: white;
89
+ }
90
+
91
+ input[type='range']:-moz-focusring {
92
+ outline: 1px solid white;
93
+ outline-offset: -1px;
94
+ }
95
+
96
+ input[type='range']::-ms-track {
97
+ width: 100%;
98
+ height: 1px;
99
+ background: white;
100
+ border-radius: 10px;
101
+ color: transparent;
102
+ border: none;
103
+ outline: none;
104
+ }
105
+ input[type='range']::-ms-thumb {
106
+ height: 10px;
107
+ width: 10px;
108
+ border-radius: 50%;
109
+ background: white;
110
+ border: none;
111
+ outline: none;
112
+ margin-top: 2px;
113
+ }
114
+
115
+ input:focus {
116
+ outline: none;
117
+ opacity: 1;
118
+ }
119
+
120
+ /* list of joint sliders */
121
+ ul {
122
+ flex: 1;
123
+ overflow-y: auto;
124
+ }
125
+
126
+ li {
127
+ font-size: 16px;
128
+ display: flex;
129
+ align-items: center;
130
+ padding: 1px 0;
131
+
132
+ width: 100%;
133
+ user-select: text;
134
+
135
+ transition: background 0.25s ease;
136
+ }
137
+
138
+ li[robot-hovered] {
139
+ background: rgba(255, 255, 255, 0.35);
140
+ }
141
+
142
+ li:hover {
143
+ background: rgba(255, 255, 255, 0.35);
144
+ }
145
+
146
+ li span {
147
+ padding: 0 5px;
148
+ max-width: 125px;
149
+ text-overflow: ellipsis;
150
+ overflow: hidden;
151
+ }
152
+
153
+ li input[type='number'] {
154
+ width: 50px;
155
+ overflow: hidden;
156
+ }
157
+ `
158
+ ]
159
+
160
+ @property({ type: Object }) viewer?: URDFManipulatorElement
161
+ @property({ type: Object }) robot?: URDFRobot
162
+
163
+ render() {
164
+ const { joints = {} } = this.robot || {}
165
+ const { ignoreLimits = false } = this.viewer || {}
166
+ const jointsArray = Object.values(joints)
167
+
168
+ return html`
169
+ <ul>
170
+ ${jointsArray.map(joint => {
171
+ var { jointType, name, angle, limit } = joint
172
+ var degVal: number | string = Number(angle || 0)
173
+
174
+ if (jointType === 'revolute' || jointType === 'continuous') {
175
+ degVal *= RAD2DEG
176
+ }
177
+
178
+ if (Math.abs(degVal) > 1) {
179
+ degVal = degVal.toFixed(1)
180
+ } else {
181
+ degVal = degVal.toPrecision(2)
182
+ }
183
+
184
+ var sliderMin
185
+ var sliderMax
186
+ var inputMin
187
+ var inputMax
188
+
189
+ if (ignoreLimits || jointType === 'continuous') {
190
+ sliderMin = -6.28
191
+ sliderMax = 6.28
192
+
193
+ inputMin = -6.28 * RAD2DEG
194
+ inputMax = 6.28 * RAD2DEG
195
+ } else {
196
+ sliderMin = limit.lower
197
+ sliderMax = limit.upper
198
+
199
+ inputMin = Number(limit.lower) * RAD2DEG
200
+ inputMax = Number(limit.upper) * RAD2DEG
201
+ }
202
+
203
+ return html`
204
+ <li joint-type=${jointType} joint-name=${name}>
205
+ <span title=${name}>${name}</span>
206
+ <input
207
+ type="range"
208
+ .value=${String(angle)}
209
+ step="0.0001"
210
+ min=${sliderMin}
211
+ max=${sliderMax}
212
+ @input=${(e: Event) => {
213
+ this.viewer?.setJointValue(name, (e.target as any).value)
214
+ }}
215
+ />
216
+ <input
217
+ type="number"
218
+ .value=${degVal}
219
+ step="0.0001"
220
+ min=${inputMin}
221
+ max=${inputMax}
222
+ @change=${(e: Event) => {
223
+ this.viewer?.setJointValue(name, Number((e.target as HTMLInputElement).value) * DEG2RAD)
224
+ }}
225
+ />
226
+ </li>
227
+ `
228
+ })}
229
+ </ul>
230
+ `
231
+ }
232
+
233
+ updated(changes: PropertyValues<this>) {
234
+ if (changes.has('viewer')) {
235
+ this._setupViewer(this.viewer)
236
+ }
237
+ }
238
+
239
+ _setupViewer(viewer?: URDFManipulatorElement) {
240
+ if (!viewer) {
241
+ return
242
+ }
243
+
244
+ this.robot = viewer.robot
245
+
246
+ viewer.addEventListener('urdf-processed', () => {
247
+ this.robot = viewer.robot
248
+ })
249
+
250
+ viewer.addEventListener('ignore-limits-change', () => {
251
+ this.requestUpdate()
252
+ })
253
+
254
+ viewer.addEventListener('angle-change', (e: Event) => {
255
+ this.requestUpdate()
256
+ })
257
+
258
+ viewer.addEventListener('joint-mouseover', (e: Event) => {
259
+ const j = this.renderRoot.querySelector(`li[joint-name="${(e as CustomEvent).detail}"]`)
260
+ if (j) {
261
+ j.setAttribute('robot-hovered', 'true')
262
+ }
263
+ })
264
+
265
+ viewer.addEventListener('joint-mouseout', (e: Event) => {
266
+ const j = this.renderRoot.querySelector(`li[joint-name="${(e as CustomEvent).detail}"]`)
267
+ if (j) {
268
+ j.removeAttribute('robot-hovered')
269
+ }
270
+ })
271
+
272
+ let originalNoAutoRecenter: boolean
273
+ viewer.addEventListener('manipulate-start', (e: Event) => {
274
+ const j = this.renderRoot.querySelector(`li[joint-name="${(e as CustomEvent).detail}"]`)
275
+ if (j) {
276
+ // ??
277
+ j.scrollIntoView({ block: 'nearest' })
278
+ window.scrollTo(0, 0)
279
+ }
280
+
281
+ originalNoAutoRecenter = viewer.noAutoRecenter
282
+ viewer.noAutoRecenter = true
283
+ })
284
+
285
+ viewer.addEventListener('manipulate-end', (e: Event) => {
286
+ viewer.noAutoRecenter = originalNoAutoRecenter
287
+ })
288
+
289
+ registerDragEvents(viewer, () => {
290
+ this.setColor('#263238')
291
+ })
292
+ }
293
+
294
+ setColor(color: string) {
295
+ this.viewer!.highlightColor = '#' + new THREE.Color(0xffffff).lerp(new THREE.Color(color), 0.35).getHexString()
296
+ }
297
+ }
@@ -0,0 +1,248 @@
1
+ import { Raycaster, Vector3, Plane, Vector2, Scene, Camera, Object3D, Ray } from 'three'
2
+ import { URDFJoint } from 'urdf-loader'
3
+
4
+ // Find the nearest parent that is a joint
5
+ function isJoint(j: URDFJoint) {
6
+ return j.isURDFJoint && j.jointType !== 'fixed'
7
+ }
8
+
9
+ function findNearestJoint(child: Object3D) {
10
+ let curr: Object3D | null = child
11
+ while (curr) {
12
+ if (isJoint(curr as URDFJoint)) {
13
+ return curr
14
+ }
15
+
16
+ curr = curr.parent
17
+ }
18
+
19
+ return curr
20
+ }
21
+
22
+ const prevHitPoint = new Vector3()
23
+ const newHitPoint = new Vector3()
24
+ const pivotPoint = new Vector3()
25
+ const tempVector = new Vector3()
26
+ const tempVector2 = new Vector3()
27
+ const projectedStartPoint = new Vector3()
28
+ const projectedEndPoint = new Vector3()
29
+ const plane = new Plane()
30
+ export class URDFDragControls {
31
+ scene: Scene
32
+
33
+ enabled: boolean
34
+ raycaster: Raycaster
35
+ initialGrabPoint: Vector3
36
+ hitDistance: number
37
+
38
+ hovered: any
39
+ manipulating: any
40
+
41
+ constructor(scene: Scene) {
42
+ this.enabled = true
43
+ this.scene = scene
44
+
45
+ this.raycaster = new Raycaster()
46
+ this.initialGrabPoint = new Vector3()
47
+
48
+ this.hitDistance = -1
49
+ this.hovered = null
50
+ this.manipulating = null
51
+ }
52
+
53
+ update() {
54
+ const { raycaster, hovered, manipulating, scene } = this
55
+
56
+ if (manipulating) {
57
+ return
58
+ }
59
+
60
+ let hoveredJoint = null
61
+ const intersections = raycaster.intersectObject(scene, true)
62
+ if (intersections.length !== 0) {
63
+ const hit = intersections[0]
64
+ this.hitDistance = hit.distance
65
+ hoveredJoint = findNearestJoint(hit.object)
66
+ this.initialGrabPoint.copy(hit.point)
67
+ }
68
+
69
+ if (hoveredJoint !== hovered) {
70
+ if (hovered) {
71
+ this.onUnhover(hovered)
72
+ }
73
+
74
+ this.hovered = hoveredJoint
75
+
76
+ if (hoveredJoint) {
77
+ this.onHover(hoveredJoint as URDFJoint)
78
+ }
79
+ }
80
+ }
81
+
82
+ updateJoint(joint: URDFJoint, angle: number) {
83
+ joint.setJointValue(angle)
84
+ }
85
+
86
+ onDragStart(joint: URDFJoint) {}
87
+
88
+ onDragEnd(joint: URDFJoint) {}
89
+
90
+ onHover(joint: URDFJoint) {}
91
+
92
+ onUnhover(joint: URDFJoint) {}
93
+
94
+ getRevoluteDelta(joint: URDFJoint, startPoint: Vector3, endPoint: Vector3) {
95
+ // set up the plane
96
+ tempVector.copy(joint.axis).transformDirection(joint.matrixWorld).normalize()
97
+ pivotPoint.set(0, 0, 0).applyMatrix4(joint.matrixWorld)
98
+ plane.setFromNormalAndCoplanarPoint(tempVector, pivotPoint)
99
+
100
+ // project the drag points onto the plane
101
+ plane.projectPoint(startPoint, projectedStartPoint)
102
+ plane.projectPoint(endPoint, projectedEndPoint)
103
+
104
+ // get the directions relative to the pivot
105
+ projectedStartPoint.sub(pivotPoint)
106
+ projectedEndPoint.sub(pivotPoint)
107
+
108
+ tempVector.crossVectors(projectedStartPoint, projectedEndPoint)
109
+
110
+ const direction = Math.sign(tempVector.dot(plane.normal))
111
+ return direction * projectedEndPoint.angleTo(projectedStartPoint)
112
+ }
113
+
114
+ getPrismaticDelta(joint: URDFJoint, startPoint: Vector3, endPoint: Vector3) {
115
+ tempVector.subVectors(endPoint, startPoint)
116
+ plane.normal.copy(joint.axis).transformDirection(joint.parent!.matrixWorld).normalize()
117
+
118
+ return tempVector.dot(plane.normal)
119
+ }
120
+
121
+ moveRay(toRay: Ray) {
122
+ const { raycaster, hitDistance, manipulating } = this
123
+ const { ray } = raycaster
124
+
125
+ if (manipulating) {
126
+ ray.at(hitDistance, prevHitPoint)
127
+ toRay.at(hitDistance, newHitPoint)
128
+
129
+ let delta = 0
130
+ if (manipulating.jointType === 'revolute' || manipulating.jointType === 'continuous') {
131
+ delta = this.getRevoluteDelta(manipulating, prevHitPoint, newHitPoint)
132
+ } else if (manipulating.jointType === 'prismatic') {
133
+ delta = this.getPrismaticDelta(manipulating, prevHitPoint, newHitPoint)
134
+ }
135
+
136
+ if (delta) {
137
+ this.updateJoint(manipulating, manipulating.angle + delta)
138
+ }
139
+ }
140
+
141
+ this.raycaster.ray.copy(toRay)
142
+ this.update()
143
+ }
144
+
145
+ setGrabbed(grabbed: boolean) {
146
+ const { hovered, manipulating } = this
147
+ if (grabbed) {
148
+ if (manipulating !== null || hovered === null) {
149
+ return
150
+ }
151
+
152
+ this.manipulating = hovered
153
+ this.onDragStart(hovered)
154
+ } else {
155
+ if (this.manipulating === null) {
156
+ return
157
+ }
158
+
159
+ this.onDragEnd(this.manipulating)
160
+ this.manipulating = null
161
+ this.update()
162
+ }
163
+ }
164
+ }
165
+
166
+ export class PointerURDFDragControls extends URDFDragControls {
167
+ camera: Camera
168
+ domElement: HTMLElement
169
+
170
+ _mouseDown: (e: MouseEvent) => void
171
+ _mouseMove: (e: MouseEvent) => void
172
+ _mouseUp: (e: MouseEvent) => void
173
+
174
+ constructor(scene: Scene, camera: Camera, domElement: HTMLElement) {
175
+ super(scene)
176
+
177
+ this.camera = camera
178
+ this.domElement = domElement
179
+
180
+ const raycaster = new Raycaster()
181
+ const mouse = new Vector2()
182
+
183
+ function updateMouse(e: MouseEvent) {
184
+ mouse.x = (e.offsetX / domElement.offsetWidth) * 2 - 1
185
+ mouse.y = -(e.offsetY / domElement.offsetHeight) * 2 + 1
186
+ }
187
+
188
+ this._mouseDown = (e: MouseEvent) => {
189
+ updateMouse(e)
190
+ raycaster.setFromCamera(mouse, this.camera)
191
+ this.moveRay(raycaster.ray)
192
+ this.setGrabbed(true)
193
+ }
194
+
195
+ this._mouseMove = (e: MouseEvent) => {
196
+ updateMouse(e)
197
+ raycaster.setFromCamera(mouse, this.camera)
198
+ this.moveRay(raycaster.ray)
199
+ }
200
+
201
+ this._mouseUp = (e: MouseEvent) => {
202
+ updateMouse(e)
203
+ raycaster.setFromCamera(mouse, this.camera)
204
+ this.moveRay(raycaster.ray)
205
+ this.setGrabbed(false)
206
+ }
207
+
208
+ domElement.addEventListener('pointerdown', this._mouseDown)
209
+ domElement.addEventListener('pointermove', this._mouseMove)
210
+ domElement.addEventListener('pointerup', this._mouseUp)
211
+ }
212
+
213
+ getRevoluteDelta(joint: URDFJoint, startPoint: Vector3, endPoint: Vector3) {
214
+ const { camera, initialGrabPoint } = this
215
+
216
+ // set up the plane
217
+ tempVector.copy(joint.axis).transformDirection(joint.matrixWorld).normalize()
218
+ pivotPoint.set(0, 0, 0).applyMatrix4(joint.matrixWorld)
219
+ plane.setFromNormalAndCoplanarPoint(tempVector, pivotPoint)
220
+
221
+ tempVector.copy(camera.position).sub(initialGrabPoint).normalize()
222
+
223
+ // if looking into the plane of rotation
224
+ if (Math.abs(tempVector.dot(plane.normal)) > 0.3) {
225
+ return super.getRevoluteDelta(joint, startPoint, endPoint)
226
+ } else {
227
+ // get the up direction
228
+ tempVector.set(0, 1, 0).transformDirection(camera.matrixWorld)
229
+
230
+ // get points projected onto the plane of rotation
231
+ plane.projectPoint(startPoint, projectedStartPoint)
232
+ plane.projectPoint(endPoint, projectedEndPoint)
233
+
234
+ tempVector.set(0, 0, -1).transformDirection(camera.matrixWorld)
235
+ tempVector.cross(plane.normal)
236
+ tempVector2.subVectors(endPoint, startPoint)
237
+
238
+ return tempVector.dot(tempVector2)
239
+ }
240
+ }
241
+
242
+ dispose() {
243
+ const { domElement } = this
244
+ domElement.removeEventListener('pointerdown', this._mouseDown)
245
+ domElement.removeEventListener('pointermove', this._mouseMove)
246
+ domElement.removeEventListener('pointerup', this._mouseUp)
247
+ }
248
+ }
@@ -0,0 +1,133 @@
1
+ import { customElement } from 'lit/decorators.js'
2
+ import * as THREE from 'three'
3
+ import { PointerURDFDragControls } from './urdf-drag-controls'
4
+ import { Material, MeshPhongMaterial } from 'three'
5
+ import URDFViewerElement from './urdf-viewer-element'
6
+ import { URDFJoint } from 'urdf-loader'
7
+
8
+ // urdf-manipulator element
9
+ // Displays a URDF model that can be manipulated with the mouse
10
+
11
+ // Events
12
+ // joint-mouseover: Fired when a joint is hovered over
13
+ // joint-mouseout: Fired when a joint is no longer hovered over
14
+ // manipulate-start: Fires when a joint is manipulated
15
+ // manipulate-end: Fires when a joint is done being manipulated
16
+
17
+ @customElement('urdf-viewer')
18
+ export default class URDFManipulatorElement extends URDFViewerElement {
19
+ static get observedAttributes() {
20
+ return ['highlight-color', ...super.observedAttributes]
21
+ }
22
+
23
+ highlightMaterial: MeshPhongMaterial
24
+ dragControls: PointerURDFDragControls
25
+
26
+ get disableDragging() {
27
+ return this.hasAttribute('disable-dragging')
28
+ }
29
+
30
+ set disableDragging(val) {
31
+ val ? this.setAttribute('disable-dragging', String(!!val)) : this.removeAttribute('disable-dragging')
32
+ }
33
+
34
+ get highlightColor() {
35
+ return this.getAttribute('highlight-color') || '#FFFFFF'
36
+ }
37
+
38
+ set highlightColor(val) {
39
+ val ? this.setAttribute('highlight-color', val) : this.removeAttribute('highlight-color')
40
+ }
41
+
42
+ constructor() {
43
+ super()
44
+
45
+ // The highlight material
46
+ this.highlightMaterial = new THREE.MeshPhongMaterial({
47
+ shininess: 10,
48
+ color: this.highlightColor,
49
+ emissive: this.highlightColor,
50
+ emissiveIntensity: 0.25
51
+ })
52
+
53
+ const isJoint = (j: URDFJoint) => {
54
+ return j.isURDFJoint && j.jointType !== 'fixed'
55
+ }
56
+
57
+ // Highlight the link geometry under a joint
58
+ const highlightLinkGeometry = (m: THREE.Material, revert: boolean) => {
59
+ const traverse = (c: any) => {
60
+ // Set or revert the highlight color
61
+ if (c.type === 'Mesh') {
62
+ if (revert) {
63
+ c.material = c.__origMaterial
64
+ delete c.__origMaterial
65
+ } else {
66
+ c.__origMaterial = c.material
67
+ c.material = this.highlightMaterial
68
+ }
69
+ }
70
+
71
+ // Look into the children and stop if the next child is
72
+ // another joint
73
+ if (c === m || !isJoint(c)) {
74
+ for (let i = 0; i < c.children.length; i++) {
75
+ traverse(c.children[i])
76
+ }
77
+ }
78
+ }
79
+
80
+ traverse(m)
81
+ }
82
+
83
+ const el = this.renderer.domElement
84
+
85
+ const dragControls = new PointerURDFDragControls(this.scene, this.camera, el)
86
+
87
+ dragControls.onDragStart = joint => {
88
+ this.dispatchEvent(new CustomEvent('manipulate-start', { bubbles: true, cancelable: true, detail: joint.name }))
89
+ this.controls.enabled = false
90
+ this.redraw()
91
+ }
92
+
93
+ dragControls.onDragEnd = joint => {
94
+ this.dispatchEvent(new CustomEvent('manipulate-end', { bubbles: true, cancelable: true, detail: joint.name }))
95
+ this.controls.enabled = true
96
+ this.redraw()
97
+ }
98
+
99
+ dragControls.updateJoint = (joint, angle) => {
100
+ this.setJointValue(joint.name, angle)
101
+ }
102
+
103
+ dragControls.onHover = (joint: URDFJoint) => {
104
+ highlightLinkGeometry(joint as any, false)
105
+ this.dispatchEvent(new CustomEvent('joint-mouseover', { bubbles: true, cancelable: true, detail: joint.name }))
106
+ this.redraw()
107
+ }
108
+
109
+ dragControls.onUnhover = joint => {
110
+ highlightLinkGeometry(joint as any, true)
111
+ this.dispatchEvent(new CustomEvent('joint-mouseout', { bubbles: true, cancelable: true, detail: joint.name }))
112
+ this.redraw()
113
+ }
114
+
115
+ this.dragControls = dragControls
116
+ }
117
+
118
+ disconnectedCallback() {
119
+ super.disconnectedCallback()
120
+ this.dragControls.dispose()
121
+ }
122
+
123
+ attributeChangedCallback(attr: string, oldval: any, newval: any) {
124
+ super.attributeChangedCallback(attr, oldval, newval)
125
+
126
+ switch (attr) {
127
+ case 'highlight-color':
128
+ this.highlightMaterial.color.set(this.highlightColor)
129
+ this.highlightMaterial.emissive.set(this.highlightColor)
130
+ break
131
+ }
132
+ }
133
+ }