Reputation: 331
I'm trying to find the distance that the mouse has traveled along a normal vector.
The idea is to move a set of vertices within an object along the intersecting face's normal vector.
Currently, I have an onmousedown event handler that finds the intersecting face, adjacent faces with the same normal, and the vertices associated to those faces. I also have an onmousemove event handler that moves the vertices along the normal.
Right now, the onmousemove just moves the vertices 1 unit along the face normal every time the event is fired. I'd like them to move with the mouse.
The code that I am working off of came largely from the three.js editor. Any help is very much appreciated, thanks!
var object; // Set outside this code
var camera; // Set outside this code
var viewport; // Set outside this code
var raycaster = new THREE.Raycaster();
var point = new THREE.Vector2();
var mouse = new THREE.Vector2();
var _dragging = false;
var faces = [];
var vertices = [];
function onMouseDown(event) {
if (object === undefined || _dragging === true) {
return;
}
event.preventDefault();
event.stopPropagation();
var intersect = getIntersects(event, object)[0];
if (intersect && intersect.face) {
faces = getAdjacentNormalFaces(intersect.object.geometry, intersect.face);
vertices = getFaceVertices(intersect.object.geometry, self.faces);
}
_dragging = true;
}
function onMouseMove(event) {
if (object === undefined || vertices.length === 0 || _dragging === false) {
return;
}
event.preventDefault();
event.stopPropagation();
var normal = faces[0].normal;
/*
* Get the distance to move the vertices
*/
var distance = 1;
var i;
for (i = 0; i < self.vertices.length; i++) {
self.vertices[i].x += (normal.x * distance);
self.vertices[i].y += (normal.y * distance);
self.vertices[i].z += (normal.z * distance);
}
object.geometry.verticesNeedUpdate = true;
object.geometry.computeBoundingBox();
object.geometry.computeBoundingSphere();
}
var getIntersects = function (event, object) {
var rect = viewport.getBoundingClientRect();
point.fromArray([
( event.clientX - rect.left ) / rect.width,
( event.clientY - rect.top ) / rect.height
]);
mouse.set(( point.x * 2 ) - 1, -( point.y * 2 ) + 1);
raycaster.setFromCamera(mouse, camera);
if (object instanceof Array) {
return raycaster.intersectObjects(object);
}
return raycaster.intersectObject(object);
};
var getAdjacentNormalFaces = function (geometry, face) {
// Returns an array of all faces that are adjacent and share the same normal vector
};
var getFaceVertices = function (geometry, faces) {
// Returns an array of vertices that belong to the array of faces
};
Update: As a summary... I have the event handlers, the set of vertices that need to be moved and the normal vector that the vertices should be moved on. What I need is the offset distance that the vertices should be moved based on where the mouse is.
My first thought is to create a plane perpendicular to the normal vector and track the mouse position on that plane. However, I am not sure 1. how to create the perpendicular plane where the largest side is visible to the camera and 2. how to translate the x/y coordinates of the mouse on the plane to the distance the vertices should be moved.
Upvotes: 2
Views: 1574
Reputation: 331
The way I solved this is to map the zero and normal points on the 2D plane and then use the inverse slope to find the perpendicular line that intersects the normal line. I can then use the starting point and the point of intersection to find the distance the mouse moved. I also had to scale the final distance using the camera.
For a quick reference:
// linear slope/intercept: y = mx + b
// solve for b: b = y - mx
// solve for m: (y2 - y1) / (x2 - x1)
// get inverse slope: -1 / m
// get intersect point: (b2 - b1) / (m1 - m2)
There may be an easier way but this is what I did and hopefully it helps others:
On Mousedown
Project the center (0,0,0) vector, the face normal vector and an arbitrary 1 unit vector (1,0,0) onto the camera and get the screen position of the three points
var zero2D = toScreenPosition(0, 0, 0);
var one2D = toScreenPosition(1, 0, 0);
var normal2D = toScreenPosition(intersect.face.normal.x, intersect.face.normal.y, intersect.face.normal.z);
/ ***** /
var toScreenPosition = function (x, y, z) {
var rect = viewport.getBoundingClientRect();
var point = new THREE.Vector2();
screenPositionVector.set(x || 0, y || 0, z || 0);
screenPositionVector.project(camera);
point.set((screenPositionVector.x + 1) / 2 * rect.width, -(screenPositionVector.y - 1) / 2 * rect.height);
return point;
};
Store the mouse starting point and the x direction of the normal (1 or -1).
start2D.set(event.clientX, event.clientY);
normalDir = zero2D.x < normal2D.x ? 1 : -1;
Store the slope and inverse slope of the zero/normal line.
slope = (normal2D.y - zero2D.y) / (normal2D.x - zero2D.x); // TODO: Handle zero slope
inverseSlope = -1 / slope; // TODO: If slope is 0, inverse is infinity
Store the y intercept of the normal line based on the mouse coordinates.
startingYIntercept = event.clientY - (slope * event.clientX);
Use the zero2D and one2D point to find the camera scale. The camera scale is the distance between the two 2D points divided by the distance between the two 3D points (1).
cameraScale = one2D.distanceTo(zero2D);
For better accuracy, we are going to move the vertices based on total movement, not the delta between event handler calls. Because of this, we need to track the starting position of all the vertices.
startingVertices = [];
var i;
for (i = 0; i < vertices.length; i++) {
startingVertices.push({x: vertices[i].x, y: vertices[i].y, z: vertices[i].z});
}
On Mousemove
Using the mouse position and the inverse slope, find the perpendicular line's y intercept.
var endingYIntercept = event.clientY - (inverseSlope * event.clientX);
Use the intercept equation to find the x location where the normal line and perpendicular line intercept.
var endingX = (endingYIntercept - startingYIntercept) / (slope / inverseSlope);
Plug x back in to find the y point. Since the lines intercept at x, you can use either the normal line or perpendicular line. Set the end point based on this.
var endingY = (slope * endingX) + startingYIntercept;
end2D.set(endingX, endingY);
Find the distance between the points and divide by the camera scale.
var distance = end2D.distanceTo(start2D) / cameraScale;
If the normal is in the opposite direction of the mouse movement, multiply distance by -1.
if ((normalDir > 0 && endingX < start2D.x) || (normalDir < 0 && endingX > start2D.x)) {
distance = distance * -1;
}
Since we are moving the vertices by a total distance and not the delta between event handlers, the vertex update code is a little different.
var i;
for (i = 0; i < self.vertices.length; i++) {
vertices[i].x = startingVertices[i].x + (normal.x * distance);
vertices[i].y = startingVertices[i].y + (normal.y * distance);
vertices[i].z = startingVertices[i].z + (normal.z * distance);
}
Extra Credit On Mouseup
When the vertices are moved, the geometry's center is not changed and needs to be updated. To update the center, I can call geometry.center(), however, in Three.js, the geometry's position is based off of its center so this will effectively move the center and the position of the geometry in the opposite direction at half the distance of the vertex move. I don't want this, I want the geometry to stay in the same position when I move the vertices. To do this, I take the first vertex's ending position minus its start position divided by 2 and add that vector to the geometry's position. I then recenter the geometry.
if (_dragging && self.vertices.length > 0) {
offset.set(self.vertices[0].x - startingVertices[0].x, self.vertices[0].y - startingVertices[0].y, self.vertices[0].z - startingVertices[0].z);
offset.divideScalar(2);
object.position.add(offset);
object.geometry.center();
}
All Together
var object; // Set outside this code
var camera; // Set outside this code
var viewport; // Set outside this code
var raycaster = new THREE.Raycaster();
var point = new THREE.Vector2();
var mouse = new THREE.Vector2();
var _dragging = false;
var faces = [];
var vertices = [];
var startingVertices = [];
var slope = 0;
var inverseSlope;
var startingYIntercept = 0;
var normalDir = 1;
var cameraScale = 1;
var start2D = new THREE.Vector2();
var end2D = new THREE.Vector2();
var offset = new THREE.Vector3();
var onMouseDown = function (event) {
if (object === undefined || _dragging === true) {
return;
}
event.preventDefault();
event.stopPropagation();
var intersect = getIntersects(event, object)[0];
if (intersect && intersect.face) {
var zero2D = toScreenPosition(0, 0, 0);
var one2D = toScreenPosition(1, 0, 0);
var normal2D = toScreenPosition(intersect.face.normal.x, intersect.face.normal.y, intersect.face.normal.z);
start2D.set(event.clientX, event.clientY);
normalDir = zero2D.x < normal2D.x ? 1 : -1;
slope = (normal2D.y - zero2D.y) / (normal2D.x - zero2D.x); // TODO: Handle zero slope
inverseSlope = -1 / slope; // TODO: If slope is 0, inverse is infinity
startingYIntercept = event.clientY - (slope * event.clientX);
cameraScale = one2D.distanceTo(zero2D);
faces = getAdjacentNormalFaces(intersect.object.geometry, intersect.face);
vertices = getFaceVertices(intersect.object.geometry, self.faces);
startingVertices = [];
var i;
for (i = 0; i < vertices.length; i++) {
startingVertices.push({x: vertices[i].x, y: vertices[i].y, z: vertices[i].z});
}
}
_dragging = true;
}
var onMouseMove = function (event) {
if (object === undefined || vertices.length === 0 || _dragging === false) {
return;
}
event.preventDefault();
event.stopPropagation();
var normal = faces[0].normal;
var endingYIntercept = event.clientY - (inverseSlope * event.clientX);
var endingX = (endingYIntercept - startingYIntercept) / (slope / inverseSlope);
var endingY = (slope * endingX) + startingYIntercept;
end2D.set(endingX, endingY);
var distance = end2D.distanceTo(start2D) / cameraScale;
if ((normalDir > 0 && endingX < start2D.x) || (normalDir < 0 && endingX > start2D.x)) {
distance = distance * -1;
}
var i;
for (i = 0; i < self.vertices.length; i++) {
vertices[i].x = startingVertices[i].x + (normal.x * distance);
vertices[i].y = startingVertices[i].y + (normal.y * distance);
vertices[i].z = startingVertices[i].z + (normal.z * distance);
}
object.geometry.verticesNeedUpdate = true;
object.geometry.computeBoundingBox();
object.geometry.computeBoundingSphere();
}
var onMouseUp = function (event) {
if (_dragging && vertices.length > 0) {
offset.set(vertices[0].x - startingVertices[0].x, vertices[0].y - startingVertices[0].y, vertices[0].z - startingVertices[0].z);
offset.divideScalar(2);
object.position.add(offset);
object.geometry.center();
}
}
var getIntersects = function (event, object) {
var rect = viewport.getBoundingClientRect();
point.fromArray([
( event.clientX - rect.left ) / rect.width,
( event.clientY - rect.top ) / rect.height
]);
mouse.set(( point.x * 2 ) - 1, -( point.y * 2 ) + 1);
raycaster.setFromCamera(mouse, camera);
if (object instanceof Array) {
return raycaster.intersectObjects(object);
}
return raycaster.intersectObject(object);
};
var toScreenPosition = function (x, y, z) {
var rect = viewport.getBoundingClientRect();
var point = new THREE.Vector2();
screenPositionVector.set(x || 0, y || 0, z || 0);
screenPositionVector.project(camera);
point.set((screenPositionVector.x + 1) / 2 * rect.width, -(screenPositionVector.y - 1) / 2 * rect.height);
return point;
};
var getAdjacentNormalFaces = function (geometry, face) {
// Returns an array of all faces that are adjacent and share the same normal vector
};
var getFaceVertices = function (geometry, faces) {
// Returns an array of vertices that belong to the array of faces
};
Upvotes: 3
Reputation: 567
You could achieve two ways, on mouse move or in animationframe.
onmouseMove(){
mouseX = ( event.clientX - windowHalfX ) / resistanceh;
mouseY = ( event.clientY - windowHalfY ) / resistancew;
var raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects(objects);
if ( intersects.length > 0 ) {
if(mousedown){
//do your thing
}
or in your animation updating these values is more accurate I found.
AnimationFrame(){
mouseX = ( event.clientX - windowHalfX ) / resistanceh;
mouseY = ( event.clientY - windowHalfY ) / resistancew;
Upvotes: 0