Check if point is inside a custom mesh geometry - three.js

What would be the easiest way to check if a point is inside a custom (irregular) mesh geometry?

If your mesh is close-up. You can use the THREE.js built-in ray-caster. Sample code is as
const point = new THREE.Vector3(2,2,2) // Your point
const geometry = new THREE.BoxBufferGeometry( 5, 5, 5 )
const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } )
const mesh = new THREE.Mesh( geometry, material )
const raycaster = new THREE.Raycaster()
raycaster.set(point, new THREE.Vector3(1,1,1))
const intersects = raycaster.intersectObject(mesh)
if( intersects.length %2 === 1) { // Points is in objet
console.log(`Point is in object`)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/89/three.js"></script>

Just raycast once from the point to any direction, then check the intersects num, if is odd, the point is in the geometry, here is the demo

This is a computational geometry problem. You can look at Finding if point is inside geometry. Since your geometry is irregular the problem is much harder.
But if precision is not too important you can check if the point is inside the bounding box of the geometry.

Its better to check it using the dot product of ray direction and face normal
tested on three.js (r103)
const point = new THREE.Vector3(2, 2, 2) // Your point
const direction = new THREE.Vector3(1, 1, 1);
const geometry = new THREE.BoxGeometry(5, 5, 5)
const material = new THREE.MeshBasicMaterial({ color: 0xffff00, side: THREE.DoubleSide });
const mesh = new THREE.Mesh(geometry, material)
const raycaster = new THREE.Raycaster()
raycaster.set(point, direction)
const intersects = raycaster.intersectObject(mesh);
if (intersects.length && direction.dot(intersects[0].face.normal) > 0) {
console.log(`Point is in object`);
} else {
console.log(`Point is out of object`);
}
In rare cases you can get even number of interections with point located inside the mesh
(try point = new THREE.Vector3(0, 0, 0), that should give 4 intersections)

Related

Get center position of hole in ExtrudeGeometry

I trying to get position of hole in extruded geometry. I created plane and made hole in her geometry. I want get x,y,z coordinates in center of hole. Is there some methods to get it?
Here demo: https://codepen.io/DYDOI-NSK/pen/XWqJzXG?editors=0011
Here code:
I created shape of plane
let shape = new THREE.Shape();
let width = 30;
let height = 30;
shape.moveTo(-width, height);
shape.lineTo(-width, -height);
shape.lineTo(width, -height);
shape.lineTo(width, height);
shape.lineTo(-width, height);
I created hole path and add it to shape
let hole = new THREE.Path();
hole.absarc(20, 10, 10, 0, Math.PI * 2, false) //first two argumets is x,y coord of hole
shape.holes.push(hole)
I created plane add add extruded geometry
let geometry = new THREE.PlaneGeometry( 30, 30);
let material = new THREE.MeshBasicMaterial( {color: new THREE.Color('#cea6a6'), side: THREE.DoubleSide} );
let mesh = new THREE.Mesh( geometry, material );
let newGeometry = new THREE.ExtrudeGeometry(shape, settings);
mesh.geometry.dispose()
mesh.geometry = newGeometry;
After 4 days I understand how can do it. I simple created line from mesh center to hole config position. Applied quaternion to line and got x,y,z cords of hole.
Maybe there are more optimized solutions, but it only that I could create. I will be glad if someone share more optimized solution :D
Here codepen: https://codepen.io/DYDOI-NSK/pen/XWqJzXG?editors=0011
Here code:
/*
* findCoords - function to find hole coords in 3d space
* data - parameter that require x,y of hole
*/
let findCoords = function (data) {
let vertexes = [];
//Set coords where you was created hole
let hole = new THREE.Vector3(
data.x,
data.y,
0
);
vertexes.push( new THREE.Vector3() );
vertexes.push(hole)
//Create line
const material = new THREE.LineBasicMaterial({
color: 0x0000ff
});
const geometry = new THREE.BufferGeometry().setFromPoints( vertexes );
const line = new THREE.Line( geometry, material );
scene.add(line)
//Set line to center of mesh
line.position.copy(mesh.position)
//Rotate line like rotated mesh
line.applyQuaternion(mesh.quaternion)
//Extract hole coords from second vertex of line
let holeCoord = new THREE.Vector3()
const positionAttribute = line.geometry.getAttribute( 'position' );
holeCoord.fromBufferAttribute( positionAttribute, 1);
return holeCoord;
}

how to change starting position of a sphere in threejs

I created a 3D Earth using three.js that rotates, but the problem is that it starts spinning over the ocean.
i triedto change starting position using matrixWorld but didn't change anything
can someone help plz
here is my js:
...
const earthgeometry = new THREE.SphereGeometry(0.344,64,32);
const eatrhmaterial = new THREE.MeshPhongMaterial({
roughness: 1,
metalness:0,
map: THREE.ImageUtils.loadTexture('static/img/earthmap1k.jpg'),
bumpMap: THREE.ImageUtils.loadTexture('static/img/earthbump.jpg'),
bumpScale: 0.3,
});
const earthmesh = new THREE.Mesh(earthgeometry,eatrhmaterial);
earthmesh.position.set (0.49,0.035,0.58);
earthmesh.matrixWorld.setPosition(new THREE.Vector3(100, 100, 100));
...
The same way you set a position, you can set a rotation of your object.
const earthgeometry = new THREE.SphereGeometry(0.344,64,32);
const earthmaterial = new THREE.MeshPhongMaterial({
//material definition here
});
const earthmesh = new THREE.Mesh(earthgeometry,earthmaterial);
earthmesh.position.set (0.49,0.035,0.58);
earthmesh.rotation.set (0,1.1,0);//or any other values
remember :
rotation are in radians: 360° = 2*Math.PI
Y axis is the vertical one

how to draw a flat shape in 3-space in three.js?

I'm trying to construct a collection of flat shapes in three.js. Each one is defined as a series of coplanar Vector3 points, but the shapes are not all coplanar. Imagine two flat rectangles as the roof of a house, but with much more complex shapes.
I can make flat Shape objects and then rotate and position them, but since my shapes are conceived in 3d coordinates, it would be much simpler to keep it all in 3-space, which the Shape object doesn't like.
Is there some much more direct way to simply specify an array of coplanar Vector3's, and let three.js do the rest of the work?
I thought about this problem and came up with the idea, when you have a set of co-planar points and you know the normal of the plane (let's name it normal), which your points belong to.
We need to rotate our set of points to make it parallel to the xy-plane, thus the normal of that plane is [0, 0, 1] (let's name it normalZ). To do it, we find quaternions with .setFromUnitVectors() of THREE.Quaternion():
var quaternion = new THREE.Quaternion().setFromUnitVectors(normal, normalZ);
var quaternionBack = new THREE.Quaternion().setFromUnitVectors(normalZ, normal);
Apply quaternion to our set of points
As it's parallel to xy-plane now, z-coordinates of points don't matter, so we can now create a THREE.Shape() object of them. And then create THREE.ShapeGeometry() (name it shapeGeom) from given shape, which will triangulate our shape.
We need to put our points back to their original positions, so we'll apply quaternionBack to them.
After all, we'll assign our set of points to the .vertices property of the shapeGeom.
That's it. If it'll work for you, let me know ;)
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 20, 40);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target = new THREE.Vector3(10, 0, 10);
controls.update();
var grid = new THREE.GridHelper(50, 50, 0x808080, 0x202020); // xy-grid
grid.geometry.rotateX(Math.PI * 0.5);
scene.add(grid);
var points = [ // all of them are on the xz-plane
new THREE.Vector3(5, 0, 5),
new THREE.Vector3(25, 0, 5),
new THREE.Vector3(25, 0, 15),
new THREE.Vector3(15, 0, 15),
new THREE.Vector3(15, 0, 25),
new THREE.Vector3(5, 0, 25),
new THREE.Vector3(5, 0, 5)
]
var geom = new THREE.BufferGeometry().setFromPoints(points);
var pointsObj = new THREE.Points(geom, new THREE.PointsMaterial({
color: "red"
}));
scene.add(pointsObj);
var line = new THREE.LineLoop(geom, new THREE.LineBasicMaterial({
color: "aqua"
}));
scene.add(line);
// normals
var normal = new THREE.Vector3(0, 1, 0); // I already know the normal of xz-plane ;)
scene.add(new THREE.ArrowHelper(normal, new THREE.Vector3(10, 0, 10), 5, 0xffff00)); //yellow
var normalZ = new THREE.Vector3(0, 0, 1); // base normal of xy-plane
scene.add(new THREE.ArrowHelper(normalZ, scene.position, 5, 0x00ffff)); // aqua
// 1 quaternions
var quaternion = new THREE.Quaternion().setFromUnitVectors(normal, normalZ);
var quaternionBack = new THREE.Quaternion().setFromUnitVectors(normalZ, normal);
// 2 make it parallel to xy-plane
points.forEach(p => {
p.applyQuaternion(quaternion)
});
// 3 create shape and shapeGeometry
var shape = new THREE.Shape(points);
var shapeGeom = new THREE.ShapeGeometry(shape);
// 4 put our points back to their origins
points.forEach(p => {
p.applyQuaternion(quaternionBack)
});
// 5 assign points to .vertices
shapeGeom.vertices = points;
var shapeMesh = new THREE.Mesh(shapeGeom, new THREE.MeshBasicMaterial({
color: 0x404040
}));
scene.add(shapeMesh);
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.90.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.90.0/examples/js/controls/OrbitControls.js"></script>

Three.js Collision detection of spheres with THREE.Raycaster

For my project I need collision tests in Three.js. In my CollisionDetection class I'm trying to get a Raycaster to work. And I found some weirdness that I can't explain and can't find a way around:
My CollisionDetector works fine for Cubes.. but when I use Spheres instead, it doesn't give me the same results – Am I wrong to expect the same results as for the cubes? Or do I miss something else?
Here is my Code:
var renderer, camera, scene;
init();
animate();
function init() {
var container = document.getElementById("scene");
var width = window.innerWidth;
var height = window.innerHeight;
renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
camera = new THREE.OrthographicCamera( 0, width, 0, height, 1, 10000 );
camera.position.z = 300;
scene = new THREE.Scene();
scene.add(camera);
container.appendChild(renderer.domElement);
var geometry = new THREE.SphereGeometry(10,16, 16);
//var geometry = new THREE.CubeGeometry( 10, 10, 10 );
var material1 = new THREE.MeshBasicMaterial( { color: 0xFF3333} );
var material2 = new THREE.MeshBasicMaterial( { color: 0xFF3333} );
var material3 = new THREE.MeshBasicMaterial( { color: 0xFF3333} );
var material4 = new THREE.MeshBasicMaterial( { color: 0xFF3333} );
var material5 = new THREE.MeshBasicMaterial( { color: 0xFF3333} );
var element1 = new THREE.Mesh( geometry, material1 );
var element2 = new THREE.Mesh( geometry, material2 );
var element3 = new THREE.Mesh( geometry, material3 );
var element4 = new THREE.Mesh( geometry, material4 );
var element5 = new THREE.Mesh( geometry, material5 );
element1.position.set(200,200,0);
element2.position.set(200,100,0);
element3.position.set(200,300,0);
element4.position.set(100,200,0);
element5.position.set(300,200,0);
scene.add(element1);
scene.add(element2);
scene.add(element3);
scene.add(element4);
scene.add(element5);
var CollisionDetector = new CollisionDetection();
CollisionDetector.addRay(new THREE.Vector3(0, -1, 0));
CollisionDetector.addRay(new THREE.Vector3(0, 1, 0));
CollisionDetector.addRay(new THREE.Vector3(1, 0, 0));
CollisionDetector.addRay(new THREE.Vector3(-1, 0, 0));
CollisionDetector.addElement(element1);
CollisionDetector.addElement(element2);
CollisionDetector.addElement(element3);
CollisionDetector.addElement(element4);
CollisionDetector.addElement(element5);
document.onclick = function(){
CollisionDetector.testElement(element1);
};
}
function CollisionDetection(){
var caster = new THREE.Raycaster();
var rays = [];
var elements = [];
this.testElement = function(element){
for(var i=0; i<rays.length; i++) {
caster.set(element.position, rays[i]);
var hits = caster.intersectObjects(elements, true);
for(var k=0; k<hits.length; k++) {
console.log("hit", hits[k]);
hits[k].object.material.color.setHex(0x0000ff);
}
}
}
this.addRay = function(ray) {
rays.push(ray.normalize());
}
this.addElement = function(element){
elements.push(element);
}
}
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
Or best, see for yourself how it behaves: http://jsfiddle.net/mymL5/12/
On Click every element hit by a ray should turn blue and all hits are registered in the console.
Note the (imho) weird console output for spheres.
Also, why is the lower sphere not hit while the upper is?
You can switch between Cubes and Spheres by Commenting/Uncommenting lines 19/20
Can anyone help me? What am I not getting?
PS: I'm new to Three.js, so I'm probably being dumb.
Since this is homework-related, I am only going to provide some tips.
Your scene is rendering upside down because your args to orthographic camera are incorrect.
Your sphere is bigger than your cube.
Your rays are hitting the north and south poles of your spheres exactly. What is different about those points?
The material.side property tells Raycaster which side(s) of a face to consider the "front".
Your fidde example is running an old version (r.54) of three.js.
three.js r.58
Increased spheres size.
Rotated spheres by some non-trivial angle (so they don't get hit right in the N/S pole).
Now it works? :P
var geometry = new THREE.SphereGeometry(20,17, 17);
element1.position.set(0,0,0);
element2.position.set(0,100,0);
element3.position.set(100,0,0);
element4.position.set(0,-100,0);
element5.position.set(-100,0,0);
element1.rotation.set(0,0,10);
element2.rotation.set(0,0,10);
element3.rotation.set(0,0,10);
element4.rotation.set(0,0,10);
element5.rotation.set(0,0,10);
Still, ray test should be aware of the hitting exact vertex or edge of the triangle, so that might be considered as a place-to-improve for Three.js.
I filed an issue about this in the Three.js repository:
https://github.com/mrdoob/three.js/issues/3541

Visualizing Raycaster

I am trying to detect an intersection by using a raycast. My current problem is that I am not sure about my raycast aiming into the desired direction. So my general question is: Is there a way to make a raycast visible? And if so: How is it done? This would help me a lot.
Michael
Here is another method to show your raycsters:
scene.add(new THREE.ArrowHelper(raycaster.ray.direction, raycaster.ray.origin, 300, 0xff0000) );
Why dont you draw a line from your origin to the direction of the ray.
To be more specific (using r83):
// Draw a line from pointA in the given direction at distance 100
var pointA = new THREE.Vector3( 0, 0, 0 );
var direction = new THREE.Vector3( 10, 0, 0 );
direction.normalize();
var distance = 100; // at what distance to determine pointB
var pointB = new THREE.Vector3();
pointB.addVectors ( pointA, direction.multiplyScalar( distance ) );
var geometry = new THREE.Geometry();
geometry.vertices.push( pointA );
geometry.vertices.push( pointB );
var material = new THREE.LineBasicMaterial( { color : 0xff0000 } );
var line = new THREE.Line( geometry, material );
scene.add( line );
Codepen at: https://codepen.io/anon/pen/evNqGy

Resources