I am making a 3D map in Three.js.
I am constructing the geometry and texture from fetched 256x256 pixel images, construct a THREE.Mesh and add it to the scene when it's done.
However, adding tiles to the map causes MapControls to lag noticeably, if the user is panning or zooming around quickly. This can be alleviated by using smaller tiles (such as 128*128), but I want to find a better way, as I've seen examples of very smooth maps using Three.js. The controls work smoothly after all the tiles in view have loaded.
I have 2 event listeners. One triggers when controls change:
this.controls.addEventListener('change', this.update);
And renders the map:
update = () => {
this.renderer.render(this.scene, this.camera);
};
The other listens for the move to end, and then fetches the tiles in view:
this.controls.addEventListener('end', this.fetchTilesIfNecessary);
fetchTilesIfNecessary then creates Promises, which start fetching the tiles. When the tiles are fetched, they are added to the map and this.update is called:
addTile(tile) {
this.scene.add(tile.mesh);
this.update();
}
Perhaps I should mention that I have a callback when the tile mesh gets rendered:
this.mesh.onAfterRender = this.disposeAttributes;
Which clears the attributes which are causing a lot of memory usage
disposeAttributes(renderer, scene, camera, geometry, material, group) {
geometry.getAttribute('position').array = [];
geometry.getAttribute('normal').array = [];
geometry.getAttribute('uv').array = [];
}
Is there a better way? How can I add meshes to the scene dynamically and keep the controls running smoothly?
One way to mitigate this issue is to pre-compile all shaders via WebGLRenderer.compile(). Meaning when your app starts, you add all tiles to your scene and then call renderer.compile() once with the scene's camera.
this.mesh.onAfterRender = this.disposeAttributes;
This is not recommended since it will be executed on each render call. If you want to free the geometry data one the JS side, use the onUpdate() callback of BufferAttribute().
geometry.getAttribute( 'position' ).onUpload( disposeArray );
function disposeArray() {
this.array = null;
}
Related
So I want a simple solution. A toggle button which refreshes a Three.js scene depending on the user, if he wants an anti-aliased version of the scene or not. My refresh_scene function is as follows:
function refresh_scene()
{
scene.remove( floor );
floor.geometry.dispose();
floor.material.dispose();
floor = null;
scene.dispose();
scene = null;
controls.dispose();
controls = null;
camera = null;
renderer.dispose();
renderer = null;
canvas = null;
cancelAnimationFrame(reqAnimFrame);
visualizer();
}
This function is called every time the button is pressed. The visualizer() then just displays all objects again. This also includes getting the canvas, making a new renderer, new scene, etc. For some reason, the new renderer does not display an anti-aliased version of the scene, but the same scene over and over again, despite the fact that the toggle button toggles an antialiasing_ value which is then used in the visualizer() function to reinitialize the renderer according to the new value:
renderer = new THREE.WebGLRenderer({canvas, antialias: antialiasing_});
where antialiasing_ is a bool which toggles true or false. Am I doing something wrong? Any help would be appreciated. Can upload other parts of code if necessary.
I use framework phaser js.
I create a two circles using graphics
var graphics;
var ball;
var graphics1;
var ballHome;
function create(){
game.physics.startSystem(Phaser.Physics.ARCADE);
graphics=game.add.graphics(500,500); //first circle
game.physics.arcade.enable(graphics);
graphics.enableBody=true;
graphics.beginFill(0xFFFFFF,1);
ball=graphics.drawCircle(10,10,15);
graphics.endFill();
graphics1=game.add.graphics(500,500); //second circle
game.physics.arcade.enable(graphics1);
graphics1.enableBody = true;
graphics1.beginFill(0xFFF55F,1);
ballHome=graphics1.drawCircle(300,300,500);
graphics1.endFill();
}
function update() {
game.physics.arcade.collide(ball,ballHome);
}
I want them to collide
Why does not game.physics.arcade.collide(ball,ballHome) work?
Thanks for the help
The problem is that the collide() function takes Phaser.Sprites as its input, but ball and ballHome are not sprites; they are PIXI.Graphics objects. You need to create sprites from your Graphics objects, and then pass those sprites into collide().
To make a sprite from your graphics object, first call Graphics.generateTexture() (doc) to create a texture, then call game.add.sprite() (doc) with the texture you just created.
For more information on Phaser, plus loads of really helpful tutorials, see the Phaser website.
So i am porting my game from cocos2d c++ to js and sorted lots of js related differences already, but can not understand nor find online the solution to particular problem involving switching of the scenes with the cc.director.runScene().
So i have a complex game scene which i load like that:
cc.LoaderScene.preload(gameplay_resources, function () {
cc.director.runScene(new GameScene(level, epoch));
}, this);
Which takes stage and level params and returns the scene object which contains all the gameplay elements.
Fragment of Game scene looks like this:
var GameNode = cc.Node.extend({
levelID:null,
epoch:null,
platforms:null,
boardSize:null,
numberOfMovesLeft:null,
numberOfMovesAllowed:null,
sessionInfo:null,
ctor:function (levelID, epochID)
{
this._super();
this.levelID = levelID;
this.setEpoch(Epoch.epochForNumber(epochID));
this.platforms = [];
var self = this;
var gameplayUI = ccs.load(gameplayRes.GameplayUI, "res/");
this.addChild(gameplayUI.node);
var replayButton = gameplayUI.node.getChildByName("replay");
cc.assert(cc.sys.isObjectValid(replayButton), "Replay button not valid");
replayButton.addTouchEventListener(function (sender, type) {
if(type != ccui.Widget.TOUCH_ENDED) return;
cc.LoaderScene.preload(gameplay_resources, function () {
cc.director.runScene(new GameScene(self.levelID, self.epoch.ID));
}, this);
}, this);
return true;
}
//... Other methods
});
var GameScene = cc.Scene.extend({
ctor:function (levelID, epochID) {
this._super();
var gameNode = new GameNode(levelID, epochID);
this.addChild(gameNode);
}
});
All works fine in the end in the scene itself, but say if i change the scene by the same method to main menu, and then go to gameplay again, it seems as the 'new' game scene object has previous (supposed to be released) object`s values. So if i press 'Replay' button, it uses the old game scenes values as well.
I mean in C++ when you replace scenes, old scene`s memory is released by cocos engine. And if you go to that scene by creating new object - its new.
So my question is why is that happening i js? Maybe i do not know something about cocos2d js memory management so i need to do something extra to release old scene?
What i have ensured:
When i extend the engines classes all my added variables a null'ed and objects values are assigned in ctor's using this.value = ...; not to have static values for all objects in that way.
There are no children retaining the Gameplay scene (or it seems not to be) so i am assuming it should be released when i replace scene?
I am not using retain()/release(), and all children are added to the gamescene to be retained.
Any suggestions would be appreciated.
Cheers!
So i have solved it - i was using event manager therefore sending events and game scene with its children have been registered for some of them. I was assuming that eventManager retains nodes on a 'weak' basis, but it seems you have to call removeListeners() for each registered node in its onExit method to remove listeners manually. So now it seems to be working fine.
Question: How should I update a sphere's material texture map with images (stored in a HTML5 canvas) very rapidly (50ms ~ 200ms interval) without the browser choking?
I have a series of panoramic images to map onto a sphere. These images are updated every 50ms ~ 200ms. The code seems to work but in practice it chokes very badly - causing some re-draws to skip or wait for 2s ~ 3s to show up.
Here is the mesh code and the texture (first texture map is a placeholder):
var geometry = new THREE.SphereGeometry(100.0, 64, 64);
var material = new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture(image_path), // placeholder
side: THREE.DoubleSide,
});
When playPanos(0) is triggered, it tries to update the material texture map with the image drawn in a HTML5 canvas, stored in an array “loaded_canvas”:
function playPanos(curr_id) {
if (curr_id >= loaded_canvas.length) return;
paintPanos(curr_id);
setTimeout(function() {
playPanos(curr_id + 1);
}, 100); // update every 100ms
}
function paintPanos(curr_id) {
material.map = new THREE.Texture(loaded_canvas[curr_id]);
material.map.needsUpdate = true;
}
Thanks. Please pardon prototype style code.
A few tips:
You don't need to create new THREE.Texture() everytime you update the sphere texture. Create the texture once, and then update it using: material.map.needsUpdate = true; each time you draw something new in the canvas context.
I supose, since you store the canvases in an array, you don't have a dynamic animation, you have just a loop animation. In that case you could store the finished textures and just update material.map instead of updating the texture with the canvases stored in the array.
If your canvas resolution is too high, try to reduce the pixel dimensions as much as possible. I guess you allready done this.
Do you really need: side: THREE.DoubleSide? Try using THREE.BackSide if you are using the sphere for a skybox.
With theese optimisations, you should be able to run texture updates at 60 fps.
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)
};