I would like to take the world space coordinates of a sphere to screenspace coordinates in order to get the screen space bounds, which I can then use to overlay a div.
Ideally I would like to extend this function to return the height and width of the object, as well as the x & y :
toScreenPosition : function (obj, camera)
{
var vector = new THREE.Vector3();
var widthHalf = 0.5*world.renderer.context.canvas.width;
var heightHalf = 0.5*world.renderer.context.canvas.height;
obj.updateMatrixWorld();
vector.setFromMatrixPosition(obj.matrixWorld);
vector.project(camera);
vector.x = ( vector.x * widthHalf ) + widthHalf;
vector.y = - ( vector.y * heightHalf ) + heightHalf;
return {
x: vector.x,
y: vector.y
};
},
You can create few THREE.Object3D and locate them in the scene in position of the border of the main object you want to project to the screen.
then you can use the method you used on the main object on the other empty objects and get the pixels position on the screen of the border of the main object.
If for example you want to know the screen coordinates of the border of a sphere that has a radius of 5:
var first = new THREE.Object3D();
var second = new THREE.Object3D();
var third = new THREE.Object3D();
var fourth = new THREE.Object3D();
first.position.set(sphere.x,sphere.y+5,sphere.z);
second.position.set(sphere.x,sphere.y-5,sphere.z);
then you can apply the function you wrote, but instead of:
obj.updateMatrixWorld();
etc...
you will do:
first.updateMatrixWorld();
second.updateMatrixWorld();
etc...
then you will have the x,y coordinates of those two objects (that are on border of the main object) on screen and you can check the height by subtracting.
I would write a function that takes as input a 3D point and returns the 2D screenpoint, like the one presented there. Then if you deal with a sphere, that would be easy: get the center of the sphere in 3D, compute the 2D point, that will be the center of the overlay div, get any 3D point on the surface of the sphere, compute the 2D point, you will know the required radius of the overlay div, from there you can easily compute a rectangular area.
Related
Like this site http://www.gsmlondon.ac.uk/global-oil-map/
I would like to click on this marker, let this marker turn to the center of the screen.
Now know the mark latitude and longitude, how to turn after the click? I do not understand.
Interesting question, I tried Matrix to get right orientation, but the result is a little bit strange. I choose another way using spherical coordinates system, and it works now.
we need to get two points coordinates, one is the point on the sphere surface which is closest to the camera we note it as P(the line from camera to center of sphere intersect with the sphere). another point is where we click on the sphere surface, we note it as Q .
we use raycaster to get P and Q Cartesian Coordinates. and convert the Cartesian Coordinates to Spherical Coordinates(always described like (r,θ,φ)).
then, we calculate the angular displacement from Q to P. and make the displacement as an addition to sphere rotation.
Here is my snippet:
//get the point coordinates which a line from camera to sphere center intersect with the sphere
var vector = new THREE.Vector3().copy(sphere.position);
vector = vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects([sphere],true);
var intersected_point = new THREE.Vector3().copy(intersects[0].point);
//calculate the intersected point spherical coordinates
var radius = sphere.children[0].geometry.parameters.radius;
var heading = Math.atan2(intersects[0].point.x,intersects[0].point.z);
var pitch = Math.asin(-(intersects[0].point.y)/radius);
document.addEventListener("click",OnDocumentClick,false);
function OnDocumentClick(event)
{
//get the point coordinates which you click on the sphere surface
var vector = new THREE.Vector3(( event.clientX / window.innerWidth ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5);
vector = vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects([sphere],true);
if(intersects.length > 0)
{
//get click point spherical coordinates
var heading1 = Math.atan2(intersects[0].point.x,intersects[0].point.z);
var pitch1 = Math.asin(-(intersects[0].point.y)/radius);
//calculate displacement of click point to intersected point
var delta_heading = heading - heading1;
var delta_pitch = pitch - pitch1;
var target_pitch = parseFloat(sphere.rotation.x) +delta_pitch;
var target_heading = parseFloat(sphere.rotation.y) + delta_heading;
//using an animation to rotate the sphere
anime({
targets:sphere.rotation,
x:target_pitch,
y:target_heading,
elasticity: 0
});
}
}
At the end, I use an animation lib to make the rotation smooth.
Here is my demo:rotate the earth.
I made a little progress, the previous version has a little bit off. when I turn up and down the earth, I got a bad result. I think the code sphere.rotation.x += delta_pitch make sphere rotate on object axises. but what we need is making the sphere rotate on the world space axises. we know world axises coordinates are always x_axis = (1,0,0) ; y_axis = (0,1,0) ; z_axis = (0,0,1); then, I convert the world coordinates to object coordinates, Sphere matrix interpret sphere rotate from indentity rotation to current rotation. and the inverse matrix interpret the backword. so we can apply the inverse matrix to basic axises to get object space coordinates. make sphere rotate on new axises. I just make a little change in OnDcoumentClick function:
var heading1 = Math.atan2(intersects[0].point.x,intersects[0].point.z);
var pitch1 = Math.asin(-(intersects[0].point.y)/radius);
//get the sphere inverse matrix;
var sphere_matrix = new THREE.Matrix4().copy(sphere.matrix);
var inverse_sphere_matrix = new THREE.Matrix4();
inverse_sphere_matrix.getInverse(sphere_matrix);
//convert world space x and y axises to sphere object space coordinates.
var x_axis = new THREE.Vector3(1,0,0);
var y_axis = new THREE.Vector3(0,1,0);
x_axis.applyMatrix4(inverse_sphere_matrix);
y_axis.applyMatrix4(inverse_sphere_matrix);
//calculate displacement of click point to intersected point
var delta_heading = heading - heading1;
var delta_pitch = pitch - pitch1;
//make sphere rotate around whith world x and y axises.
sphere.rotateOnAxis(x_axis,delta_pitch);
sphere.rotateOnAxis(y_axis,delta_heading);
Here is my new demo: rotate earth new version.
My problem is the following:
I have two intersecting surfaces created with THREE.ParametricGeometry. Like this:
I need to draw the intersection of these two surfaces. Using the Wolfram|Alpha API I get the intersection function and render it. Like this:
But, as you can see, the intersection mesh is much bigger than the two surfaces.
So I though that I could compute the intersection of the surfaces bound box (this intersection can be seen in the image above) and 'limit', so to speak, the intersection mesh to this box's dimensions.
I've tried setting the intersection mesh's scale property to the bounding box's dimensions (the difference between the box's max and min); but this only makes the intersection mesh even bigger.
Any though of how I can accomplish this?
The intersection mesh is created like this (ThreeJS r81):
// 'intersections' is an array of mathematical functions in string format.
intersections.forEach(function (value) {
var rangeX = bbox.getSize().x - (bbox.getSize().x * -1);
var rangeY = bbox.getSize().y - (bbox.getSize().y * -1);
var zFunc = math.compile(value); // The parsing is done with MathJS
// 'bbox' is the intersected bounding box.
var meshFunction = function (x, y) {
x = rangeX * x + (bbox.getSize().x * -1);
y = rangeY * y + (bbox.getSize().y * -1);
var scope = {x: x, y: y};
var z = zFunc.eval(scope);
if (!isNaN(z))
return new THREE.Vector3(x, y, z);
else
return new THREE.Vector3();
};
var geometry = new THREE.ParametricGeometry(meshFunction, segments, segments,
true);
var material = new THREE.MeshBasicMaterial({
color: defaults.intersectionColor,
side: THREE.DoubleSide
});
var mesh = new THREE.Mesh(geometry, material);
intersectionMeshes.push(mesh);
// 'intersectionMeshes' is returned and then added to the scene.
});
I think that scaling the intersection mesh wouldn't work as the intersection would become incorrect.
Let's try to do this with Three.js clipping :
Set renderer.localClippingEnabled to true ;
Compute the bounding box of the surfaces ;
For every 6 sides of the bounding box, compute a plane with the normal pointing inside the box.
(e.g. right-side : new THREE.Plane(new THREE.Vector3(-1,0,0), -bbox.max.x);)
You now have an array of clipping planes ;
Create a new THREE.Material with material.clippingPlanes being the array of clipping planes ;
Use this material for the intersection mesh.
Note that with local clipping, the intersection mesh and the surface meshes should share the same world transformation. (putting all these meshes into a THREE.Group would be reasonable.)
I'm trying to achieve a functionality to load the 3D model on the current mouse coordinates. I'm using jQuery drag and drop functionality to load the 3D model. I can load the model into the canvas, but it is not snapping to the mouse coordinates.
I've used the below code to convert the 2D mouse coordinates to 3D coordinates but it's not providing the exact position.
function 2DTo3DSpace(event) {
var planeZ = new THREE.Plane(new THREE.Vector3(1500, 1500, 1500), 0);
var mv = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
var raycaster = projector.pickingRay(mv, camera);
var pos = raycaster.ray.intersectPlane(planeZ);
return pos;
}
Make sure to use mouse position relative to the canvas elemente.
See this thread.
Ive been having the linewidth problem (something to do with ANGLE on window). I have resorted to using cylinders between 2 points (in 3D space). I have already solved the issue on getting the length of the cylinder based on the 2 points-3D distance formula.
I have been having trouble however getting the angle. I want the cylinder to rotate so that the angle found will make it so that the cylinder connects the two points.
Essensially I am trying to find a way to find the angle between (x1,y1,z1) and (x2,y2,z2). And having it modify a cylinder (cylinder.rotation.x, cylinder.rotation.y, and cylinder.rotation.z).
You can use a transformation matrix to do that. Here's some example code:
function createCylinderFromEnds( material, radiusTop, radiusBottom, top, bottom, segmentsWidth, openEnded)
{
// defaults
segmentsWidth = (segmentsWidth === undefined) ? 32 : segmentsWidth;
openEnded = (openEnded === undefined) ? false : openEnded;
// Dummy settings, replace with proper code:
var length = 100;
var cylAxis = new THREE.Vector3(100,100,-100);
var center = new THREE.Vector3(-100,100,100);
////////////////////
var cylGeom = new THREE.CylinderGeometry( radiusTop, radiusBottom, length, segmentsWidth, 1, openEnded );
var cyl = new THREE.Mesh( cylGeom, material );
// pass in the cylinder itself, its desired axis, and the place to move the center.
makeLengthAngleAxisTransform( cyl, cylAxis, center );
return cyl;
}
// Transform cylinder to align with given axis and then move to center
function makeLengthAngleAxisTransform( cyl, cylAxis, center )
{
cyl.matrixAutoUpdate = false;
// From left to right using frames: translate, then rotate; TR.
// So translate is first.
cyl.matrix.makeTranslation( center.x, center.y, center.z );
// take cross product of cylAxis and up vector to get axis of rotation
var yAxis = new THREE.Vector3(0,1,0);
// Needed later for dot product, just do it now;
// a little lazy, should really copy it to a local Vector3.
cylAxis.normalize();
var rotationAxis = new THREE.Vector3();
rotationAxis.crossVectors( cylAxis, yAxis );
if ( rotationAxis.length() < 0.000001 )
{
// Special case: if rotationAxis is just about zero, set to X axis,
// so that the angle can be given as 0 or PI. This works ONLY
// because we know one of the two axes is +Y.
rotationAxis.set( 1, 0, 0 );
}
rotationAxis.normalize();
// take dot product of cylAxis and up vector to get cosine of angle of rotation
var theta = -Math.acos( cylAxis.dot( yAxis ) );
//cyl.matrix.makeRotationAxis( rotationAxis, theta );
var rotMatrix = new THREE.Matrix4();
rotMatrix.makeRotationAxis( rotationAxis, theta );
cyl.matrix.multiply( rotMatrix );
}
I didn't write this. Find the full working sample here.
It's part of Chapter 5: Matrices from this awesome free Interactive 3D Graphics course taught using three.js.
I warmly recommend it. If you didn't have a chance to play with transformations you might want to start with Chapter 4.
As a side note. You can also cheat a bit and use Matrix4's lookAt() to solve the rotation, offset the translation so the pivot is at the tip of the cylinder, then apply the matrix to the cylinder.
I am defining a cone that I need to be able to rotate around its top point (the point where the cone thickness is the smallest one). I couldn't find yet the way to set the point around which the rotation to happen.
var coneGeometry = new THREE.CylinderGeometry(1000, 0, width, 50, 50, false);
var cone = new THREE.Mesh(coneGeometry, material);
cone.position.x = x;
cone.position.y = y + width / 2;
cone.position.z = z;
// I want this rotation to happen around the point given by the (x, y, z) location
cone.rotation.x = dip;
The cone geometry is centered at the origin. What you need to do is translate the cone geometry right after it is created so the tip of the cone is at the origin.
coneGeometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, coneHeight/2, 0 ) );
(The sign of the translation changes depending on which end of the cone is the small end.)
Now, when you rotate the cone, it will rotate around the tip. The tip will also be located at the position you set.
EDIT: You can now do this, instead:
coneGeometry.translate( 0, coneHeight/2, 0 ); // three.js r.72