How to make Three.js FOV responsive to height AND width? - javascript

Background
I size everything using viewport units:
body {
font-size: calc((1vw + 1vh) / 2);
}
h6 {
font-size: 1em;
}
div {
width: 20em;
}
As the number of pixels in the screen increases, so will the size of units. For example, a 1920 x 1080 display will have smaller type than a 2560 x 1080 display. This allows automatic support of ultra-wide, vertical, and high DPI (8k or even 16K) displays without media queries.
Problem
Using Three.js, object scale responds only to the height of the screen. The object will appear the same size on a 1920x1080 monitor as it is on a 2560x1080 monitor. This is because Three.js camera uses a vertical field of view (FOV).
Default Behaviour - 2560x1080 compared to 1080x1080. Notice, the text becomes smaller, but the 3D object stays the same size. codepen example
My Attempt
The reason Three.js only responds to height is because it uses a vertical-fov. I tried to change the vertical-fov to diagonal-fov using this formula I found on stackOverflow here.
var height = window.innerHeight;
var width = window.innerWidth;
var distance = 1000;
var diag = Math.sqrt((height*height)+(width*width))
var fov = 2 * Math.atan((diag) / (2 * distance)) * (180 / Math.PI);
camera = new THREE.PerspectiveCamera(fov , width / height, 1, distance * 2);
camera.position.set(0, 0, distance);
The resulting behavior is the opposite of what should happen.
On a smaller screen the object becomes larger.
On a larger screen the object becomes smaller.
current results
Objects in Three.js only become larger when increasing the viewport height. I attempted to modify the default vertical-fov to a diagonal-fov. However, this did not work.
desired results
When the viewport is resized, the object should change perceived size based on the formula ((viewport height + viewport width) / 2). This will ensure and text placed on the page remains the same relative scale to 3D objects. I would like to achieve this by altering the camera, instead of the 3D objects themselves.

You should create a scale ratio based on the width or height of the screen. For example:
SCREEN_WIDTH = window.innerWidth;
SCREEN_HEIGHT = window.innerHeight;
if(SCREEN_WIDTH > {something} && SCREEN_WIDTH < {something}){
camera.fov = SCREEN_WIDTH / {something}; //This is your scale ratio.
};
//Repeat for window.innerHeight or SCREEN_HEIGHT.
if(SCREEN_HEIGHT > {something} && SCREEN_HEIGHT < {something}){
camera.fov = SCREEN_HEIGHT / {something}; //This is your scale ratio for height, it could be same as window.innerWidth if you wanted.
};
//Updating SCREEN_WIDTH and SCREEN_HEIGHT as window resizes.
window.addEventListener('resize', onResize, false); //When window is resized, call onResize() function.
function onResize() {
SCREEN_WIDTH = window.innerWidth; //Re-declaring variables so they are updated based on current sizes.
SCREEN_HEIGHT = window.innerHeight;
camera.aspect = window.innerWidth / window.innerHeight; //Camera aspect ratio.
camera.updateProjectionMatrix(); //Updating the display
renderer.setSize(window.innerWidth, window.innerHeight) //Setting the renderer to the height and width of the window.
};
Hope this helps! Let me know if you still have any questions
-Anayttal

Related

Fabric js How to set percentage object's position

I'm using Fabric.js and i changing canvas size dynamically, so i need scale all canvas objects with appropriate objects indents (in percent depending on canvas size) top and left. How i can set top and left objects position in percent?
like in css:
top: 32%
left: 10%
// set canvas size dynamically by resize event
this.canvas.setWidth(size.width);
this.canvas.setHeight(size.height);
In my opinion you should have two values in order to calculate resize ratio.
initialCanvasWidth and finalCanvasWidth.
By assuming that you have initialCanvasWidth value in page load. You can calculate resize ratio on every resize by a function like this:
window.onresize = utility.debounce(() => {
const finalCanvasWidth = canvasWrapper.clientWidth
const resizeRatio = (finalCanvasWidth / initialCanvasWidth).toFixed(3)
setCanvasSizeAndPosition()
scaleObjectsOnCanvas(resizeRatio)
}, 250)
By wrapping your canvas with a div and by using its new width after resize, you can set your new canvas width and height (in setCanvasSizeAndPosition function)
this.canvas.setWidth(newWidth).setHeight(newHeight)
I think you will also need to store your aspect ratio of your canvas at the beginning in order to calculate newHeight value
After setting your new canvas size, you can continue with scaling and positioning objects on canvas (scaleObjectsOnCanvas function)
scaleObjectsOnCanvas(resizeRatio) {
const objects = canvas.getObjects()
for (let i = 0; i < objects.length; i++) {
objects[i].left = objects[i].left * resizeRatio
objects[i].top = objects[i].top * resizeRatio
objects[i].scaleX = objects[i].scaleX * resizeRatio
objects[i].scaleY = objects[i].scaleY * resizeRatio
}
this.canvas.renderAll()
}
Hope that helps.
Here's a real world working example of responsive canvas/fabric.js usage

CreateJS: Elements in Stage or Canvas are not scaling properly on window resize

So I am adjusting the size of the stage (canvas) upon window resize. While the canvas itself is readjusting correctly, the content of the canvas are not really scaling and in some cases some of the elements (ex: text, buttons etc.) aren't fully visible.
Here's the code
stage = new Stage(document.getElementById("canvas"));
window.addEventListener('resize',resize,false);
function resize() {
var w = window.innerWidth;
var h = window.innerHeight;
var ow = stage.canvas.width;
var oh = stage.canvas.height;
// keep aspect ratio
scale = Math.min(w / ow, h / oh);
stage.scaleX = scale;
stage.scaleY = scale;
// adjust canvas size
stage.width = ow * scale;
stage.height = oh * scale;
stage.update();
}
Is there something I am missing?
A few quick things:
You can't set the width/height of the stage, but should instead set the size of the canvas. You also don't need to constrain its aspect ratio, unless you are trying to crop content.
stage.width = ow * scale;
stage.height = oh * scale;
Next, you might consider putting your content in a Container, and scaling that, instead of the stage directly.
Hope that helps!

Change generated image resolution Fabric.js

I'm creating a Fabric.js based image editor, but I have a problem with final image resolution. I need generated image in high resolution but dimension for my editor are in pixel in low resolution.
For example: canvas has 800px x 600px and I need an final image with 100 cm x 400 cm, in other words, in real size.
Let me put some idea from my experience here -
if the final resolution is large, but not extreme large, you can do zoom canvas to its size before generate image data (toDataURL, for instance)
if the final resolution is extreme large, I suggest you can deal with it from PHP directly
For the first one -
var originWidth = canvas.getWidth();
function zoom (width)
{
var scale = width / canvas.getWidth();
height = scale * canvas.getHeight();
canvas.setDimensions({
"width": width,
"height": height
});
canvas.calcOffset();
var objects = canvas.getObjects();
for (var i in objects) {
var scaleX = objects[i].scaleX;
var scaleY = objects[i].scaleY;
var left = objects[i].left;
var top = objects[i].top;
objects[i].scaleX = scaleX * scale;
objects[i].scaleY = scaleY * scale;
objects[i].left = left * scale;
objects[i].top = top * scale;
objects[i].setCoords();
}
canvas.renderAll();
}
zoom (2000);
// here you got width = 2000 image
var imageData = canvas.toDataURL({
format: 'jpeg',
quality: 1
});
zoom (originWidth);
I never tried it on 100cm x 400cm, because it's really large, so if you can't do it from fabric.js, do it in PHP or else, this link may be help Convert SVG image to PNG with PHP
100cm = 39.3inch and 400cm = 39.3 * 4inch, if you have 300dpi image for final output. You will need width = 39.3 * 300 and height 39.3 * 4 * 300 size, if browser can handle it or not, I am not sure.

ThreeJS: Screen position to camera position

I've seen a lot of posts people want to have the camera position to screen position. My question is how to do the contrary.
What I currently want to achieve is set the "door" position to a % of the screen, this calculation is ready, and I do have the final screen X, Y (px) position. The current Z offset = 250 of the camera.
I've found this code to convert camera position to screen position:
var vector = projector.projectVector(door.position.clone(), camera);
vector.x = (vector.x + 1) / 2 * window.innerWidth;
vector.y = -(vector.y - 1) / 2 * window.innerHeight;
To do the reversie I tried this, but does not give the result I expect:
var mouseX = ((perc.x) / window.innerWidth) * 2 - 1,
mouseY = -((perc.y) / window.innerHeight) * 2 + 1;
var vector = new THREE.Vector3(mouseX, mouseY, 1);
projector.unprojectVector(vector, camera);
return vector.sub(camera.position).normalize()
I have tried several ways and tried to Google, but didn't found an answer.
Q: How to convert screen position to camera position?
Well, as posted by Gero3, what you want to do is to create a plane that will cover all of your visible camera area (something like new THREE.PlaneGeometry(5000,5000); and then use raycaster to locate screen coordinated on that plane.
Here is an example from another similar question:
http://jsfiddle.net/PwWbT/
Hope that helps.
Use a large invisble plane that defines in which direction the door can expand and then use a raycaster on the plane to search for the position to where the door object should extend.

Three.js - Orthographic camera

I've working on an app which displays some 3D models. We load the models, create the meshes, add them to the scene...standard procedure. After the last mesh is added, we compute the bounding box in order to move the camera and cover all the scene, using the size of the total geometry and the size of the viewport to do the math.
if (bounds.bx / bounds.by < camera.aspect) {
/* Vertical max */
r = bounds.by / (2 * Math.tan(Math.PI / 8));
} else {
/* Horizontal max */
hFOV = 2 * Math.atan(Math.tan(Math.PI / 8) * camera.aspect);
r = bounds.bx / (2 * Math.tan((hFOV / 2)));
}
bounds is an object containing the width and height of the bounding box. After this calculation, we move the camera(plus a little ratio, just aesthetics, we want a little room between the geometry and the screen border :) ) and render
camera.position.z = r * 1.05;
So far this is implemented and runs ok. This has been done with PerspectiveCamera. Now we want to change that and use OrthographicCamera...turns out to be a mess. Models are too small, we lose the mousewheel zoom from the TrackBall Controls and the algorithm to move the camera is not working anymore. Also I don't understand the parameters of the constructor for the camera...these width and height are for the geometry or the viewport?
The pattern for instantiating an orthographic camera in three.js is:
var camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, near, far );
where width and height are the width and height of the camera's cuboid-shaped frustum measured in world-space units.
near and far are the world-space distances to the near and far planes of the frustum. Both near and far should be greater than zero.
To prevent distortion, you will typically want the aspect ratio of the orthographic camera ( width / height ) to match the aspect ratio of the render's canvas. (see *Note below)
It is unfortunate that many of the three.js examples pass window.innerWidth and window.innerHeight as args to this constructor. Doing so only makes sense if the orthographic camera is used for rendering to a texture, or if the world units for your orthographic scene are in pixels.
*Note: Actually, the camera aspect ratio should match the aspect ratio of the renderer's viewport. The viewport can be a sub-region of the canvas. If you do not set the renderer's viewport directly using renderer.setViewport(), the viewport will be the same size as the canvas, and hence have the same aspect ratio as the canvas.
three.js r.73
For future reference:
Updated video
var w = container.clientWidth;
var h = container.clientHeight;
var viewSize = h;
var aspectRatio = w / h;
_viewport = {
viewSize: viewSize,
aspectRatio: aspectRatio,
left: (-aspectRatio * viewSize) / 2,
right: (aspectRatio * viewSize) / 2,
top: viewSize / 2,
bottom: -viewSize / 2,
near: -100,
far: 100
}
_camera = new THREE.OrthographicCamera (
_viewport.left,
_viewport.right,
_viewport.top,
_viewport.bottom,
_viewport.near,
_viewport.far
);
In my specific case, my world units are pixels. Hence, I am using container.clientWidth and container.clientHeight as width and height. You probably don't want to do this.
camera.top = (.95*camera.top);
camera.bottom = (.95*camera.bottom);
camera.left = (.95*camera.left);
camera.right = (.95*camera.right);

Categories

Resources