Relatively new to THREE.js. I am trying to figure out how to project a DIV with text into an equirectangular panorama.
I have this simple example working with my panorama images.
https://threejs.org/examples/webgl_panorama_equirectangular
The question: I have a latitude and longitude of a feature that's in my panorama, I'd like to project a DIV labeling such item into 3D space. How do I convert longitude and latitude into X and Y on the canvas so I can change the DIVS left and top style attributes so the label renders in 3D space and appears fixed to its coordinates?
UPDATE:
For clarity, how does one take planet earth longitude and latitude, and convert it into X Y pixels inside a mesh? I know where the image was taken on earth, and I know where an item in that picture was taken on earth. I want to label that item in 3D space.
Any help would be much appreciated. Thanks.
Code from this question seems to do the trick:
3d coordinates to 2d screen position
function getCoordinates( element, camera ) {
var screenVector = new THREE.Vector3();
element.localToWorld( screenVector );
screenVector.project( camera );
var posx = Math.round(( screenVector.x + 1 ) * renderer.domElement.offsetWidth / 2 );
var posy = Math.round(( 1 - screenVector.y ) * renderer.domElement.offsetHeight / 2 );
console.log( posx, posy );
}
I updated the jsfiddle with the new version of Three.js
http://jsfiddle.net/L0rdzbej/409/
Related
The bounding box doesn't seem to be aligned with the 3d mesh. This is the reason. Can you please give me some tips on how should I rotate the bounding box so that it's properly aligned with 3d mesh?
This is the screenshot of the 3d mesh and misaligned object bounding box:
Things I've tried:
Tried the approach of offsetting center position from object.
Tried to remove the rotation and translation of the object before calling 'setFromObject'.
Tried to rotate the box directly.
You can try to use the center point of the model's AABB in order to offset it as requested. The code for this looks like so:
const box = new THREE.Box3().setFromObject( model );
const center = box.getCenter( new THREE.Vector3() );
object.position.x += ( object.position.x - center.x );
object.position.y += ( object.position.y - center.y );
object.position.z += ( object.position.z - center.z );
I have a special control called SphericalControls. Its similar to OrbitControls, but it keeps camera at position 0,0,0 and instead rotates camera on x and y to look around a scene. It is placed in the middle of a SphereBufferGeometry which has a 360 equirectangular image projected upon it. The user can look around the 360 image, and as he does the camera x and y rotation values change.
When a user clicks a button, I need to take these x and y rotation values and rotate the sphere to the rotation of the camera. I then set camera back to x:0 and y:0.
The result is that the camera is reset and the 360 scene has now rotated to show the same rotation view that the camera was previously looking at. So to the user, the view stays basically static, just the values for camera.rotation and sphere rotation have swapped.
This works great if I offset the texture on the sphere:
sphereObj.material.map.wrapS = THREE.RepeatWrapping;
sphereObj.material.map.offset.x = ((camera.rotation.x) / (Math.PI * 2));
sphereObj.material.map.needsUpdate = true;
sphereObj.material.needsUpdate = true;
camera.rotation.set(0, 0);
// Success!
But what I need to do is not offset the texture, but rotate the entire geometry. I have tried:
var axis = new THREE.Vector3(0, 1, 0).normalize();;
var offsetRadian = ((camera.rotation.x) / (Math.PI * 2));
sphere.rotateOnAxis(axis, offsetRadian);
// Fail
But the result is that the sphere rotation is off by approx 30%. Any help is appreciated.
Every objects' rotational data is stored in their respective .quaternion object. Both camera and sphereObj have a quaternion, so what you could do is copy the camera's rotational data into the sphere:
// Get camera's rotation
targetRotation = camera.quaternion;
// Invert rotation
targetRotation.inverse();
// Set sphere's rotation
sphereObj.quaternion.copy(targetRotation);
camera.rotation.set(0, 0, 0);
I'm not entirely sure if you need the .inverse() line... if you're noticing the sphere is rotating in the opposite direction, just get rid of it to get the desired result.
I'm struggling with the positioning of some aframe text geometry and am wondering if I'm going about this the wrong way 😅
I'm finding that when the box renders, the center point is at the minimum point of all the axises (bottom-left-close). This means the text expands more to the top-right-far than I would expect. This is different from aframe geometry entitites where the center point is at the very center of all axises.
Sorry if the above phrasing is confusing, I'm still not sure how to best describe things in a 3d space 😆
What I'm thinking I need to do is calculate the bounding box after the element has loaded and change the position to the center. I've based that approach on the answer here AFRAME text-geometry component rotation from center?.
Does that seem like the right direction? If so, I'm currently trying to do this through an aframe component
aframe.registerComponent('center-all', {
update() {
// Need to wait for the element to be loaded
setTimeout(() => {
const mesh = this.el.getObject3D('mesh');
const bbox = new THREE.Box3().setFromObject(this.el.object3D);
const offsetX = (bbox.min.x - bbox.max.x) / 2;
const offsetY = (bbox.min.y - bbox.max.y) / 2;
const offsetZ = (bbox.min.z - bbox.max.z) / 2;
mesh.position.set(offsetX, offsetY, offsetZ);
}, 0);
}
});
This code illustrates the problem I'm seeing
This code shows my attempted solution
This code (with the translation hard coded) is more like what I would like
TextGeometry and TextBufferGeometry are both subclasses of the respective geometry classes, and so both have the boundingBox property. You just need to compute it, then get its center point:
textGeo.computeBoundingBox();
const center = textGeo.boundingBox.getCenter(new Vector3());
Then center will accurately reflect the center of the geometry, in local space. If you need it in global space, you will need to apply the matrix of the mesh that contains textGeo to the center vector, e.g.
textMesh.updateMatrixWorld();
center.applyMatrix4(textMesh.matrixWorld);
I know a method from Unity whichs is very useful to convert a screen position to a world position : https://docs.unity3d.com/ScriptReference/Camera.ScreenToWorldPoint.html
I've been looking for something similar in A-Frame/THREE.js, but I didn't find anything.
Is there an easy way to convert a screen position to a world position in a plane which is positioned a given distance from the camera ?
This is typically done using Raycaster. An equivalent function using three.js would be written like this:
function screenToWorldPoint(screenSpaceCoord, target = new THREE.Vector3()) {
// convert the screen-space coordinates to normalized device coordinates
// (x and y ranging from -1 to 1):
const ndc = new THREE.Vector2()
ndc.x = 2 * screenSpaceCoord.x / screenWidth - 1;
ndc.y = 2 * screenSpaceCoord.y / screenHeight - 1;
// `Raycaster` can be used to convert this into a ray:
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(ndc, camera);
// finally, apply the distance:
return raycaster.ray.at(screenSpaceCoord.z, target);
}
Note that coordinates in browsers are usually measured from the top/left corner with y pointing downwards. In that case, the NDC calculation should be:
ndc.y = 1 - 2 * screenSpaceCoord.y / screenHeight;
Another note: instead of using a set distance in screenSpaceCoord.z you could also let three.js compute an intersection with any Object in your scene. For that you can use raycaster.intersectObject() and get a precise depth for the point of intersection with that object. See the documentation and various examples linked here: https://threejs.org/docs/#api/core/Raycaster
I need to convert the position and rotation on a 3d object to screen position and rotation. I can convert the position easily but not the rotation. I've attempted to convert the rotation of the camera but it does not match up.
Attached is an example plunkr & conversion code.
The white facebook button should line up with the red plane.
https://plnkr.co/edit/0MOKrc1lc2Bqw1MMZnZV?p=preview
function toScreenPosition(position, camera, width, height) {
var p = new THREE.Vector3(position.x, position.y, position.z);
var vector = p.project(camera);
vector.x = (vector.x + 1) / 2 * width;
vector.y = -(vector.y - 1) / 2 * height;
return vector;
}
function updateScreenElements() {
var btn = document.querySelector('#btn-share')
var pos = plane.getWorldPosition();
var vec = toScreenPosition(pos, camera, canvas.width, canvas.height);
var translate = "translate3d("+vec.x+"px,"+vec.y+"px,"+vec.z+"px)";
var euler = camera.getWorldRotation();
var rotate = "rotateX("+euler.x+"rad)"+
" rotateY("+(euler.y)+"rad)"+
" rotateY("+(euler.z)+"rad)";
btn.style.transform= translate+ " "+rotate;
}
... And a screenshot of the issue.
I would highly recommend not trying to match this to the camera space, but instead to apply the image as a texture map to the red plane, and then use a raycast to see whether a click goes over the plane. You'll save yourself headache in translating and rotating and then hiding the symbol when it's behind the cube, etc
check out the THREEjs examples to see how to use the Raycaster. It's a lot more flexible and easier than trying to do rotations and matching. Then whatever the 'btn' onclick function is, you just call when you detect a raycast collision with the plane