The best way to save and open three.js scene

Hello,

basically what I have is a scene containing plane geometry and a number of meshes ( drawn or taken from GLB files). What would be the best way to save this scene and open it later in order to continue with work? I tried to do it with GLTFExporter but once the scene is imported it lost some information so it is clearly not the best way to achieve this. I’ve read that the best would be to export scene as JSON and import it later. If this is the case, could you please write how to do it? I’ve tried some examples from the internet but none of them worked. Many thanks!!

You can serialize the scene like so:

const json = scene.toJSON();

And load it via ObjectLoader:

const scene = new THREE.ObjectLoader().parse( json );
1 Like

Hello Mugen87,

thank you for your answer!
However when I tried to export I got the json file (stringified) with this function:

function exportSceneToJSON(input){
  control.detach();
  scene.remove(control);
  var result=scene.toJSON();
  var output =JSON.stringify(result);
  download(output, 'scene.json', 'application/json');
}

the file I got is attached.
Then i tried to load the file with this function:

function importObject(inp){

  control.detach();
  if (inp.files && inp.files[0]) {

    var reader = new FileReader();
    reader.onload = function (e) {

    var loader = new THREE.ObjectLoader();

    loader.load(e.target.result,
      function(json){
        scene = JSON.parse( e.target.result );
        render();
    });

    }
    reader.readAsDataURL(inp.files[0]);
  }
   
 }

and I got:
three.js:41299 Uncaught TypeError: Cannot read property ‘type’ of undefined
at ObjectLoader.parseObject (three.js:41299)
at ObjectLoader.parse (three.js:40697)
at importObject (index.js:217)
at HTMLInputElement.onchange ((index):27)[testJSON.json|attachment]

Any idea what is wrong here?
(upload://6WbrPCF2qs8CwSfXiLvJmfGzXdo.json) (3.9 KB)

Here is the link for the JSON file:

The usage does not look right. In the onLoad() callback, it’s not necessary to call JSON.parse(). Besides, I don’t think you have to use ObjectLoader.load() in this context. Try it similar to the editor:

The data variable is already a JSON object.

You can also try saving it to glTF using the GLTFExporter. If you do this it’s reccomended not to embed textures as the browser will rewrite them usually with worse compression than you started out with.

Hi guys,

thank you so much! I was able to get it to work based on your advice.

Mirko

Hi guys,

as I wrote, saving the scene and loading it is now possible without any errors.
But what I see as loaded scene is not what I previously saved.

For example I have this scene (plane geom and mesh that I created on it):

I saved it to the file scene1.json:

scene1.json (6.6 KB)

But when I load it I am getting this:

when I use orbit controls and move the scene I see this:

I am not quite sure what went wrong there. As if there are some values which I did not save but I should have.

Thanks for your help!!

Before exporting the scene to JSON, can you please call scene.updateMatrixWorld()? Depending on how your app works, this call might missing.

1 Like

Hello,

i’ve tried that one:

  function exportSceneToJSON(input){
  control.detach();
  scene.remove(control);
  scene.updateMatrixWorld();
  var result=scene.toJSON();
  var output =JSON.stringify(result);
  download(output, 'scene.json', 'application/json');
}

but it did not change anything…

Can you please share your entire project as a GitHub repository? It’s probably best to debug the issue.

Hello,

I am sharing index.three.js file.
The entire project I can share in 2-3 days, in case I do not find solution for this.

All three.js related stuff is in this file.

index.three.js (6.4 KB)

In the code you will also see the section ‘export to png’ where I was trying to export scene to png image. This works but the quality of image is pretty low - is there a way to improve it?

Thanks a lot for your help!

Hello,

Project is here: https://github.com/mirkosimanic/maps.git

Please let me know if you need something else.

Many thanks!
Mirko

Okay, the problem in your app is related to such code:

var planeGeom = new THREE.PlaneGeometry( planedimensions, planedimensions );
planeGeom.rotateX( - Math.PI / 2 );

Although the API allows such a usage, it does not work when serializing/deserializing. three.js only saves the parameters of geometry generators in JSON and not their actual buffer attributes. Meaning if you transform the geometries via rotateX() or translate(), you don’t save these changes in JSON. Only the width and height of the plane (defined by planedimensions). Hence, use this approach:

var planeGeom = new THREE.PlaneBufferGeometry( planedimensions, planedimensions );
plane.rotation.x = Math.PI * - 0.5;

Meaning never transform the result of a geometry generator. Also use the BufferGeometry version whenever possible.

1 Like

Hello,

thanks a lot! It works much better now!
The scene and meshes are now loaded correctly.

However I still have a little issue when I try to make wall out of mouse clicks.
Everything works fine but new mesh has position of 0,0,0 which makes problem when I add transform controls to it as they show in the center of the scene and not above the object itself.

I’ve tried to set the position after mesh rotation but it would just move mesh away from the clicked place.

This is the function taking care of it:

function makeWalls(){
  control.detach();
  if(controlPoints.length > 1){
    shape = new THREE.Shape();
     shape.moveTo(controlPoints[0].x, -controlPoints[0].z);
     for(var i = 1; i < controlPoints.length; i++){
        shape.lineTo(controlPoints[i].x, -controlPoints[i].z);
     }
    shape.lineTo(controlPoints[0].x, -controlPoints[0].z);

var extrudeSettings = {
  steps: 1,
  depth: 40,
  bevelEnabled: false
};

var extrudeGeom = new THREE.ExtrudeBufferGeometry(shape, extrudeSettings);
console.log(extrudeGeom);
//extrudeGeom.rotateX(-Math.PI / 2);

var wall = new THREE.Mesh( extrudeGeom, [
  new THREE.MeshLambertMaterial({color: 0xffffff, transparent: true, opacity: 1}),
  new THREE.MeshLambertMaterial({color: 0xcfcfcf, transparent: true, opacity: 1})
] ) ;

wall.rotation.x = Math.PI * - 0.5;

/*wall.centroid = new THREE.Vector3();
for (var i = 0, l = controlPoints.length; i < l; i++) { wall.centroid.add(controlPoints[i].clone()); }
wall.centroid.divideScalar(controlPoints.length);
wall.centroid.z = 0;
var offset = wall.centroid.clone();
console.log(offset);
wall.geometry.applyMatrix4(new THREE.Matrix4().makeTranslation(-offset.x, -offset.y, -offset.z));
wall.position.copy(wall.centroid);
*/


allObjects.push(wall);

splineArray= [];
updatePositions();

scene.add(wall);
controlPoints = [];
clickCount = 0;
main_action = "";
//scene.remove(lineDrawMe);

  }
  else{
    console.log('You need to click to create points first. Click on \'Draw Walls\' and make a shape.');
  }  

in commented lines you will see how problem was resolved while using rotateX function.
When using rotation.x it does not work.

Many thanks for your help!!

Thank you very much, Mugen87. This was so helpful!