I'm working on a WebGL application using ThreeJS and OrbitControls. How do I write and make use of a custom event handler for wheel spin events?
Suppose my custom handler is
function custom_handleMouseWheel(event) { ... }
I tried adding it as a listener:
window.addEventListener("wheel", custom_handleMouseWheel);
but I suspect this adds my handler to a list of existing handlers (probably only one), and I'd have to remove the original handler. Not sure how to do that. And anyway, my handler never was called, which I checked for by adding a console.log("Wheel!") line to my handler.
Another thing I tried is to replace the handleWheelMouse method in the controls object, like this:
let original_handleMouseWheel;
function custom_handleMouseWheel(event) {
console.log("Custom Wheel!");
... fancy geometry calculations ...
original_handleMouseWheel(event);
}
// somewhere after defining scene, camera, renderer, etc...
controls = new THREE.OrbitControls( camera );
original_handleMouseWheel = controls.handleMouseWheel;
controls.handleMouseWheel = custom_handleMouseWheel;
but again the console.log line never executes.
What is the right way to go about doing this?
Just setting controls.enableZoom = false will stop the default zoom.
The default "wheel" listener is added to the "domElement" which should be the second parameter sent to the OrbitControls constructor.
Try renderer.domElement.addEventListener('wheel', custom_handleMouseWheel);
Related
I am trying to add or remove an object from scene:
const rootObject = this.scene.getObjectByName('Object Group');
rootObject.remove(obj);// OR rootObject.add(obj);
What I observe is that to make change visible in scene, user needs to perform some action, like panning or zoomin/out etc.
How can change be made visible immediately without needing a manual action?
It looks like the scene is being redrawn not in the animation loop, but in some events (click, panning or zoomin/out, etc.).
So when adding or removing an object, you need to force a frame to be rendered. For example:
document
.getElementById('toggle')
.addEventListener('click', function() {
if (obj) {
removeObject()
} else {
addObject()
}
renderer.render(scene, camera);
})
[ https://jsfiddle.net/xmke5u20/ ]
In trying to figure out how to start and stop Sprite animations from a SpriteSheet, I tried this:
// other code...
// define animations in SpriteSheet:
"animations": {"intro": [0, 19, "false"]}
// other code...
var spriteSheet = new createjs.SpriteSheet(data);
var intro = new createjs.Sprite(spriteSheet, "intro");
stage.addChild(intro);
createjs.Ticker.addEventListener("tick", stage);
intro.stop();
var btnStart = document.getElementById("btnStart");
btnStart.onclick = function() {
console.log("btnStart clicked");
intro.on("animationend", onStartAnimEnd);
intro.play();
};
function onStartAnimEnd(e) {
intro.removeEventListener("animationend", onStartAnimEnd);
console.log("Start anim ended");
}
In the above example, if the user clicks the btnStart button, the onStartAnimEnd callback fires indefinitely, even though by defining "false" in the animation configuration to signal we want to stop on the last frame, and the animation does in fact stop, and I'm removing the event listener in the first line of the callback.
If I add:
function onStartAnimEnd(e) {
intro.removeEventListener("animationend", onStartAnimEnd);
intro.stop();
console.log("Start anim ended");
}
The problem goes away, but that doesn't seem right... So, if I change the listener assignment of the animationend event from:
intro.on("animationend", onStartAnimEnd);
to:
ask.addEventListener("animationend", onAskAnimEnd);
...and with this change, the indefinite event captures goes away. So my questions are:
What's the difference with these two event listener assignments?
Is this animationend event continually firing in the background because we're updating the stage on the tick event, even though nothing needs re-rendering?
Thanks for your time!
This is actually a duplicated question. As I answered you previous question, your animation definition is wrong, you need to use the boolean value (false) and not the string value ("false").
Now sure what ask is, but the method on is a wrapper for addEventListener, and where you can specify things such as the scope of the callback and if it will run only once. Take a look at the API to know more:
http://www.createjs.com/Docs/EaselJS/classes/EventDispatcher.html#method_on
I have a problem while cleaning up my WebGl-Scenes. I'm using Three.js with a WebGlRenderer. In my application I have to change the views quite often and therefore need to render new scenes all the time. Uptil now I destroy and reinitialize the entire Threejs scene. After switching the scenes about 15 - 20 times I get following warning:
WARNING: Too many active WebGL contexts. Oldest context will be lost.
After switching a couple of times more the context is lost completly and the application crashes.
Is there a way to destroy the current WebGl context, when cleaning up? Or does the WebGlRenderer always create a new WebGl context when being instantiated?
I'm using Three.js R64.
I've got same problem, but i couldn't solve it using SPA, because of requirements.
There is .forceContextLoss() method in WebGLRenderer (rev 71, maybe was early) for this situations.
So, my code in 'deallocate' method is somemethig like
_self.renderer.forceContextLoss();
_self.renderer.context = null;
_self.renderer.domElement = null;
_self.renderer = null;
You can keep the same renderer for different scenes. The renderer does not care what scene it will render. You can supply a different Scene everytime you call render() if you like.
// instantiate only once and keep it
var renderer = new THREE.WebGLRenderer();
// current scene and camera. Switch whenever you like
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(...);
fillScene(scene);
// rendering always uses current scene
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
/* ...
* somewhere in your application
* ...
*/
if(condition) {
// switch scene
scene = new THREE.Scene();
fillOtherScene(scene);
}
You should create and use only one WebGlRenderer. In my SPA (single page application) I had weird problem with camera/scene in THREE.js after few redirects. It was because WebGlRenderer was created every time when one particular page was rendered. There was no error in console log (only the warning you wrote).
Bug appeared as change in camera position and problems with rendering.
I switched the controls in a script I was writing from trackball controls to the fly controls. I added an event handler to trackballcontrols without problem.
controls.addEventListener( 'change', function () {
cameraChanged = true;
signals.cameraChanged.dispatch( camera );
render();
} );
However when I tried to add the event listener to the flycontrols instance I received the error:
Object [object Object] has no method 'addEventListener'
I assume that the flycontrols class doesn't have a mechanism to add event listeners. I was wondering how I would go about adding it.
FlyControls doesn't have EventDispatcher implemented.
I want to handle a lost context event in Three.js. There is a nice documentation about that here but unfortunately it doesn't work when I apply it to my renderer.domElement. I try to lose the context by clicking and some variable in loseContext() are undefined.
I guess the structure is different in Three.js. Any expert?
You should be able to do something like this about the renderer was initialized and assuming of course that the variable you stored the renderer into is named 'renderer'.
renderer.context.canvas.addEventListener("webglcontextlost", function(event) {
event.preventDefault();
// animationID would have been set by your call to requestAnimationFrame
cancelAnimationFrame(animationID);
}, false);
renderer.context.canvas.addEventListener("webglcontextrestored", function(event) {
// Do something
}, false);
BTW - loseContext is not defined by Three.JS and it isn't a standard method as of this time. You can simulate it by doing the following.
Load this script before Three.JS
https://github.com/vorg/webgl-debug
Then after you've started your renderer you can hook the loseContext to the canvas.
renderer.context.canvas = WebGLDebugUtils.makeLostContextSimulatingCanvas(renderer.context.canvas);
To trigger the loseContext you would do this.
renderer.context.canvas.loseContext();
And you can then also have it fail after a set number of calls by doing this.
renderer.context.canvas.loseContextInNCalls(5);