I have created a three.js scene that includes a plane that intersects a mesh. What I would like to do is get an array of points for all locations where an edge of the mesh crosses the plane. I have had a good look for solutions and can't seem to find anything.
Here is an image of what I currently have:
And here I have highlighted the coordinates I am trying to gather:
If anybody can point me in the right direction, that would be most appreciated.
Thanks,
S
This is not the ultimate solution. This is just a point where you can start from.
UPD: Here is an extension of this answer, how to form contours from given points.
Also, it's referred to this SO question with awesome anwers from WestLangley and Lee Stemkoski about the .localToWorld() method of THREE.Object3D().
Let's imagine that you want to find points of intersection of a usual geometry (for example, THREE.DodecahedronGeometry()).
The idea:
THREE.Plane() has the .intersectLine ( line, optionalTarget ) method
A mesh contains faces (THREE.Face3())
Each face has a, b, c properties, where indices of vertices are stored.
When we know indices of vertices, we can get them from the array of vertices
When we know coordinates of vertices of a face, we can build three THREE.Line3() objects
When we have three lines, we can check if our plane intersects them.
If we have a point of intersection, we can store it in an array.
Repeat steps 3 - 7 for each face of the mesh
Some explanation with code:
We have plane which is THREE.PlaneGeometry() and obj which is THREE.DodecahedronGeometry()
So, let's create a THREE.Plane():
var planePointA = new THREE.Vector3(),
planePointB = new THREE.Vector3(),
planePointC = new THREE.Vector3();
var mathPlane = new THREE.Plane();
plane.localToWorld(planePointA.copy(plane.geometry.vertices[plane.geometry.faces[0].a]));
plane.localToWorld(planePointB.copy(plane.geometry.vertices[plane.geometry.faces[0].b]));
plane.localToWorld(planePointC.copy(plane.geometry.vertices[plane.geometry.faces[0].c]));
mathPlane.setFromCoplanarPoints(planePointA, planePointB, planePointC);
Here, three vertices of any face of plane are co-planar, thus we can create mathPlane from them, using the .setFromCoplanarPoints() method.
Then we'll loop through faces of our obj:
var a = new THREE.Vector3(),
b = new THREE.Vector3(),
c = new THREE.Vector3();
obj.geometry.faces.forEach(function(face) {
obj.localToWorld(a.copy(obj.geometry.vertices[face.a]));
obj.localToWorld(b.copy(obj.geometry.vertices[face.b]));
obj.localToWorld(c.copy(obj.geometry.vertices[face.c]));
lineAB = new THREE.Line3(a, b);
lineBC = new THREE.Line3(b, c);
lineCA = new THREE.Line3(c, a);
setPointOfIntersection(lineAB, mathPlane);
setPointOfIntersection(lineBC, mathPlane);
setPointOfIntersection(lineCA, mathPlane);
});
where
var pointsOfIntersection = new THREE.Geometry();
...
var pointOfIntersection = new THREE.Vector3();
and
function setPointOfIntersection(line, plane) {
pointOfIntersection = plane.intersectLine(line);
if (pointOfIntersection) {
pointsOfIntersection.vertices.push(pointOfIntersection.clone());
};
}
In the end we'll make our points visible:
var pointsMaterial = new THREE.PointsMaterial({
size: .5,
color: "yellow"
});
var points = new THREE.Points(pointsOfIntersection, pointsMaterial);
scene.add(points);
jsfiddle example. Press the button there to get the points of intersection between the plane and the dodecahedron.
Update THREE.js r.146
Sharing complete example using BufferGeometry since Geometry is deprecated since r.125, while following the wonderful example of #prisoner849 and discourse thread Plane intersects mesh with three.js r125
Example includes clipping the geometry based on the intersection points which are used to generate the LineSegments.
Can also instead create a Plane from the PlanarGeometry Quanternion and Normal
let localPlane = new THREE.Plane();
let normal = new THREE.Vector3();
let point = new THREE.Vector3();
normal.set(0, -1, 0).applyQuaternion(planarGeometry.quaternion);
point.copy(planarGeometry.position);
localPlane.setFromNormalAndCoplanarPoint(normal, point).normalize();**
Function updates Lines with current intersection based on the current position of the PlanarGeometry
let lines = new THREE.LineSegments(
new THREE.BufferGeometry(),
new THREE.LineBasicMaterial({
color: 0x000000,
linewidth: 5
})
);
function drawIntersectionLine() {
let a = new THREE.Vector3();
let b = new THREE.Vector3();
let c = new THREE.Vector3();
const isIndexed = obj.geometry.index != null;
const pos = obj.geometry.attributes.position;
const idx = obj.geometry.index;
const faceCount = (isIndexed ? idx.count : pos.count) / 3;
const clippingPlane = createPlaneFromPlanarGeometry(plane);
obj.material.clippingPlanes = [clippingPlane];
let positions = [];
for (let i = 0; i < faceCount; i++) {
let baseIdx = i * 3;
let idxA = baseIdx + 0;
a.fromBufferAttribute(pos, isIndexed ? idx.getX(idxA) : idxA);
let idxB = baseIdx + 1;
b.fromBufferAttribute(pos, isIndexed ? idx.getX(idxB) : idxB);
let idxC = baseIdx + 2;
c.fromBufferAttribute(pos, isIndexed ? idx.getX(idxC) : idxC);
obj.localToWorld(a);
obj.localToWorld(b);
obj.localToWorld(c);
lineAB = new THREE.Line3(a, b);
lineBC = new THREE.Line3(b, c);
lineCA = new THREE.Line3(c, a);
setPointOfIntersection(lineAB, clippingPlane, positions);
setPointOfIntersection(lineBC, clippingPlane, positions);
setPointOfIntersection(lineCA, clippingPlane, positions);
}
lines.geometry.setAttribute(
"position",
new THREE.BufferAttribute(new Float32Array(positions), 3)
);
}
function setPointOfIntersection(line, planarSrf, pos) {
const intersect = planarSrf.intersectLine(line, new THREE.Vector3());
if (intersect !== null) {
let vec = intersect.clone();
pos.push(vec.x);
pos.push(vec.y);
pos.push(vec.z);
}
}
Example CodePen
Related
I'm trying to render a plane a set of 3 vertices (as shown). However every method I tried (mostly from SO or the official three.js forum) doesn't work for me.
// example vertices
const vert1 = new THREE.Vector3(768, -512, 40)
const vert2 = new THREE.Vector3(768, -496, 40)
const vert3 = new THREE.Vector3(616, -496, 40)
I already tried the following code for calculating the width and height of the plane, but I think it's way over-complicated (as I only calculate the X and Y coords and I think my code would grow exponentially if I'd also add the Z-coordinate and the plane's position to this logic).
const width = vert1.x !== vert2.x ? Math.abs(vert1.x - vert2.x) : Math.abs(vert1.x - vert3.x)
const height = vert1.y !== vert2.y ? Math.abs(vert1.y - vert2.y) : Math.abs(vert1.y - vert3.y)
Example:
I want to create a plane with 3 corners of points A, B and C and a plane with 3 corners of points D, E and F.
Example Video
You can use THREE.Plane.setFromCoplanarPoints() to create a plane from three coplanar points. However, an instance of THREE.Plane is just a mathematical representation of an infinite plane dividing the 3D space in two half spaces. If you want to visualize it, consider to use THREE.PlaneHelper. Or you use the approach from the following thread to derive a plane mesh from your instance of THREE.Plane.
Three.js - PlaneGeometry from Math.Plane
I create algorithm which compute mid point of longest edge of triangle. After this compute vector from point which isn't on longest edge to midpoint. On end just add computed vector to midpoint and you have coordinates of fourth point.
On end just create PlaneGeometry from this points and create mesh. Code is in typescript.
Code here:
type Line = {
startPoint: Vector3;
startPointIdx: number;
endPoint: Vector3;
endPointIdx: number;
vector: Vector3;
length: Vector3;
}
function createTestPlaneWithTexture(): void {
const pointsIn = [new Vector3(28, 3, 3), new Vector3(20, 15, 20), new Vector3(1, 13, 3)]
const lines = Array<Line>();
for (let i = 0; i < pointsIn.length; i++) {
let length, distVect;
if (i <= pointsIn.length - 2) {
distVect = new Vector3().subVectors(pointsIn[i], pointsIn[i + 1]);
length = distVect.length()
lines.push({ vector: distVect, startPoint: pointsIn[i], startPointIdx: i, endPoint: pointsIn[i + 1], endPointIdx: i + 1, length: length })
} else {
const distVect = new Vector3().subVectors(pointsIn[i], pointsIn[0]);
length = distVect.length()
lines.push({ vector: distVect, startPoint: pointsIn[i], startPointIdx: i, endPoint: pointsIn[0], endPointIdx: 0, length: length })
}
}
// find longest edge of triangle
let maxLine: LineType;
lines.forEach(line => {
if (maxLine) {
if (line.length > maxLine.length)
maxLine = line;
} else {
maxLine = line;
}
})
//get midpoint of longest edge
const midPoint = maxLine.endPoint.clone().add(maxLine.vector.clone().multiplyScalar(0.5));
//get idx unused point
const idx = [0, 1, 2].filter(value => value !== maxLine.endPointIdx && value !== maxLine.startPointIdx)[0];
//diagonal point one
const thirdPoint = pointsIn[idx];
const vec = new Vector3().subVectors(midPoint, thirdPoint);
//diagonal point two diagonal === longer diagonal of reactangle
const fourthPoint = midPoint.clone().add(vec);
const edge1 = thirdPoint.clone().sub(maxLine.endPoint).length();
const edge2 = fourthPoint.clone().sub(maxLine.endPoint).length();
//const topLeft = new Vector3(bottomLeft.x, topRight.y, bottomLeft.y);
const points = [thirdPoint, maxLine.startPoint, maxLine.endPoint, fourthPoint];
// console.log(points)
const geo = new PlaneGeometry().setFromPoints(points)
const texture = new TextureLoader().load(textureImage);
texture.wrapS = RepeatWrapping;
texture.wrapT = RepeatWrapping;
texture.repeat.set(edge2, edge1);
const mat = new MeshBasicMaterial({ color: 0xFFFFFFF, side: DoubleSide, map: texture });
const plane = new Mesh(geo, mat);
}
I am developing a web application to estimate the cost of a 3D printing model with three.js and also do other stuff.
I have easily calculate bounding box and volume of object and I am now approaching slices.
Following this question I have managed to get intersections with a plane: Three JS - Find all points where a mesh intersects a plane and I put the function inside a for loop that appends to a three.js group.
// Slices
function drawIntersectionPoints() {
var contours = new THREE.Group();
for(i=0;i<10;i++){
a = new THREE.Vector3(),
b = new THREE.Vector3(),
c = new THREE.Vector3();
planePointA = new THREE.Vector3(),
planePointB = new THREE.Vector3(),
planePointC = new THREE.Vector3();
lineAB = new THREE.Line3(),
lineBC = new THREE.Line3(),
lineCA = new THREE.Line3();
var planeGeom = new THREE.PlaneGeometry(50,50);
planeGeom.rotateX(-Math.PI / 2);
var plane = new THREE.Mesh(planeGeom, new THREE.MeshBasicMaterial({
color: "lightgray",
transparent: true,
opacity: 0.75,
side: THREE.DoubleSide
}));
plane.position.y = i;
scene.add(plane);
var mathPlane = new THREE.Plane();
plane.localToWorld(planePointA.copy(plane.geometry.vertices[plane.geometry.faces[0].a]));
plane.localToWorld(planePointB.copy(plane.geometry.vertices[plane.geometry.faces[0].b]));
plane.localToWorld(planePointC.copy(plane.geometry.vertices[plane.geometry.faces[0].c]));
mathPlane.setFromCoplanarPoints(planePointA, planePointB, planePointC);
meshGeometry.faces.forEach(function(face) {
mesh.localToWorld(a.copy(meshGeometry.vertices[face.a]));
mesh.localToWorld(b.copy(meshGeometry.vertices[face.b]));
mesh.localToWorld(c.copy(meshGeometry.vertices[face.c]));
lineAB = new THREE.Line3(a, b);
lineBC = new THREE.Line3(b, c);
lineCA = new THREE.Line3(c, a);
setPointOfIntersection(lineAB, mathPlane);
setPointOfIntersection(lineBC, mathPlane);
setPointOfIntersection(lineCA, mathPlane);
});
var lines = new THREE.LineSegments(pointsOfIntersection, new THREE.LineBasicMaterial({
color: 0xbc4e9c,
lineWidth: 2,
}));
contours.add(lines);
function setPointOfIntersection(line, plane) {
pointOfIntersection = plane.intersectLine(line);
if (pointOfIntersection) {
pointsOfIntersection.vertices.push(pointOfIntersection.clone());
};
};
};
console.log(contours);
scene.add(contours);
And this is what I see:
The for loop works and I visualise all the planes and also the lines inside the group but only the first intersection is showing in the canvas (pink).
screenshot
Many thanks.
You have to call .updateMatrixWorld(true) on your plane object after you set its y-coordinate in the loop:
plane.position.y = i;
plane.updateMatrixWorld(true);
scene.add(plane);
jsfiddle example.
I am new to Three.js so perhaps I am not going abut this optimally,
I have geometry which I create as follows,
const geo = new THREE.PlaneBufferGeometry(10,0);
I then apply a rotation to it
geo.applyMatrix( new THREE.Matrix4().makeRotationX( Math.PI * 0.5 ) );
then I create a Mesh from it
const open = new THREE.Mesh( geo, materialNormal);
I then apply a bunch of operations to the mesh to position it correctly, as follows:
open.position.copy(v2(10,20);
open.position.z = 0.5*10
open.position.x -= 20
open.position.y -= 10
open.rotation.z = angle;
Now what is the best way to get the vertices of the mesh both before and after it's position is changed? I was surpised to discover that the vertices of a mesh are not in-built into three.js.
Any hints and code samples would be greatly appreciated.
I think you're getting tripped-up by some semantics regarding three.js objects.
1) A Mesh does not have vertices. A Mesh contains references to Geometry/BufferGeometry, and Material(s). The vertices are contained in the Mesh's geometry property/object.
2) You're using PlaneBufferGeometry, which means an implementation of a BufferGeometry object. BufferGeometry keeps its vertices in the position attribute (mesh.geometry.attributes.position). Keep in mind that the vertex order may be affected by the index property (mesh.geometry.index).
Now to your question, the geometric origin is also its parent Mesh's origin, so your "before mesh transformation" vertex positions are exactly the same as when you created the mesh. Just read them out as-is.
To get the "after mesh transformation" vertex positions, you'll need to take each vertex, and convert it from the Mesh's local space, into world space. Luckily, three.js has a convenient function to do this:
var tempVertex = new THREE.Vector3();
// set tempVertex based on information from mesh.geometry.attributes.position
mesh.localToWorld(tempVertex);
// tempVertex is converted from local coordinates into world coordinates,
// which is its "after mesh transformation" position
Here's an example written by typescript.
It gets the grid's position in the world coordinate system.
GetObjectVertices(obj: THREE.Object3D): { pts: Array<THREE.Vector3>, faces: Array<THREE.Face3> }
{
let pts: Array<THREE.Vector3> = [];
let rs = { pts: pts, faces: null };
if (obj.hasOwnProperty("geometry"))
{
let geo = obj["geometry"];
if (geo instanceof THREE.Geometry)
{
for (let pt of geo.vertices)
{
pts.push(pt.clone().applyMatrix4(obj.matrix));
}
rs.faces = geo.faces;
}
else if (geo instanceof THREE.BufferGeometry)
{
let tempGeo = new THREE.Geometry().fromBufferGeometry(geo);
for (let pt of tempGeo.vertices)
{
pts.push(pt.applyMatrix4(obj.matrix));
}
rs.faces = tempGeo.faces;
tempGeo.dispose();
}
}
return rs;
}
or
if (geo instanceof THREE.BufferGeometry)
{
let positions: Float32Array = geo.attributes["position"].array;
let ptCout = positions.length / 3;
for (let i = 0; i < ptCout; i++)
{
let p = new THREE.Vector3(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]);
}
}
Working off the "Basic Scene" example on babylonjs-playground.com here, I am trying to do a simple modification on the color of the sphere.
Here is my attempt, which can be run interactively:
https://www.babylonjs-playground.com/#95BNBS
Here is the code:
var createScene = function () {
// The original example, without comments:
var scene = new BABYLON.Scene(engine);
var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
camera.setTarget(BABYLON.Vector3.Zero());
camera.attachControl(canvas, true);
var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);
light.intensity = 0.7;
var sphere = BABYLON.Mesh.CreateSphere("sphere1", 16, 2, scene);
sphere.position.y = 1;
var ground = BABYLON.Mesh.CreateGround("ground1", 6, 6, 2, scene);
// My attempt to color the sphere
var material = new BABYLON.StandardMaterial(scene);
material.alpha = 1;
material.diffuseColor = new BABYLON.Color3(1,0,0);
scene.material = material;
return scene;
};
My attempt to add the colored material to the sphere has no effect.
I also tried to look for color-related attributes on the sphere object:
Object.keys(sphere).filter((key) => return key.includes("Color") )
// => "outlineColor", "overlayColor", "_useVertexColors", "edgesColor"
Except for _useVertexColors, all of these seem to be color objects, but changing them has no effect:
sphere.overlayColor.g = 1;
sphere.outlineColor.g = 1;
sphere.edgesColor.g = 1;
You're pretty close. You are setting a color correctly with diffuseColor but you aren't actually adding it specifically to your sphere.
Your sphere object is stored in sphere so what you need to do is you need to set the material you created on sphere and not on scene.
// My attempt to color the sphere
var material = new BABYLON.StandardMaterial(scene);
material.alpha = 1;
material.diffuseColor = new BABYLON.Color3(1.0, 0.2, 0.7);
sphere.material = material; // <--
See this tutorial
I have a model witch intersects with my raycaster. The raycaster returns the correct point, but the face normal vector is not what i'm waiting. Three.js has as built in VertexNormalsHelper, when i use that it display the correct normals, but when i create two cubes one will be at the position of the intersection point and the other will be at the normal vector it will be like this:
The red cube is the raycaster intersection point, blue cube is the face normal
My code is simple just a simple raycaster, and i copy the position of the points to the cubes. When i load my model i update everything on the geomtery. I use the Orbitcontrols for the camera movement.
var intersects = this.checkIntersection(this.surfaceModel);
for (var i = 0; i < intersects.length; i++) {
var p = intersects[ 0 ].point;
var normal = intersects[ 0 ].face.normal.clone();
//Red & Blue cube position update
this.pointHelper_A.position.copy(p);
this.pointHelper_B.position.copy(normal);
Here is an image when the VertexNormalsHelper is turned on, so you can see that the normals are fine here:
I've learned some vector math, so the normal vector is in the right position. When you need a point at the ray intersection perpendicular to the intersected face then you need to multiply the faceNormalVector with a scalar, this will be the distance between the face and the new point, and then add this new vector to the intersection point.
I've read the source code of the VertexNormalsHelper and from that i've created a function wich gives you the normal vector of a face. So it can tell you that point but i don't think that this is the real solution for the main problem, also it has many operations.
Here is the code:
(object: THREE.Mesh,
face: THREE.Face3)
this.getFaceNormalPosition = function (object, face) {
var v1 = new THREE.Vector3();
var keys = ['a', 'b', 'c', 'd'];
object.updateMatrixWorld(true);
var size = (size !== undefined) ? size : 1;
var normalMatrix = new THREE.Matrix3();
normalMatrix.getNormalMatrix(object.matrixWorld);
var vertices = new THREE.Vector3();
var verts = object.geometry.vertices;
var faces = object.geometry.faces;
var worldMatrix = object.matrixWorld;
for (var j = 0, jl = face.vertexNormals.length; j < jl; j++) {
var vertexId = face[ keys[ j ] ];
var vertex = verts[ vertexId ];
var normal = face.vertexNormals[ j ];
vertices.copy(vertex).applyMatrix4(worldMatrix);
v1.copy(normal).applyMatrix3(normalMatrix).normalize().multiplyScalar(size);
v1.add(vertices);
}
return v1;
};
I had the same problem of not understanding the normal face I'm getting. The solution for me was to multiply the normal with the normal matrix of the object (didnt know that was a thing). Here is a simple helper class written in typescript, porting it to javascript shouldn't be too hard:
import { Vector3, Object3D, Matrix3, Face3 } from 'three';
export class Face3Utils {
private static _matrix3: Matrix3;
private static get matrix3(): Matrix3 {
if(this._matrix3 === undefined) {
this._matrix3 = new Matrix3();
}
return this._matrix3;
}
public static getWorldNormal(face: Face3, object: Object3D, normalVector?: Vector3): Vector3 {
if(normalVector === null || normalVector === undefined) {
normalVector = new Vector3();
}
object.updateMatrixWorld( true );
Face3Utils.matrix3.getNormalMatrix( object.matrixWorld );
normalVector.copy(face.normal).applyMatrix3( Face3Utils.matrix3 ).normalize();
return normalVector;
}
}