javascript - Find distance the mouse has moved along a vector in Three.js -
i'm trying find distance mouse has traveled along normal vector.
the idea move set of vertices within object along intersecting face's normal vector.
currently, have onmousedown event handler finds intersecting face, adjacent faces same normal, , vertices associated faces. have onmousemove event handler moves vertices along normal.
right now, onmousemove moves vertices 1 unit along face normal every time event fired. i'd them move mouse.
the code working off of came largely three.js editor. appreciated, thanks!
var object; // set outside code var camera; // set outside code var viewport; // set outside 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; /* * distance move vertices */ var distance = 1; var i; (i = 0; < 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 array of faces adjacent , share same normal vector }; var getfacevertices = function (geometry, faces) { // returns array of vertices belong array of faces }; update: summary... have event handlers, set of vertices need moved , normal vector vertices should moved on. need offset distance vertices should moved based on mouse is.
my first thought create plane perpendicular normal vector , track mouse position on plane. however, not sure 1. how create perpendicular plane largest side visible camera , 2. how translate x/y coordinates of mouse on plane distance vertices should moved.
the way solved map 0 , normal points on 2d plane , use inverse slope find perpendicular line intersects normal line. can use starting point , point of intersection find distance mouse moved. had scale final distance using camera.
for quick reference:
// linear slope/intercept: y = mx + b // solve b: b = y - mx // solve m: (y2 - y1) / (x2 - x1) // inverse slope: -1 / m // intersect point: (b2 - b1) / (m1 - m2) there may easier way did , helps others:
on mousedown
project center (0,0,0) vector, face normal vector , arbitrary 1 unit vector (1,0,0) onto camera , screen position of 3 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 mouse starting point , x direction of normal (1 or -1).
start2d.set(event.clientx, event.clienty); normaldir = zero2d.x < normal2d.x ? 1 : -1;store slope , inverse slope of zero/normal line.
slope = (normal2d.y - zero2d.y) / (normal2d.x - zero2d.x); // todo: handle 0 slope inverseslope = -1 / slope; // todo: if slope 0, inverse infinitystore y intercept of normal line based on mouse coordinates.
startingyintercept = event.clienty - (slope * event.clientx);use zero2d , one2d point find camera scale. camera scale distance between 2 2d points divided distance between 2 3d points (1).
camerascale = one2d.distanceto(zero2d);for better accuracy, going move vertices based on total movement, not delta between event handler calls. because of this, need track starting position of vertices.
startingvertices = []; var i; (i = 0; < vertices.length; i++) { startingvertices.push({x: vertices[i].x, y: vertices[i].y, z: vertices[i].z}); }
on mousemove
using mouse position , inverse slope, find perpendicular line's y intercept.
var endingyintercept = event.clienty - (inverseslope * event.clientx);use intercept equation find x location normal line , perpendicular line intercept.
var endingx = (endingyintercept - startingyintercept) / (slope / inverseslope);plug x in find y point. since lines intercept @ x, can use either normal line or perpendicular line. set end point based on this.
var endingy = (slope * endingx) + startingyintercept; end2d.set(endingx, endingy);find distance between points , divide camera scale.
var distance = end2d.distanceto(start2d) / camerascale;if normal in opposite direction of mouse movement, multiply distance -1.
if ((normaldir > 0 && endingx < start2d.x) || (normaldir < 0 && endingx > start2d.x)) { distance = distance * -1; }since moving vertices total distance , not delta between event handlers, vertex update code little different.
var i; (i = 0; < 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 vertices moved, geometry's center not changed , needs updated. update center, can call geometry.center(), however, in three.js, geometry's position based off of center move center and position of geometry in opposite direction @ half distance of vertex move. don't want this, want geometry stay in same position when move vertices. this, take first vertex's ending position minus start position divided 2 , add vector geometry's position. recenter 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 code var camera; // set outside code var viewport; // set outside 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 0 slope inverseslope = -1 / slope; // todo: if slope 0, inverse 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; (i = 0; < 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; (i = 0; < 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 array of faces adjacent , share same normal vector }; var getfacevertices = function (geometry, faces) { // returns array of vertices belong array of faces };
Comments
Post a Comment