urdf-loader
Advanced tools
Comparing version 0.9.2 to 0.9.3
@@ -7,2 +7,11 @@ # Changelog | ||
## [0.9.3] - 2021-01-19 | ||
### Fixed | ||
- Unnecessary creation of a new quaternion when setting a joint angle. | ||
- Throw a human readable error when fetch fails. | ||
- The model failing to clone if the object names were changed. | ||
### Added | ||
- Ability to set the `packages` option to a function. | ||
## [0.9.2] - 2020-10-23 | ||
@@ -9,0 +18,0 @@ ### Added |
{ | ||
"name": "urdf-loader", | ||
"version": "0.9.2", | ||
"version": "0.9.3", | ||
"description": "URDF Loader for THREE.js and webcomponent viewer", | ||
@@ -5,0 +5,0 @@ "main": "umd/URDFLoader.js", |
# urdf-loader | ||
[![npm version](https://img.shields.io/npm/v/urdf-loader.svg?style=flat-square)](https://www.npmjs.com/package/urdf-loader) | ||
[![travis build](https://img.shields.io/travis/gkjohnson/urdf-loaders/master.svg?style=flat-square)](https://travis-ci.com/gkjohnson/urdf-loaders) | ||
[![build](https://img.shields.io/github/workflow/status/gkjohnson/urdf-loaders/Node.js%20CI?style=flat-square&label=build)](https://github.com/gkjohnson/urdf-loaders/actions) | ||
[![lgtm code quality](https://img.shields.io/lgtm/grade/javascript/g/gkjohnson/urdf-loaders.svg?style=flat-square&label=code-quality)](https://lgtm.com/projects/g/gkjohnson/urdf-loaders/) | ||
@@ -9,4 +9,6 @@ | ||
[Simple example with VR here!](https://gkjohnson.github.io/urdf-loaders/javascript/example/simple.html) | ||
[Basic loader example here!](https://gkjohnson.github.io/urdf-loaders/javascript/example/simple.html) | ||
[VR example here!](https://gkjohnson.github.io/urdf-loaders/javascript/example/vr.html) | ||
[Drag and drop web component tool here!](https://gkjohnson.github.io/urdf-loaders/javascript/example/index.html) | ||
@@ -44,5 +46,5 @@ | ||
#### Custom Mesh Loader | ||
#### Custom Mesh Loader & Error Handling | ||
Adding a custom mesh loader. | ||
Implementing custom error handling and / or adding a custom loader for meshes can be done using the [loadMeshCb](#loadMeshCb) callback. | ||
@@ -59,7 +61,18 @@ ```js | ||
const gltfLoader = new GLTFLoader( manager ); | ||
gltfLoader.load( path, result => { | ||
gltfLoader.load( | ||
path, | ||
result => { | ||
onComplete( result.scene ); | ||
onComplete( result.scene ); | ||
} ); | ||
}, | ||
undefined, | ||
err => { | ||
// try to load again, notify user, etc | ||
onComplete( null, err ); | ||
} | ||
); | ||
@@ -112,3 +125,3 @@ }; | ||
```js | ||
packages = '' : String | Object | ||
packages = '' : String | Object | ( pkg : String ) => String | ||
``` | ||
@@ -129,2 +142,4 @@ | ||
If the setting is set to a function then it takes the package name and is expected to return the package path. | ||
### .loadMeshCb | ||
@@ -131,0 +146,0 @@ |
import * as THREE from 'three'; | ||
import URDFViewer from './urdf-viewer-element.js'; | ||
import { PointerURDFDragControls } from './URDFDragControls.js'; | ||
@@ -40,40 +41,2 @@ // urdf-manipulator element | ||
const el = this.renderer.domElement; | ||
// Saved mouse data between frames and initial | ||
// click point in space | ||
const mouse = new THREE.Vector2(); | ||
const lastMouse = new THREE.Vector2(); | ||
const clickPoint = new THREE.Vector3(); | ||
// Reuseable variables | ||
const raycaster = new THREE.Raycaster(); | ||
const delta = new THREE.Vector2(); | ||
const plane = new THREE.Plane(); | ||
const line = new THREE.Line3(); | ||
// The joint being manipulated | ||
let dragging = null; | ||
const toMouseCoord = (e, v) => { | ||
v.x = ((e.pageX - el.offsetLeft) / el.offsetWidth) * 2 - 1; | ||
v.y = -((e.pageY - el.offsetTop) / el.offsetHeight) * 2 + 1; | ||
}; | ||
// Get which part of the robot is hit by the mouse click | ||
const getCollisions = m => { | ||
if (!this.robot) return []; | ||
raycaster.setFromCamera(m, this.camera); | ||
const meshes = []; | ||
this.robot.traverse(c => c.type === 'Mesh' && meshes.push(c)); | ||
return raycaster.intersectObjects(meshes); | ||
}; | ||
const isJoint = j => { | ||
@@ -85,22 +48,2 @@ | ||
// Find the nearest parent that is a joint | ||
const findNearestJoint = m => { | ||
let curr = m; | ||
while (curr) { | ||
if (isJoint(curr)) { | ||
break; | ||
} | ||
curr = curr.parent; | ||
} | ||
return curr; | ||
}; | ||
// Highlight the link geometry under a joint | ||
@@ -146,221 +89,41 @@ const highlightLinkGeometry = (m, revert) => { | ||
const temp = new THREE.Vector3(); | ||
const intersect1 = new THREE.Vector3(); | ||
const intersect2 = new THREE.Vector3(); | ||
const el = this.renderer.domElement; | ||
// Get the changed angle between mouse position 1 and 2 | ||
// when manipulating target | ||
const getAngle = (tg, m1, m2) => { | ||
const dragControls = new PointerURDFDragControls(this.scene, this.camera, el); | ||
dragControls.onDragStart = joint => { | ||
// TODO: Why is the constant negated? | ||
plane.normal.copy(tg.axis).transformDirection(tg.matrixWorld).normalize(); | ||
plane.constant = -plane.normal.dot(clickPoint); | ||
this.dispatchEvent(new CustomEvent('manipulate-start', { bubbles: true, cancelable: true, detail: joint.name })); | ||
this.controls.enabled = false; | ||
this.redraw(); | ||
// If the camera is looking at the rotation axis at a skewed angle | ||
temp.copy(this.camera.position).sub(clickPoint).normalize(); | ||
if (Math.abs(temp.dot(plane.normal)) < 0.2) { | ||
}; | ||
dragControls.onDragEnd = joint => { | ||
// distance to the clicked point | ||
const dist = temp.copy(clickPoint).sub(this.camera.position).length() * 0.9; | ||
this.dispatchEvent(new CustomEvent('manipulate-end', { bubbles: true, cancelable: true, detail: joint.name })); | ||
this.controls.enabled = true; | ||
this.redraw(); | ||
// Get the point closest to the original clicked point | ||
// and use that as center of the rotation axis | ||
temp.set(0, 0, 0).applyMatrix4(tg.matrixWorld); | ||
temp.addScaledVector(plane.normal, -plane.distanceToPoint(temp)); | ||
// Project out from the camera | ||
raycaster.setFromCamera(m1, this.camera); | ||
intersect1.copy(raycaster.ray.origin).add( | ||
raycaster.ray.direction.normalize().multiplyScalar(dist), | ||
); | ||
intersect1.sub(temp); | ||
raycaster.setFromCamera(m2, this.camera); | ||
intersect2.copy(raycaster.ray.origin).add( | ||
raycaster.ray.direction.normalize().multiplyScalar(dist), | ||
); | ||
intersect2.sub(temp); | ||
temp.crossVectors(intersect2, intersect1).normalize(); | ||
// Multiply by a magic number to make it feel good | ||
return temp.dot(plane.normal) * intersect2.angleTo(intersect1) * 2; | ||
} else { | ||
// Get the point closest to the original clicked point | ||
// and use that as center of the rotation axis | ||
temp.set(0, 0, 0).applyMatrix4(tg.matrixWorld); | ||
temp.addScaledVector(plane.normal, -plane.distanceToPoint(temp)); | ||
// project onto the plane of rotation | ||
raycaster.setFromCamera(m1, this.camera); | ||
line.start.copy(raycaster.ray.origin); | ||
line.end.copy(raycaster.ray.origin).add(raycaster.ray.direction.normalize().multiplyScalar(1e5)); | ||
plane.intersectLine(line, intersect1); | ||
intersect1.sub(temp); | ||
raycaster.setFromCamera(m2, this.camera); | ||
line.start.copy(raycaster.ray.origin); | ||
line.end.copy(raycaster.ray.origin).add(raycaster.ray.direction.normalize().multiplyScalar(1e5)); | ||
plane.intersectLine(line, intersect2); | ||
intersect2.sub(temp); | ||
temp.crossVectors(intersect2, intersect1); | ||
return Math.sign(temp.dot(plane.normal)) * intersect2.angleTo(intersect1); | ||
} | ||
}; | ||
dragControls.updateJoint = (joint, angle) => { | ||
// Get the amount to move the prismatic joint based on the mouse move | ||
const getMove = (tg, m1, m2) => { | ||
this.setJointValue(joint.name, angle); | ||
const dist = temp.copy(clickPoint).sub(this.camera.position).length(); | ||
raycaster.setFromCamera(m1, this.camera); | ||
raycaster.ray.direction.normalize().multiplyScalar(dist); | ||
intersect1.copy(raycaster.ray.origin).add(raycaster.ray.direction); | ||
raycaster.setFromCamera(m2, this.camera); | ||
raycaster.ray.direction.normalize().multiplyScalar(dist); | ||
intersect2.copy(raycaster.ray.origin).add(raycaster.ray.direction); | ||
temp.copy(intersect2).sub(intersect1); | ||
plane.normal.copy(tg.axis).transformDirection(tg.parent.matrixWorld).normalize(); | ||
return temp.length() * -Math.sign(temp.dot(plane.normal)); | ||
}; | ||
dragControls.onHover = joint => { | ||
el.addEventListener('mousedown', e => { | ||
highlightLinkGeometry(joint, false); | ||
this.dispatchEvent(new CustomEvent('joint-mouseout', { bubbles: true, cancelable: true, detail: joint.name })); | ||
this.redraw(); | ||
if (this.disableDragging) return; | ||
toMouseCoord(e, mouse); | ||
lastMouse.copy(mouse); | ||
// get the information on the clicked item | ||
// and set the dragged joint | ||
const target = getCollisions(mouse).shift(); | ||
if (target) { | ||
dragging = findNearestJoint(target.object); | ||
if (dragging) { | ||
clickPoint.copy(target.point); | ||
this.dispatchEvent(new CustomEvent('manipulate-start', { bubbles: true, cancelable: true, detail: dragging.name })); | ||
this.controls.enabled = false; | ||
} | ||
} | ||
}, true); | ||
let hovered = null; | ||
this._mouseMoveFunc = e => { | ||
toMouseCoord(e, mouse); | ||
delta.copy(mouse).sub(lastMouse); | ||
// Keep track of the hovered item. If an item is being | ||
// dragged, then it is considered hovered | ||
const wasHovered = hovered; | ||
if (hovered) { | ||
hovered = null; | ||
} | ||
if (dragging == null && this.disableDragging === false) { | ||
const collision = getCollisions(mouse).shift() || null; | ||
const joint = collision && findNearestJoint(collision.object); | ||
if (joint) { | ||
hovered = joint; | ||
} | ||
} else if (dragging) { | ||
hovered = dragging; | ||
} | ||
// Highlight the meshes and broadcast events if the hovered item changed | ||
if (hovered !== wasHovered) { | ||
if (wasHovered) { | ||
highlightLinkGeometry(wasHovered, true); | ||
this.dispatchEvent(new CustomEvent('joint-mouseout', { bubbles: true, cancelable: true, detail: wasHovered.name })); | ||
} | ||
if (hovered) { | ||
highlightLinkGeometry(hovered, false); | ||
this.dispatchEvent(new CustomEvent('joint-mouseover', { bubbles: true, cancelable: true, detail: hovered.name })); | ||
} | ||
this.redraw(); | ||
} | ||
// Apply the manipulation | ||
if (dragging !== null) { | ||
let delta = null; | ||
if (dragging.jointType === 'revolute' || dragging.jointType === 'continuous') { | ||
delta = getAngle(dragging, mouse, lastMouse); | ||
} else if (dragging.jointType === 'prismatic') { | ||
delta = getMove(dragging, mouse, lastMouse); | ||
} else { | ||
// Not supported | ||
} | ||
if (delta) { | ||
this.setJointValue(dragging.name, dragging.angle + delta); | ||
} | ||
} | ||
lastMouse.copy(mouse); | ||
}; | ||
dragControls.onUnhover = joint => { | ||
// Clean up | ||
this._mouseUpFunc = e => { | ||
highlightLinkGeometry(joint, true); | ||
this.dispatchEvent(new CustomEvent('joint-mouseover', { bubbles: true, cancelable: true, detail: joint.name })); | ||
this.redraw(); | ||
if (dragging) { | ||
this.dispatchEvent(new CustomEvent('manipulate-end', { bubbles: true, cancelable: true, detail: dragging.name })); | ||
dragging = null; | ||
this.controls.enabled = true; | ||
} | ||
}; | ||
} | ||
this.dragControls = dragControls; | ||
connectedCallback() { | ||
super.connectedCallback(); | ||
window.addEventListener('mousemove', this._mouseMoveFunc, true); | ||
window.addEventListener('mouseup', this._mouseUpFunc, true); | ||
} | ||
@@ -371,4 +134,3 @@ | ||
super.disconnectedCallback(); | ||
window.removeEventListener('mousemove', this._mouseMoveFunc, true); | ||
window.removeEventListener('mouseup', this._mouseUpFunc, true); | ||
this.dragControls.dispose(); | ||
@@ -375,0 +137,0 @@ } |
@@ -1,4 +0,4 @@ | ||
import { Object3D, Quaternion } from 'three'; | ||
import { Object3D } from 'three'; | ||
class URDFCollider extends Object3D { | ||
class URDFBase extends Object3D { | ||
@@ -8,5 +8,4 @@ constructor(...args) { | ||
super(...args); | ||
this.isURDFCollider = true; | ||
this.type = 'URDFCollider'; | ||
this.urdfNode = null; | ||
this.urdfName = ''; | ||
@@ -18,3 +17,5 @@ } | ||
super.copy(source, recursive); | ||
this.urdfNode = source.urdfNode; | ||
this.urdfName = source.urdfName; | ||
@@ -27,3 +28,3 @@ return this; | ||
class URDFVisual extends Object3D { | ||
class URDFCollider extends URDFBase { | ||
@@ -33,15 +34,17 @@ constructor(...args) { | ||
super(...args); | ||
this.isURDFVisual = true; | ||
this.type = 'URDFVisual'; | ||
this.urdfNode = null; | ||
this.isURDFCollider = true; | ||
this.type = 'URDFCollider'; | ||
} | ||
copy(source, recursive) { | ||
} | ||
super.copy(source, recursive); | ||
this.urdfNode = source.urdfNode; | ||
class URDFVisual extends URDFBase { | ||
return this; | ||
constructor(...args) { | ||
super(...args); | ||
this.isURDFVisual = true; | ||
this.type = 'URDFVisual'; | ||
} | ||
@@ -51,3 +54,3 @@ | ||
class URDFLink extends Object3D { | ||
class URDFLink extends URDFBase { | ||
@@ -59,18 +62,8 @@ constructor(...args) { | ||
this.type = 'URDFLink'; | ||
this.urdfNode = null; | ||
} | ||
copy(source, recursive) { | ||
super.copy(source, recursive); | ||
this.urdfNode = source.urdfNode; | ||
return this; | ||
} | ||
} | ||
class URDFJoint extends Object3D { | ||
class URDFJoint extends URDFBase { | ||
@@ -125,3 +118,2 @@ get jointType() { | ||
this.urdfNode = null; | ||
this.jointValue = null; | ||
@@ -143,3 +135,2 @@ this.jointType = 'fixed'; | ||
this.urdfNode = source.urdfNode; | ||
this.jointType = source.jointType; | ||
@@ -184,3 +175,3 @@ this.axis = source.axis ? source.axis.clone() : null; | ||
if (angle == null) return false; | ||
if (angle === this.angle) return false; | ||
if (angle === this.jointValue[0]) return false; | ||
@@ -194,6 +185,5 @@ if (!this.ignoreLimits && this.jointType === 'revolute') { | ||
// FromAxisAngle seems to rotate the opposite of the | ||
// expected angle for URDF, so negate it here | ||
const delta = new Quaternion().setFromAxisAngle(this.axis, angle); | ||
this.quaternion.multiplyQuaternions(this.origQuaternion, delta); | ||
this.quaternion | ||
.setFromAxisAngle(this.axis, angle) | ||
.premultiply(this.origQuaternion); | ||
@@ -218,3 +208,3 @@ if (this.jointValue[0] !== angle) { | ||
if (pos == null) return false; | ||
if (pos === this.angle) return false; | ||
if (pos === this.jointValue[0]) return false; | ||
@@ -291,23 +281,23 @@ if (!this.ignoreLimits) { | ||
if (c.isURDFJoint && c.name in source.joints) { | ||
if (c.isURDFJoint && c.urdfName in source.joints) { | ||
this.joints[c.name] = c; | ||
this.joints[c.urdfName] = c; | ||
} | ||
if (c.isURDFLink && c.name in source.links) { | ||
if (c.isURDFLink && c.urdfName in source.links) { | ||
this.links[c.name] = c; | ||
this.links[c.urdfName] = c; | ||
} | ||
if (c.isURDFCollider && c.name in source.colliders) { | ||
if (c.isURDFCollider && c.urdfName in source.colliders) { | ||
this.colliders[c.name] = c; | ||
this.colliders[c.urdfName] = c; | ||
} | ||
if (c.isURDFVisual && c.name in source.visual) { | ||
if (c.isURDFVisual && c.urdfName in source.visual) { | ||
this.visual[c.name] = c; | ||
this.visual[c.urdfName] = c; | ||
@@ -314,0 +304,0 @@ } |
@@ -97,8 +97,16 @@ import * as THREE from 'three'; | ||
if (onProgress) { | ||
if (res.ok) { | ||
onProgress(null); | ||
if (onProgress) { | ||
onProgress(null); | ||
} | ||
return res.text(); | ||
} else { | ||
throw new Error(`URDFLoader: Failed to load url '${ urdfPath }' with error code ${ res.status } : ${ res.statusText }.`); | ||
} | ||
return res.text(); | ||
@@ -176,2 +184,6 @@ }) | ||
} else if (packages instanceof Function) { | ||
return packages(targetPkg) + '/' + relPath; | ||
} else if (typeof packages === 'object') { | ||
@@ -283,2 +295,3 @@ | ||
obj.name = joint.getAttribute('name'); | ||
obj.urdfName = obj.name; | ||
obj.jointType = jointType; | ||
@@ -349,2 +362,3 @@ | ||
target.name = link.getAttribute('name'); | ||
target.urdfName = target.name; | ||
target.urdfNode = link; | ||
@@ -364,2 +378,3 @@ | ||
v.name = name; | ||
v.urdfName = name; | ||
visualMap[name] = v; | ||
@@ -385,2 +400,3 @@ | ||
c.name = name; | ||
c.urdfName = name; | ||
colliderMap[name] = c; | ||
@@ -387,0 +403,0 @@ |
@@ -11,360 +11,450 @@ (function (global, factory) { | ||
// urdf-manipulator element | ||
// Displays a URDF model that can be manipulated with the mouse | ||
// Find the nearest parent that is a joint | ||
function isJoint(j) { | ||
// Events | ||
// joint-mouseover: Fired when a joint is hovered over | ||
// joint-mouseout: Fired when a joint is no longer hovered over | ||
// manipulate-start: Fires when a joint is manipulated | ||
// manipulate-end: Fires when a joint is done being manipulated | ||
class URDFManipulator extends URDFViewer__default['default'] { | ||
return j.isURDFJoint && j.jointType !== 'fixed'; | ||
static get observedAttributes() { | ||
}; | ||
return ['highlight-color', ...super.observedAttributes]; | ||
function findNearestJoint(child) { | ||
} | ||
let curr = child; | ||
while (curr) { | ||
get disableDragging() { return this.hasAttribute('disable-dragging'); } | ||
set disableDragging(val) { val ? this.setAttribute('disable-dragging', !!val) : this.removeAttribute('disable-dragging'); } | ||
if (isJoint(curr)) { | ||
get highlightColor() { return this.getAttribute('highlight-color') || '#FFFFFF'; } | ||
set highlightColor(val) { val ? this.setAttribute('highlight-color', val) : this.removeAttribute('highlight-color'); } | ||
return curr; | ||
constructor(...args) { | ||
} | ||
super(...args); | ||
curr = curr.parent; | ||
// The highlight material | ||
this.highlightMaterial = | ||
new THREE.MeshPhongMaterial({ | ||
shininess: 10, | ||
color: this.highlightColor, | ||
emissive: this.highlightColor, | ||
emissiveIntensity: 0.25, | ||
}); | ||
} | ||
const el = this.renderer.domElement; | ||
return curr; | ||
// Saved mouse data between frames and initial | ||
// click point in space | ||
const mouse = new THREE.Vector2(); | ||
const lastMouse = new THREE.Vector2(); | ||
const clickPoint = new THREE.Vector3(); | ||
}; | ||
// Reuseable variables | ||
const raycaster = new THREE.Raycaster(); | ||
const delta = new THREE.Vector2(); | ||
const plane = new THREE.Plane(); | ||
const line = new THREE.Line3(); | ||
const prevHitPoint = new THREE.Vector3(); | ||
const newHitPoint = new THREE.Vector3(); | ||
const pivotPoint = new THREE.Vector3(); | ||
const tempVector = new THREE.Vector3(); | ||
const tempVector2 = new THREE.Vector3(); | ||
const projectedStartPoint = new THREE.Vector3(); | ||
const projectedEndPoint = new THREE.Vector3(); | ||
const plane = new THREE.Plane(); | ||
class URDFDragControls { | ||
// The joint being manipulated | ||
let dragging = null; | ||
constructor(scene) { | ||
const toMouseCoord = (e, v) => { | ||
this.enabled = true; | ||
this.scene = scene; | ||
this.raycaster = new THREE.Raycaster(); | ||
this.initialGrabPoint = new THREE.Vector3(); | ||
v.x = ((e.pageX - el.offsetLeft) / el.offsetWidth) * 2 - 1; | ||
v.y = -((e.pageY - el.offsetTop) / el.offsetHeight) * 2 + 1; | ||
this.hitDistance = -1; | ||
this.hovered = null; | ||
this.manipulating = null; | ||
}; | ||
} | ||
// Get which part of the robot is hit by the mouse click | ||
const getCollisions = m => { | ||
update() { | ||
if (!this.robot) return []; | ||
const { | ||
raycaster, | ||
hovered, | ||
manipulating, | ||
scene, | ||
} = this; | ||
raycaster.setFromCamera(m, this.camera); | ||
if (manipulating) { | ||
const meshes = []; | ||
this.robot.traverse(c => c.type === 'Mesh' && meshes.push(c)); | ||
return; | ||
return raycaster.intersectObjects(meshes); | ||
} | ||
}; | ||
let hoveredJoint = null; | ||
const intersections = raycaster.intersectObject(scene, true); | ||
if (intersections.length !== 0) { | ||
const isJoint = j => { | ||
const hit = intersections[0]; | ||
this.hitDistance = hit.distance; | ||
hoveredJoint = findNearestJoint(hit.object); | ||
this.initialGrabPoint.copy(hit.point); | ||
return j.isURDFJoint && j.jointType !== 'fixed'; | ||
} | ||
}; | ||
if (hoveredJoint !== hovered) { | ||
// Find the nearest parent that is a joint | ||
const findNearestJoint = m => { | ||
if (hovered) { | ||
let curr = m; | ||
while (curr) { | ||
this.onUnhover(hovered); | ||
if (isJoint(curr)) { | ||
} | ||
break; | ||
this.hovered = hoveredJoint; | ||
} | ||
if (hoveredJoint) { | ||
curr = curr.parent; | ||
this.onHover(hoveredJoint); | ||
} | ||
return curr; | ||
} | ||
}; | ||
} | ||
// Highlight the link geometry under a joint | ||
const highlightLinkGeometry = (m, revert) => { | ||
updateJoint(joint, angle) { | ||
const traverse = c => { | ||
joint.setJointValue(angle); | ||
// Set or revert the highlight color | ||
if (c.type === 'Mesh') { | ||
} | ||
if (revert) { | ||
onDragStart(joint) { | ||
c.material = c.__origMaterial; | ||
delete c.__origMaterial; | ||
} | ||
} else { | ||
onDragEnd(joint) { | ||
c.__origMaterial = c.material; | ||
c.material = this.highlightMaterial; | ||
} | ||
} | ||
onHover(joint) { | ||
} | ||
} | ||
// Look into the children and stop if the next child is | ||
// another joint | ||
if (c === m || !isJoint(c)) { | ||
onUnhover(joint) { | ||
for (let i = 0; i < c.children.length; i++) { | ||
} | ||
traverse(c.children[i]); | ||
getRevoluteDelta(joint, startPoint, endPoint) { | ||
} | ||
// set up the plane | ||
tempVector | ||
.copy(joint.axis) | ||
.transformDirection(joint.matrixWorld) | ||
.normalize(); | ||
pivotPoint | ||
.set(0, 0, 0) | ||
.applyMatrix4(joint.matrixWorld); | ||
plane | ||
.setFromNormalAndCoplanarPoint(tempVector, pivotPoint); | ||
} | ||
// project the drag points onto the plane | ||
plane.projectPoint(startPoint, projectedStartPoint); | ||
plane.projectPoint(endPoint, projectedEndPoint); | ||
}; | ||
// get the directions relative to the pivot | ||
projectedStartPoint.sub(pivotPoint); | ||
projectedEndPoint.sub(pivotPoint); | ||
traverse(m); | ||
tempVector.crossVectors(projectedStartPoint, projectedEndPoint); | ||
}; | ||
const direction = Math.sign(tempVector.dot(plane.normal)); | ||
return direction * projectedEndPoint.angleTo(projectedStartPoint); | ||
const temp = new THREE.Vector3(); | ||
const intersect1 = new THREE.Vector3(); | ||
const intersect2 = new THREE.Vector3(); | ||
} | ||
// Get the changed angle between mouse position 1 and 2 | ||
// when manipulating target | ||
const getAngle = (tg, m1, m2) => { | ||
getPrismaticDelta(joint, startPoint, endPoint) { | ||
// TODO: Why is the constant negated? | ||
plane.normal.copy(tg.axis).transformDirection(tg.matrixWorld).normalize(); | ||
plane.constant = -plane.normal.dot(clickPoint); | ||
tempVector.subVectors(endPoint, startPoint); | ||
plane | ||
.normal | ||
.copy(joint.axis) | ||
.transformDirection(joint.parent.matrixWorld) | ||
.normalize(); | ||
// If the camera is looking at the rotation axis at a skewed angle | ||
temp.copy(this.camera.position).sub(clickPoint).normalize(); | ||
if (Math.abs(temp.dot(plane.normal)) < 0.2) { | ||
return tempVector.dot(plane.normal); | ||
// distance to the clicked point | ||
const dist = temp.copy(clickPoint).sub(this.camera.position).length() * 0.9; | ||
} | ||
// Get the point closest to the original clicked point | ||
// and use that as center of the rotation axis | ||
temp.set(0, 0, 0).applyMatrix4(tg.matrixWorld); | ||
temp.addScaledVector(plane.normal, -plane.distanceToPoint(temp)); | ||
moveRay(toRay) { | ||
// Project out from the camera | ||
raycaster.setFromCamera(m1, this.camera); | ||
intersect1.copy(raycaster.ray.origin).add( | ||
raycaster.ray.direction.normalize().multiplyScalar(dist), | ||
); | ||
intersect1.sub(temp); | ||
const { raycaster, hitDistance, manipulating } = this; | ||
const { ray } = raycaster; | ||
raycaster.setFromCamera(m2, this.camera); | ||
intersect2.copy(raycaster.ray.origin).add( | ||
raycaster.ray.direction.normalize().multiplyScalar(dist), | ||
); | ||
intersect2.sub(temp); | ||
if (manipulating) { | ||
temp.crossVectors(intersect2, intersect1).normalize(); | ||
ray.at(hitDistance, prevHitPoint); | ||
toRay.at(hitDistance, newHitPoint); | ||
// Multiply by a magic number to make it feel good | ||
return temp.dot(plane.normal) * intersect2.angleTo(intersect1) * 2; | ||
let delta = 0; | ||
if (manipulating.jointType === 'revolute' || manipulating.jointType === 'continuous') { | ||
} else { | ||
delta = this.getRevoluteDelta(manipulating, prevHitPoint, newHitPoint); | ||
// Get the point closest to the original clicked point | ||
// and use that as center of the rotation axis | ||
temp.set(0, 0, 0).applyMatrix4(tg.matrixWorld); | ||
temp.addScaledVector(plane.normal, -plane.distanceToPoint(temp)); | ||
} else if (manipulating.jointType === 'prismatic') { | ||
// project onto the plane of rotation | ||
raycaster.setFromCamera(m1, this.camera); | ||
line.start.copy(raycaster.ray.origin); | ||
line.end.copy(raycaster.ray.origin).add(raycaster.ray.direction.normalize().multiplyScalar(1e5)); | ||
plane.intersectLine(line, intersect1); | ||
intersect1.sub(temp); | ||
delta = this.getPrismaticDelta(manipulating, prevHitPoint, newHitPoint); | ||
raycaster.setFromCamera(m2, this.camera); | ||
line.start.copy(raycaster.ray.origin); | ||
line.end.copy(raycaster.ray.origin).add(raycaster.ray.direction.normalize().multiplyScalar(1e5)); | ||
plane.intersectLine(line, intersect2); | ||
intersect2.sub(temp); | ||
} | ||
temp.crossVectors(intersect2, intersect1); | ||
if (delta) { | ||
return Math.sign(temp.dot(plane.normal)) * intersect2.angleTo(intersect1); | ||
this.updateJoint(manipulating, manipulating.angle + delta); | ||
} | ||
}; | ||
} | ||
// Get the amount to move the prismatic joint based on the mouse move | ||
const getMove = (tg, m1, m2) => { | ||
this.raycaster.ray.copy(toRay); | ||
this.update(); | ||
const dist = temp.copy(clickPoint).sub(this.camera.position).length(); | ||
} | ||
raycaster.setFromCamera(m1, this.camera); | ||
raycaster.ray.direction.normalize().multiplyScalar(dist); | ||
intersect1.copy(raycaster.ray.origin).add(raycaster.ray.direction); | ||
setGrabbed(grabbed) { | ||
raycaster.setFromCamera(m2, this.camera); | ||
raycaster.ray.direction.normalize().multiplyScalar(dist); | ||
intersect2.copy(raycaster.ray.origin).add(raycaster.ray.direction); | ||
const { hovered, manipulating } = this; | ||
temp.copy(intersect2).sub(intersect1); | ||
if (grabbed) { | ||
plane.normal.copy(tg.axis).transformDirection(tg.parent.matrixWorld).normalize(); | ||
if (manipulating !== null || hovered === null) { | ||
return temp.length() * -Math.sign(temp.dot(plane.normal)); | ||
return; | ||
} | ||
this.manipulating = hovered; | ||
this.onDragStart(hovered); | ||
} else { | ||
if (this.manipulating === null) { | ||
return; | ||
} | ||
this.onDragEnd(this.manipulating); | ||
this.manipulating = null; | ||
this.update(); | ||
} | ||
} | ||
} | ||
class PointerURDFDragControls extends URDFDragControls { | ||
constructor(scene, camera, domElement) { | ||
super(scene); | ||
this.camera = camera; | ||
this.domElement = domElement; | ||
const raycaster = new THREE.Raycaster(); | ||
const mouse = new THREE.Vector2(); | ||
function updateMouse(e) { | ||
mouse.x = ((e.pageX - domElement.offsetLeft) / domElement.offsetWidth) * 2 - 1; | ||
mouse.y = -((e.pageY - domElement.offsetTop) / domElement.offsetHeight) * 2 + 1; | ||
} | ||
this._mouseDown = e => { | ||
updateMouse(e); | ||
raycaster.setFromCamera(mouse, this.camera); | ||
this.moveRay(raycaster.ray); | ||
this.setGrabbed(true); | ||
}; | ||
el.addEventListener('mousedown', e => { | ||
this._mouseMove = e => { | ||
if (this.disableDragging) return; | ||
updateMouse(e); | ||
raycaster.setFromCamera(mouse, this.camera); | ||
this.moveRay(raycaster.ray); | ||
toMouseCoord(e, mouse); | ||
lastMouse.copy(mouse); | ||
}; | ||
// get the information on the clicked item | ||
// and set the dragged joint | ||
const target = getCollisions(mouse).shift(); | ||
if (target) { | ||
this._mouseUp = e => { | ||
dragging = findNearestJoint(target.object); | ||
updateMouse(e); | ||
raycaster.setFromCamera(mouse, this.camera); | ||
this.moveRay(raycaster.ray); | ||
this.setGrabbed(false); | ||
if (dragging) { | ||
}; | ||
clickPoint.copy(target.point); | ||
this.dispatchEvent(new CustomEvent('manipulate-start', { bubbles: true, cancelable: true, detail: dragging.name })); | ||
this.controls.enabled = false; | ||
domElement.addEventListener('mousedown', this._mouseDown); | ||
domElement.addEventListener('mousemove', this._mouseMove); | ||
domElement.addEventListener('mouseup', this._mouseUp); | ||
} | ||
} | ||
} | ||
getRevoluteDelta(joint, startPoint, endPoint) { | ||
}, true); | ||
const { camera, initialGrabPoint } = this; | ||
let hovered = null; | ||
this._mouseMoveFunc = e => { | ||
// set up the plane | ||
tempVector | ||
.copy(joint.axis) | ||
.transformDirection(joint.matrixWorld) | ||
.normalize(); | ||
pivotPoint | ||
.set(0, 0, 0) | ||
.applyMatrix4(joint.matrixWorld); | ||
plane | ||
.setFromNormalAndCoplanarPoint(tempVector, pivotPoint); | ||
toMouseCoord(e, mouse); | ||
delta.copy(mouse).sub(lastMouse); | ||
tempVector | ||
.copy(camera.position) | ||
.sub(initialGrabPoint) | ||
.normalize(); | ||
// Keep track of the hovered item. If an item is being | ||
// dragged, then it is considered hovered | ||
const wasHovered = hovered; | ||
if (hovered) { | ||
// if looking into the plane of rotation | ||
if (Math.abs(tempVector.dot(plane.normal)) > 0.3) { | ||
hovered = null; | ||
} | ||
return super.getRevoluteDelta(joint, startPoint, endPoint); | ||
if (dragging == null && this.disableDragging === false) { | ||
} else { | ||
const collision = getCollisions(mouse).shift() || null; | ||
const joint = collision && findNearestJoint(collision.object); | ||
if (joint) { | ||
// get the up direction | ||
tempVector.set(0, 1, 0).transformDirection(camera.matrixWorld); | ||
hovered = joint; | ||
// get points projected onto the plane of rotation | ||
plane.projectPoint(startPoint, projectedStartPoint); | ||
plane.projectPoint(endPoint, projectedEndPoint); | ||
} | ||
tempVector.set(0, 0, -1).transformDirection(camera.matrixWorld); | ||
tempVector.cross(plane.normal); | ||
tempVector2.subVectors(endPoint, startPoint); | ||
} else if (dragging) { | ||
return tempVector.dot(tempVector2); | ||
hovered = dragging; | ||
} | ||
} | ||
} | ||
// Highlight the meshes and broadcast events if the hovered item changed | ||
if (hovered !== wasHovered) { | ||
dispose() { | ||
if (wasHovered) { | ||
const { domElement } = this; | ||
domElement.removeEventListener('mousedown', this._mouseDown); | ||
domElement.removeEventListener('mousemove', this._mouseMove); | ||
domElement.removeEventListener('mouseup', this._mouseUp); | ||
highlightLinkGeometry(wasHovered, true); | ||
this.dispatchEvent(new CustomEvent('joint-mouseout', { bubbles: true, cancelable: true, detail: wasHovered.name })); | ||
} | ||
} | ||
} | ||
if (hovered) { | ||
// urdf-manipulator element | ||
// Displays a URDF model that can be manipulated with the mouse | ||
highlightLinkGeometry(hovered, false); | ||
this.dispatchEvent(new CustomEvent('joint-mouseover', { bubbles: true, cancelable: true, detail: hovered.name })); | ||
// Events | ||
// joint-mouseover: Fired when a joint is hovered over | ||
// joint-mouseout: Fired when a joint is no longer hovered over | ||
// manipulate-start: Fires when a joint is manipulated | ||
// manipulate-end: Fires when a joint is done being manipulated | ||
class URDFManipulator extends URDFViewer__default['default'] { | ||
} | ||
static get observedAttributes() { | ||
this.redraw(); | ||
return ['highlight-color', ...super.observedAttributes]; | ||
} | ||
} | ||
// Apply the manipulation | ||
if (dragging !== null) { | ||
get disableDragging() { return this.hasAttribute('disable-dragging'); } | ||
set disableDragging(val) { val ? this.setAttribute('disable-dragging', !!val) : this.removeAttribute('disable-dragging'); } | ||
let delta = null; | ||
if (dragging.jointType === 'revolute' || dragging.jointType === 'continuous') { | ||
get highlightColor() { return this.getAttribute('highlight-color') || '#FFFFFF'; } | ||
set highlightColor(val) { val ? this.setAttribute('highlight-color', val) : this.removeAttribute('highlight-color'); } | ||
delta = getAngle(dragging, mouse, lastMouse); | ||
constructor(...args) { | ||
} else if (dragging.jointType === 'prismatic') { | ||
super(...args); | ||
delta = getMove(dragging, mouse, lastMouse); | ||
// The highlight material | ||
this.highlightMaterial = | ||
new THREE.MeshPhongMaterial({ | ||
shininess: 10, | ||
color: this.highlightColor, | ||
emissive: this.highlightColor, | ||
emissiveIntensity: 0.25, | ||
}); | ||
} else { | ||
const isJoint = j => { | ||
// Not supported | ||
return j.isURDFJoint && j.jointType !== 'fixed'; | ||
}; | ||
// Highlight the link geometry under a joint | ||
const highlightLinkGeometry = (m, revert) => { | ||
const traverse = c => { | ||
// Set or revert the highlight color | ||
if (c.type === 'Mesh') { | ||
if (revert) { | ||
c.material = c.__origMaterial; | ||
delete c.__origMaterial; | ||
} else { | ||
c.__origMaterial = c.material; | ||
c.material = this.highlightMaterial; | ||
} | ||
} | ||
if (delta) { | ||
// Look into the children and stop if the next child is | ||
// another joint | ||
if (c === m || !isJoint(c)) { | ||
this.setJointValue(dragging.name, dragging.angle + delta); | ||
for (let i = 0; i < c.children.length; i++) { | ||
traverse(c.children[i]); | ||
} | ||
} | ||
} | ||
}; | ||
lastMouse.copy(mouse); | ||
traverse(m); | ||
}; | ||
// Clean up | ||
this._mouseUpFunc = e => { | ||
const el = this.renderer.domElement; | ||
if (dragging) { | ||
const dragControls = new PointerURDFDragControls(this.scene, this.camera, el); | ||
dragControls.onDragStart = joint => { | ||
this.dispatchEvent(new CustomEvent('manipulate-end', { bubbles: true, cancelable: true, detail: dragging.name })); | ||
dragging = null; | ||
this.controls.enabled = true; | ||
this.dispatchEvent(new CustomEvent('manipulate-start', { bubbles: true, cancelable: true, detail: joint.name })); | ||
this.controls.enabled = false; | ||
this.redraw(); | ||
} | ||
}; | ||
dragControls.onDragEnd = joint => { | ||
this.dispatchEvent(new CustomEvent('manipulate-end', { bubbles: true, cancelable: true, detail: joint.name })); | ||
this.controls.enabled = true; | ||
this.redraw(); | ||
}; | ||
dragControls.updateJoint = (joint, angle) => { | ||
} | ||
this.setJointValue(joint.name, angle); | ||
connectedCallback() { | ||
}; | ||
dragControls.onHover = joint => { | ||
super.connectedCallback(); | ||
window.addEventListener('mousemove', this._mouseMoveFunc, true); | ||
window.addEventListener('mouseup', this._mouseUpFunc, true); | ||
highlightLinkGeometry(joint, false); | ||
this.dispatchEvent(new CustomEvent('joint-mouseout', { bubbles: true, cancelable: true, detail: joint.name })); | ||
this.redraw(); | ||
}; | ||
dragControls.onUnhover = joint => { | ||
highlightLinkGeometry(joint, true); | ||
this.dispatchEvent(new CustomEvent('joint-mouseover', { bubbles: true, cancelable: true, detail: joint.name })); | ||
this.redraw(); | ||
}; | ||
this.dragControls = dragControls; | ||
} | ||
@@ -375,4 +465,3 @@ | ||
super.disconnectedCallback(); | ||
window.removeEventListener('mousemove', this._mouseMoveFunc, true); | ||
window.removeEventListener('mouseup', this._mouseUpFunc, true); | ||
this.dragControls.dispose(); | ||
@@ -379,0 +468,0 @@ } |
@@ -7,3 +7,3 @@ (function (global, factory) { | ||
class URDFCollider extends THREE.Object3D { | ||
class URDFBase extends THREE.Object3D { | ||
@@ -13,5 +13,4 @@ constructor(...args) { | ||
super(...args); | ||
this.isURDFCollider = true; | ||
this.type = 'URDFCollider'; | ||
this.urdfNode = null; | ||
this.urdfName = ''; | ||
@@ -23,3 +22,5 @@ } | ||
super.copy(source, recursive); | ||
this.urdfNode = source.urdfNode; | ||
this.urdfName = source.urdfName; | ||
@@ -32,3 +33,3 @@ return this; | ||
class URDFVisual extends THREE.Object3D { | ||
class URDFCollider extends URDFBase { | ||
@@ -38,15 +39,17 @@ constructor(...args) { | ||
super(...args); | ||
this.isURDFVisual = true; | ||
this.type = 'URDFVisual'; | ||
this.urdfNode = null; | ||
this.isURDFCollider = true; | ||
this.type = 'URDFCollider'; | ||
} | ||
copy(source, recursive) { | ||
} | ||
super.copy(source, recursive); | ||
this.urdfNode = source.urdfNode; | ||
class URDFVisual extends URDFBase { | ||
return this; | ||
constructor(...args) { | ||
super(...args); | ||
this.isURDFVisual = true; | ||
this.type = 'URDFVisual'; | ||
} | ||
@@ -56,3 +59,3 @@ | ||
class URDFLink extends THREE.Object3D { | ||
class URDFLink extends URDFBase { | ||
@@ -64,18 +67,8 @@ constructor(...args) { | ||
this.type = 'URDFLink'; | ||
this.urdfNode = null; | ||
} | ||
copy(source, recursive) { | ||
super.copy(source, recursive); | ||
this.urdfNode = source.urdfNode; | ||
return this; | ||
} | ||
} | ||
class URDFJoint extends THREE.Object3D { | ||
class URDFJoint extends URDFBase { | ||
@@ -130,3 +123,2 @@ get jointType() { | ||
this.urdfNode = null; | ||
this.jointValue = null; | ||
@@ -148,3 +140,2 @@ this.jointType = 'fixed'; | ||
this.urdfNode = source.urdfNode; | ||
this.jointType = source.jointType; | ||
@@ -189,3 +180,3 @@ this.axis = source.axis ? source.axis.clone() : null; | ||
if (angle == null) return false; | ||
if (angle === this.angle) return false; | ||
if (angle === this.jointValue[0]) return false; | ||
@@ -199,6 +190,5 @@ if (!this.ignoreLimits && this.jointType === 'revolute') { | ||
// FromAxisAngle seems to rotate the opposite of the | ||
// expected angle for URDF, so negate it here | ||
const delta = new THREE.Quaternion().setFromAxisAngle(this.axis, angle); | ||
this.quaternion.multiplyQuaternions(this.origQuaternion, delta); | ||
this.quaternion | ||
.setFromAxisAngle(this.axis, angle) | ||
.premultiply(this.origQuaternion); | ||
@@ -223,3 +213,3 @@ if (this.jointValue[0] !== angle) { | ||
if (pos == null) return false; | ||
if (pos === this.angle) return false; | ||
if (pos === this.jointValue[0]) return false; | ||
@@ -296,23 +286,23 @@ if (!this.ignoreLimits) { | ||
if (c.isURDFJoint && c.name in source.joints) { | ||
if (c.isURDFJoint && c.urdfName in source.joints) { | ||
this.joints[c.name] = c; | ||
this.joints[c.urdfName] = c; | ||
} | ||
if (c.isURDFLink && c.name in source.links) { | ||
if (c.isURDFLink && c.urdfName in source.links) { | ||
this.links[c.name] = c; | ||
this.links[c.urdfName] = c; | ||
} | ||
if (c.isURDFCollider && c.name in source.colliders) { | ||
if (c.isURDFCollider && c.urdfName in source.colliders) { | ||
this.colliders[c.name] = c; | ||
this.colliders[c.urdfName] = c; | ||
} | ||
if (c.isURDFVisual && c.name in source.visual) { | ||
if (c.isURDFVisual && c.urdfName in source.visual) { | ||
this.visual[c.name] = c; | ||
this.visual[c.urdfName] = c; | ||
@@ -466,8 +456,16 @@ } | ||
if (onProgress) { | ||
if (res.ok) { | ||
onProgress(null); | ||
if (onProgress) { | ||
onProgress(null); | ||
} | ||
return res.text(); | ||
} else { | ||
throw new Error(`URDFLoader: Failed to load url '${ urdfPath }' with error code ${ res.status } : ${ res.statusText }.`); | ||
} | ||
return res.text(); | ||
@@ -545,2 +543,6 @@ }) | ||
} else if (packages instanceof Function) { | ||
return packages(targetPkg) + '/' + relPath; | ||
} else if (typeof packages === 'object') { | ||
@@ -652,2 +654,3 @@ | ||
obj.name = joint.getAttribute('name'); | ||
obj.urdfName = obj.name; | ||
obj.jointType = jointType; | ||
@@ -718,2 +721,3 @@ | ||
target.name = link.getAttribute('name'); | ||
target.urdfName = target.name; | ||
target.urdfNode = link; | ||
@@ -733,2 +737,3 @@ | ||
v.name = name; | ||
v.urdfName = name; | ||
visualMap[name] = v; | ||
@@ -754,2 +759,3 @@ | ||
c.name = name; | ||
c.urdfName = name; | ||
colliderMap[name] = c; | ||
@@ -756,0 +762,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
257404
17
2880
542