330 lines
7.3 KiB
JavaScript
330 lines
7.3 KiB
JavaScript
import { Raycaster, Vector3, Plane, Vector2 } from 'three';
|
|
|
|
// Find the nearest parent that is a joint
|
|
function isJoint(j) {
|
|
|
|
return j.isURDFJoint && j.jointType !== 'fixed';
|
|
|
|
};
|
|
|
|
function findNearestJoint(child) {
|
|
|
|
let curr = child;
|
|
while (curr) {
|
|
|
|
if (isJoint(curr)) {
|
|
|
|
return curr;
|
|
|
|
}
|
|
|
|
curr = curr.parent;
|
|
|
|
}
|
|
|
|
return curr;
|
|
|
|
};
|
|
|
|
const prevHitPoint = new Vector3();
|
|
const newHitPoint = new Vector3();
|
|
const pivotPoint = new Vector3();
|
|
const tempVector = new Vector3();
|
|
const tempVector2 = new Vector3();
|
|
const projectedStartPoint = new Vector3();
|
|
const projectedEndPoint = new Vector3();
|
|
const plane = new Plane();
|
|
export class URDFDragControls {
|
|
|
|
constructor(scene) {
|
|
|
|
this.enabled = true;
|
|
this.scene = scene;
|
|
this.raycaster = new Raycaster();
|
|
this.initialGrabPoint = new Vector3();
|
|
|
|
this.hitDistance = -1;
|
|
this.hovered = null;
|
|
this.manipulating = null;
|
|
|
|
}
|
|
|
|
update() {
|
|
|
|
const {
|
|
raycaster,
|
|
hovered,
|
|
manipulating,
|
|
scene,
|
|
} = this;
|
|
|
|
if (manipulating) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let hoveredJoint = null;
|
|
const intersections = raycaster.intersectObject(scene, true);
|
|
if (intersections.length !== 0) {
|
|
|
|
const hit = intersections[0];
|
|
this.hitDistance = hit.distance;
|
|
hoveredJoint = findNearestJoint(hit.object);
|
|
this.initialGrabPoint.copy(hit.point);
|
|
|
|
}
|
|
|
|
if (hoveredJoint !== hovered) {
|
|
|
|
if (hovered) {
|
|
|
|
this.onUnhover(hovered);
|
|
|
|
}
|
|
|
|
this.hovered = hoveredJoint;
|
|
|
|
if (hoveredJoint) {
|
|
|
|
this.onHover(hoveredJoint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
updateJoint(joint, angle) {
|
|
|
|
joint.setJointValue(angle);
|
|
|
|
}
|
|
|
|
onDragStart(joint) {
|
|
|
|
}
|
|
|
|
onDragEnd(joint) {
|
|
|
|
}
|
|
|
|
onHover(joint) {
|
|
|
|
}
|
|
|
|
onUnhover(joint) {
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
tempVector.crossVectors(projectedStartPoint, projectedEndPoint);
|
|
|
|
const direction = Math.sign(tempVector.dot(plane.normal));
|
|
return direction * projectedEndPoint.angleTo(projectedStartPoint);
|
|
|
|
}
|
|
|
|
getPrismaticDelta(joint, startPoint, endPoint) {
|
|
|
|
tempVector.subVectors(endPoint, startPoint);
|
|
plane
|
|
.normal
|
|
.copy(joint.axis)
|
|
.transformDirection(joint.parent.matrixWorld)
|
|
.normalize();
|
|
|
|
return tempVector.dot(plane.normal);
|
|
|
|
}
|
|
|
|
moveRay(toRay) {
|
|
|
|
const { raycaster, hitDistance, manipulating } = this;
|
|
const { ray } = raycaster;
|
|
|
|
if (manipulating) {
|
|
|
|
ray.at(hitDistance, prevHitPoint);
|
|
toRay.at(hitDistance, newHitPoint);
|
|
|
|
let delta = 0;
|
|
if (manipulating.jointType === 'revolute' || manipulating.jointType === 'continuous') {
|
|
|
|
delta = this.getRevoluteDelta(manipulating, prevHitPoint, newHitPoint);
|
|
|
|
} else if (manipulating.jointType === 'prismatic') {
|
|
|
|
delta = this.getPrismaticDelta(manipulating, prevHitPoint, newHitPoint);
|
|
|
|
}
|
|
|
|
if (delta) {
|
|
|
|
this.updateJoint(manipulating, manipulating.angle + delta);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.raycaster.ray.copy(toRay);
|
|
this.update();
|
|
|
|
}
|
|
|
|
setGrabbed(grabbed) {
|
|
|
|
const { hovered, manipulating } = this;
|
|
|
|
if (grabbed) {
|
|
|
|
if (manipulating !== null || hovered === null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.manipulating = hovered;
|
|
this.onDragStart(hovered);
|
|
|
|
} else {
|
|
|
|
if (this.manipulating === null) {
|
|
return;
|
|
}
|
|
|
|
this.onDragEnd(this.manipulating);
|
|
this.manipulating = null;
|
|
this.update();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
export class PointerURDFDragControls extends URDFDragControls {
|
|
|
|
constructor(scene, camera, domElement) {
|
|
|
|
super(scene);
|
|
this.camera = camera;
|
|
this.domElement = domElement;
|
|
|
|
const raycaster = new Raycaster();
|
|
const mouse = new Vector2();
|
|
|
|
function updateMouse(e) {
|
|
|
|
const rect = domElement.getBoundingClientRect();
|
|
mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
|
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
|
|
|
}
|
|
|
|
this._mouseDown = e => {
|
|
|
|
updateMouse(e);
|
|
raycaster.setFromCamera(mouse, this.camera);
|
|
this.moveRay(raycaster.ray);
|
|
this.setGrabbed(true);
|
|
|
|
};
|
|
|
|
this._mouseMove = e => {
|
|
|
|
updateMouse(e);
|
|
raycaster.setFromCamera(mouse, this.camera);
|
|
this.moveRay(raycaster.ray);
|
|
|
|
};
|
|
|
|
this._mouseUp = e => {
|
|
|
|
updateMouse(e);
|
|
raycaster.setFromCamera(mouse, this.camera);
|
|
this.moveRay(raycaster.ray);
|
|
this.setGrabbed(false);
|
|
|
|
};
|
|
|
|
domElement.addEventListener('mousedown', this._mouseDown);
|
|
domElement.addEventListener('mousemove', this._mouseMove);
|
|
domElement.addEventListener('mouseup', this._mouseUp);
|
|
|
|
}
|
|
|
|
getRevoluteDelta(joint, startPoint, endPoint) {
|
|
|
|
const { camera, initialGrabPoint } = this;
|
|
|
|
// set up the plane
|
|
tempVector
|
|
.copy(joint.axis)
|
|
.transformDirection(joint.matrixWorld)
|
|
.normalize();
|
|
pivotPoint
|
|
.set(0, 0, 0)
|
|
.applyMatrix4(joint.matrixWorld);
|
|
plane
|
|
.setFromNormalAndCoplanarPoint(tempVector, pivotPoint);
|
|
|
|
tempVector
|
|
.copy(camera.position)
|
|
.sub(initialGrabPoint)
|
|
.normalize();
|
|
|
|
// if looking into the plane of rotation
|
|
if (Math.abs(tempVector.dot(plane.normal)) > 0.3) {
|
|
|
|
return super.getRevoluteDelta(joint, startPoint, endPoint);
|
|
|
|
} else {
|
|
|
|
// get the up direction
|
|
tempVector.set(0, 1, 0).transformDirection(camera.matrixWorld);
|
|
|
|
// 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);
|
|
|
|
return tempVector.dot(tempVector2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dispose() {
|
|
|
|
const { domElement } = this;
|
|
domElement.removeEventListener('mousedown', this._mouseDown);
|
|
domElement.removeEventListener('mousemove', this._mouseMove);
|
|
domElement.removeEventListener('mouseup', this._mouseUp);
|
|
|
|
}
|
|
|
|
} |