I am attempting to move an object with Three.js by having the object track the mouse movements of a user. Whenever the mouse leaves the canvas and re-enters the canvas in a different spot, the object will jump to the position of the mouse in the new spot.
I am trying to avoid this jerky object movement and have it smoothly transition to the new location of the mouse when a user moves their cursor off screen and has it re-enter in a new position.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, 1, 1, 1000);
camera.position.set(0, 0, 20);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
var canvas = renderer.domElement;
document.body.appendChild(canvas);
var box = new THREE.Mesh(new THREE.BoxBufferGeometry(), new THREE.MeshNormalMaterial());
box.geometry.translate(0, 0, 0.5);
box.scale.set(1, 1, 3);
scene.add(box);
var plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), -10);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var pointOfIntersection = new THREE.Vector3();
canvas.addEventListener("mousemove", onMouseMove, false);
//Move object view based on user mouse position
function onMouseMove(event){
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
raycaster.ray.intersectPlane(plane, pointOfIntersection);
box.lookAt(pointOfIntersection);
}
renderer.setAnimationLoop(() => {
if (resize(renderer)) {
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
});
function resize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
Example of jump when moving mouse in canvas, then move mouse out of canvas to new position, and finally re-entering canvas: https://codepen.io/prisoner849/pen/yGMWNg
You will want to store the box look x y position in addition to the current mouse x y position coordinates.
Then inside the loop call an update function. Inside the update function you will ease the look position towards the current mouse position.
You then use lookPosition instead of mouse in raycaster.setFromCamera.
The easing can look like this:
const easeAmount = 8;
function update(){
lookPosition.x += ( mouse.x - lookPosition.x ) / easeAmount;
lookPosition.y += ( mouse.y - lookPosition.y ) / easeAmount;
}
Working solution:
https://codepen.io/cdigital/pen/aboEeGp
Related
My three.js scene is completely distorted until I move the mouse somewhere on the site.
You can see the nature of the distortion on the image below:
When I move the mouse, the scene suddenly pops and everything is fine. It doesn't seem to matter where exactly the cursor is within the site, it doesn't have to be over the canvas where my scene is rendered.
This is how the scene looks after moving the mouse:
The following three.js related dependencies are used:
"three": "^0.108.0"
"three-orbitcontrols": "^2.102.2"
"three.meshline": "^1.2.0"
I tried updating three to the latest version (0.116.1), but that didn't solve the issue either. I managed to replicate this issue on Firefox and Edge, but not on Chrome.
Some extra context: we use OffscreenCanvas for better performance, the mouse positions are sent from the main thread to the web worker on mousemove event, we use that information to slightly move the camera and the background (with offsets). I temporarily removed to mousemove handler logic from the web worker code and the issue still popped up, so it's probably unrelated. We use tween.js to make the camera animations smooth.
Relevant code snippets:
Scene setup:
const {scene, camera} = makeScene(elem, cameraPosX, 0, 60, 45);
const supportsWebp = (browser !== 'Safari');
imageLoader.load(backgroundImage, mapImage => {
const texture = new THREE.CanvasTexture(mapImage);
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
texture.minFilter = THREE.LinearFilter;
// Repeat background so we don't run out of it during offset changes on mousemove
texture.wrapS = THREE.MirroredRepeatWrapping;
texture.wrapT = THREE.MirroredRepeatWrapping;
scene.background = texture;
});
// Creating objects in the scene
let orbitingPlanet = getPlanet(0xffffff, true, 1 * mobilePlanetSizeAdjustment);
scene.add(orbitingPlanet);
// Ellipse class, which extends the virtual base class Curve
let curveMain = new THREE.EllipseCurve(
0, 0, // ax, aY
80, 30, // xRadius, yRadius
0, 2 * Math.PI, // aStartAngle, aEndAngle
false, // aClockwise
0.2 // aRotation
);
let ellipseMainGeometry = new THREE.Path(curveMain.getPoints(100)).createPointsGeometry(100);
let ellipseMainMaterial = new MeshLine.MeshLineMaterial({
color: new THREE.Color(0xffffff),
opacity: 0.2,
transparent: true,
});
let ellipseMain = new MeshLine.MeshLine();
ellipseMain.setGeometry(ellipseMainGeometry, function(p) {
return 0.2; // Line width
});
const ellipseMainMesh = new THREE.Mesh(ellipseMain.geometry, ellipseMainMaterial );
scene.add(ellipseMainMesh);
// Create a halfish curve on which one of the orbiting planets will move
let curveMainCut = new THREE.EllipseCurve(
0, 0, // ax, aY
80, 30, // xRadius, yRadius
0.5 * Math.PI, 1.15 * Math.PI, // aStartAngle, aEndAngle
false, // aClockwise
0.2 // aRotation
);
let lastTweenRendered = Date.now();
let startRotation = new THREE.Vector3(
camera.rotation.x,
camera.rotation.y,
camera.rotation.z);
let tweenie;
return (time, rect) => {
camera.aspect = state.width / state.height;
camera.updateProjectionMatrix();
let pt1 = curveMainCut.getPointAt(t_top_faster);
orbitingPlanet.position.set(pt1.x, pt1.y, 1);
t_top_faster = (t_top_faster >= 1) ? 0 : t_top_faster += 0.001;
// Slightly rotate the background on mouse move
if (scene && scene.background) {
// The rotation mush be between 0 and 0.01
scene.background.rotation =
Math.max(-0.001,Math.min(0.01, scene.background.rotation + 0.00005 * target.x));
let offsetX = scene.background.offset.x + 0.00015 * target.x;
let offsetY = scene.background.offset.y + 0.00015 * target.y;
scene.background.offset = new THREE.Vector2(
(offsetX > -0.05 && offsetX < 0.05) ? offsetX : scene.background.offset.x,
(offsetY > -0.05 && offsetY < 0.05) ? offsetY : scene.background.offset.y);
}
lastTweenRendered = tweenAnimateCamera(lastTweenRendered, tweenie, camera, startRotation, 200);
renderer.render(scene, camera);
};
makeScene function:
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(fieldOfView, state.width / state.height, 0.1, 100000000);
camera.position.set(camPosX, camPosY, camPosZ);
camera.lookAt(0, 0, 0);
scene.add(camera);
return {scene, camera};
Camera animation based on mouse positions:
function tweenAnimateCamera(lastTweenRendered, tween, camera, startRotation, period) {
target.x = (1 - mouse.x) * 0.002;
target.y = (1 - mouse.y) * 0.002;
let now = Date.now();
if ((
// Don't let the camera go too far
startRotation.x > -0.01 && startRotation.x < 0.01) &&
now - lastTweenRendered > (period / 2)) {
if (tween) {
tween.stop();
}
lastTweenRendered = now;
let endRotation = new THREE.Vector3(
camera.rotation.x + 0.005 * (target.y - camera.rotation.x),
camera.rotation.y + 0.005 * (target.x - camera.rotation.y),
camera.rotation.z);
tween = new TWEEN.Tween(startRotation)
.to(endRotation, period * 2)
.easing(TWEEN.Easing.Quadratic.InOut)
.onUpdate(function (v) {
camera.rotation.set(v.x, v.y, v.z);
})
.onComplete(function(v) {
startRotation = v.clone();
});
tween.start();
}
TWEEN.update();
return lastTweenRendered
}
Mouse position receiver logic:
if (e.data.type === 'mousePosUpdate') {
if (e.data.x !== -100000 && e.data.y !== -100000) {
mouse.x = ( e.data.x - state.width / 2 );
mouse.y = ( e.data.y - state.height / 2 );
target.x = ( 1 - mouse.x ) * 0.002;
target.y = ( 1 - mouse.y ) * 0.002;
}
}
Render loop:
function render(time) {
time *= 0.001;
for (const {elem, fn, ctx} of sceneElements) {
// get the viewport relative position of this element
canvasesUpdatedPos.forEach( canvasUpdate => {
if (canvasUpdate.id === elem.id) {
elem.rect = canvasUpdate.rect;
}
});
const rect = elem.rect;
const bottom = rect.bottom;
const height = rect.height;
const left = rect.left;
const right = rect.right;
const top = rect.top;
const width = rect.width;
const rendererCanvas = renderer.domElement;
const isOffscreen =
bottom < 0 ||
top > state.height ||
right < 0 ||
left > state.width;
if (!isOffscreen && width !== 0 && height !== 0) {
// make sure the renderer's canvas is big enough
let isResize = resizeRendererToDisplaySize(renderer, height, width);
// make sure the canvas for this area is the same size as the area
if (ctx.canvas.width !== width || ctx.canvas.height !== height) {
ctx.canvas.width = width;
ctx.canvas.height = height;
state.width = width;
state.height = height;
}
renderer.setScissor(0, 0, width, height);
renderer.setViewport(0, 0, width, height);
fn(time, rect);
// copy the rendered scene to this element's canvas
ctx.globalCompositeOperation = 'copy';
ctx.drawImage(
rendererCanvas,
0, rendererCanvas.height - height, width, height, // src rect
0, 0, width, height); // dst rect
}
}
// Limiting to 35 FPS.
setTimeout(function() {
if (!stopAnimating) {
requestAnimationFrame(render);
}
}, 1000 / 35);
}
I don't see where you're initiating target and mouse anywhere. My best guess is that target.x, target.y or mouse.x, mouse.y are undefined or 0, and it's probably causing a division by 0, or a calculation that returns NaN, which is giving you that infinitely stretched texture. You should be able to fix this if you initiate those vectors:
var target = new THREE.Vector2();
var mouse = new THREE.Vector2();
First of anyone running into similar issues please take a look at Marquizzo's answer and Ethan Hermsey's comment (on the question) as well, since they provided a good possible cause for this issue, although the issue was different in my case.
The distortion was related to the OffscreenCanvas in our case. When the application starts we send the OffscreenCanvas along with it's size to the web worker:
const rect = element.getBoundingClientRect();
const offScreenCanvas = element.transferControlToOffscreen();
worker.post({
type: 'canvas',
newCanvas: offScreenCanvas,
width: rect.width,
height: rect.height
}, [offScreenCanvas]);
The cause of the issue was the height, which was incorrect in certain cases, 1px to be precise in the example pictured in the question. The incorrect height popped up because of a race condition, in a separate script we used to set up the height of certain canvas container elements with the following script:
$(".fullscreen-block").css({ height: var wh = $(window).height() });
However, we usually sent the size of the canvas to the worker before this happened. Substituting this JS code with a simple CSS rule solved this issue:
.fullscreen-block {
height: 100vh;
}
So, in the end, the issue was not related to the mouse event's handled by us, I can only guess why moving the mouse fixed the distortion. I'd say Firefox probably revalidates/recalculates the size of DOM elements when the mouse is moved and we were notified about size changes, causing the animation to pop to the correct size and state.
I want to make a parallax effect on mouse move using three.js.
Basically I want to generate bunch of clouds to canvas and want them to move on x axis when mouse is moved.
So I have already tried to add clouds to the scene as images. How could I link mouse move even to these clouds? Or should I add clouds to the scene differently?
How I added clouds:
var imgCloud = new THREE.MeshBasicMaterial({
map:THREE.ImageUtils.loadTexture('img/cloud.jpg')
});
imgCloud.map.needsUpdate = true;
// Cloud
var cloud = new THREE.Mesh(new THREE.PlaneGeometry(200, 200), imgCloud);
cloud.overdraw = true;
scene.add(cloud);
You need to add an event listener to the window to get the mouse position. Then you can use that to modify the position of all clouds individually or just the position of the scene.
window.addEventListener('mousemove', onMouseMove, false);
function onMouseMove(event) {
scene.position.x = (event.clientX - window.innerWidth / 2);
}
You can simply catch the mouse position and then move the camera along an axis relative to the new mouse position.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 150;
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Add cloud
for (let i = 0; i <= 200; i++) {
const geometry = new THREE.SphereGeometry(5, 32, 32);
const material = new THREE.MeshBasicMaterial({ color: 0x3399ff, transparent: true, opacity: 0.8});
const sphere = new THREE.Mesh(geometry, material);
sphere.position.x += Math.round(Math.random() * 500) - 250;
sphere.position.y += Math.round(Math.random() * 500) - 250;
sphere.position.z += Math.round(Math.random() * 200);
scene.add(sphere);
}
// Control
var mouseTolerance = 0.1;
document.onmousemove = function (e) {
var centerX = window.innerWidth * 0.5;
var centerY = window.innerHeight * 0.5;
camera.position.x = (e.clientX - centerX) * mouseTolerance;
camera.position.y = (e.clientY - centerY) * mouseTolerance;
};
//Render loop
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
body{
margin: 0;
overflow: hidden;
height: 400px;
width: 600px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js"></script>
The mouse position can be obtained withdocument.onmousemove as usual. Camera can be repositioned with camera.position.[axis]. You can play with different axes and see how it looks. Note that some objects must be closer to the camera than others otherwise it will not look like Parallax
I am trying to implement the current scenario:
on mouse down event on the mesh focus perspective camera on the point where a user clicked
allow a user to rotate the camera via orbitControls;
after rotation is completed on mouse up event change camera position and target in such fashion that it would seem that camera is still looking at the mesh at the same angle that it was before focusing and rotation.
So it would appear that a user rotated the camera around some point on the mesh and after rotation is able to look at it at from the same place it used to be before the focusing and rotation.
There is no problem with 1) and 2)
And I am quite confused about the 3)
Here I've added some pictures which illustrate main steps
1) User clicks on some point on the mesh
2) Camera focus is set on the point where the user clicked
3) User rotates the camera around the selected pivot point
4) On mouse up camera should be placed in such fashion so in user's eyes selected point is brought back to the place where it was and the camera is looking from the same angle
The question is how to implement behavior illustrated in the 4th picture.
Important thing is that thought it seems that the camera is looking from the same position and to the same target the pyramid appears to be rotated around the point where the user clicked.
I would appreciate any help. Thanks!
I hope I got you correctly.
Find the difference between the initial target and the target point on an object, and on mouse up - shift your camera position with this value.
var w = window.innerWidth,
h = window.innerHeight;
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, w / h, 1, 1000);
camera.position.setScalar(5);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(w, h);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper(10, 10));
var prismGeom = new THREE.ConeBufferGeometry(1, 2, 3);
prismGeom.translate(0, 1, 0);
var prismMat = new THREE.MeshBasicMaterial({
color: "red",
wireframe: true
});
var prism = new THREE.Mesh(prismGeom, prismMat);
prism.position.set(-1, 0, -2);
scene.add(prism);
var centralMarker = new THREE.Mesh(new THREE.SphereBufferGeometry(0.125, 4, 2), new THREE.MeshBasicMaterial({
color: "aqua"
}));
scene.add(centralMarker);
var pointMarker = new THREE.Mesh(centralMarker.geometry, new THREE.MeshBasicMaterial({
color: "magenta"
}));
pointMarker.visible = false;
scene.add(pointMarker);
var oldTarget = scene.position;
var targeted = false;
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var intersects = [];
window.addEventListener("mousedown", onMouseDown, false);
window.addEventListener("mouseup", onMouseUp, false);
function onMouseDown(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersects = raycaster.intersectObject(prism);
if (intersects.length > 0) {
targeted = true;
controls.target.copy(intersects[0].point);
pointMarker.position.copy(intersects[0].point);
pointMarker.visible = true;
controls.update();
}
}
function onMouseUp(event) {
if (!targeted) return;
let shift = new THREE.Vector3().copy(oldTarget).sub(controls.target);
camera.position.add(shift);
controls.target.copy(oldTarget);
controls.update();
targeted = false;
pointMarker.visible = false;
}
renderer.setAnimationLoop(() => {
renderer.render(scene, camera)
})
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
Here I'm trying to zoom in the object(say cube) based on the on mouse pointer position. Here I googled and got a idea that it can be achieved in D3 JS and also it can achieved by capturing the mouse pointer 3D point and passing it in the mouse wheel function.
Here's the fiddle https://jsfiddle.net/fpt9hswo/
var scene, renderer, camera;
var cube;
var controls;
var containerWidth = window.innerWidth,
containerHeight = window.innerHeight;
init();
animate();
function init() {
configureRenderer();
scene = new THREE.Scene();
configureCube();
configureCamera();
configureLight();
configureControls();
fitAll();
}
function configureRenderer() {
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(containerWidth, containerHeight);
document.body.appendChild(renderer.domElement);
}
function configureCube() {
var cubeGeometry = new THREE.BoxGeometry(20, 20, 20);
var cubeMaterial = new THREE.MeshLambertMaterial({
color: 0xff0000
});
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(50, 0, 0);
scene.add(cube);
}
function configureCamera() {
camera = new THREE.PerspectiveCamera(45, containerWidth / containerHeight, 1, 1000);
camera.position.set(0, 160, 400);
camera.lookAt(scene);
}
function configureLight() {
pointLight = new THREE.PointLight(0xffffff, 1.0, 100000);
pointLight.position.set(0, 300, 200);
scene.add(pointLight);
}
function configureControls() {
controls = new THREE.TrackballControls(camera, renderer.domElement);
// configuration of controls
controls.rotateSpeed = 5.0;
controls.zoomSpeed = 5.0;
controls.panSpeed = 2.0;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0;
}
function fitAll() {
// Calculate bounding box of the whole scene
var boundingBoxOfNode = new THREE.Box3().setFromObject(scene),
centerOfGravity = boundingBoxOfNode.getCenter();
/************* CAMERA *************************/
camera.position.addVectors(camera.position, centerOfGravity);
camera.lookAt(centerOfGravity);
//new camera positions will be set here
//Eg: camera.position.set(newCamera.x,newCamera.y,newCamera.z);
//Similarly new camera rotation and quaternion coordinates will be set
//Eg: camera.rotation.set(newCamera.rotatex,newCamera.rotatey,newCamera.rotatez);
//Eg: camera.quaternion.set(newCamera.qw,newCamera.qx,newCamera.qy,newCamera.qz);
/************* CONTROLS *************************/
controls.target.set(centerOfGravity.x, centerOfGravity.y, centerOfGravity.z);
//new controls.target values will be set here
//Eg: controls.target.set(newCamera.targetx,newCamera.targety,newCamera.targetz);
}
function animate() {
controls.update();
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Thanks in advance
To do what you want you've to implement your own mouse wheel event and you've to disable the default zoom of THREE.TrackballControls:
controls = new THREE.TrackballControls(camera, renderer.domElement);
//...
controls.zoomSpeed = 0.0;
Add a wheel event:
e.g.
renderer.domElement.addEventListener("wheel", event => {
const delta = event.deltaY;
// [...]
}
Zooming in perspective projection can be achieved, by shifting the camera position in the depth of the world.
If you want to zoom to a point, this can be achieved by moving the camera along a ray, from the camera position through the cursor (mouse) position and to move the target position in parallel.
A point on the ray from the camera to the cursor in normalized device space can be found with ease. In normalized device space all coordinates are in range [-1, 1] and all points with the same x and y coordinate are at the same ray. If the z coordinate is -1, the the point is on the near plane and if z is 1 then the point is on the far plane.
e.g. NDC point on the far plane and on a ray through the cursor:
let x = 2 * event.clientX / window.innerWidth - 1;
let y = 1 - 2 * event.clientY / window.innerHeight;
let cursorpos = new THREE.Vector3(x, y, 1);
This point in normalized device space can be transformed to a point in world space, by THREE.Vector3.unproject(). The parameter to the function has to be the THREE.Camera which defines the view and projection:
cursorpos.unproject(camera);
The direction for the movement of the camera is the normalized direction from the camera position to the cursor position in world space:
let dir = new THREE.Vector3().copy(cursorpos).sub(camera.position).normalize();
Calculate the movement dependent on the direction and the mouse wheel delta and update the camera and THREE.TrackballControls:
let shift = new THREE.Vector3().copy(dir).multiplyScalar(delta * 0.1);
camera.position.add(shift);
controls.position0.add(shift);
controls.target.add(shift);
See the example, where I applied the suggestions to the code of the question:
var scene, renderer, camera;
var cube;
var controls;
var containerWidth = window.innerWidth,
containerHeight = window.innerHeight;
init();
animate();
function init() {
configureRenderer();
scene = new THREE.Scene();
configureCube();
configureCamera();
configureLight();
configureControls();
}
function configureRenderer() {
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(containerWidth, containerHeight);
document.body.appendChild(renderer.domElement);
window.onresize = function() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
if (controls)
controls.handleResize();
}
renderer.domElement.addEventListener("wheel", event => {
const delta = event.deltaY;
let x = 2 * event.clientX / window.innerWidth - 1;
let y = 1 - 2 * event.clientY / window.innerHeight;
let cursorpos = new THREE.Vector3(x, y, 1);
cursorpos.unproject(camera);
let dir = new THREE.Vector3().copy(cursorpos).sub(camera.position).normalize();
let shift = new THREE.Vector3().copy(dir).multiplyScalar(delta * 0.1);
camera.position.add(shift);
controls.position0.add(shift);
controls.target.add(shift);
});
}
function configureCube() {
var cubeGeometry = new THREE.BoxGeometry(20, 20, 20);
var cubeMaterial = new THREE.MeshLambertMaterial({
color: 0xff0000
});
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(50, 0, 0);
scene.add(cube);
}
function configureCamera() {
camera = new THREE.PerspectiveCamera(45, containerWidth / containerHeight, 1, 1000);
camera.position.set(0, 160, 400);
camera.lookAt(scene);
}
function configureLight() {
pointLight = new THREE.PointLight(0xffffff, 1.0, 100000);
pointLight.position.set(0, 300, 200);
scene.add(pointLight);
}
function configureControls() {
controls = new THREE.TrackballControls(camera, renderer.domElement);
// configuration of controls
controls.rotateSpeed = 5.0;
controls.zoomSpeed = 0.0;
controls.panSpeed = 2.0;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0;
}
function animate() {
controls.update();
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script>
I am using this example for my WebGL panorama cube:
https://threejs.org/examples/?q=pano#webgl_panorama_equirectangular
I want to zoom in and out within limits, i.e. set maximum and minimum zoom level, but not infinitely as code provides by default. For infinite zoom, which later reverses the view if scrolled too much, the function from example above looks like this:
function onDocumentMouseWheel( event ) {
camera.fov += event.deltaY * 0.05;
camera.updateProjectionMatrix();
}
To address the issue I tried to update FOV when inside my allowed range:
function onDocumentMouseWheel( event ) {
var fovMAX = 95;
var fovMIN = 5;
var newFov = camera.fov + event.deltaY * 0.05;
if (newFov > fovMIN && newFov < fovMAX) {
camera.fov = newFov;
camera.updateProjectionMatrix();
};
}
Please note my FOV is 90:
camera = new THREE.PerspectiveCamera( 90, window.innerWidth / window.innerHeight), 0.1, 100 );
This seems to have perfectly worked for maximum zoomed-in level - it stops when FOV is 5 and does not zoom in further. However, as I zoom out to FOV 95, it would stop, but if I continue zooming out with mouse more, I would zoom infinitely again, even though FOV remains 95.
How to stop/control infinite zoom out?
Your code works as expected in the minimal example below. (I did make a small change in that I'm using >=/<= rather than >/<.)
var renderer, scene, camera, controls, stats;
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000;
var fovMax = 95,
fovMin = 5,
fovTmp = 0;
function zoom(event) {
event.preventDefault();
fovTmp = camera.fov + (event.deltaY * 0.05);
if (fovTmp >= fovMin && fovTmp <= fovMax) {
camera.fov = fovTmp;
camera.updateProjectionMatrix();
}
}
function init() {
document.body.style.backgroundColor = "slateGray";
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
document.body.appendChild(renderer.domElement);
document.body.style.overflow = "hidden";
document.body.style.margin = "0";
document.body.style.padding = "0";
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
camera.position.z = 50;
scene.add(camera);
var light = new THREE.PointLight(0xffffff, 1, Infinity);
camera.add(light);
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0';
document.body.appendChild(stats.domElement);
resize();
window.onresize = resize;
// POPULATE EXAMPLE
var cubeGeo = new THREE.BoxBufferGeometry(1, 1, 1),
cubeMat = new THREE.MeshPhongMaterial({
color: "red"
});
var mesh = new THREE.Mesh(cubeGeo, cubeMat);
scene.add(mesh);
window.addEventListener("wheel", zoom);
animate();
}
function resize() {
WIDTH = window.innerWidth;
HEIGHT = window.innerHeight;
if (renderer && camera) {
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
}
}
function render() {
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame(animate);
render();
stats.update();
}
function threeReady() {
init();
}
(function() {
function addScript(url, callback) {
callback = callback || function() {};
var script = document.createElement("script");
script.addEventListener("load", callback);
script.setAttribute("src", url);
document.head.appendChild(script);
}
addScript("https://threejs.org/build/three.js", function() {
addScript("https://threejs.org/examples/js/libs/stats.min.js", function() {
threeReady();
})
})
})();
Something else is changing your camera's data. Are you sure you're not updating camera.zoom anywhere?
For what it's worth, the only other (core) three.js method that modifies the FOV is PerspectiveCamera.setFocalLength
Edit based on new information
Ah, there's your problem. I didn't know you were using an external mouse controller. OrbitControls doesn't adjust the FOV to create its zoom effect. Instead, it literally moves the camera nearer to/further from the control's target. Your zoom-in likely (by chance) corresponded with it reaching 0 distance. But because it can zoom out to infinity, that's why it continued to zoom out.
Disabling zoom like in your comment should resolve the issue.
three.js r87