I have a larger model that freeze my scene.
As I don't need this model from the beginning it would be cool to load this model in the background. Are webworkers a solution for this?
Can anyone guide me how to accomplish it , or is it possible at all ?
Thanks.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var loader = new THREE.JSONLoader();
loader.load("models.js", function (smooth) {
smooth.mergeVertices();
smooth.computeFaceNormals();
smooth.computeVertexNormals();
var modifier = new THREE.SubdivisionModifier(1);
modifier.modify(smooth);
var mesh = new THREE.Mesh(smoothnew THREE.MeshBasicMaterial({
color: 0x00ff00
}));
scene.add(mesh);
});
var render = function () {
requestAnimationFrame(render);
renderer.render(scene, camera);
};
render();
JavaScript is single threaded and uses "cooperative" scheduling for its events, which means that if you have a function with a long loop that doesn't give up its execution nothing else will be able to be executed in the meantime.
Your method in the loader is essentially that, a single method that'll run its computations to completion before allowing other JavaScript code to run. So rather than using a worker you might be able to split it up a bit by making it more event driven and improve the experience that way, something like this:
loader.load("models.js", function (smooth) {
smooth.mergeVertices();
smooth.computeFaceNormals();
smooth.computeVertexNormals();
window.setTimeout( function() {
var modifier = new THREE.SubdivisionModifier(1);
modifier.modify(smooth);
window.setTimeout( function() {
var mesh = new THREE.Mesh(smoothnew THREE.MeshBasicMaterial({
color: 0x00ff00
}));
scene.add(mesh);
}, 100 );
}, 100 );
});
(The points where I added setTimeout() are entirely arbitrary since I have no way of knowing where the biggest delays are without a jsfiddle to run your code + data).
This will only really work if none of these method calls themselves take up the majority of time. If e.g. mergeVertices() itself takes up most of the CPU time the only way to solve the freezing is by offloading that computation to a worker. The you'll need to pass the data around, compute it on the worker, and have the main code add it to the scene (the worker doesn't have access to the WebGL context). The complexity of that solution might not make the effort worth it however.
Workers are not suitable for a solution that requires DOM manipulation
Related
I am trying to make multiple WebGLRenderer render on the same canvas, it works for the very first frame, but if it does the work in the animation loop - it can't render things properly. I wonder if there are some limitations in that approach?
Here is the sample code i can't make working properly if requestAnimationFrame is called inside the update function.
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, WIDTH / HEIGHT, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
// + scene filler
const scene2 = new THREE.Scene();
const camera2 = new THREE.OrthographicCamera(-WIDTH / 2, WIDTH / 2, HEIGHT / 2, -HEIGHT / 2, 0, 30);
const renderer2 = new THREE.WebGLRenderer({ canvas: renderer.domElement });
renderer2.autoClear = false;
// + scene2 filler
function update() {
renderer.render(scene, camera);
renderer2.render(scene2, camera2);
requestAnimationFrame(update);
}
https://codesandbox.io/s/l75w6qyw2m?module=%2Fsrc%2Findex.js
I am not sure what to expect though, but i would really want to make that working, i need to have a separate renderer to render several scenes with different cameras, and i was looking for opportunity to do that in separate renderer to provide a nicer public interface to work with for my plugin.
It's not possible to have 2 WebGLRenderers in one canvas. The reason is that each canvas has ONE WebGLContext as a native limitation, it cannot create a second context.
However, it looks like you're just trying to render two scenes, one on top of the other. You could do that with a single renderer. You just have to turn off its auto-clear property, then use it to render the two scenes, one on top of the other like this:
renderer.autoClear = false;
function update() {
renderer.render(scene, camera);
renderer.render(hudScene, hudCamera);
requestAnimationFrame(update);
}
PREMISE:
I'm trying to create a static scene that has a fixed image background and a geometry in front of it. Since the scene is static, I don't need and envMap.
I created two scenes and cameras (one for the background and one for the geometries) as suggested in this SO question, and this demo, and updated the procedure to consider that THREE.ImageUtils.loadTexture() is deprecated. Here is the working code:
// load the background texture
var loader = new THREE.TextureLoader();
texture = loader.load('path_to_image.jpg');
var backgroundMesh = new THREE.Mesh(
new THREE.PlaneGeometry(2, 2, 0),
new THREE.MeshBasicMaterial({
map: texture
})
);
backgroundMesh.material.depthTest = false;
backgroundMesh.material.depthWrite = false;
// create your background scene
backgroundScene = new THREE.Scene();
backgroundCamera = new THREE.Camera();
backgroundScene.add( backgroundCamera );
backgroundScene.add( backgroundMesh );
The variables backgroundScene and backgroundCamera are global and the following procedure is called inside the init() function. All scenes and cameras are later rendered using:
renderer.autoClear = false;
renderer.clear();
renderer.render( backgroundScene , backgroundCamera);
renderer.render(scene, camera);
PROBLEM:
I implemented an event listener that is supposed to change the background image and geometry when a button is pressed, however this is not working.
I thought that loading the new texture and changing the material property of the backgroundScene variable, clearing the renderer and rendering the scene again would do the job. Here is the code:
var loader = new THREE.TextureLoader();
var texture = loader.load('path_to_new_image.jpg');
console.debug(texture);
console.debug(backgroundScene.children[1].material.map);
backgroundScene.children[1].material.map = texture;
console.debug(backgroundScene.children[1].material.map);
renderer.clear();
renderer.render( backgroundScene , backgroundCamera );
renderer.render(scene, camera);
The console.debug() show me that the new texture is actually loaded and the backgroundScene material is changed accordingly.
However, while the geometries are rendered fine I am left with a blank background and get the following error: [.Offscreen-For-WebGL-0x364ad7e56700]RENDER WARNING: there is no texture bound to the unit 0.
Any ideas of what is going on? Thanks for your help!
you will need to call object.material.needsUpdate = true; for the change to take effect (see here). When the map-property of the material is changed, three.js needs to re-setup the texture-binding, which is skipped unless the needsUpdate-flag is set.
Alternatively, if you just change the material.map.image-property it should work without that extra-step.
I have a world map layer as a plane geometry, now i want to handle click events on different parts (For example continents or country). How to handle popup events on particular part like (popup of information or video playing or image data with links etc.)
Here's a fiddle that already has the scene setup.
var camera, scene, renderer;
var geometry, material, mesh;
window.addEventListener("click", onClick, false);
function onClick() {
alert("Replace me with code to add an object!");
}
var init = function () {
renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 500;
scene = new THREE.Scene();
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshBasicMaterial({
color: 0x000000,
wireframe: true,
wireframeLinewidth: 2
});
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
var animate = function () {
requestAnimationFrame(animate);
mesh.rotation.x = Date.now() * 0.0005;
mesh.rotation.y = Date.now() * 0.001;
renderer.render(scene, camera);
}
init();
animate();
the click event is applied to whole scene, i have created multiple cubes in that example and when clicked on that it is showing the same click event for all parts.
What you ask is too complex, you need to break it down. First problem you want to solve is to find out which object is being clicked.
You can do raycasting or gpu picking. since you sound like a beginner I would suggest you to start with raycasting.
There are some things you need to do in order to figure out where on the map the click occured (and this is just one way to do it, another one would be to use GPU-picking, which could be even easier):
use the raycaster that three.js provides to get information about the object that is under the cursor when the click happened. (docs/example)
in the result from the raycaster you will get the uv-coordinates for the point of intersection (so basically, where in your map-texture the click occurred).
now, you can either store your geographic features as values relative to these UV-coordinates or you need to convert the UV-values into a geographical format to work with (WGS84, or latitude/longitude). For that you need to know the type of projection that was used to create the map (probably a mercator-projection). Note that there are lots of libraries available for these kinds of conversions.
Now that you know where in your world-map the click occurred, you can test it against geographic features. Let's say continents. For each continent you will need to define a polygon in the same coordinate-system you are using for the coordinates of the clicks. Then you can do a 'point in polygon'-test (here's an implementation) to check if the clicked location is inside the polygon.
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)
};
For one of my courses I need to program a 3D application in html5 canvas. For this I chose to use Three.js. For now, all I'm trying to do is render a simple plane. I also want to be able to move the camera along the x-axis by holding down the mouse and dragging. I based my code on the code found here. It's basically a plane which is spinning around (or the camera is circling around the plane, not sure which). I threw away all the code for the animation and added code for mousedown, mousemove, etc events. When I load the page it renders the plane (inanimate) just fine, but when I try to use my mouse to move it around it remains completely unresponsive. In my code you may have noticed I changed some things I maybe shouldn't have (made some variables global and changed some function names) in order to get things working, but the result remained the same.
This is the first time I'm working with javascript and I realize I can't just change existing code and expect it to work. I looked into javascript verification and I found JSLint. I downloaded the plugin for notepad++ and tried to verify my code that way. It doesn't like the html tags and it especially hates the Three.js library I'm trying to import. It doesn't pick up on it when I import it as url and it doesn't import it when I download it and import the file either. When I just copied the code of the entire library into the file it just gave me a lot of errors about the library itself and stopped verifying somehwere before my code even started.
In conclusion: I would like some help with the project itself but also with setting up a pleasant development environment (with proper verification that doesn't ignore imports).
Finally, my code so far:
<!DOCTYPE HTML>
<html>
<head>
<title>Canvas demo</title>
</head>
<body>
<script type="text/javascript" src="three.js">
</script>
<script type="text/javascript">
var camera;
var scene;
var plane;
var renderer;
var clickX;
var clickY;
var mousedown;
window.onload = function () {
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//camera
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.y = -450;
camera.position.z = 400;
camera.rotation.x = 45;
//scene
scene = new THREE.Scene();
//plane
plane = new THREE.Mesh(new THREE.PlaneGeometry(300, 300), new THREE.MeshBasicMaterial({color: 0x0000ff}));
plane.overdraw = true;
scene.add(plane);
rerender();
};
window.requestAnimFrame = (function (callback){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
function rerender(){
//render
renderer.render(scene, camera);
//request new frame
requestAnimFrame(function(){
rerender();
});
}
$('#canvas').mousedown(function(e){
clickX = e.pageX - this.offsetLeft;
mousedown = true;
});
$('#canvas').mousemove(function(e){
if(mousedown) {
var xDiff = e.pageX - this.offsetLeft - clickX;
camera.position.x = xDiff;
rerender();
}
});
$('#canvas').mouseup(function(e){
mousedown = false;
});
$('#canvas').mouseleave(function(e){
mousedown = false;
});
</script>
</body>
</html>
You have various issues here that are preventing your code from running properly. I've gone through and fixed them, and you can now move the camera along the x-axis:
http://jsfiddle.net/TVWYn/6/
Your issues were:
You're using jQuery and not including the jQuery library. The dollar
sign function is not part of JavaScript, it's (in this case) part of
a third-party library called jQuery that makes working with the DOM
easier. Without including the jQuery library, your mouse handlers
couldn't run.
Even with jQuery included, the order in which your setup functions
executed was incorrect. Originally you had your Three.js setup
process such that it would execute once the DOM was initialized.
That's good. However, you were trying to register mouse event
handlers immediately after the JS was acquired by the browser. This
is not good. You're asking JS to attach mouse event handlers to DOM elements that don't exist at that point in time. To fix this,
you need to ensure that the DOM has been initialized (ex. running your setup inside a window.onload) and your
required elements have already been inserted (in this case,
#canvas). Or, attach your event handlers to the DOM objects directly (ex. renderer.domElement).
You were referencing #canvas without ever setting the id of your
canvas element.
With regard to setting up a proper development environment, I would strongly suggest that you start using Chrome and its developer tools (which are launched via ctrl+shift+j).