Marker position in Screen format (using AR.js) - javascript

I was able to get the marker position using the following code
this.marker.object3D.getWorldPosition(vector);
I would like to know if it is possible to convert this position (x,y,z) in the equivalent screen position (width, height).
Could you please give me some ideas to solve this issue?

You can do this with two steps:
Convert the position in world space to NDC space via Vector3.project().
Use the width and height of your canvas for the screen space conversion.
const vector = new THREE.Vector3();
vector.project( camera );
vector.x = ( vector.x + 1) * width / 2;
vector.y = - ( vector.y - 1) * height / 2;
vector.z = 0;

Related

How to get the visible width and height of a ThreeJS mesh object in pixel unit

I am facing the challenge of figuring out the visible view width and height of a ThreeJS mesh object in pixel units.
In the screenshot below you can see objects floating in 3D space, on mouse click I need to be able to figure out what view width and height they are occupying in pixels
As I am rather new to ThreeJS it is taking me rather long to find a solution, so I would welcome any kind of assistance.
The below function shows what kind of approaches I have been trying.
getObjectSizeInViewSpace(object){
const size = new THREE.Vector3()
const box = new THREE.Box3().setFromObject(object).getSize(size)
size.project(this.camera)
let halfWidth = window.innerWidth / 2;
let halfHeight = window.innerHeight / 2;
size.x = (size.x*halfWidth)
size.y = (size.y*halfHeight)
return new THREE.Vector2(size.x,size.y)
}
You're looking for Vector3.project(). This basically takes world-space (3D) coordinates, and uses the camera's viewport to convert into normalized device coordinates, which range from [-1, 1]. For example x: -1 is the left side of the screen, and x: 1 is the right side. So you'll have to take the 4 vectors (top left, top right, bottom left, bottom right)` of your plane to calculate their pixel dimensions in your browser:
// Get 3D positions of top left corner (assuming they're not rotated)
vec3 topLeft = new Vector3(
plane.position.x - planeWidth / 2,
plane.position.y - planeHeight / 2,
plane.positon.z
);
// This converts x, y, z to the [-1, 1] range
topLeft.project(camera);
// This converts from [-1, 1] to [0, windowWidth]
const topLeftX = (1 + topLeft.x) / 2 * window.innerWidth;
const topLeftY = (1 - topLeft.y) / 2 * window.innerHeight;
Notice the topLeftY value is inverted, since -y in 3D space goes +y in pixel coordinates. Do this 4 times (once for each corner), and then you can subtract (right - left) to get the width, and the same for the height.

How can I incorporate the camera's position into this algorithm to calculate a sprite's position on the screen?

I'm working on a 2D rendering system in 3D space, for practice. The sprites are 2D, but they're rendered in 3D space. The "camera" can move in 3D space and turn 360 degrees horizontally. I'm having trouble figuring out the right formula to calculate, based on the position/rotation of the camera and the position of the assets, where they should exist on the screen.
What I have is like this:
chunk.assets.forEach(asset => {
let x = Math.round(
asset.coords.x * Math.cos(angle) - asset.coords.y * Math.sin(angle)
);
let y = Math.round(
asset.coords.y * Math.cos(angle) + asset.coords.x * Math.sin(angle)
);
if (!depthMap[y]) {
depthMap[y] = [];
}
depthMap[y].push(asset);
});
But this does not take into account the camera's (player's) position (stored at player.coords.x, player.coords.y), only the angle the camera/player is facing (angle). So right now the camera can't move. Note: Depth map is just storing the assets in order so the renderer knows which order to render the sprites so things appear in the right order based on which is closer to the player/camera.
How can I incorporate the camera's position into this algorithm?
Some assumptions:
If the camera and the sprite are at the same position, the sprite will be rendered at 0|0.
Wether the camera moves left or the sprite moves right does not matter.
From that we can conclude that only the relative position matters, and that can be easily calculated (by subtracting both positions).
let x = Math.round(
(asset.coords.x - player.coords.x) * Math.cos(angle) - (asset.coords.y - player coords.y) * Math.sin(angle)
);
let y = Math.round(
(asset.coords.y - player.coords.y) * Math.cos(angle) + (asset.coords.x - player.coords.x) * Math.sin(angle)
);

Find the Points of Intersection of a Circle with a Line in Javascript

I'm trying to animate a given element to go around a pre-defined radius and I'm having trouble getting the position of the element at a Y point given.
I'm trying to find each point with the circle equation, but I can only get one point out of the two possible ones.
In Javascript, I use Math.sqrt( Math.pow(radius, 2) - Math.pow(y, 2) , 2) to get the point. assuming the center of the of the circle is 0,0.
but then I need to translate it to pixels on the screen since there are no negative pixels in positions on the browser.
All the sizing is relative to the window. so the radius, for example, is 80% of the height of the window in my tests.
Also, I'm trying to calculate what the distance of the element between each frame should be for the duration, but I'm not using it yet because I try to fix the issue above first.
This is what I have(a cleaned up version):
let height = window.innerHeight * 0.8,
radius = height / 2,
circumferance = (radius * 2) * Math.PI,
container = document.getElementById('container'),
rotating = document.querySelector('.rotating'),
centerX = radius - (rotating.offsetWidth / 2),
centerY = radius - (rotating.offsetHeight / 2),
duration = 10,
stepDistance = circumferance / 16;
// Setting the dimensions of the container element.
container.style.height = height + 'px';
container.style.width = height + 'px';
// return positive X of any given Y.
function getXOffset(y) {
return Math.sqrt( Math.pow(radius, 2) - Math.pow(y, 2) , 2);
}
// Setting the position of the rotating element to the start.
rotating.style.top = 0 + 'px';
rotating.style.left = centerX + 'px';
setInterval(() => {
let top = parseInt(rotating.style.top),
y = radius - top;
rotating.style.top = (top + 1) + 'px';
rotating.style.left = (centerX + getXOffset(y)) + 'px';
}, 16);
Here is a fiddle with a bit more code for trying to get the right amount of distance between points for a smoother animation(currently needs fixing, but it doesn't bother me yet.)
https://jsfiddle.net/shock/1qcfvr4y/
Last note: I know that there might be other ways to do this with CSS, but I chose to use javascript for learning purposes.
Math.sqrt would only return the positive root. You'll have to account for the negative value based on the application. In this case, you need the positive x value during the 1st half of the cycle and negative during the 2nd half.
To do that, you should implement a method to track the progress and reverse the sign accordingly.
Here is a sample. I modified upon yours.
edit:
Instead of Math.sqrt( Math.pow(radius, 2) - Math.pow(y, 2) , 2) You can use the full formula to get x if you do not want to assume origin as center, which in this case is Math.sqrt( Math.pow(radius, 2) - Math.pow((actualY - centerY), 2) , 2)
explanation:
The original equation (x-a)² + (y'-b)² = r²
becomes x = √(r² - (y'-b)²) + a
Assuming .rotating box have 0 width and height.
The variable equivalents in your code are centerX = a, centerY = b.
By assuming origin as center you're basically doing a pre-calculation so that your y value becomes the equivalent of (y'-b). Hence x = √(r² - y²) + a is valid.
At initial state top = 0
i.e (y'-b) => height - centerY.
In your code y = radius => height/2.
Now (height - centerY) being equal to (height/2) is a side effect of your circle being bound by a square container whose height determines the y value.
In other words, when you use origin as center, you are taking the center offsets outside of circle equation and handling it separately. You could do the same thing by using the whole formula, that is, x = √(r² - (y'-b)²) + a

Calculate radius of helix so that models in helix are inside the frustum

I'm building an app in which I present some planes with textures. However, I would like to calculate the radius of the helix (which I use in my calculations to create a helix), dynamically based on the frustum width and the camera position.
The helix is positioned at the center of the screen x=0, y=0, z=0.
I would like this to take under consideration the screen orientation (landscape/ portrait).So far this is the code I have but it seems that I'm missing something because the planes at the left and the right are not inside the viewport.
App.prototype.calculateHelixRadius = function(){
// plane width = height = 512;
var friend = this.getFriend();
var vFOV = friend.camera.fov * Math.PI / 180;
var dist = utils.getAbsPointsDistance3D(friend.camera.position, friend.scene.position);
var aspect = friend.settings.container.clientWidth / friend.settings.container.clientHeight;
var frustumHeight = 2.0 * dist * Math.tan(0.5 * vFOV);
var frustumWidth = frustumHeight * aspect;
return utils.isLandscape() ? frustumHeight / 2 : frustumWidth / 2 ;
};
What am I doing wrong and why are the planes at the edges of the screen not inside?
Also for reference here is the code of getAbsPointsDistance3D
var utils = {
// other helpers...
getAbsPointsDistance3D: function(p1, p2) {
var xd = p2.x - p1.x;
var yd = p2.y - p1.y;
var zd = p2.z - p1.z;
return Math.sqrt(xd * xd + yd * yd + zd * zd);
}
};
update
I tried decreasing the dist parameter but the results are not consistent...
I wonder if the following explains your clipping.
You calculate your frustum characteristics, then calculate the helix radius using, say, the frustum width (width or height depending on the screen aspect...I may be getting some of the particulars wrong here because your question does not completely explain the details, but the general concepts still hold). The image below is a top view of the scenario which shows a circle representing the cylinder that encloses the helix. I believe you have calculated radius1. If so, note that there will be clipping of the cylinder (the shaded area), and thus the helix, in "front" of the cylinder centre.
Instead you need to calculate the cylinder/helix radius as shown in the second image, i.e. you need radius2. If the large angle at the image left is fov (again, vFOV? or hFOV?, etc., depending on whether your helix is going up-down or side-to-side, etc.), then its half angle is fov/2. This is the same angle shown in the centre of the cylinder. Thus, you need to decrease your helix radius as follows: radius2 = radius1 * cos(fov/2).

Calculate camera zoom required for object to fit in screen height

In Three.js I'm using this formulas to calculate visible width & height
var vFOV = camera.fov * Math.PI / 180; // convert vertical fov to radians
var height = 2 * Math.tan( vFOV / 2 ) * dist; // visible height
var aspect = window.width / window.height;
var width = height * aspect; // visible width
And with that I calculate the camera zoom required for object to fit exactly into render area by WIDTH
var zoom = (ObjectHeight/aspect) / (2*Math.tan(vFOV/2)) + ObjectDepth;
How do I calculate the camera zoom required for object to fit exactly into render area by HEIGHT?
Thanks to GuyGood, I found the solution:
var zoom = (ObjectHeight/2) / Math.tan(vFOV/2) - ObjectDepth;
I am doing the following which is based on the boundingSphere radius:
geometry.computeBoundingSphere();
radius = geometry.boundingSphere.radius;
distanceFactor = Math.abs( aspect * radius / Math.sin( fov/2 );
This is based on this stuff right here and I hope i interpreted it the right way:
http://www.flipcode.com/forums/thread/4172
This distanceFactor is the factor you need to move the camera along its viewing direction to fit it correctly. At the moment i am not sure if it is by height or width but maybe it helps you figure it out. :)

Categories