[SOLVED] Loading scene json from remote location

I am trying to load a scene json file from a remote location, but I can’t seem to get it to work. I made a build from an editor project and took the scene.json file, trying to load that in the same project again, but from my database.

SceneLoadTest.prototype.initialize = function() {
    var addedScene = pc.app.scenes.add("test", "https://gist.githubusercontent.com____/testscene.json");
    console.log("added scene", addedScene); //true
    var scene = pc.app.scenes.find("test");
    console.log(scene); //Object with name and url, correct.
    pc.app.scenes.loadSceneHierarchy(scene, function (err, entity) {
        console.log(err, entity); //Not Found, undefined
    });
};

In start.js it shows how it loads the set scene as well, but no luck with that either.

pc.app.scenes.loadScene(scene.url // also Not found, undefined

Is there a way to do this? I want some people to configure a scene layout and export it to our database, then load it back in from there.

Hi @Ivolutio,

Before loading the scene you need to load all the assets required by it. You can fetch that list from the config.json that comes with the build you exported.

I am doing something similar in a project of mine, though it can be complicated since it uses internal methods from the engine:

  • fetch and read the config.json that you exported with the scene. It contains info of all assets required.
  • prepare the remote assets for loading, updating their relative asset url with your server url and then add the assets to the registry. Here is how the engine is doing this internally:
  • load the scene and attach the final hierarchy to the scene currently running, using loadSceneHierarchy():

https://developer.playcanvas.com/en/api/pc.SceneRegistry.html#loadSceneHierarchy

  • optionally you can load in the same way the scene settings.
1 Like

Thank you!
If that’s the case I will need a bit more time to rework some of our project :wink: - I will mark this as solved for now as it was the information I was looking for.

1 Like

I have got this to work almost perfectly, however I am trying to access the model’s meshinstances property after the scene has loaded:

app.scenes.loadSceneHierarchy(scene.url, function (err, root) {
  if (err) return console.error(err);
  const cameraTargetEntity = root.findByGuid(sceneAttributes.focusEntity);
  console.log(cameraTargetEntity.model.meshInstances); // NULL
  setTimeout(() => console.log(cameraTargetEntity.model.meshInstances), 500); // Array
});

It seems like the meshInstances are not set immediately, is there a way to detect it being done loading?

Hi @Ivolutio,

Make sure that your model asset is available and loaded the moment you load the hierarchy, that’s one reason why the mesh instances array may be empty (waiting for the model to load).

If that’s not the issue, there may be an issue with the order the scene hierarchy is loaded and all the components are initialized. I remember I had a similar issue and had to run my code at the next frame, after the hierarchy has fully finished executing its components.

You can do this the same way you do, with a timeout, but with a zero delay. Not elegant, but it will make sure to execute your code at the next frame:

setTimeout(() => console.log(cameraTargetEntity.model.meshInstances), 0);
1 Like

Running it on the next frame works indeed, seems like the easiest solution thank you :slight_smile:

1 Like

I guess I never got it fully working… :sweat_smile:
For some reason it does not load my textures in correctly, which I need to load from a blob url.
This is my code to load a texture asset type:

const loadTexture = (
  file,
  fileName,
  data = undefined,
  id = undefined
) => {
  const app = pc.app;
  return new Promise((resolve, reject) => {
    //create asset
    const asset = new pc.Asset(
      fileName,
      "texture",
      {
        url: "",
      },
      data
    );
    if (id !== undefined) asset.id = id;

    var tex = new pc.Texture(app.graphicsDevice, {
      mipmaps: false,
      flipY: true,
    });

    tex.minFilter = pc.FILTER_LINEAR;
    tex.magFilter = pc.FILTER_LINEAR;
    tex.addressU = pc.ADDRESS_CLAMP_TO_EDGE;
    tex.addressV = pc.ADDRESS_CLAMP_TO_EDGE;

    var img = document.createElement("img");
    img.crossOrigin = "anonymous";

    img.onload = function () {
      tex.setSource(img);
      asset.resource = tex;
      asset.fire("load", asset);
      asset.loaded = true;
      app.assets.add(asset);
    };
    img.onerror = (e) => {
      console.error(e);
      reject(e);
      throw new Error(e);
    };

    const blob = bytesToB64Blob(new Uint8Array(file));
    const url = window.URL.createObjectURL(blob);
    img.src = url;
    // const a = document.createElement("a");
    // a.href = url;
    // a.target = "_blank";
    // a.click();

    asset.ready((asset) => {
      resolve(asset);
      URL.revokeObjectURL(url);
    });
  });
};

Now, I can see a blob url in the network tab and I can call app.assets.get(id) with the id listed in the material’s json data, but in the app the model is still without texture, and the diffuseMap is null on the material.

Am I doing something wrong here? :slight_smile:

This is my complete build-loading script (doing engine-only, with NPM)

for (const key in buildData.assets) {
  const element = buildData.assets[key];

  if (element.file) {
    request.file = element.file.url;

    let responseType = "arraybuffer";
    if (element.file.filename.split(".")[1] === "json")
      responseType = "text";
    const file = await axios.post(..., request, {
      responseType: responseType,
    }); // this returns arraybuffer of file data

    if (element.type === "texture") {
      console.log(element)
      const asset = await loadTexture(
        file.data,
        element.name,
        element.data,
        parseInt(element.id, 10)
      );
      // pc.app.assets.load(asset);
      console.log(asset);
    } else {
      // Turn those assets in a blob reference
      const blob = new Blob([JSON.stringify(file.data)]);
      const url = URL.createObjectURL(blob);
      const assetPromise = new Promise((resolve) => {
        ///TODO: reject?
        const file = {
          url: url,
          filename: element.name,
        };
        const newAsset = new pc.Asset(element.name, element.type, file);
        newAsset.data = element.data;
        newAsset.id = parseInt(element.id, 10);
        // tags
        newAsset.tags.add(element.tags);
        // i18n
        if (element.i18n) {
          for (let locale in element.i18n) {
            newAsset.addLocalizedAssetId(locale, element.i18n[locale]);
          }
        }
        newAsset.once("load", function (modelAsset) {
          resolve(modelAsset);
          URL.revokeObjectURL(url);
        });
        pc.app.assets.add(newAsset);
        pc.app.assets.load(newAsset);
      });
      const asset = await assetPromise;
      console.log(asset);
    }
  } else {
    // if not, we're a material or something so PC can handle it.
    const asset = new pc.Asset(
      element.name,
      element.type,
      element.file,
      element.data
    );

    asset.id = parseInt(element.id, 10);
    // asset.preload = element.preload ? element.preload : false;
    // tags
    asset.tags.add(element.tags);
    // i18n
    if (element.i18n) {
      for (let locale in element.i18n) {
        asset.addLocalizedAssetId(locale, element.i18n[locale]);
      }
    }
    pc.app.assets.add(asset);
    console.log(asset.name);
  }
}

What happens if you remove ‘URL.revokeObjectURL(url);’? in the ready callback?

Same thing :slight_smile:

Any errors in console etc?

Any way you can make a reproducible version of this? Hard coded blob data etc?

Nope, it seems to load completely fine. As I said, the textures also show up as assets in the asset registry. :sweat_smile:
I’ll try to get a test going in a bit.

1 Like

Thanks!

Also, thank you for showing me URL.revokeObjectURL(); You may have showed me a fix to an unrelated issue that I’ve been banging my head against for a few days :sweat_smile:

1 Like

Here you go, https://we.tl/t-MrP1fV5FoY. It is with NPM, so when you open it run npm install first, as you by no doubt know :wink: , then npm serve it with webpack and it should load the files from your localhost (with axios).

See the build files in dist/playcanvas_build and src/index.js for the general code.
I added a boolean to use a base64 string loaded in a img tag in dist/index.html, which gives the same results.

If you need anything else/different from me, let me know :smiley:

I get the following error when trying to npm install

> fsevents@1.2.13 install /Users/stevenyau/Downloads/test-env/node_modules/watchpack-chokidar2/node_modules/fsevents
> node install.js

internal/modules/cjs/loader.js:968
  throw err;
  ^

Error: Cannot find module 'nan'
Require stack:
- /Users/stevenyau/Downloads/test-env/node_modules/watchpack-chokidar2/node_modules/fsevents/[eval]
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:965:15)
    at Function.Module._load (internal/modules/cjs/loader.js:841:27)
    at Module.require (internal/modules/cjs/loader.js:1025:19)
    at require (internal/modules/cjs/helpers.js:72:18)
    at [eval]:1:1
    at Script.runInThisContext (vm.js:120:18)
    at Object.runInThisContext (vm.js:309:38)
    at Object.<anonymous> ([eval]-wrapper:10:26)
    at Module._compile (internal/modules/cjs/loader.js:1137:30)
    at evalScript (internal/process/execution.js:94:25) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [
    '/Users/stevenyau/Downloads/test-env/node_modules/watchpack-chokidar2/node_modules/fsevents/[eval]'
  ]
}
gyp: Call to 'node -e "require('nan')"' returned exit status 1 while in binding.gyp. while trying to load binding.gyp
gyp ERR! configure error
gyp ERR! stack Error: `gyp` failed with exit code: 1
gyp ERR! stack     at ChildProcess.onCpExit (/usr/local/lib/node_modules/npm/node_modules/node-gyp/lib/configure.js:351:16)
gyp ERR! stack     at ChildProcess.emit (events.js:315:20)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:275:12)
gyp ERR! System Darwin 19.6.0
gyp ERR! command "/usr/local/bin/node" "/usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /Users/stevenyau/Downloads/test-env/node_modules/watchpack-chokidar2/node_modules/fsevents
gyp ERR! node -v v12.18.3
gyp ERR! node-gyp -v v5.1.0
gyp ERR! not ok

> core-js@2.6.11 postinstall /Users/stevenyau/Downloads/test-env/node_modules/core-js
> node -e "try{require('./postinstall')}catch(e){}"

Thank you for using core-js ( https://github.com/zloirock/core-js ) for polyfilling JavaScript standard library!

The project needs your help! Please consider supporting of core-js on Open Collective or Patreon:
> https://opencollective.com/core-js
> https://www.patreon.com/zloirock

Hmm that is odd, I just tried myself and it’s fine. Try removing the package-lock.json first and retry npm install? https://stackoverflow.com/questions/34464673/error-cannot-find-module-nan

I’m looking at the source code and can’t see where you assign the texture to a material to assign to the model?

Debugging the scene at runtime, it looks like the apple still has the default material

As far as I can tell, the texture has been setup correctly, it just hasn’t been applied to the apple.

So when you add the scene to the app, it does not automatically restore the asset links? Or did I do something wrong?

As far as I can tell, you created the model asset, the texture asset but not the material asset.

I’ve added logs to where the assets are added to the registry and I only see two being created (model and texture).

Looking at the assets registry: