Calculate camera zoom required for object to fit in screen height - javascript

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. :)

Related

Marker position in Screen format (using AR.js)

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;

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).

Make a Circle Radius Based on viewportwidth (vw)

I have a free jQuery image gallery that I am trying to make some modifications to to make it suit my project.
The gallery is a spinning circle with images.
The radius of the circle is defined in this manner:
radius = Math.round( (250) / Math.tan( Math.PI / itemLength ) );
However what I need is to make a new radius based on viewportwidth (vw)
Can anyone help me approach this correctly?
Also I would be very appreciative if someone would help me understand what is happening in the above code.
Here is the context for that line of code:
w = $(window);
container = $( '#contentContainer' );
carousel = $( '#carouselContainer' );
item = $( '.carouselItem' );
itemLength = $( '.carouselItem' ).length;
fps = $('#fps');
rY = 360 / itemLength;
radius = Math.round( (250) / Math.tan( Math.PI / itemLength ) );
https://jsfiddle.net/mxp5svjx/
here is a picture as requested:
the main problem is when i resize the window then radius of the circle stays the same.
here is a working demo:
http://codepen.io/johnblazek/full/nceyw/
I could be wrong but I think
Math.tan( Math.PI / itemLength )
is calculating the angle of each segment, the higher the value of itemLength gets, the smaller the angle. All items need to fit in the circle. The tan function generates a value depending on the angle value.
Then 250 is divided by the former result.
I guess swapping 250 with vw results in too high a value.
If you know the viewwidth it looks good on already then you can try something like:
radius = Math.round( (250 * (vw/default_vw) ) / Math.tan( Math.PI / itemLength ) );
I Found out the Key is 250. 250 is 13.02% of 1920 assuming you have a 1920px width window which I do. So i fiqured out I need 13.02% of window width.
I have obtained that by doing this:
radius = Math.round10( (($(window).width()) * 0.1302) / Math.tan( Math.PI / itemLength ) );
Math.round10(); // rounds to the nearest decimal.
$(window).width(); // is the width of the window. In my case 1920px
0.1302 is 13.02% // when you multiply it with something.
Final result is that I get a radius based on 13.02vw

Image jumping at the beginning of scrolling

I wrote some code to zoom in my image, but when I scroll at the very beginning this picture jumps a little. How to fix the problem?
Full page view.
Editor view.
HTML
<canvas id="canvas"></canvas>
JS
function draw(scroll) {
scroll = (window.scrollY || window.pageYOffset) / (document.body.clientHeight - window.innerHeight) * 3000;
canvas.setAttribute('width', window.innerWidth);
canvas.setAttribute('height', window.innerHeight);
//The main formula that draws and zooms the picture
drawImageProp(ctx, forest, 0, (-scroll * 3.9) / 4, canvas.width, canvas.height + (scroll * 3.9) / 2);
}
Not a bug fix
I had a look at the Codepen example and it does jump at the top (sometimes). I have a fix for you but I did not have the time to locate the source of your code problem. I did notice that the jump involved a aspect change so it must be in the scaling that your error is. (look out for negatives)
GPU is a better clipper
Also your code is actually doing unnecessary work, because you are calculating the image clipping region. Canvas context does the clipping for you and is especially good at clipping images. Even though you provide the clip area the image will still go through clip as that is part of the render pipeline. The only time you should be concerned about the clipped display of an image is whether or not any part of the image is visible so that you don't send a draw call, and it only really matters if you are pushing the image render count (ie game sprite counts 500+)
Code example
Anyway I digress. Below is my code. You can add the checks and balances. (argument vetting, scaling max min, etc).
Calling function.
// get a normalised scale 0-1 from the scroll postion
var scale = (window.scrollY || window.pageYOffset) / (document.body.clientHeight - window.innerHeight);
// call the draw function
// scale 0-1 where 0 is min scale and 1 is max scale (min max determined in function
// X and y offset are clamped but are ranged
// 0 - image.width and 0 - image.height
// where 0,0 shows top left and width,height show bottom right
drawImage(ctx, forest, scale, xOffset, yOffset);
The function.
The comments should cover what you need to know. You will notice that all I am concerned with is how big the image should be and where the top left corner will be. The GPU will do the clipping for you, and will not cost you processing time (even for unaccelerated displays). I personally like to work with normalised values 0-1, it is a little extra work but my brain likes the simplicity, it also reduces the need for magic numbers (magics number are a sign that code is not adaptable) . Function will work for any size display and any size image. Oh and I like divide rather than multiply, (a bad coding habit that comes from a good math habit) replacing the / 2 and needed brackets with * 0.5 will make it more readable.
function drawImage(ctx, img, scale, x, y){
const MAX_SCALE = 4;
const MIN_SCALE = 1;
var w = canvas.width; // set vars just for source clarity
var h = canvas.height;
var iw = img.width;
var ih = img.height;
var fit = Math.max(w / iw, h / ih); // get the scale to fill the avalible display area
// Scale is a normalised value from 0-1 as input arg Convert to range
scale = (MAX_SCALE - MIN_SCALE) * scale + MIN_SCALE;
var idw = iw * fit * scale; // get image total display size;
var idh = ih * fit * scale;
x /= iw; // normalise offsets
y /= ih; //
x = - (idw - w) * x; // transform offsets to display coords
y = - (idh - h) * y;
x = Math.min( 0, Math.max( - (idw - w), x) ); // clamp image to display area
y = Math.min( 0, Math.max( - (idh - h), y) );
// use set transform to scale and translate
ctx.setTransform(scale, 0, 0, scale, idw / 2 + x, idh / 2 + y);
// display the image to fit;
ctx.drawImage(img, ( - iw / 2 ) * fit, (- ih / 2 ) * fit);
// restore transform.
ctx.setTransform(1, 0, 0, 1, 0, 0)
}
Sorry I did not solve the problem directly, but hopefully this will help you redesign your approch.
I recently added a similar answer involving zooming and panning (and rotation) with the mouse which you may be interested in How to pan the canvas? Its a bit messy still "note to self (my clean it up)" and has no bounds clamping. But shows how to set a zoom origin, and convert from screen space to world space. (find where a screen pixel is on a pan/scale/rotated display).
Good luck with your project.

Tweening Object3D towards camera in Three.js - this works, need to set new screen position

I have some code that tweens an object towards the camera and fits to half the screen
var vFOV = camera.fov * Math.PI / 180;
var ratio = 2 * Math.tan( vFOV / 2 );
var screen = ratio * (window.innerWidth * 0.6 / window.innerHeight * 0.6) ;
var size = getCompoundBoundingBox( object ).max.y;
var width = getCompoundBoundingBox( object ).max.x;
var dist = (size/screen) * (object.scale.x * 2);
//get final position in front of camera
var pLocal = new THREE.Vector3( 0, 0, -dist );
//apply the direction the camera is facing
var target = pLocal.applyMatrix4( camera.matrixWorld );
//tween the object towards the camera
var tweenMove = new TWEEN.Tween(object.position).to(target, 1500).easing(TWEEN.Easing.Cubic.InOut);
The next thing I need to do is able able to move it either to the left or up, for other UI on the screen and the responsive element.
i.e. to move it to the left 3rd, or top third. It also needs to still be square onto the camera.
I've tried changing the pLocal value to something like (1, 0, - dist) but this just rotates the object when it tweens up.
Any ideas how I can add that functionality?
Solved by adding a new look position to left of camera

Categories