Selecting vertices that are visible from a particular position - three.js

how to determine which vertices of buffer geometry are visible from a particular position in the scene?
I am using Autodesk view and data api and raytracing doesn’t seem to work.
The purpose is to apply functions to only parts of 3D objects that is visible to viewpoints/vectors located along a line.
Any help is appreciated
bellow is code sample:
var origin = new THREE.Vector3(1, 2, 3);
var direction = new THREE.Vector3(3, 4, 7);
var sphereMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000 });
sphereMaterial.side = THREE.DoubleSide;
viewer.impl.matman().addMaterial('adn-material-vertex', sphereMaterial,true);
var directionSphere = new THREE.Mesh(new THREE.SphereGeometry(2, 20), sphereMaterial);
viewer.impl.scene.updateMatrixWorld();
var raycaster = new THREE.Raycaster(origin, direction.normalize());
var intersect = raycaster.intersectObject(directionSphere, true);
console.log("Intersect is: " + intersect); // returned intersect is empty
//I also tried: intersectObjects with ray of objects and the result is the same

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 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 Apply multiple textures on same mesh or new mesh with a cloned geometry

SEE THE SOLUTION BELOW
I am really confused. The target i am trying to achieve is;
Use multiple textures on a loaded mesh.
I have searched multiple times and still i can see the similar questions but nothing helped me.
What i'v tried is (yet);
Created a new mesh with the target mesh's geometry and pushed to target object3d element. (Like a photoshop layer.)
var texture = new THREE.Texture(mapCanvas);
texture.minFilter = THREE.LinearFilter;
texture.needsUpdate = true;
var material = new THREE.MeshPhongMaterial({map: texture, transparent: true});
var targetMesh = book.children[0].children[1],
newMesh = new THREE.Mesh(targetMesh.geometry, material);
book.children[0].children.push(newMesh);
Result, wrong geometry attributes, or am i missing something?.
But i think it could be a easier solution like using multiple textures at the same time with a correct order.
Full code:
sampleImage.onload = function() {
var mapCanvas = document.createElement('canvas');
mapCanvas.width = sampleImage.width;
mapCanvas.height = sampleImage.height;
var ctx = mapCanvas.getContext('2d');
ctx.translate(sampleImage.width / 2, sampleImage.height / 2);
ctx.rotate(Math.PI);
ctx.translate(-sampleImage.width / 2, -sampleImage.height / 2);
ctx.drawImage(sampleImage, 0, 0, sampleImage.width, sampleImage.height);
var texture = new THREE.Texture(mapCanvas);
texture.minFilter = THREE.LinearFilter;
texture.needsUpdate = true;
var material = new THREE.MeshPhongMaterial({map: texture, transparent: true});
var targetMesh = book.children[0].children[1],
newMesh = new THREE.Mesh(targetMesh.geometry, material);
book.children[0].children.push(newMesh);
};
I found a solution with cloning.
Do not push directly to the mesh group using javascript array push.
use .add method.
book.children[0].add(newMesh);

Threejs merge with multiple materials applies only one material

I have searched on this and found several examples on stackoverflow, but the answers have not solved my problem.
What I have tried:
First I create the geometry bucket to be used for the group and an array to store my materials.
var totalGeom = new THREE.Geometry();
var materials = [];
I then run through my data (strData) with a for loop and call addMapMarker3d.
for(var i=0; i<strData.length;i++){
addMapMarker3d([strData[i].lat,strData[i].lon], strData[i].time, measureColors[i]);
}
The addMapMarker3d function is as follows:
addMapMarker3d = function(latLng, height, color){
var max = 600;
var dist = 25;
var opacity = (height/max);
var geometry = new THREE.BoxGeometry( Math.floor(dist), height, Math.floor(dist));
//create the material and add it to the materials array
var material = new THREE.MeshBasicMaterial({ transparent:true, color: Number(color), opacity:opacity});
this.materials.push(material);
//create a mesh from the geometry and material.
var cube = new THREE.Mesh( geometry, material);
//leaf is a simple lat/lng to pixel position converter
var actualMarkerPos = leaf.getPoint(latLng);
//apply the position on a 5000x5000 cartesian plane
var extent = 5000;
cube.position.setZ((actualMarkerPos[1] * extent) - extent/2);
cube.position.setX(-((actualMarkerPos[0] * extent) - extent/2));
cube.position.setY(height/2);
//update the matrix and add the cube to the totalGeom bucket
cube.updateMatrix();
totalGeom.merge( cube.geometry, cube.matrix);
}
After the for loop runs and all the cubes are created:
var mats = new THREE.MeshFaceMaterial(materials)
var total = new THREE.Mesh(totalGeom, mats);
world.scene.add(total);
The question
While the geometry merge functions, and my view port is running at a much improved FPS, all the cubes have exactly the same color and opacity. It appears the merge is using a single material of the 10k I supplied. Is there some way to ensure that the geometry uses the material supplied in the array? Am I doing something incorrect?
If I try this in addMapMarker3d:
totalGeom.merge( cube.geometry, cube.matrix, materials.length-1);
I get "Uncaught TypeError: Cannot read property 'transparent' of undefined" and nothing renders, which I don't understand, because by the examples, each geometry should index to a material in the materials array.
three.js r.70
The following technique uses just one material, but allows you to retain the individual color of each merged object. I don't know if it's possible to retain the individual alpha of each merged object.
http://jsfiddle.net/looshi/nsknn53p/61/
For each mesh, set each of its geometry.faces color :
function makeCube(size, color) {
var geom = new THREE.BoxGeometry(size, size, size);
for (var i = 0; i < geom.faces.length; i++) {
face = geom.faces[i];
face.color.setHex(color);
}
var cube = new THREE.Mesh(geom);
return cube;
}
Then, in the parent geometry you are going to mesh into, set its material vertexColors property.
var parentGeometry = new THREE.Geometry();
var parentMatrial = new THREE.MeshLambertMaterial({
color: 0xffffff,
shading: THREE.SmoothShading,
vertexColors: THREE.VertexColors
});
// in a loop you could create many objects and merge them
for (var i = 0; i < 1000; i++) {
cube = makeCube(size, color);
cube.position.set(x, y, z);
cube.rotation.set(rotation,rotation,rotation);
cube.updateMatrix()
parentGeometry.merge(cube.geometry, cube.matrix);
}
// after you're done creating objects and merging them, add the parent to the scene
parentMesh = new THREE.Mesh(parentGeometry, parentMatrial);
scene.add(parentMesh);
My original question was not answered: Is there some way to ensure that the geometry uses the material supplied in the array?
The answer is yes. Multiple materials can be applied to a single mesh during merge. After pushing the material to the materials array, the merge will utilize the geometry face material index. The merge will apply multiple materials when an array is supplied to new THREE.MeshFaceMaterial([materialsArray]) as the total mesh is created. This solves the mystery of the syntax. Just because you supply an array of materials does not mean that the merge will use each material in an iterative fashion as the objects are merged as of r71. The faces must inform the merge which material in the material array to use.
I am using this for a non rendered scene, and the final obj is exported. If you need render performance, see one of the other answers for some options.
A simple for loop on the face array on the geometry informs the merge which material to apply:
addMapMarker3d = function(latLng, height, color){
var max = 600;
var dist = 25;
var opacity = (height/max);
var geometry = new THREE.BoxGeometry( Math.floor(dist), height, Math.floor(dist));
//create the material and add it to the materials array
var material = new THREE.MeshBasicMaterial({ transparent:true, color: Number(color), opacity:opacity});
this.materials.push(material);
//set the material index of each face so a merge knows which material to apply
for ( var i = 0; i < geometry.faces.length; i ++ ) {
geometry.faces[i].materialIndex = this.materials.length-1;
}
//create a mesh from the geometry and material.
var cube = new THREE.Mesh( geometry, material);
//leaf is a simple lat/lng to pixel position converter
var actualMarkerPos = leaf.getPoint(latLng);
//apply the position on a 5000x5000 cartesian plane
var extent = 5000;
cube.position.setZ((actualMarkerPos[1] * extent) - extent/2);
cube.position.setX(-((actualMarkerPos[0] * extent) - extent/2));
cube.position.setY(height/2);
//update the matrix and add the cube to the totalGeom bucket
cube.updateMatrix();
totalGeom.merge( cube.geometry, cube.matrix);
}
The only reason you are seeing improved performance is because, after the merge, there is only one material. If you want your scene to have multiple materials you should not merge.
Adding to the same group:
var group = new THREE.Object3D();
group.add (object1);
group.add (object2);
group.add (object3);

Check if point is inside a custom mesh geometry

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)

Resources