Jump to content

Working code using SceneLoader.ImportMesh does not work in playground


JohnK
 Share

Recommended Posts

Trying to help @SuperPudding in this topic

I created some code to show that my answers still worked. The code worked so I copied the relevant parts to the playground where it failed and got something like SuperPudding was detailing.

Any explanations gratefully received.

This is the playground http://www.babylonjs-playground.com/#YEKUO  that does not work. Check console for error messages.

The code below works for me. You should get a red box on top of a blue box if you copy, save and run it.

[Tried putting it in as a code snippet but it took over everything else so settled on using quotes.]

Quote

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Babylon.js sample code</title>
    <script src="http://cdn.babylonjs.com/2-3/babylon.max.js"></script>
    <style>
        html, body {
            overflow: hidden;
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }
        #renderCanvas {
            width: 100%;
            height: 100%;
            touch-action: none;
        }
    </style>
</head>
<body>
    <canvas id="renderCanvas"></canvas>
    <script>
        var canvas = document.getElementById("renderCanvas");
        var engine = new BABYLON.Engine(canvas, true);
        var createScene = function () {
            var scene = new BABYLON.Scene(engine);
            var camera = new BABYLON.ArcRotateCamera("Camera", -3*Math.PI/8,  3*Math.PI/8, 580, BABYLON.Vector3.Zero(), scene);
            camera.attachControl(canvas, true);
            var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);
            light.intensity = 0.7;
            var content = '{"materials":[{"name":"mat02","ambient":[0,0,0],"diffuse":[1,1,1],"specular":[1,1,1],"specularPower":64,"emissive":[0,0,1],"alpha":1,"id":"mat02","tags":null,"backFaceCulling":true},{"name":"mat00","ambient":[0,0,0],"diffuse":[1,1,1],"specular":[1,1,1],"specularPower":64,"emissive":[1,0,0],"alpha":1,"id":"mat00","tags":null,"backFaceCulling":true}],"geometries":{"boxes":[],"spheres":[],"cylinders":[],"toruses":[],"grounds":[],"planes":[],"torusKnots":[],"vertexData":[{"id":"56826136-11bc-4bd7-917e-bcd31e77c577","positions":[30,-30,30,-30,-30,30,-30,30,30,30,30,30,30,30,-30,-30,30,-30,-30,-30,-30,30,-30,-30,30,30,-30,30,-30,-30,30,-30,30,30,30,30,-30,30,30,-30,-30,30,-30,-30,-30,-30,30,-30,-30,30,30,-30,30,-30,30,30,-30,30,30,30,30,-30,30,30,-30,-30,-30,-30,-30,-30,-30,30],"normals":[0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0],"uvs":[1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0],"indices":[0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23]}]},"meshes":[{"name":"Lmodel0¬box0","id":"box","position":[30,30,30],"rotation":[0,0,0],"scaling":[1,1,1],"localMatrix":{"0":1,"1":0,"2":0,"3":0,"4":0,"5":1,"6":0,"7":0,"8":0,"9":0,"10":1,"11":0,"12":0,"13":0,"14":0,"15":1},"isEnabled":true,"isVisible":true,"infiniteDistance":false,"pickable":true,"receiveShadows":false,"billboardMode":0,"visibility":1,"checkCollisions":false,"geometryId":"56826136-11bc-4bd7-917e-bcd31e77c577","subMeshes":[{"materialIndex":0,"verticesStart":0,"verticesCount":24,"indexStart":0,"indexCount":36}],"materialId":"mat02","instances":[],"animations":[],"layerMask":268435455},{"name":"Lmodel0¬box1","id":"box","position":[30,90,30],"rotation":[0,0,0],"scaling":[1,1,1],"localMatrix":{"0":1,"1":0,"2":0,"3":0,"4":0,"5":1,"6":0,"7":0,"8":0,"9":0,"10":1,"11":0,"12":0,"13":0,"14":0,"15":1},"isEnabled":true,"isVisible":true,"infiniteDistance":false,"pickable":true,"receiveShadows":false,"billboardMode":0,"visibility":1,"checkCollisions":false,"geometryId":"56826136-11bc-4bd7-917e-bcd31e77c577","subMeshes":[{"materialIndex":0,"verticesStart":0,"verticesCount":24,"indexStart":0,"indexCount":36}],"materialId":"mat00","instances":[],"animations":[],"layerMask":268435455}]}';
            var re = /\s*/gi;
            content = content.replace(re, '');
            BABYLON.SceneLoader.ImportMesh("", "", 'data:'+content, scene, function (meshes, particleSystems, skeletons) {
                for (var i=0; i<meshes.length; i++) {                                
                    console.log(i, meshes.name);                                
                }  
            });
            return scene;
        };
        var scene = createScene();
            engine.runRenderLoop(function () {
                scene.render();
            });
        window.addEventListener("resize", function () {
                engine.resize();
        });
    </script>
</body>
</html>

 

 

Link to comment
Share on other sites

Hi John!

First, a working playground - http://www.babylonjs-playground.com/#YEKUO#1

Second, an explanation - When trying to load a file, the file extension is used to detect which plugin will be used. As this demo is using data (and not a filename), there is no way of finding our what the file type is. so the last added plugin is used. which is the object loader (and not the .babylon loader).

The quickest solution is to "pop" out the object loader. But this is not the nicest solution.

Let me think of a nice solution that won't break anything :)

Link to comment
Share on other sites

Just a quick update - the default loader is not the first and not the last, so the scene should word even without this "pop" method.

Not sure if DK has updated the playground already, but it is already in the framework.

And a note - this will prevent developers from using data: and obj data. The solution, in this case, would be to pop the default loader out of the array before loading the obj loader.

 

Link to comment
Share on other sites

Thank you @RaananW original code now works in the playground. However I am not sufficiently knowledgeable to understand the last sentence

1 hour ago, RaananW said:

 And a note - this will prevent developers from using data: and obj data. The solution, in this case, would be to pop the default loader out of the array before loading the obj loader.

Are you talking about developers of BabylonJS or developers of games using BabylonJS.

Would you please be able to give me an example of a line of code or two to show what developers cannot write using data: and obj data and how they should write it for it to work?

Sorry to be a pain but I need very simple explanations as I am not on the same level of understanding as many members.

 

Link to comment
Share on other sites

Sure John!

This is for game developers. As there is no way of choosing the loader that will be used to load the model, if you use the "data:..." model you will have to set the loader yourself. The current behavior, after the change from yesterday, is to use the first registered loader-plugin, which is the .babylon loader.

Now, if you want to paste an .obj file and use this information as "data:..." model, you will have a small problem - the loader that will be used is the .babylon file. Imposrtant note - this is ONLY when using data:... and not when using a URL ending with .obj (which is the way the loader can know that it is a .obj file).

To do that, you will have to change the order of the plugin array, stored in SceneLoader._registeredPlugins, which is just what I did in the PG link I provided. Doing this:

BABYLON.SceneLoader._registeredPlugins.shift();

will remove the first element from the array (the .babylon) loader, leaving only the .obj loader.

A smarter move would be:

BABYLON.SceneLoader._registeredPlugins.push(BABYLON.SceneLoader._registeredPlugins.shift());

Which will take the first element, and push it as the last. This way, the default loader will be the .obj file.

 

I hope I am somehow making sense :)

Link to comment
Share on other sites

Thank you for your time and explanation @RaananW. So if I understand correctly my code in the first post will still work as it is (even with 2.4) because I have not done any other loading within the code. However should I use SceneLoader to load an .obj file previously within the code then I need one of the above lines of code if I use SceneLoader with the data: method. Even better would be to use it every time with the SceneLoader data: method.

Link to comment
Share on other sites

It will work as it is because the .babylon loader is the first and the default loader, and you are trying to load a .babylon format using "data:..." . If you try loading an .obj using "data:..." you will have to run the second line of code I provided in my last post.

Each import is searching for the right plugin to use. If none was found, it chooses the default plugin. Which now is the .babylon loader.

Link to comment
Share on other sites

@JohnK @RaananW

While it works in the playground I have a few problems when I try to use it in my own web application.

First of all technical details: I work with Microsoft Visual Studio 2012 and use the 2.2 version of Babylon.
I tried building an application in which the user can save a stringified mesh in a database and then load it.
When I try to load a mesh using the SceneLoader it works, but for some reason it works in a wierd asynchronous way - it runs through my script and only after the next rendering it loads the mesh.
It's not just the graphical representation, the mesh itself does not exist in scene.meshes until the next rendering after I use the SceneLoader.

This is a problem for me since I want to work with the mesh before the user sees it (to give it a parent, material etc...). It also makes it hard for me to get access to the mesh as a variable.

I checked in the playground and it doesn't happen there. It's probably something with the version of Babylon or the Visual Studio compiler.

 

Any ideas how to get around that?
If you need more details ask me and I'll try to deliver (I didn't include code because it's a little messy and might require some more explanations, if it helps I will share it in another comment).

Thanks in advance

Link to comment
Share on other sites

Hi SP,

This seems like a proper behavior - the mesh load is async, and therefore will only be called when the next JS task is to be executed. There is a success callback you can define when loading a mesh. When this callback is called, the mesh should be in the meshes list and you can do whatever you want with it, before it renders.

I would also try upgrading to 2.4 . DK has always "forced" us (in a very positive way) to keep the framework backwards-compatible. Unless you hacked your way around the framework, everything should be working as expected.

If you want to share a simplified scene (not the playground, as it is working properly there as far as I understood), please do.

Link to comment
Share on other sites

@RaananW

Thanks for the explanation, for some reason I thought the mesh is added after this callback and not before. The success callback solves my problem, besides one thing:

Inside the callback I try building an object of type "Body" (a type I created that one of its properties is a mesh).
When I call the constructor from inside the onSuccess callback it doesn't run at all, and it also breaks out of the callback.
I guess I can "cheat" and just build an object with the same properties but without the constructor (oh the wonders of javascript) but it bothers me a little bit.

It's not critical, but I will appreciate an explanation and a solution if you can provide any or both of them.

Link to comment
Share on other sites

Hi SP,

this is probably due to the way you define your objects (not Babylon issue but a JavaScript issue). Can't really help without seeing any code.

Make sure you reference the object correctly. Don't forget that "this" inside the callback is not "this" outside of it. Which I guess is the main problem.

Link to comment
Share on other sites

@RaananW

Here is the relevent part of my code:

(Outside of the callback I have a string array with the properties of the Body object called bodyData and an array of Body objects called bodies)
then inside the callback:

//getting the properties from the bodyData array
var geometricCenter = new BABYLON.Vector3(Number(bodyData[0]), Number(bodyData[1]), Number(bodyData[2]));
var mass = Number(bodyData[3]);
var centerOfMass = new BABYLON.Vector3(Number(bodyData[4]), Number(bodyData[5]), Number(bodyData[6]));
var velocity = new BABYLON.Vector3(Number(bodyData[7]), Number(bodyData[8]), Number(bodyData[9]));
var angularVelocity = new BABYLON.Vector3(Number(bodyData[10]), Number(bodyData[11]), Number(bodyData[12]));
var elacticity = Number(bodyData[13]);
var name = bodyData[14];
var diffuseColor = new BABYLON.Color3(Number(bodyData[15]), Number(bodyData[16]), Number(bodyData[17]));
var emissiveColor = new BABYLON.Color3(Number(bodyData[18]), Number(bodyData[19]), Number(bodyData[20]));
var mesh = scene.meshes[scene.meshes.length - 1];

//setting the material of the new mesh that was loaded
mesh.name = name;
var material = new BABYLON.StandardMaterial(name + "Material", scene);
material.diffuseColor = diffuseColor;
material.emissiveColor = emissiveColor;
mesh.material = material;

//adding a new Body to the bodies array
bodies[bodies.length] = new Body(mesh, geometricCenter, mass, centerOfMass, velocity, angularVelocity, elasticity);

Any code inside the callback after this last line doesn't execute.

 

The Body constructor called here should do this:

function Body(mesh, geometricCenter, mass, centerOfMass, velocity, angularVelocity, elasticity) {
    this.mesh = mesh;
    this.centerOfMass = BABYLON.Mesh.CreateBox(mesh.name + "CenterOfMass", 1, mesh.getScene());
    this.centerOfMass.visibility = 0;
    this.mesh.parent = this.centerOfMass;
    this.centerOfMass.translate(centerOfMass, 1, BABYLON.Space.WORLD);
    this.mesh.translate(geometricCenter.subtract(centerOfMass), 1, BABYLON.Space.WORLD);
    this.mass = mass;
    this.velocity = velocity;
    this.angularVelocity = angularVelocity;
    this.elasticity = elasticity;
}

This constructor has worked before.


I checked the oreder in which things happened using alerts and found out that the constructor and everything in the success callback after calling the constructor don't run at all.
If I don't call the constructor the success callback keeps running to the lines after that.

Link to comment
Share on other sites

Hi SP,

An exception is being thrown for sure. Something is not defined correctly somewhere. Maybe the mesh, maybe one of the vectors, maybe the constructor is not defined in this context, maybe bodies is not defined. There are a lot of reasons for that to happen. try debugging your code or look at the console for help. You will see where it fails and you will be able to fix that then. Parts of the code are not enough in that case. Debug your code and keep us updated on your results!

If you want my comments on your code (again, this might be the wrong forum for this, as this is a JS question, but since we are already here...):

  • Why a string array? why not an array holding the right values? this way you don't need this Number all over. Actually, the only place where you need a string is the name... Maybe even store it in an object and not an array. would make things more structured.
  • mesh can be retrieved using the scene.getMeshByID (or any other "getMeshBy..." function). This is a safer way of getting the mesh. It is not guarantied that the mesh added is the last in the array.
  • bodies[bodies.length] is the same as "push", why not simply use native JS functions? bodies.push(.....) ?
  • if you already have the body data, why create the extra object that holds those values? this can be done from the beginning, avoiding redundant data structures.
Link to comment
Share on other sites

@RaananW

  • I'm using a string array because I'm getting this information from the database using ajax, and it only returns strings as far as I know.
  • I'm somewhat new to JS so the bodies[bodies.length] is simply me not remembering the push method.
  • bodyData is a string array that I retrive from the database. I want an object of type Body so I can work with it in the scene easily. Besides, bodyData is defined only in this region of code specifically for the particular body I'm trying to create, while bodies is defined for the whole JS file and can be called form other places.

I will try the getMeshById idea, didn't know it existed. But I doubt it will solve the entire problem.

I'll try debugging more thoroughly and I'll keep you updated.
Thank you so much for helping me. By the way it's always nice to get tips and ideas for how to program better, despite this not being a JS forum.

Link to comment
Share on other sites

@RaananW

Update: I managed to find the line where an exception is thrown, and I've played a little in the playground and found out it happens there as well.

In the playground the mesh loading works synchronously, which is not like in my project, but there was an exception in the exact same place:

var bodies = [];
alert("before: " + scene.meshes.length);
BABYLON.SceneLoader.ImportMesh("", "", 'data:' + data, scene, function (meshes, particleSystems, skeletons) {
	alert("import");
	var mesh = meshes[0];
	var body;
	alert('0');
	body.mesh = mesh; //exception
	alert('1');
	bodies.push(body);
});
alert("after: " + scene.meshes.length);
alert(bodies.length);

I used the alerts to debug. It runs synchronously up to the line I marked. The "alert('1')" is not executed, instead it breaks out of the function and continues at 'alert("after: " + scene.meshes.length)'.
The length of scene.meshes is increased, but the length of bodies is still 0 after the callback.

Link to comment
Share on other sites

Only on mobile for a few days. Give a link to your playground then people are more likely to help by making corrections for you. In the meantime try

var body = {mesh:mesh}

to correctly set the property mesh for object body.

Also (and this is where my knowledge fails) does pushing a local object variable into a global array make the local object available globally?

You could also try declaring body outside the callback function and use

 

body = {mesh:mesh} inside the callback function

Link to comment
Share on other sites

@JohnK

Thank you so much! Your correction was enough to make it work.
The exception was thrown because I declared body as a variable but not specifically as an object.
Adding "var body = {};" helped. This was again my lack of experience in JS.

The asynchronously of the onSuccess callback still gives me some trouble, but you just solved one of my main problems. I'll keep playing with it.
Thank you! 

Link to comment
Share on other sites

Update: I finally got everything to work!

I fixed the problems with exceptions and I managed to "trick" my way around the async problems.
The problem with async was that I was setting the bodyData array for each body individually in a for loop, but it would go through the entire loop and only then go to the onSuccess callback.
I used another counter, outside of the loop, and manually set it to increase inside the onSuccess callback. This way I got the correct index for each body and therefore the correct data.

Thank you both @RaananW and @JohnK, you guys helped me so much.


I can finally move on to other parts of my project (I have to finish it soon as it's a high school project).
I still can't get my head around collisions but I'll open another topic for that.

Thanks again!

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...