I have a Mesh created with a BufferGeometry.
I also have the coordinates of where my mouse intersects the Mesh, using the Raycaster.
I am trying to detect faces within(and touching) a radius from the intersection point.
Once I detect the "tangent" faces, I then want to color the faces. Because I am working with a BufferGeometry, I am manipulating the buffer attributes on my geometry.
Here is my code:
let vertexA;
let vertexB;
let vertexC;
let intersection;
const radius = 3;
const color = new THREE.Color('red');
const positionsAttr = mesh.geometry.attributes.position;
const colorAttr = mesh.geometry.attributes.color;
// on every mouseMove event, do below:
vertexA = new THREE.Vector3();
vertexB = new THREE.Vector3();
vertexC = new THREE.Vector3();
intersection = raycaster.intersectObject(mesh).point;
// function to detect tangent edge
function isEdgeTouched(v1, v2, point, radius) {
const line = new THREE.Line3();
const closestPoint = new THREE.Vector3();
line.set(v1, v2);
line.closestPointToPoint(point, true, closestPoint);
return point.distanceTo(closestPoint) < radius;
}
// function to color a face
function colorFace(faceIndex) {
colorAttr.setXYZ(faceIndex * 3 + 0, color.r, color.g, color.b);
colorAttr.setXYZ(faceIndex * 3 + 0, color.r, color.g, color.b);
colorAttr.setXYZ(faceIndex * 3 + 0, color.r, color.g, color.b);
colorAttr.needsUpdate = true;
}
// iterate over each face, color it if tangent
for (let i=0; i < (positionsAttr.count) /3); i++) {
vertexA.fromBufferAttribute(positionsAttr, i * 3 + 0);
vertexB.fromBufferAttribute(positionsAttr, i * 3 + 1);
vertexC.fromBufferAttribute(positionsAttr, i * 3 + 2);
if (isEdgeTouched(vertexA, vertexB, point, radius)
|| isEdgeTouched(vertexA, vertexB, point, radius)
|| isEdgeTouched(vertexA, vertexB, point, radius)) {
colorFace(i);
}
While this code works, it seems to be very poor in performance especially when I am working with a geometry with many many faces. When I checked the performance monitor on Chrome DevTools, I notices that both the isEdgeTouched and colorFace functions take up too much time on each iteration for a face.
Is there a way to improve this algorithm, or is there a better algorithm to use to detect adjacent faces?
Edit
I got some help from the THREE.js slack channel, and modified the algorithm to use Three's Sphere. I am now no longer doing "edge" detection, but instead checking whether a face is within the Sphere
Updated code below:
const sphere = new THREE.Sphere(intersection, radius);
// now checking if each vertex of a face is within sphere
// if all are, then color the face at index i
for (let i=0; i < (positionsAttr.count) /3); i++) {
vertexA.fromBufferAttribute(positionsAttr, i * 3 + 0);
vertexB.fromBufferAttribute(positionsAttr, i * 3 + 1);
vertexC.fromBufferAttribute(positionsAttr, i * 3 + 2);
if (sphere.containsPoint(vertexA)
&& sphere.containsPoint(vertexA)
&& sphere.containsPoint(vertexA)) {
colorFace(i);
}
When I tested this in my app, I noticed that the performance has definitely improved from the previous version. However, I am still wondering if I could improve this further.
This seem to be a classic Nearest Neighbors problem.
You can narrow the search by finding the nearest triangles to a given point very fast by building a Bounding Volume Hierarchy (BVH) for the mesh, such as the AABB-tree.
BVH:
https://en.m.wikipedia.org/wiki/Bounding_volume_hierarchy
AABB-Tree:
https://www.azurefromthetrenches.com/introductory-guide-to-aabb-tree-collision-detection/
Then you can query against the BVH a range query using a sphere or a box of a given radius. That amounts to traverse the BVH using a sphere/box "query" which is used to discard quickly and very early the Bounding Volume Nodes that does not clip the sphere/box "query". At the end the real distance or intersection test is made only with triangles whose BV intersect the sphere/box "query", typically a very small fraction of the triangles.
The complexity of the query against the BVH is O(log n) in contrast with your approach which is O(n).
Related
I am making a cube sphere with LOD, and I ran into a little problem the normal that I was generating is by this code block.
// VectorVertices is an array of Vector3
let VectorNormals = new Array(this.VectorVertices.length);
for (let i = 0; i < VectorNormals.length; i++) {
VectorNormals[i] = new THREE.Vector3();
}
for (let i = 0; i < this.Triangles.length; i += 3) {
let vertexIndexA = this.Triangles[i];
let vertexIndexB = this.Triangles[i + 1];
let vertexIndexC = this.Triangles[i + 2];
let pointA = this.VectorVertices[vertexIndexA];
let pointB = this.VectorVertices[vertexIndexB];
let pointC = this.VectorVertices[vertexIndexC];
pointB.sub(pointA);
pointC.sub(pointA);
let vertexNormal = new THREE.Vector3().crossVectors(pointB, pointC).normalize();
VectorNormals[vertexIndexA].add(vertexNormal);
VectorNormals[vertexIndexB].add(vertexNormal);
VectorNormals[vertexIndexC].add(vertexNormal);
}
for (let i = 0; i < VectorNormals.length; i++) {
VectorNormals[i].normalize();
this.Normals.push(VectorNormals[i].x, VectorNormals[i].y, VectorNormals[i].z);
}
The this.Normals is then set to a bufferGeometry. I am creating the mesh with MeshPhongMaterial.
The normals between the neighbouring faces weren't calculated properly, and I don't what's going wrong. I apologize for my grammar. Thanks!
EDIT: Showing my image problem This is the result I am getting
You're trying to make a smooth sphere, but you are assigning all of the normals of a triangle to the value of the face normal, which is what you're calculating by subtracting and crossing the vertex values. (Note: You may need to be careful regarding the direction of the crossed vector! Ensure it's pointing in the same direction as your original vertex vectors!)
To make a smooth surface, the normals of adjoining vetices need to be the same. So if you have two triangles:
A -- C
| / |
| / |
B -- D
Then to make the transition from ABC to DCB a smooth one, the normals at B and C must be the same. In the case of a sphere, they should also be the average of all surrounding face normals, which ensures a smooth transition in all directions.
Actually for a sphere, if the vertices all originate from the geometric origin, then all you have to do is normalize the vertex value, and that is the normal for that vertex.
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]);
}
}
The API for Hull Geom states: "Assumes the vertices array is greater than three in length. If vertices is of length <= 3, returns []." (https://github.com/mbostock/d3/wiki/Hull-Geom)
I need to draw convex hulls around 2 nodes. I am using the force layout, so the convex hull needs to be dynamic in that it moves around the nodes if I click a node and drag it around. My code is currently based off of this example: http://bl.ocks.org/donaldh/2920551
For context, this is what I am trying to draw a convex hull around:
Here it works when there are 3 nodes:
Here is what I am trying to draw a convex hull around (doesn't work with the code from the example above because Hull Geom will only take arrays with 3+ vertices):
I understand the traditional use of a convex hull would never involve only two points, but I have tried drawing ellipses, rectangles, etc around the 2 nodes and it doesn't look anywhere near as good as the 3 nodes does.
I understand that Hull Geom ultimately just spits out a string that is used for pathing, so I could probably write a modified version of Hull Geom for 2 nodes.
Any suggestions on how to write a modified Hull Geom for 2 nodes or any general advice to solve my problem is really appreciated.
Basically, you need to at least one fake point very close to the line to achieve the desired result. This can be achieved in the groupPath function.
For d of length 2 you can create a temporary array and attach it to the result of the map function as follows:
var groupPath = function(d) {
var fakePoints = [];
if (d.values.length == 2)
{
//[dx, dy] is the direction vector of the line
var dx = d.values[1].x - d.values[0].x;
var dy = d.values[1].y - d.values[0].y;
//scale it to something very small
dx *= 0.00001; dy *= 0.00001;
//orthogonal directions to a 2D vector [dx, dy] are [dy, -dx] and [-dy, dx]
//take the midpoint [mx, my] of the line and translate it in both directions
var mx = (d.values[0].x + d.values[1].x) * 0.5;
var my = (d.values[0].y + d.values[1].y) * 0.5;
fakePoints = [ [mx + dy, my - dx],
[mx - dy, my + dx]];
//the two additional points will be sufficient for the convex hull algorithm
}
//do not forget to append the fakePoints to the input data
return "M" +
d3.geom.hull(d.values.map(function(i) { return [i.x, i.y]; })
.concat(fakePoints))
.join("L")
+ "Z";
}
Here a fiddle with a working example.
Isolin has a great solution, but it can be simplified. Instead of making the virtual point on the line at the midpoint, it's enough to add the fake points basically on top of an existing point...offset by an imperceptible amount. I adapted Isolin's code to also handle cases of groups with 1 or 2 nodes.
var groupPath = function(d) {
var fakePoints = [];
if (d.length == 1 || d.length == 2) {
fakePoints = [ [d[0].x + 0.001, d[0].y - 0.001],
[d[0].x - 0.001, d[0].y + 0.001],
[d[0].x - 0.001, d[0].y + 0.001]]; }
return "M" + d3.geom.hull(d.map(function(i) { return [i.x, i.y]; })
.concat(fakePoints)) //do not forget to append the fakePoints to the group data
.join("L") + "Z";
};
I've seen a lot of similar questions to this on StackOverflow and elsewhere, but none directly addressed my problem. I'm generating n-sided polyhedrons using a convex hull generator in THREE.js. I want to map a square texture onto each face of each polyhedron such that it is not distorted, and gets drawn correctly with perspective (ie. not counteracting perspective or anything like that).
My first attempt, and most of what I've seen elsewhere, looked like this:
function faceUv( v1, v2, v3 ) {
return [
new THREE.Vector2(0,0),
new THREE.Vector2(1,0),
new THREE.Vector2(1,1)
];
}
This changes the aspect ratio of the texture on faces that are not XY aligned right triangles. That is, all of them ;)
So, I thought I would try rotating the face to be aligned with the XY plane, then compute the UVs as if a 1x1 square were drawn over them. So a triangle like this--after XY alignment--would get the following uv coords:
0,0 1,0
* v1(uv=1.0,0.0)
v3(uv=0.1,0.5)
*
* v2(uv=0.5,0.8)
0,1 1,1
This would be good enough for my purposes.
Here's my current solution:
function faceUv(v1, v2, v3) {
var z = new THREE.Vector3(0, 0, 1),
n = new THREE.Triangle(v1, v2, v3).normal(),
// from http://stackoverflow.com/questions/13199126/find-opengl-rotation-matrix-for-a-plane-given-the-normal-vector-after-the-rotat
// RotationAxis = cross(N, N')
// RotationAngle = arccos(dot(N, N') / (|N| * |N'|))
axis = new n.clone().cross(z),
angle = Math.acos(n.clone().dot(z) / (n.length() * z.length()));
var mtx = new THREE.Matrix4().makeRotationAxis(axis, angle);
var inv = new THREE.Matrix4().getInverse(mtx);
var v1r = v1.clone().applyMatrix4(inv),
v2r = v2.clone().applyMatrix4(inv),
v3r = v3.clone().applyMatrix4(inv),
ul = v1r.clone().min(v2r).min(v3r),
lr = v1r.clone().max(v2r).max(v3r),
scale = new THREE.Vector2(1.0 / (lr.x - ul.x), 1.0 / (lr.y - ul.y));
v1r.sub(ul);
v2r.sub(ul);
v3r.sub(ul);
return [
new THREE.Vector2(v1r.x * scale.x, v1r.y * scale.y),
new THREE.Vector2(v2r.x * scale.x, v2r.y * scale.y),
new THREE.Vector2(v3r.x * scale.x, v3r.y * scale.y)
];
}
It seems to work pretty well, with the exception of faces that are aligned with the YZ plane (ie. with a normal of 0,-1,0). Here's a screenshot, notice the bottoms of the polyhedrons.
So my questions are: a) is there a better/simpler way to do this? b) how can I fix it for the bottom faces?, c) is there a way to at least simplify the code by using more existing functionality from THREE.js?
Thanks!
Do you mean this?
axis = n.clone().cross( z );
Also, UVs are normally ( 0, 0 ) in the lower left.
Regarding questions (a) and (c): Given what you are doing, 'No'.
Regarding question (b): I expect you can track that down yourself. :-)
I'm using Three.js to procedurally generate a regular N-gon based on a user-provided number of sides. The long-term goal is to use this as the first step in rendering a polyhedral prism.
I'm using the solution discussed here to calculate the vertices of the N-gon.
I'm then using the technique discussed here to generate faces on the N-gon.
My first attempt to produce the necessary Geometry object resulted in the following, which doesn't seem to render anything after being added to a Mesh:
function createGeometry (n, circumradius) {
var geometry = new THREE.Geometry(),
vertices = [],
faces = [],
x;
// Generate the vertices of the n-gon.
for (x = 1; x <= n; x++) {
geometry.vertices.push(new THREE.Vector3(
circumradius * Math.sin((Math.PI / n) + (x * ((2 * Math.PI)/ n))),
circumradius * Math.cos((Math.PI / n) + (x * ((2 * Math.PI)/ n))),
0
));
}
// Generate the faces of the n-gon.
for (x = 0; x < n-2; x++) {
geometry.faces.push(new THREE.Face3(0, x + 1, x + 2));
}
geometry.computeBoundingSphere();
return geometry;
}
After toying with that for too long, I discovered the ShapeGeometry class. This uses the same vertex algorithm as the above example, but this one renders properly after being added to a Mesh:
function createShapeGeometry (n, circumradius) {
var shape = new THREE.Shape(),
vertices = [],
x;
// Calculate the vertices of the n-gon.
for (x = 1; x <= sides; x++) {
vertices.push([
circumradius * Math.sin((Math.PI / n) + (x * ((2 * Math.PI)/ n))),
circumradius * Math.cos((Math.PI / n) + (x * ((2 * Math.PI)/ n)))
]);
}
// Start at the last vertex.
shape.moveTo.apply(shape, vertices[sides - 1]);
// Connect each vertex to the next in sequential order.
for (x = 0; x < n; x++) {
shape.lineTo.apply(shape, vertices[x]);
}
// It's shape and bake... and I helped!
return new THREE.ShapeGeometry(shape);
}
What's wrong with the Geometry example that's resolved with the ShapeGeometry example?
I don't think it's an issue with camera or positioning because replacing the complex vertex calculations with simpler whole numbers produces a polygon without an issue, provided the values make sense.
The reason I'm asking is because, as I mentioned initially, I'd like to eventually use this as the first step in rendering a polyhedron. ShapeGeometry objects can be extruded to give them depth, but even with the options that Three.js makes available, this may not be enough for my needs in the long run as the required polyhedra become more irregular.
Any thoughts?
You can create prisms using THREE.CylinderGeometry; for an n-sided prism, you could use
// radiusAtTop, radiusAtBottom, height, segmentsAroundRadius, segmentsAlongHeight
var nPrism = new THREE.CylinderGeometry( 30, 30, 80, n, 4 );
You can also use CylinderGeometry to create pyramids and frustums; for more examples of built-in shapes, you can check out:
http://stemkoski.github.io/Three.js/Shapes.html
Since you also sound like you may be interested in more general polyhedra, you might also want to check out:
http://stemkoski.github.io/Three.js/Polyhedra.html
which includes models of the Platonic Solids, Archimedean Solids, Prisms, Antiprisms, and Johnson Solids; however, in that program the polyhedra are "thick" from using spheres for vertices and cylinders for edges.
Hope this helps!
Your function works as expected.
Look at this fiddle http://jsfiddle.net/Elephanter/mUah5/
there is a modified threejs fiddle with your createGeometry function
So you have problem in another place, not at createGeometry function