Tracking down webGL crash reason - possible video memory leak - debugging

I am creating new AnimatedSprites on pointerdown event (and deleting them on next pointerdown event) over the course if application lifetime. At some point around 20 clicks, the app crashes with an error: Rats! webGl crashed. At that point no error is displayed in console. Crash happens on a mobile device, and would probably also happen on desktop, if someone perform plenty of clicks.
Code that is repeatedly used is below. I also include live example at http://forwardingsolutions.club/. Can someone please point out what am I doing wrong?
From other answers I deduced that you need to reset the loader and call destroy() on animated sprite. I call both of this methods, but there is still an issue.
function setupNextSpritesAnimation(){
setupNextAnimation();
console.log("next color: "+nextColor);
switch (nextColor) {
case "red":
var newLoader = new PIXI.loaders.Loader()
.add('nextSprite', '/assets/sprite/red.png.png')
.load(function (loader, resources){
var interval = setInterval(function(){
if (!isAnimating){
clearInterval(interval);
createNewAnimatedSprite(resources,newLoader);
}
},50);
});
break;
case "aqua":
var newLoader = new PIXI.loaders.Loader()
.add('nextSprite', '/assets/sprite/aqua.png.png')
.load(function (loader, resources){
var interval = setInterval(function(){
if (!isAnimating){
clearInterval(interval);
createNewAnimatedSprite(resources,newLoader);
}
},50);
});
break;
case "green":
var newLoader = new PIXI.loaders.Loader()
.add('nextSprite', '/assets/sprite/blue.png.png')
.load(function (loader, resources){
var interval = setInterval(function(){
if (!isAnimating){
clearInterval(interval);
createNewAnimatedSprite(resources,newLoader);
}
},50);
});
break;
case "purple":
var newLoader = new PIXI.loaders.Loader()
.add('nextSprite', '/assets/sprite/purple.png.png')
.load(function (loader, resources){
var interval = setInterval(function(){
if (!isAnimating){
clearInterval(interval);
createNewAnimatedSprite(resources,newLoader);
}
},50);
});
break;
}
}
function createNewAnimatedSprite(resources,newLoader){
var tmpSprite = new PIXI.extras.AnimatedSprite(setupFrames(resources["nextSprite"].texture.baseTexture));
app.stage.addChild(tmpSprite);
spritesArray.push(tmpSprite);
setupNextSprites(tmpSprite);
app.renderer.plugins.prepare.upload(tmpSprite, function(){
console.log("updoaded now");
canRunNext = true;
newLoader.reset();
//console.log("kill");
delete tmpSprite;
});
}
function setupNextSprites(nextSprite){
nextSprite.x = app.renderer.width / 2;
nextSprite.y = app.renderer.height / 2;
nextSprite.anchor.set(0.5);
nextSprite.loop = false;
nextSprite.animationSpeed = 0.5;
nextSprite.visible = false;
nextSprite.onComplete = function (){
console.log("animation finished");
isAnimating = false;
};
}
function setupNextAnimation(){
var randomNumber = getRandomInt(0,3);
switch (randomNumber) {
case 0:
nextColor = "red";
break;
case 1:
nextColor = "aqua";
break;
case 2:
nextColor = "green";
break;
case 3:
nextColor = "purple";
break;
}
}
app.stage.on("pointerdown", function () {
if (firstRun && !isAnimating) {
firstRun = false;
isAnimating = true;
currentSprite.gotoAndPlay(0);
currentSprite.gotoAndPlay(0);
}else{
if (canRunNext && !isAnimating){
isAnimating=true;
if (currentSprite.visible){
currentSprite.visible = false;
currentSprite.destroy(true);
}
spritesArray[spritesArray.length-1].visible = true;
spritesArray[spritesArray.length-1].gotoAndPlay(0);
app.stage.removeChild(spritesArray[spritesArray.length-2]);
spritesArray[spritesArray.length-2].destroy({ children:true, texture:true, baseTexture:true});
canRunNext = false;
setupNextSpritesAnimation();
}
}
});
function setupSpritesAnimation(){
//created currentSprite just once at the start of app
spritesArray.push(currentSprite);
}

Related

How to deactivate DragControls in Three.js?

I would like to stop DragControls functionality after checkbox deselection. I try to use removeEventListener, but it seems not to be working. Do you have any idea how to deactivate DragControls?
let dragControls;
function dragControlsStart() {
dragControls.addEventListener('dragstart', function (event) {
controls.enabled = false;
});
dragControls.addEventListener('dragend', function (event) {
controls.enabled = true;
});
}
function dragControlsStop() {
dragControls.removeEventListener('dragstart', function (event) {
controls.enabled = false;
});
dragControls.removeEventListener('dragend', function (event) {
controls.enabled = true;
});
}
function dragAndDropActivate() {
let checkBox = document.getElementById("dragAndDropCheckbox");
dragControls = new THREE.DragControls(meshes, camera, renderer.domElement);
if (checkBox.checked == true) {
dragControlsStart();
}
else if (checkBox.checked == false) {
dragControlsStop();
}
}
It looks like the controls have an activate and deactivate function on them that add and remove all the events, which sounds like what you're looking for:
// create drag controls once and activate or deactivate
const dragControls = new THREE.DragControls(meshes, camera, renderer.domElement);
function dragAndDropActivate() {
let checkBox = document.getElementById("dragAndDropCheckbox");
if (checkBox.checked == true) {
dragControls.activate();
}
else if (checkBox.checked == false) {
dragControls.deactivate();
}
}
Another thing to note in your code is that the way you are adding and removing events will not work in Javascript because you are creating new function handles every time you call those functions. Instead you want to create a function once and use the same reference when adding or removing listeners.
function dragEndCallback(event) {
// ...
}
dragControls.addEventListener('dragEnd', dragEndCallback);
dragControls.removeEventListener('dragEnd', dragEndCallback);
Hope that helps!
Solution:
let dragControls = new THREE.DragControls(ArrayOfMesh, camera, renderer.domElement);
dragControls.addEventListener('dragstart', function (event) {
controls.enabled = false;
});
dragControls.addEventListener('dragend', function (event) {
controls.enabled = true;
});
dragControls.deactivate();
function dragAndDropActivate() {
let checkBox = document.getElementById("dragAndDropCheckbox");
if (checkBox.checked == true) {
dragControls.activate();
}
else if (checkBox.checked == false) {
dragControls.deactivate();
}
}

three.js order of loading resources

I have a obj model that I'm loading via THREE.LoadingManager and a few textures. When I'm opening the page with clear cache - I'm always getting model without textures. I can see them after pressing F5. My code looks like this:
var manager = new THREE.LoadingManager();
var loader = new THREE.TextureLoader(manager);
var textures_loaded = 0;
var id;
for (id in materials) {
loader.load('images/' + materials[id].unique_id + '.jpg', function(t) {
t.magFilter = THREE.NearestFilter;
t.minFilter = THREE.LinearMipMapLinearFilter;
var re = /images\/(\d+)\.jpg/g;
var result = re.exec(t.image.currentSrc);
material = materials.filter(function(obj) {
return obj.unique_id == result[1];
}).shift();
material.setTexture(t);
textures_loaded += 1;
if (textures_loaded == materials.length) {
mainLoop();
}
});
}
var loader = new THREE.OBJLoader(manager);
loader.load('obj/' + model.model_name, function (object) {
object.traverse(function(child) {
if (child instanceof THREE.Mesh) {
var mesh = model.meshes.filter(function(mesh) {
return mesh.name == child.name;
}).shift();
child.material.map = mesh.material.texture;
child.geometry.buffersNeedUpdate;
child.geometry.uvsNeedUpdate;
}
});
scene.add(object);
}
);
I'm running mainLoop only after all textures are loaded. But sometimes model loads faster. How can I control order of loading objects using THREE.LoadingManager ?. How can I show model only after everything is loaded ?
I would do something like this. Have two functions responsible for loading each set of content inline. In this case the meshes will not be loaded until the materials are done loading.
var manager = new THREE.LoadingManager();
var loader = new THREE.TextureLoader(manager);
function loadMaterials(materials, callback){
var textures_loaded = 0;
for (var id in materials) {
loader.load('images/' + materials[id].unique_id + '.jpg', function(t) {
t.magFilter = THREE.NearestFilter;
t.minFilter = THREE.LinearMipMapLinearFilter;
var re = /images\/(\d+)\.jpg/g;
var result = re.exec(t.image.currentSrc);
material = materials.filter(function(obj) {
return obj.unique_id == result[1];
}).shift();
material.setTexture(t);
textures_loaded += 1;
if (textures_loaded == materials.length) {
callback("materialsLoaded");
//mainLoop();
}
});
}
}
var loadedMeshes = [];
function loadMeshes(meshes, callback){
var meshes_loaded = 0;
for(var i in meshes){
var loader = new THREE.OBJLoader(manager);
loader.load('obj/' + model.model_name, function (object) {
loadedMeshes.push(object);
object.traverse(function(child) {
if (child instanceof THREE.Mesh) {
//you'll need to map to the right material.
child.material.map = mesh.material.texture;
child.geometry.buffersNeedUpdate;
child.geometry.uvsNeedUpdate;
}
});
if(loadedMeshes.length == meshes.length){
callback("meshesLoaded");
}
};
}
}
var materials = ["material1", "material2"];
var meshes = ["mesh1.obj", "mesh2.obj"];
function onLoad(){
for(var i in loadedMeshes){
scene.add(loadedMeshes[i]);
}
mainLoop();
}
function init(){
loadMaterials(materials, function(t){
console.log(t);
loadMeshes(meshes, function(t){
console.log(t);
onLoad(t);
})
})
}
Maybe you can try to execute your mainLoop only when the document is ready, using some jQuery:
$('document').ready(function mainLoop(){});
"this event does not get triggered until all assets such as images have been completely received"
from jQuery .ready event

Issue with google.maps.event.trigger(map, “resize”) in jquery tabs

I have a button named update details.On clicking the button a dialog is created which contains 3 tabs.In the third tab a field named map is there where users can select their location in map.I have 2 hidden fields which contain latitude and longitude of user stored in database.If the values are null,I need to show marker to their current location.My code is as follows.
<script>
$(document).ready(function(){
var directionsDisplay,
directionsService,
map;
$("#tabs").tabs({
show: function(e, ui) {
if (ui.index == 2) {
google.maps.event.trigger(map, "resize");//for showing google
//map in tabs
}
}
});
if(!window.google||!window.google.maps){
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://maps.googleapis.com/maps/api/js?v=3&' +
'callback=initialize';
document.body.appendChild(script);
}
else{
initialize();
}
});
</script>
<script>
//var directionsDisplay,
//directionsService,
//map;
function initialize() {
//var directionsService = new google.maps.DirectionsService();
//directionsDisplay = new google.maps.DirectionsRenderer();
if(($("#latitude_val").val().length >3) || ($("#longitude_val").val().length>3))
{
var chicago = new google.maps.LatLng($("#latitude_val").val(), $("#longitude_val").val());
}
else
{
geocoder = new google.maps.Geocoder();
geocoder.geocode( { 'address': 'Dubai internet city'}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK)
{
console.log("Latitude: "+results[0].geometry.location.lat());
console.log("Longitude: "+results[0].geometry.location.lng());
}
else
{
console.log("Geocode was not successful for the following reason: " + status);
}
//console.log("latitude"+position.coords.latitude+'longitude='+position.coords.longitude);
});
}
//chicago = new google.maps.LatLng(51.508742, -0.120850);
var mapOptions = { zoom:16, mapTypeId: google.maps.MapTypeId.ROADMAP, center: chicago }
map = new google.maps.Map(document.getElementById("googlemap"), mapOptions);
var marker=new google.maps.Marker({
position:chicago,
map:map,
draggable:true,
animation: google.maps.Animation.DROP
});
marker.setMap(map);
google.maps.event.addListener(
marker,
'drag',
function() {
document.getElementById('latitude_val').value = marker.position.lat();
document.getElementById('longitude_val').value = marker.position.lng();
console.log($("#latitude_val").val());
console.log($("#longitude_val").val());
}
);
//directionsDisplay.setMap(map);
}
function fail(){
alert('navigator.geolocation failed, may not be supported');
}
</script>
When I run this code,it showing the following error.
ReferenceError: google is not defined
google.maps.event.trigger(map, "resize");
You're calling google.maps.event.trigger before you've added the call to load the google maps javascript. Maybe just swap the two parts of what's going on in your document.ready
$(document).ready(function(){
var directionsDisplay,
directionsService,
map;
if(!window.google||!window.google.maps){
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://maps.googleapis.com/maps/api/js?v=3&' +
'callback=initialize';
document.body.appendChild(script);
}
else{
initialize();
}
$("#tabs").tabs({
show: function(e, ui) {
if (ui.index == 2) {
google.maps.event.trigger(map, "resize");//for showing google
//map in tabs
}
}
});
});

Three.js skeletal animations not working unless source is modified

Using Three.js r68+ I have been needing to very slightly modify the source to get my animations working correctly. Unmodified only one of each type of model are animated (there are multiple spawns of each type of model).
This is the modified source at line 29407 (posted code beginning at 29389):
THREE.AnimationHandler = {
LINEAR: 0,
CATMULLROM: 1,
CATMULLROM_FORWARD: 2,
//
add: function () { console.warn( 'THREE.AnimationHandler.add() has been deprecated.' ); },
get: function () { console.warn( 'THREE.AnimationHandler.get() has been deprecated.' ); },
remove: function () { console.warn( 'THREE.AnimationHandler.remove() has been deprecated.' ); },
//
animations: [],
init: function ( data ) {
// original -> if ( data.initialized === true ) return;
if ( data.initialized === true ) return data; //<-- modified
The function now returns the animation data if initialized. I'm assuming it doesn't do so because of caching. My question is what is the best practice for animating multiple models that have the same animation names? I tried naming them uniquely per model name ie: "Death_MaleWarrior" but this had no effect.
Currently my models and animations are handled like so:
var modelArray = [];
var geoCache = [];
var loader = new THREE.JSONLoader(true);
var _MODEL = function(data){
this.data = data;
this.mesh = null;
this.animations = {};
this.canAnimate = false;
this.parseAnimations = function () {
var len,i,anim;
if (this.mesh) {
if( this.mesh.geometry.animations ){
this.canAnimate = true;
len = this.mesh.geometry.animations.length;
if( len ){
for(i=0;i<len;i++){
anim = this.mesh.geometry.animations[i];
if( anim ){
this.animations[anim.name] = new THREE.Animation( this.mesh, anim );
}
}
}
}
}
};
this.playAnimation = function(label){
if (this.canAnimate) {
if( this.animations[label] ){
//if( this.animations[label].data ){
this.animations[label].play(0,1);
//}
}
}
return false;
};
this.load = function(geo){
var mat;
mat = new THREE.MeshPhongMaterial({color:somecolor, skinning:true})
this.mesh = new THREE.SkinnedMesh(geo,mat);
this.mesh.position.x = this.data.position[0];
this.mesh.position.y = this.data.position[1];
this.mesh.position.z = this.data.position[2];
this.parseAnimations();
scene.add(this.mesh);
this.playAnimation('Idle');
};
this.init = function(){
var geo;
if( geoCache[this.data.name] ){
geo = geoCache[this.data.name];
this.load(geo);
}else{
geo = loader.parse(JSON.parse(this.data.json)).geometry;
geoCache[this.data.name] = geo;
this.load(geo);
}
};
this.init();
};
var dataArray = [{name:'MaleWarrior',json:'json_data',position:[x,y,z]},{name:'FemaleWarrior',json:'json_data',position:[x,y,z]},{name:'MaleWarrior',json:'json_data',position:[x,y,z]}];
for(var i=0, len=objectArray.length; i<len; i++){
modelArray.push(new _MODEL(dataArray[i]) );
}
In this example the first MaleWarrior will animate but the second will not. If there was a second female she would not be animated either as the animation (even though it is a new THREE.Animation() ) will be considered initialized and will not return any data. If I do not check for the existence of animations.data in playAnimation I get the error ""Uncaught TypeError: Cannot read property 'name' of undefined " on line 29665".
Is what I'm doing bypassing animation caching and hurting performance? I feel like I'm missing something. How will an animation play without data?
All animation names are the same for every model "Idle", "Run", "Attack" etc.
Any help would be appreciated. If I'm not being clear enough please let me know. I have added additional detail.
This turned out to be an official three.js bug.
https://github.com/mrdoob/three.js/issues/5516

AJAX works in IE but not in Firefox or Chrome

I'm new to using AJAX and my code works in Internet Explorer but not in Firefox or Chrome.
I do not know what it is exactly what should change in the code ...
// I think that error should be here :-)
function cerrar(div)
{
document.getElementById(div).style.display = 'none';
document.getElementById(div).innerHTML = '';
}
function get_ajax(url,capa,metodo){
var ajax=creaAjax();
var capaContenedora = document.getElementById(capa);
if (metodo.toUpperCase()=='GET'){
ajax.open ('GET', url, true);
ajax.onreadystatechange = function() {
if (ajax.readyState==1){
capaContenedora.innerHTML= "<center><img src=\"imagenes/down.gif\" /><br><font color='000000'><b>Cargando...</b></font></center>";
} else if (ajax.readyState==4){
if(ajax.status==200){
document.getElementById(capa).innerHTML=ajax.responseText;
}else if(ajax.status==404){
capaContenedora.innerHTML = "<CENTER><H2><B>ERROR 404</B></H2>EL ARTISTA NO ESTA</CENTER>";
} else {
capaContenedora.innerHTML = "Error: ".ajax.status;
}
} // ****
}
ajax.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
ajax.send(null);
return
}
}
function creaAjax(){
var objetoAjax=false;
try{objetoAjax = new ActiveXObject("Msxml2.XMLHTTP");}
catch(e){try {objetoAjax = new ActiveXObject("Microsoft.XMLHTTP");}
catch (E){objetoAjax = false;}}
if(!objetoAjax && typeof XMLHttpRequest!='undefined') {
objetoAjax = new XMLHttpRequest();} return objetoAjax;
}
//These functions are connected with a form
function resultado(contenido){
var url='ajax/buscar.php?'+ contenido +'';// Vota Resultado
var capa='resultado';
var metodo='get';
get_ajax(url,capa,metodo);
}
function paginas(contenido){
var url='ajax/paginar.php?'+ contenido +'';// Vota Paginas
var capa='paginas';
var metodo='get';
get_ajax(url,capa,metodo);
}
Strongly suggest that you use a lib like jQuery that encapsulates a lot of what you're doing above, masking cross-browser issues (current and future). Even if you don't want to use jQuery site-wide, you could still use it just for its AJAX functionality.

Resources