Determine the distance traveled by the mouse along a vector using Three.js

I am currently working on a project that involves determining the distance traveled by a mouse along a normal vector.

The goal is to adjust a group of vertices within an object by moving them along the normal vector of an intersecting face.

My current setup includes an event handler for when the mouse is pressed down, which identifies the intersecting face, adjacent faces with similar normals, and the associated vertices. Additionally, I have an event handler for when the mouse moves, which moves the vertices along the normal vector.

At the moment, the movement made by the vertices when the mouse moves is fixed at 1 unit along the face's normal vector. I would like for the vertices to move in accordance with the mouse's position.

I took inspiration from code found in the three.js editor. Any assistance provided would be greatly appreciated. Thank you!

var object; // Defined outside this code
var camera; // Defined outside this code
var viewport; // Defined 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;

    /*
     * Calculate the distance by which the vertices should be moved
     */
    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: In essence... I have the necessary event handlers, a set of vertices to be moved, and the normal vector along which they should be moved. What I require now is the offset distance by which the vertices should be adjusted based on the mouse's position.

My initial idea is to create a plane perpendicular to the normal vector and monitor the mouse's position on that plane. However, I am unsure about how to: 1. create the perpendicular plane with the largest side visible to the camera, and 2. translate the x/y coordinates of the mouse on the plane into the distance by which the vertices should be moved.

Answer №1

To find the distance the mouse moved, I mapped zero and normal points on the 2D plane and used inverse slope to determine the perpendicular line intersecting the normal line. By calculating the starting point and intersection point, I could calculate the movement distance of the mouse while also factoring in camera scaling.

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)

This method might not be the simplest, but it's what I utilized. Hopefully, it proves instructive:

On Mousedown

  1. Calculate screen positions for center (0,0,0) vector, face normal vector, and an arbitrary unit vector (1,0,0) using the camera projection function.

    // Function to calculate screen position
    var toScreenPosition = function (x, y, z) {
        // Implementation details
    };
    
  2. Record 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;
    
  3. Determine the slope and inverse slope of the zero/normal line.

    slope = (normal2D.y - zero2D.y) / (normal2D.x - zero2D.x); 
    inverseSlope = -1 / slope;
    
  4. Compute the y-intercept of the normal line based on the mouse coordinates.

    startingYIntercept = event.clientY - (slope * event.clientX);
    
  5. Use zero2D and one2D points to calculate the camera scale by finding the distance between them in the 2D plane.

    cameraScale = one2D.distanceTo(zero2D);
    
  6. For more precise vertex movement, track initial vertex positions for total movement instead of delta between event calls.

    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

  1. Using mouse position and inverse slope, determine the y-intercept of the perpendicular line.

    var endingYIntercept = event.clientY - (inverseSlope * event.clientX);
    
  2. Find the x location where the normal line and perpendicular line intersect by solving the intercept equation.

    var endingX = (endingYIntercept - startingYIntercept) / (slope / inverseSlope);
    
  3. Calculate the y coordinate where the lines intersect at x to set the end point accordingly.

    var endingY = (slope * endingX) + startingYIntercept;
    end2D.set(endingX, endingY);
    
  4. Determine the distance between points and divide by the camera scale.

    var distance = end2D.distanceTo(start2D) / cameraScale;
    
  5. If the normal is opposite to the mouse movement direction, multiply the distance by -1.

    if ((normalDir > 0 && endingX < start2D.x) || (normalDir < 0 && endingX > start2D.x)) {
        distance = distance * -1;
    }
    
  6. Adjust vertex positions based on total distance rather than event handler deltas.

    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

  1. After moving vertices, update the geometry's center to keep it consistent with new vertex locations without affecting overall geometry position.

    // Code for updating geometry center after vertex movement
    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; // Define outside this code
var camera; // Define outside this code
var viewport; // Define outside this code
// Other global variables and functions as required

Answer №2

Two different approaches can be used to achieve this effect - one involving mouse movement and the other utilizing the animation frame.

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 whatever you need to do here
}

Another method is to update these values within the animation frame for a more precise result.

AnimationFrame(){    
mouseX = ( event.clientX - windowHalfX ) / resistanceh; 
mouseY = ( event.clientY - windowHalfY ) / resistancew; 

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Upon selecting the display image option

Here is a jsfiddle file provided for your reference. I am attempting to create a functionality where upon clicking buttons (1,2,3,4), the corresponding image shows up and overlaps with any previously displayed images. I have tried using the following code ...

Key Assignment in Vue FireStore - Potential Undefined Object Situation

My goal is to assign Firestore data, passed through props, to a reactive proxy object in Vue. However, I am encountering an error that says: Object is possibly 'undefined'. (property) fireStoreData: Record<string, any> | undefined To strea ...

The issue of memory leakage with ng-grid and real-time data

My intention is to utilize ng-grid for visualizing high-frequency real-time data, but I am encountering issues with a memory leak. Interestingly, the memory leak does not occur when I opt for a simple HTML table with ng-repeat. My tech stack includes node ...

JavaScript - Capture user input and store it in a cookie when the input field is changed

Any help with my issue would be greatly appreciated. I'm currently working on saving the value of a form input type="text" to a cookie when the input has changed. It seems like I'm close to getting it right, but unfortunately, it's not worki ...

DataTable failing to refresh after updating data

I'm facing a challenge in re-initializing a DataTable within my Vue.js application after updating the data. Below is the snippet of code I am using: template : . <table ref="tableUtamaAlamat" id="tableUtamaAlamat" class=" ...

What is the method for determining the total count of elements within an HTML using Selenium WebDriver in conjunction with Autohotkey?

I am currently working on a project that requires me to determine the total number of items in an HTML page based on a specified class. My tools of choice for this task are Selenium and Autohotkey. Despite extensive research on the topic, I have yet to fi ...

Cannot extract the 'name' property from 'e.target' because it is not defined

I encountered an error message stating that I cannot destructure the property 'name' of 'e.target' because it is undefined within the createform() method. Despite highlighting the line causing the error, I am still unable to comprehend ...

Encountered an issue with the property 'push' not being defined

Here's the schema for my mongoose model in JavaScript: const mongoose = require('mongoose'); const Schema = mongoose.Schema; const CartSchema = new Schema({ userID: String, items: [{ itemID: String, quantity: Number }] }); modul ...

Enhancing Watermark Functionality for Text Boxes

I am encountering an issue with three textboxes all having watermarks. When I use JavaScript to set the value of the second textbox in the OnChange event of the first textbox, the text appears as a watermark. However, when I click on the textbox, it become ...

Strategies for organizing libraries within mongoDB's system.js

Exploring techniques for storing prototype-based libraries or frameworks in mongoDB's system.js can be challenging. An issue arose when attempting to incorporate dateJS formats in a map-reduce function. JIRA #SERVER-770 clarifies that object closures, ...

I am encountering difficulties in creating a table as I continuously receive an error message stating: "Unrecognized datatype for attribute 'postagens.titulo'"

I'm experiencing an issue while trying to create a table in my database. Whenever I attempt to launch my server, I encounter a large error message that reads: Error: Unrecognized datatype for attribute "postagens.titulo" const Database = require(&a ...

Kendo UI: Harnessing the power of one data source across two dynamic widgets

UPDATE: I have provided a link to reproduce the issue here RELATED: A similar issue with Kendo UI Map was discussed in another one of my questions, which might offer some insights to help resolve this one! This question has both a failing and a working ve ...

A guide on implementing React hooks within a React class component

Just stepped into the JavaScript world and facing a bit of trouble... Currently, I'm working with a React hook import { useKeycloak } from '@react-keycloak/web'; import { useCallback } from 'react'; export const useAuthenticatedCal ...

Navigating through segments of an array in javascript

Within my condensed array, I have omitted most of the input data to demonstrate a particular task. For illustrative purposes, here is an extensive example showcasing the elements: storyArray=["#C1", "String showing first message", "String displaying secon ...

Outputting the square root of integers ranging from 4 to 9999

I'm looking to calculate the square root of all numbers up to 9999. Are there any ways to instruct the program to skip numbers that do not have a perfect square root? Below is the current code I am using: let i=1; for (i===1;i>=1 && i <10000;i ...

What is the ideal framerate for smooth animation?

I'm looking to add a snow animation using JavaScript. I currently have it running at 200ms which is decent but not smooth enough. Would changing the interval to 20ms make it more fluid, or would it be inefficient and strain the CPU? window.setInterva ...

Extract the property value and save it as an array in Vue

Looking to extract all values of a specific property and save them as an array. I attempted the following method: data() { return { roomList: null } }, methods: { getRooms() { var that = this axios.get('http://local ...

Does the built-in waiting mechanism in Protractor automatically handle promises?

While browsing through Stack Overflow, I stumbled upon this response in a discussion about Protractor tests and asynchronous solutions: 'AFAIK expect waits internally for the related promises.' I have tried looking through the official Protract ...

Mastering the art of utilizing $.get(url, function(data) in pure JavaScript

I am currently exploring how to achieve the equivalent of $.get(url,function(data) { code }; in pure JavaScript. function fetchImage(keyword){ if(!ACCESS_KEY){ alert("Please update your access key"); return; } var request = new XMLHttpRequ ...

The Stencil EventEmitter fails to send data to the Vue instance

Attempting to develop a custom component using Stencil with an input feature. The goal is to create a component with an input field that, upon value change, emits the input to the Vue instance and logs this event to the console (later updating the value in ...