Intersection of circular radii (similar to a circular brush cursor) - ThreeJS

I am looking to enhance my ability to capture the faces of an object within the radius of a circular cursor, similar to painting or Photoshop techniques.

You can see what I am referring to here: https://jsfiddle.net/Shaggisu/w7ufmutr/9/

I am interested in selecting not just the single face intersecting with the mouse point at any given moment, but all faces that may fall within the circular radius. I attempted to upload an image for the cursor, but encountered issues with external files in jsfiddle.

My question is whether there is a standard method to achieve multiple selection/intersection within a specified radius, or if I should develop code that iterates through surrounding faces around the mouse point at a specific moment.

Since I am still fairly new to three.js, I would appreciate some guidance on how to proceed and specifically if there are proven ways to accomplish this. Any advice or tips would be greatly appreciated as well.

    var brushTexture = THREE.ImageUtils.loadTexture( '/cur_circle.png' );
var brushMaterial = new THREE.SpriteMaterial( { map: brushTexture, useScreenCoordinates: true, alignment: THREE.SpriteAlignment.center } );


  brushSprite = new THREE.Sprite( brushMaterial );
  brushSprite.scale.set( 32, 32, 1.0 );
  brushSprite.position.set( 50, 50, 0 );
  scene.add( brushSprite );
  //////////////////////////////////////////////////////////////////////

    // initialize object to perform world/screen calculations
    projector = new THREE.Projector();

    // when the mouse moves, call the given function
    document.addEventListener( 'mousedown', onDocumentMouseDown, false );

}

function onDocumentMouseDown( event ) 
{
    // the following line would stop any other event handler from firing
    // (such as the mouse's TrackballControls)

    event.preventDefault();

    console.log("Click.");


    // update the mouse variable
    mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
    mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

    // find intersections

    // create a Ray with origin at the mouse position
    //   and direction into the scene (camera direction)
    var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
    projector.unprojectVector( vector, camera );
    var ray = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );

    // create an array containing all objects in the scene with which the ray intersects
    var intersects = ray.intersectObjects( targetList );

    // if there is one (or more) intersections
    if ( intersects.length > 0 )
    {
        controls.enabled = false;  // stops camera rotation

        console.log("Hit @ " + toString( intersects[0].point ) );
        // change the color of the closest face.
        intersects[ 0 ].face.color.setRGB( 0.8 * Math.random() + 0.2, 0, 0 ); 
        intersects[ 0 ].object.geometry.colorsNeedUpdate = true;

        document.addEventListener( 'mousemove', onDocumentMouseMove, false );
    }
}

function onDocumentMouseMove( event){

event.preventDefault();

mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

brushSprite.position.set( event.clientX, event.clientY, 0);

// find intersections

    // create a Ray with origin at the mouse position
    //   and direction into the scene (camera direction)
    var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
    projector.unprojectVector( vector, camera );
    var ray = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );

    // create an array containing all objects in the scene with which the ray intersects
    var intersects = ray.intersectObjects( targetList );

    // if there is one (or more) intersections
    if ( intersects.length > 0 )
    {
        console.log("Hit @ " + toString( intersects[0].point ) );
        // change the color of the closest face.
        intersects[ 0 ].face.color.setRGB( 0.8 * Math.random() + 0.2, 0, 0 ); 
        intersects[ 0 ].object.geometry.colorsNeedUpdate = true;
    }

    document.addEventListener( 'mouseup', onDocumentMouseUp, false );
}

function onDocumentMouseUp( event){

event.preventDefault();

document.removeEventListener( "mousemove", onDocumentMouseMove);

controls.enabled = true;
}

The code has been modified from stemkoskis github for my practice purposes. I have already made some enhancements for camera management during intersection events and continuous selection, but now I am particularly interested in achieving multiple face selection within a radius.

Answer №1

If you want to achieve this in JavaScript, you can modify the vertex color just like in your sample code. However, keep in mind that you might face limitations due to the number of polygons.

Think of your brush as a cone starting from Ray.origin and extending in Ray.direction. The cone's radius is determined by the brush's radius.

  • Go through each vertex.
  • For each vertex, find the minimum distance between the vertex and the Ray line.
  • Determine the brush/cone radius based on the distance between the vertex and Ray.origin.
  • If the minimum distance is less than the cone radius, the vertex is "inside". You can also adjust the distance for a smoother brush effect.

The pseudo code below demonstrates how it could be implemented, consider adapting it according to ThreeJs Math lib:

// Ensure Ray origin and direction are defined in the same space as vertices positions
// Transformation of ray origin and direction to object local space may be necessary


// Calculate the length of Ray.direction
// Might not be needed if 'direction' is normalized
var rayDirLenSq = ray.direction.length();
rayDirLenSq *= rayDirLenSq;

var brushRadius = 10.0;


for( var i = 0; i < vertices.length; i++){

  // Retrieve the vertex
  var v = vertices[i];

  var vdir = v.sub( ray.origin );

  var dot = vdir.dot( ray.direction ) / rayDirLenSq;

  if( dot < 0 ){
    // Handle vertices behind the camera if required
  }

  // v2: projection of the vertex onto ray line
  var v2 = ray.direction.clone().multiplyScalar( dot );

  // v3: projection -> vertex
  var v3 = vdir.subtract( v2 )

  // dist is the distance between the vertex and the ray line
  var dist = v3.length()

  // 0 when vertex is at the brush border
  // 1 when vertex is in the brush center
  var paintingFactor = Math.max(0.0, 1.0 - dist/brushRadius )


}

Depending on your needs, you can store the painting factor for each vertex to calculate an average factor per face. Alternatively, you can individually modify the vertex color to create gradients on faces.

Please note that the code has not been tested and may contain errors :)

An alternative approach

You have the option to use a texture for painting instead. This method eliminates the limitations imposed by vertices (and JavaScript). With textured brushes, you can paint detailed textures within triangles without relying on vertex colors.

The concept:

UV data and a texture + FBO are needed for each mesh.

In a pre-pass, render each mesh to its Fbo using its UV coordinates:

gl_Position = vec4( UVs*2.0-1.0, 0.0, 1.0 );

Provide world-space vertex position to the fragment shader to access the world space position of each pixel in the object texture.

vVertexPosition = modelMatrix * aPosition;

With vVertexPosition, you can apply the same technique as with JavaScript to determine the brushFactor for each pixel of the mesh. Additionally, you can project the world space pixel position using a custom projection matrix aligned with the Ray to map the pixel's UV coordinate in a brush texture and paint with textured brushes.

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

Modifying a JavaScript object causes changes to another object within the same array

Encountering an issue where player[0].pHand is being modified while updating player[1].pHand (where pHand is an array containing objects). for(var j = 0; j < 2; j++){ console.log(cardDeck[deckCounter]); player[0].pHand[j] = cardDeck[ ...

Update the contents of a webpage using AJAX

Can anyone help me with loading likes into a div using AJAX every second? I want the page's URL to be likes.php?p=0 on page load, likes.php?p=1 after 1 second, likes.php?p=2 after 2 seconds, and so on. This is my code snippet: var loadLikes = func ...

What is the best method for typing a component prop that is compatible with singular use and can also function within loops without disrupting intellisense?

The Scenario Within a heading component, I have defined various types as shown below: // Heading/index.d.ts import { HTMLAttributes } from 'react'; export const HeadingType: { product: 'product'; marketing: 'marketing'; ...

Discord bot that combines the power of discord.js and node.js to enhance your music

I am currently working on a Discord bot designed to play music in voice chat rooms. However, I am facing some issues with properties. Whenever I try to launch the bot using "node main", I encounter the following error message: "TypeError: Cannot read prope ...

JavaScript not functioning consistently on various browsers

When using http.responseText, it seems to not work properly in Chrome. Is there a way to modify this line so that it does? ...

Making an Ajax request to the GitHub API using Backbone

Currently delving into Backbone, I have encountered an issue while attempting to fetch JSON data from the GitHub API. My goal is to create a simple web application that retrieves a paginated list of open issues from the GitHub Rails repository. The stumbli ...

No data is being recorded in the Firestore database

This component in nextjs is designed to write data to a firestore database after the user clicks a button. Unfortunately, Firebase seems to be having trouble writing the data, even though the alert message following the supposed data dump is successful. I ...

Is it possible to update a module on an end user's device without using npm or node.js?

Currently, I am working on developing an electron application along with a module that it uses. My main concern is ensuring that this module gets updated on the end user's machine whenever there is a new version available, even if they do not have NPM ...

All components have been brought in, however, Nest is struggling to resolve the dependencies required by the stripePaymentService

I'm currently in the process of integrating the Stripe payment gateway with NestJS. I've encountered an error message stating "Nest can't resolve dependencies of the stripePaymentService". Even though I'm familiar with this type of erro ...

Unable to add or publish text in CKEditor

In my ASP.NET MVC application, I am struggling to post the updated value from a CKEditor in a textarea. Here is the code snippet: <textarea name="Description" id="Description" rows="10" cols="80"> This is my textarea to be replaced with CKEditor ...

The AJAX server call is still experiencing a timeout issue despite having already implemented a timeout

My server ajax call keeps timing out even though the server responds back within a reasonable timeframe. If the server responds within 2-4 minutes, the ajax call goes into success. However, if the server takes longer than 4 minutes to respond, the ajax ca ...

Achieving Efficiency with Handlebars: Streamlining Remote Template Organization and

I am looking for a way to better organize my HB template by splitting it into different HTML files. In this case, I have created a file called helpers.html. This file contains two script tags: <script id='alert' type='text/template>... ...

The http url on an iPad in the src attribute of an HTML 5 video tag is malfunctioning

Having trouble loading a video on iPad using an http URL in the src attribute of an HTML5 video tag. Both static and dynamic approaches have been attempted, but it works fine on web browsers. Here is an example of the code: Static: <!DOCTYPE html ...

What could be the reason behind needing to refresh my Express endpoint in order to access req.headers.cookies from the frontend?

I've encountered an issue where I must refresh my express endpoint in order to properly access req.headers.cookie. Without refreshing the endpoint, console.log(req.headers.cookie) returns undefined instead of the expected value. I have attached some i ...

Saving the data retrieved from an ajax request

I am facing an issue with storing a variable from an ajax get request that calls a PHP script. The data is successfully retrieved and stored in the variable within the PHP script, but when I return to the code containing the ajax request, the variable appe ...

Tips for resetting the lightgallery following an ajax request

I have integrated the lightgallery plugin into my website to showcase images when clicked. The initialization of the light gallery is done like this: $(document).ready(function(){ $('#lightgallery').lightGallery({ selector: '.it ...

The button seems to be unresponsive in this basic HTML clicking game

I'm having trouble with a simple clicker game I'm trying to create. The button doesn't seem to be working despite my efforts. I've spent hours on it, can anyone offer some assistance? <!DOCTYPE html> <script> ...

Retrieve the screen width using a JavaScript function and apply it as a percentage

I find myself needing to adjust the size of table elements based on the width of the screen. While I am not well-versed in javascript or html, resolving this issue is crucial. Unfortunately, I did not create the original asp page and have limited ability t ...

Creating a dropdown menu in Bootstrap 5 without using any of the Bootstrap

In my Angular application, I have a header with icons and pictures that I would like to use as dropdown menus. The code snippet for this functionality is shown below: <li class="nav-item dropdown"> <a class="nav-li ...

Changing or toggling the visibility of links once the week is finished

I have a total of 4 links available, but I want to display only one link at a time, highlighting a different lunch option every week. Once all 4 weeks have passed, the first lunch menu will be shown again, starting from Monday. <div class="wrapper& ...