I wanted to make a snapping functionality to snap to my mesh vertices. I experimented with several solutions.
One solution is to add THREE.Sprite instances for all vertices in my scene and then using a rayCaster to decide whether there is a snap point in the intersects array. It works pretty well; here is a fiddle with a demo.
The idea is to hide the sprites in the final solution so they won't be rendered, but my scenes are pretty big so it would still mean adding lots of sprites to my scene (for every vertex one so possibly thousands of sprites) to detect snap points with my rayCaster.
var intersects = rayCaster.intersectObject(scene, true);
var snap = null;
if (intersects.length > 0) {
var index = 0;
var intersect = intersects[index];
while (intersect && intersect.object.name === 'snap') {
snap = sprite.localToWorld(sprite.position.clone());
index++
intersect = intersects[index];
}
if (intersect) {
var face = intersect.face;
var point = intersect.point;
var object = intersect.object;
mouse3D.copy(point);
}
}
if (snap) {
renderer.domElement.style.cursor = 'pointer';
} else {
renderer.domElement.style.cursor = 'no-drop';
}
I also thought of an alternative solution by doing the math using results from the rayCaster. That solution is demonstrated in this fiddle.
The idea here is to test all vertices from the geometry of the object (mesh) that is intersected and then check whether the distance between the intersect point and those vertices from the geometry is smaller then the snap threshold.
var intersects = rayCaster.intersectObject(mesh, true);
if (intersects.length > 0) {
var distance, intersect = intersects[0];
var face = intersects[0].face;
var point = intersects[0].point;
var object = intersects[0].object;
var snap = null;
var test = object.worldToLocal(point);
var points = object.geometry.vertices;
for (var i = 0, il = points.length; i < il; i++) {
distance = points[i].distanceTo(test);
if (distance > threshold) {
continue;
}
snap = object.localToWorld(points[i]);
}
if (snap) {
sphereHelper.position.copy(snap);
sphereHelper.visible = true;
renderer.domElement.style.cursor = 'pointer';
} else {
sphereHelper.visible = false;
renderer.domElement.style.cursor = 'no-drop';
}
}
The sad thing is that in the second solution snap will only work when the mouse is moved from the surface of the intersected object towards a vertex. In case the mouse is moved from outside the object (so there is no intersection) the snapping won't work. In that respect the first solution with sprites is much more usable...
My question, am I overcomplicating things and is there a better/simpler/more efficient way to do this? Any suggestions for alternative approaches are welcome.
I looked into #meepzh his suggestion of using an octree and made the following solution using this threeoctree repository from github. The THREE.Octree class did not solve all my problems out-of-the-box so
I added custom method findClosestVertex to the THREE.Octree class that can be used like this.
var snap = octree.findClosestVertex(position, radius);
snap is null in case no vertices within the radius of position and returns the closest point (THREE.Vector3) in world space otherwise.
I made a Pull-Request here on github for the new method.
Here is a demo in a fiddle
Related
I want to develop a game "Cut the Shape" in JavaScript. Several components with convex polygons that will need to be clipped depending on the number of convex freedom faces. Lines and polygons are displayed on the Canvas. The Paper.js graphics load to use.
But then the question arose, which concerns the correct separation of the polygons from each other relative to the drawn line, I just can’t think of a way to do this.
Here is an example on simple polygons (the lines can be absolutely any, the user draws them himself):
I got to the points of dividing the polygon into other polygons using small dots with a line:
var polygon = new Path.RegularPolygon(new Point(200, 300), 4, 100);
polygon.strokeColor = 'blue';
polygon.fullySelected = true;
var shapesArray = [];
shapesArray.push(polygon);
function splitShape(path1, path2){
var shapesArrayCopy = path1.slice(0);
shapesArray = [];
for(var i = 0; i < shapesArrayCopy.length; i++){
var intersections = shapesArrayCopy[i].getIntersections(path2);
if(intersections.length >= 2){
var p1 = shapesArrayCopy[i].split(shapesArrayCopy[i].getNearestLocation(intersections[0].point));
var p2 = shapesArrayCopy[i].split(shapesArrayCopy[i].getNearestLocation(intersections[1].point));
p1.closed = true;
p2.closed = true;
shapesArray.push(Object.assign(p1));
shapesArray.push(Object.assign(p2));
path2.visible = false;
}
else{
shapesArray.push(shapesArrayCopy[i])
}
}
var myPath;
function onMouseDown(event) {
myPath = new Path();
myPath.strokeColor = 'black';
myPath.add(event.point);
myPath.add(event.point);
}
function onMouseDrag(event) {
myPath.segments.pop();
myPath.add(event.point);
}
function onMouseUp(event) {
splitShape(shapesArray, myPath)
myPath.visible = false;
}
In 2D You just displace the cuts in perpendicular direction to the cut line. If your cut line endpoints are: p0(x0,y0),p1(x1,y1) then the line direction is:
dp = p1-p0 = (x1-x0,y1-y0)
make it unit:
dp /= sqrt((dp.x*dp.x)+(dp.y*dp.y)
make it equal to half of the gap between cuts:
dp *= 0.5*gap
now tere are two perpendicular directions:
d0 = (-dp.y,+dp.x)
d1 = (+dp.y,-dp.x)
so now just add d0 to all vertexes of one cut, and d1 to the other one. Which use for which is simply you take point that does not lie on the cutting line (for example avg point of your cut) p and compute (only once for polygon cut):
t = dot(p-p0,d0) = ((p.x-x0)*d0.x)+((p.y-y0)*d0.y)
if (t>0) use d0, if (t<0) use d1 and if (t==0) you chose wrong point p as it lies on cutting line.
I am building an app using HTML5 in which a grid is drawn. I have some shapes on it that you can move.
What I'm trying to do is to snap objects to some points defined when you hover them while moving a shape.
What I tried is to save anchors points inside an array and when the shape is dropped, I draw the shape on the closest anchor point.
Easeljs is my main js lib so I want to keep it but if needed I can use an other one with easeljs.
Thanks in advance for your help!
This is pretty straightforward:
Loop over each point, and get the distance to the mouse
If the item is closer than the others, set the object to its position
Otherwise snap to the mouse instead
Here is a quick sample with the latest EaselJS: http://jsfiddle.net/lannymcnie/qk1gs3xt/
The distance check looks like this:
// Determine the distance from the mouse position to the point
var diffX = Math.abs(event.stageX - p.x);
var diffY = Math.abs(event.stageY - p.y);
var d = Math.sqrt(diffX*diffX + diffY*diffY);
// If the current point is closeEnough and the closest (so far)
// Then choose it to snap to.
var closest = (d<snapDistance && (dist == null || d < dist));
if (closest) {
neighbour = p;
}
And the snap is super simple:
// If there is a close neighbour, snap to it.
if (neighbour) {
s.x = neighbour.x;
s.y = neighbour.y;
// Otherwise snap to the mouse
} else {
s.x = event.stageX;
s.y = event.stageY;
}
Hope that helps!
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;
}
}
I have a sphere (globe) with objects (pins) on the surface with DOM elements (labels) what are calculated from the pin position to 2d world.
My problem is that when the pins go behind the globe (with mouse dragging or animation) then I need to hide labels which are in DOM so that the text label isn’t visible without the pin.
My logic is that if I can get the pin which is in 3D world to tell me if it’s behind the globe then I can hide the label associated with the pin.
Codepen with whole the code.
The function that I have researched together:
function checkPinVisibility() {
var startPoint = camera.position.clone();
for (var i = 0; i < pins.length; i++) {
var direction = pins[i].position.clone();
var directionVector = direction.sub(startPoint);
raycaster.set(startPoint, directionVector.clone().normalize());
var intersects = raycaster.intersectObject(pins[i]);
if (intersects.length > 0) {
// ?
}
}
}
I have researched through many posts but can’t really get the result needed:
ThreeJS: How to detect if an object is rendered/visible
Three.js - How to check if an object is visible to the camera
http://soledadpenades.com/articles/three-js-tutorials/object-picking/
I have gotten it work by mouse XY position as a ray, but can’t really get a working solution with constant rendering for all the pins.
You want to know which points on the surface of a sphere are visible to the camera.
Imagine a line from the camera that is tangent to the sphere. Let L be the length of the line from the camera to the tangent point.
The camera can only see points on the sphere that are closer to the camera than L.
The formula for L is L = sqrt( D^2 - R^2 ), where D is the distance from the camera to the sphere center, and R is the sphere radius.
WestLangley's solution in code form. Please give him the accepted answer if you feel his answer the best.
function checkPinVisibility() {
var cameraToEarth = earth.position.clone().sub(camera.position);
var L = Math.sqrt(Math.pow(cameraToEarth.length(), 2) - Math.pow(earthGeometry.parameters.radius, 2));
for (var i = 0; i < pins.length; i++) {
var cameraToPin = pins[i].position.clone().sub(camera.position);
if(cameraToPin.length() > L) {
pins[i].domlabel.style.visibility = "hidden";
} else {
pins[i].domlabel.style.visibility = "visible";
}
}
}
Oddly enough it is still susceptible to that camera pan error. Very weird, but it's still better than my Projection-onto-LOOKAT solution.
MY OLD ANSWER:
I would have assumed its something like this, but this doesn't seem to work as expected.
if (intersects.length > 0) {
pins[i].domlabel.style.visibility = "visible";
} else {
pins[i].domlabel.style.visibility = "hidden";
}
I got close with this solution, but its still not perfect. What the code below does is it finds the distance along the LOOKAT direction of the camera to a pin (cameraToPinProjection) and compares it with the distance along the LOOKAT direction to the earth (cameraToEarthProjection).
If cameraToPinProjection > cameraToEarthProjection it means the pin is behind the centre of the earth along the LOOKAT direction (and then I hide the pin).
You will realise there's a "0.8" factor I multiply the cameraToEarth projection by. This is to make it slightly shorter. Experiment with it.
Its not perfect because as you rotate the Earth around you will notice that sometimes labels don't act the way you'd like them, I'm not sure how to fix.
I hope this helps.
function checkPinVisibility() {
var LOOKAT = new THREE.Vector3( 0, 0, -1 );
LOOKAT.applyQuaternion( camera.quaternion );
var cameraToEarth = earth.position.clone().sub(camera.position);
var angleToEarth = LOOKAT.angleTo(cameraToEarth);
var cameraToEarthProjection = LOOKAT.clone().normalize().multiplyScalar(0.8 * cameraToEarth.length() * Math.cos(angleToEarth));
var startPoint = camera.position.clone();
for (var i = 0; i < pins.length; i++) {
var cameraToPin = pins[i].position.clone().sub(camera.position);
var angleToPin = LOOKAT.angleTo(cameraToPin);
var cameraToPinProjection = LOOKAT.clone().normalize().multiplyScalar(cameraToPin.length() * Math.cos(angleToPin));
if(cameraToPinProjection.length() > cameraToEarthProjection.length()) {
pins[i].domlabel.style.visibility = "hidden";
} else {
pins[i].domlabel.style.visibility = "visible";
}
}
}
I made an open source project. It is a data visualizer for your books in 3D using Three.js. (https://github.com/AlinCiocan/BooksIn3D). And now I want to do detect if a user is in front of a book if pressed a key. Something like this:
For example, in the above image, the cursor hand it is not hitting any books.
I tried my best to actually make a function to work, but I am stuck. I searched if there any question on SO on how to do hit detection using PointerLockControls, but I found nothing. I must say that my cursor hand it is not moving, it is always on the center of the screen.
function hitDetection(targets, callback) {
var projector = new THREE.Projector();
var raycaster = new THREE.Raycaster();
var mouse = {};
// it is always the center of the screen
mouse.x = 0;
mouse.y = 0;
var vector = new THREE.Vector3(mouse.x, mouse.y, 1);
var cameraDirection = controls.getDirection(vector).clone();
projector.unprojectVector(cameraDirection,camera );
var ray = new THREE.Raycaster(controls.getObject().position, cameraDirection.sub(controls.getObject().position).normalize());
var intersects = ray.intersectObjects(targets);
// if there is one (or more) intersections
if (intersects.length > 0) {
var hitObject = intersects[0].object;
console.log("hit object: ", hitObject);
callback(hitObject);
}
}