I am working with ThreeJS on a basic 3d scene that has OrbitControls. Everything works great, except it causes my entire site to lag, as it is looping itself even when the user is not looking at it. I want a function that I can call to start and stop the rendering when certain conditions are met (in this case, the user isn't viewing the canvas). I have a start function that works just fine, but the stop function does not seem to be working, as my site goes unbearably slow after ThreeJS has initialized.
I have looked and looked for a solution to this problem, and have found a couple 'solutions', but for whatever reason they do not work with my application. My assumption is that these solutions are from old versions of ThreeJS.
Here is my code in my main.js file:
var scene,
camera,
controls,
render,
requestId = undefined;
function init() {
scene = new THREE.Scene();
var threeJSCanvas = document.getElementById("threeJS");
camera = new THREE.PerspectiveCamera( 75, threeJSCanvas.width / threeJSCanvas.height, 0.1, 1000 );
controls = new THREE.OrbitControls( camera );
// Controls and Camera settings
// Create Geometry.
}
function render() {
requestId = requestAnimationFrame(render);
renderer.render(scene, camera);
}
function start() {
render();
}
function stop() {
window.cancelAnimationFrame(requestId);
requestId = undefined;
}
In my other javascript file, there is a conditional inside of my pageChange function (this is a multipage app), that looks like the following:
if (page == 5) { // The page with the canvas on it
if (!locationsRendered) {
init();
locationsRendered = true;
}
} else { // If the page is not the page with the canvas on it
if (locationsRendered) {
stop();
}
}
locationsRendered is initialized earlier in this second javascript file in the local scope.
Any help would be much appreciated, as I can not let this simple 3D scene lag my entire app after it has been loaded. It's just not realistic.
If your scene is static, there is no reason for an animation loop. You only need to re-render when the camera moves due to a mouse or touch event.
Just use this pattern:
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.addEventListener( 'change', render );
function render() {
renderer.render( scene, camera );
}
three.js r.67
I was using trackball controls in my scene and therefore couldn't use the solution above (as the trackball controls continue updating after mouse events finish triggering).
To solve this problem, I used:
function animate() {
renderer.render(scene, camera);
controls.update();
}
renderer.setAnimationLoop(animate);
That runs the animation loop indefinitely. To pause the animation, one can then specify null as the animation loop:
renderer.setAnimationLoop(null); // pause the animation
And to resume the animation, just pass the animation loop again:
renderer.setAnimationLoop(animate); // resume the animation
An alternative solution to completely stopping the render loop is to reduce the frames per second rate and thereby reducing resource consumption.
This approach is particularly useful if you need responsive update on your scene while not necessarily animating, but also need to snap back normal speeds when you need to.
a simple setTimout() achieves this nicely.
var fps 10;
function render() {
//reduce framerate
setTimeout(()=>{
requestAnimationFrame(render);
//must be called to enable rotating
controls.update();
renderer.render(scene, camera);
}, 1000/fps)
};
Related
I'm not sure if the issue has something to do with how I've set things up in my application, some configuration issue with Chrome, the DOM, OrbitControls or another feature of Three.js.
When I load my application inside a tab in Chrome, everything works perfectly. When I change to another tab in Chrome, or minimize Chrome then return to it (I'm on a Windows machine), OrbitControls behavior changes. Notably, clicking on the left mouse button and moving the mouse around no longer orbits as before, but instead zooms out, up the Y-axis. It also appears like the camera-angle is suddenly a lot wider, and camera rotation center-point changes.
The only apparent fix is to reload the application in the tab, which works, but as I'm getting near alpha release on the product I'm starting to think about how users are not going to tolerate the issue as I have...
Here's what I believe to be my relevant code.
document.addEventListener( "DOMContentLoaded", init );
function init() {
cameras();
initRenderer();
initEventListeners();
entities();
// Create the Stereoscopic viewing object (Not applied yet)
var effect = new THREE.StereoEffect( renderer );
render();
}
function render() {
renderer.render(scene, entities.cameras.perspCamera );
requestAnimationFrame( render );
}
// if the device we're using has 'alpha' attribute, then it's a mixedReality-compatible mobile browser...
function setOrientationControls(e) {
if (e.alpha) {
initVRControls ();
}
else {
initbrowserControls ();
var camera = entities.cameras.perspCamera;
entities.cameras.init( camera );
}
}
function initbrowserControls() {
// Create the Mouse-Based Controls - Hold down left mouse button and move around the window...
var camera = entities.cameras.perspCamera;
entities.browserControls = new THREE.OrbitControls ( camera , container );
entities.browserControls.target.set(
camera.position.x + 0.15,
camera.position.y,
camera.position.z
);
entities.browserControls.noPan = true;
entities.browserControls.noZoom = true;
}
function initEventListeners() {
// Listen for Device Orientation events.
window.addEventListener('deviceorientation', setOrientationControls, true);
}
function initRenderer() {
renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setClearColor( 0xffffff, 1 );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.domElement.setAttribute( 'id' , 'renderSpace' );
renderer.domElement.setAttribute( 'class' , 'threeWebGLRenderer' );
container.appendChild( renderer.domElement );
};
if the code provided isn't sufficient to answer the question, let me know. I can provide what's needed. Thanks for the help!
UPDATE:
I've edited my code as suggested by #PerrinPrograms in the comments to try to reset the controls when document.visibilityState is reset to "visible".
This seems a workaround... I'd rather get to the source of the problem, but thought following the tracks of it might provide more illumination.
The result is that the behavior is exactly the same, with the added bonus, as noted in the comments, that when I return to the application tab and have a console-break point at setOrientationControls(e) inside of my new if statement, I cannot restart the console action and have to reload the tab.
Here's what I did to my code:
function initEventListeners() {
// Listen for Device Orientation events.
window.addEventListener('deviceorientation', setOrientationControls, true);
document.addEventListener('visibilitychange', onDocumentVisible, true); // ADDED LINE
}
// ADDED FUNCTION
function onDocumentVisible(e){
if ( document.visibilityState === "visible" ){ // CONSOLE LOCKS UP IF I PLACE BREAK HERE
setOrientationControls(e); // HERE TOO
}
}
The plot thickens... Thanks all for looking into this with me.
I am having an issue with animations on some fbx models. If I have, for example an animation that lasts 20 secs, the model will stay still for 19 secs and then all changes will happen within the last second or so. On other fbx models the animation runs correctly.
The code that I am using to run the animation is a follows:
The loader.load callback is:
var clock = new THREE.Clock();
var mixers = [];
function(object){
object.position.set(0,0,0);
object.mixer = new THREE.AnimationMixer(object);
mixers.push(object.mixer);
console.log(object);
for (var a = 0; a < object.animations.length; a++){
var action = object.mixer.clipAction(object.animations[a]);
action.play();
console.log(action);
}
scene.add(object);
animate();
}
And the animate code is:
function animate() {
requestAnimationFrame(animate);
for(var i = 0; i < mixers.length; i++){
mixers[i].update(clock.getDelta());
}
render();
stats.update();
}
function render() {
if (mixer) {
mixer.update(clock.getDelta());
}
renderer.render(scene, camera);
}
Any ideas? Thanks!
From experience, I can tell you that the fbx ascii export process (at least for Autodesk Maya) doesn't always give either
the correct start and end times set in Maya or
gives a set of numbers that threejs doesn't import properly.
What you end up getting is -- as you describe -- a lot of time in the animation where nothing happens. As far as I've seen, it's usually trailing at the end, but it could certainly be at the beginning as well.
You could fix the fbx file manually, but it might be easier just add a function to set the beginning time to the time of your first frame (and if the first frame is the issue, start with the second frame).
I have the code for this somewhere, let me find it and then I'll add it to this answer.
All examples that I saw, uses a loop structure to render the scene, like that:
renderer.render(cena, camera);
function render(){
renderer.render(cena, camera);
//code to render
requestAnimationFrame(render);
}
render();
But I want to control the rendering in another structure, only when I interact with a thing... like
while(true){
//code - operations
alert('press ok to another step'); // or wait 2 sec
renderer.render(scene,camera);
}
What is the correct method to do this?
Of course, if you want, you can call renderer.render at anytime, although it would probably be a lot better to render it in the requestAnimationFrame() (much better performance).
If you really have a need to change something that you don't want to be renderer (say, several async functions modifying scene object), you can always do something like this.
function render(){
//code to update scene
if (toRender) renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
Set toRender to false while you're updating the scene, this prevent the renderer from re-drawing your scene, then set toRender back to true to have it draw the scene in the next frame.
as shown in this example, you can simply call renderer.render()
http://threejs.org/examples/#raytracing_sandbox
From the tutorial on setting up a basic scene here the standard way to call the renderer is something like:
function render() {
requestAnimationFrame( render );
renderer.render( scene, camera );
}
render();
However I am generating a static image so creating frames seems like overkill. Is there any way to render the scene once and then have the rendered image persist?
What I needed to do was delay the call to
this.renderer.render(this.scene, this.camera);
until after all calculations in the scene were finished. Calling it immediately after initialising the renderer was resulting in drawing a white screen as nothing else had been calculated yet.
As a stopgap measure I've put it in a window.setTimeout functions, but I guess the proper way to do it is to put it in a callback function when all the other calculations are finished.
Instead of calling render() just call renderer.render( scene, camera );
Using three.js render obj model in browser.
Here is my logic of program:
function animate() {
requestAnimationFrame(animate);
render(scene, camera);
}
function render(scene, camera){
scene.traverse(function(object) {
scene = setLODDistance(scene, thresHold);
});
renderer.render(scene, camera);
}
animate();
function setLODDistance will return a scene object which has been calculated by LOD.
but I do not want to call this function every fps, I just want to call it after my object in browser been translated or rotated or scaling.
so in THREE.Object3D or scene or other class provide any tags or methods that I can detected my object has been changed(translated or rotated or scaling) ??
thx
You can try to use Object.watch(), poly fill here: link.