Circular radius intersection (like a circular cursor brush) - Three js - javascript

I'd like to be able to catch the faces of an object in the radius of a circular cursor (like in painting/photoshop).
I'll show you what is it for https://jsfiddle.net/Shaggisu/w7ufmutr/9/
I'd want be able to selct not only the single face that in the moment intersects with the mouse point but all faces that might be in the circular radius, I tried to uplod some image for that cursor but cant really make it work with external files in jsfiddle.
My question is, if is there some standard method of achieving multiple selection/intersection in a specified radius or should I devised some code that would for example reiterate on suroundin faces around the mouse point in specific moment.
I'm still quite new to three.js so I would ask for some directions to go with it, and especialy if there are some solid ways to achieve, any tip would be helpful too.
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 is modified version of the stemkoskis github that I used for practice.
I have already extended it somewhat for camera management in intersection events and continous selection, but the selection of multiple faces in a radius is what interests me now the most.

You can do this in javascript, modifying vertex color, like you do it in your sample but you will be quickly limited by the number of polygon.
That said, consider your brush like a cone, which start from the Ray.origin and extend in Ray.direction. The radius of the cone is driven by the radius of your brush.
Iterate over each vertices.
For each, get the minimum distance between the vertex to the Ray line.
Get the radius of the brush/cone based on the distance between this vertex and the Ray.origin
If the minimum distance is inferior to the cone radius, your vertex is "in". You can also handle the distance to create a smooth brush.
It should looks like this, it kind of pseudo code, you may need to adapt to ThreeJs Math lib:
// Important, Ray origin and direction must be defined in the same space a vertices positions
// You may need to transform ray origin and direction in object local space.
// get the length of Ray.direction
// may be useless if 'direction' is normalized
var rayDirLenSq = ray.direction.length();
rayDirLenSq *= rayDirLenSq;
var brushRadius = 10.0;
for( var i=0;i< vertices.length;i++){
// get 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 needed
}
// 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 of what you want, you can store the painting factor of each vertices to get a mean factor per faces. Or you can modify vertex color of each vertices independantly to get gradients on your faces...
I didn't test the code, it may contain some mistakes :)
A more advanced method
You could also use a texture to paint on. You will get rid of vertex (and javascript) limitations. You will be able to paint with textured brushes, and have detail inside a triangle (no more vertex color).
The principle :
You need UVs datas and a texture + FBO for each of your meshes.
In a prepass, for each mesh, render it to it's Fbo in it's uvs coords
gl_Position = vec4( UVs*2.0-1.0, 0.0, 1.0 );
Provide the worldSpace vertex position to fragment shader, so you can access the world space position of each pixel of the object texture.
vVertexPosition = modelMatrix * aPosition;
With vVertexPosition in your fragment shader, you can then use the same method as the javascript one to get the brushFactor of each pixels of your mesh.
You can even project this world space pixel position in a custom projection matrix based on the Ray to get the uvs coordinate of the pixel in a brush texture, and paint with textured brush.

Related

Track accuracy relative to the center of a sphere in threejs

I'm building a shooter in three.js in which spheres appear that the user must shoot.
After shooting a sphere, the object disappears and another sphere pops up. I'm using Pointer Lock Controls.
I detect when a sphere gets shot using:
function onDocumentMouseDown( event ) {
var mouse3D = new THREE.Vector3();
var raycaster = new THREE.Raycaster();
mouse3D.normalize();
controls.getDirection( mouse3D );
raycaster.set( controls.getObject().position, mouse3D );
var intersects = raycaster.intersectObjects( objects );
// Change color if hit sphere
if ( intersects.length > 0 ) {
intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );
//hide shpere
object.traverse(function(child){child.visible = false;});
update_sphereplace();
}
}
This works perfectly, but now I want to integrate an accuracy feature.
How could I track the accuracy based on the center of the sphere? So for example hitting the center should give me an accuracy of 100. Hitting the sphere relatively far from the center should give an accuracy near to 0 (but not 0, since 0 would be missing the sphere).
You need to know by how much the ray misses the center of the sphere mesh.
var missDistance = raycaster.ray.distanceToPoint( intersects[ 0 ].object.position );
You then need to map that value to a score -- something like:
var score = 100 * Math.max( 1 - ( missDistance / sphereRadius ), 0 );
three.js r.76

How to rotate a THREE.PerspectiveCamera around on object

I am making this program where you can click on an object, zoom to it, then look at it from all angles by holding the right mouse button and dragging. I need the camera to be going around the object, not rotate the object with the camera looking at it. I honestly just have no idea how to math it out!
For testing there is already a game object with an xyz we have selected and are looking at
var g = new GameObject(500, 0, 0);//The game object with xyz
this.selected = g;//set selected to g
//Create and set the camera
this.camera = new THREE.PerspectiveCamera(45, w/h, 1, 10000);
this.camera.position.x = 0;
this.camera.position.y = 0;
this.camera.position.z = 0;
//set camera to look at the object which is 500 away in the x direction
this.camera.lookAt(new THREE.Vector3(this.selected.x, this.selected.y, this.selected.z));
So the radius between the camera and the object is 500 and while selected and rotating, the camera should always be 500 away.
I update the scene here:
Main.prototype.update = function(){
this.renderer.render(this.scene, this.camera);//scene is just some ambient lighting
//what to do when mouse right is held down
if(this.rightMouseDown){
//placeholder functionality, needs to rotate around object based on mouse movements
this.camera.position.x -= 5;
}
}
How do I rotate this camera around g with a radius of 500?!?!
As gaitat mentioned, trackball controls are the best place to start with many configurable parameters to make camera rotation/revolution easy. One enormous potential benefit of this method ( especially for your project ) is avoiding "gimbal lock" which is the source of much frustration when working with rotations. Here's a link that might help you with Trackball controls and Orbitcontrols:
Rotate camera in Three.js with mouse
Another option would be setting camera coordinates yourself in the animation loop which is actually quite simple:
var angle = 0;
var radius = 500;
function animate() {
...
// Use Math.cos and Math.sin to set camera X and Z values based on angle.
camera.position.x = radius * Math.cos( angle );
camera.position.z = radius * Math.sin( angle );
angle += 0.01;
...
}
Another option would be to connect the camera to a pivot object and just rotate the pivot:
var camera_pivot = new THREE.Object3D()
var Y_AXIS = new THREE.Vector3( 0, 1, 0 );
scene.add( camera_pivot );
camera_pivot.add( camera );
camera.position.set( 500, 0, 0 );
camera.lookAt( camera_pivot.position );
...
camera_pivot.rotateOnAxis( Y_AXIS, 0.01 ); // radians
If you pursue this option, be aware that the camera object is in "camera pivot space", and might be more challenging to manipulate further.

Threejs object selection issue

Im currently working on an small web-application which is using threejs. I ran into the following issue:
I build a prototype which contains my threejs content and everything works well here (The canvas is in the prototype window.innerWidth and window.innerHeight => so has the same size as my Browser window. Selecting works well but I want to use the canvas on my web page application and picking of 3d surfaces needs to work as well there.
I discovered as soon as I change the margin or top via CSS of the canvas it doesn't work anymore. The web-application is based on a scroll page and the threejs canvas is inside a div container which can only be seen by scrolling through the page.
For picking I use the following logic/code -> this one works well in the "fullscreen prototype" but not in the web application page
self.renderer.domElement.addEventListener( 'click', function(event){
event.preventDefault();
//CONVERT MOUSE POSITION TO CORRECT VECTOR
var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
//TRANSLATES A 2D POINT FROM Normalized Device Coordinates TO RAYCASTER THAT CAN BE USED FOR PICKING
self.projector.unprojectVector( vector, self.camera );
//RAYCASTER IS NEEDED TO DETECT INTERACTION WITH CUBE SURFACE
var raycaster = new THREE.Raycaster( self.camera.position, vector.sub( self.camera.position ).normalize() );
var intersects = raycaster.intersectObjects( self.scene.children );
//CHANGE COLOR BASED ON INTERSECTION WITH ELEMENT
if ( intersects.length > 0 ) {
//SELECTED OBJECT
}
}, false );
I think that the calculation is wrong for the var vector but I just can't figure it out how to do it correctly.
Any help would be appreciated
Thank you
best reards
200% way
var x = event.offsetX == undefined ? event.layerX : event.offsetX;
var y = event.offsetY == undefined ? event.layerY : event.offsetY;
var vector = new THREE.Vector3();
vector.set( ( x / renderer.domElement.width ) * 2 - 1, - ( y / renderer.domElement.height ) * 2 + 1, 0.5 );
projector.unprojectVector( vector, camera );
Or see this example. Look at messages in the console.
<script src="js/controls/EventsControls.js"></script>
EventsControls = new EventsControls( camera, renderer.domElement );
EventsControls.draggable = false;
EventsControls.onclick = function() {
console.log( this.focused.name );
}
var mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
EventsControls.attach( mesh );
//
function render() {
EventsControls.update();
controls.update();
renderer.render(scene, camera);
}
If you want to use it in your webpage, you probably need to calculate the vector with the width and height from your canvas element instead of the window which is your whole browser window.

Three.js: Rotate Cylinder into Vector3 direction

I've already searched, but didn't find anything that helps:
I got an vector and a CylinderGeometry Mesh. I want to acchieve, that the cylinder is facing the point where the vector is showing. As input I got a position (c) and a direction (m) (like a line equation: y = mx + c):
function draw (m,c, _color) {
//... create the geometry and mesh
// set the position
line.position.x = c.x;
line.position.y = c.y;
line.position.z = c.z;
// i've tried something like this:
line.lookAt(c.add(m));
//.. and add to scene
}
But it looks like the direction is the direct opposite of what I want to acchieve.
I've also tried stuff like translation:
geometry.applyMatrix( new THREE.Matrix4().makeTranslation(0, length/2, 0));
and tried to get the rotation manually like line.rotation.x = direction.angleTo(vec3(1,0,0))* 180 / Math.PI;. But none of them worked like I needed.
This works for me:
// Make the geometry (of "distance" length)
var geometry = new THREE.CylinderGeometry( 0.6, 0.6, distance, 8, 1, true );
// shift it so one end rests on the origin
geometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, distance / 2, 0 ) );
// rotate it the right way for lookAt to work
geometry.applyMatrix( new THREE.Matrix4().makeRotationX( THREE.Math.degToRad( 90 ) ) );
// Make a mesh with the geometry
var mesh = new THREE.Mesh( geometry, material );
// Position it where we want
mesh.position.copy( from.sceneObject.position );
// And make it point to where we want
mesh.lookAt( to.sceneObject.position );

Three.js project on iPhone - events issue (select&drag object )

I have building project with three.js... canvas where you can drag object
and play with the camera view as well... there is a famous example- "Draggable Cubes",
well my project is pretty similar.
On my project there are 3 main events: mouseup /mousedown/ mousemove...
Well everything was ok....but now I'm trying to run this code on iPhone,changing my events
with touchstart / touchmove / touchend...
The moving object function seems to work fine, but when I'm trying to select the object by clicking him,
it's always the same object that been selected... and not the one I'm pointing on....
I guess the problem is with this function:
function onDocumentMouseDown( event ) {
event.preventDefault();
var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
var intersects = ray.intersectObjects( objects );
if ( intersects.length > 0 ) {
SELECTED = intersects[ 0 ].object;
var intersects = ray.intersectObject( plane );
offset.copy( intersects[ 0 ].point ).subSelf( plane.position );
}
}
Is anybody have an idea what is the problem?
in this line :
var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 );
You use the mouse Vector2 object but you don't initialize it.
Something like this should work :
mouse.x = +(event.targetTouches[0].pageX / window.innerwidth) * 2 +-1;
mouse.y = -(event.targetTouches[0].pageY / window.innerHeight) * 2 + 1;

Categories

Resources