Jump to content

How to seamlessly update the TiledGround without lagging/chopping?


breakds
 Share

Recommended Posts

Hi Almighty Babylon Community,

I am new to Babylon (and JavaScript in general), and am working on rendering huge tiled maps. The tiled map consists of many square cells are each cell is a 2048 x 2048 pngs (served by HTTP from the server as static files). At each moment we only need to render ~ 12 cells covering an area centered at the main entity. As the main entity moves, it might need to render another set of cells, although the new set of cells shares most part with the old set.

My current approach is to render the set of cells with a TiledGround. The TiledGround has MultiMaterial attached to it, and each material in the MultiMaterial has a texture with the corresponding png. Each time when the main entity moves to the point that we need to update the TiledGround, the original TiledGround is disposed and a new TiledGround is created.

The problem is that, I can feel the lagging at the moment when the disposing/recreation happens. I am wondering:

  1. Since the new set of cells and the old set of cells share most of cells, is there a way to incrementally update the TiledGround so that the lagging can go away?
  2. I am setting `.isblocking = false` for all the textures hoping that this can help with the lagging (it is okay to display a black cell first and have texture filled in later when it is loaded). Is this the right way to achieve the goal?
  3. Would using multiple Grounds instead TiledGround help? I am worrying that this might not the direction to go.

Thanks a lot. Below is the code I have for this task, where `syncState` is called every time when an update on the tile map happens. I would like to do this on the PG but I am not sure how I can have it interact with the cell images, sorry!

 

Thanks a lot for your time and patience :)

import * as BABYLON from "babylonjs";
import { onSnapshot } from "mobx-state-tree";

import STORE from "store";

/** Maintains the 3D entity in BABYLON for tile maps. **/
export default class TileMap {
  constructor(scene) {
    this.scene = scene;
    // Under the hood, the tile map is represented as a tiled ground,
    // with a multi material.
    this.tiledGround = null;
    this.multiMaterial = null;

    // Create the pure black material for tiles without an image.
    this.pureBlack = new BABYLON.StandardMaterial("PureBlack", this.scene);
    this.pureBlack.diffuseColor = new BABYLON.Color3.Black();

    // Connects the global state so that this becomes a view of the
    // the tileMap state.
    onSnapshot(STORE.tileMap, state => {
      if (!state.pending) {
        this.syncState(STORE.tileMap);
      }
    });
  }

  /**
   * Updates the geometric properties of the mesh to make it in sync with the 
   * given state.
   */
  syncState(state) {
    if (this.tiledGround) {
      this.tiledGround.dispose();
    }
    
    // 1. Prepare the multi material for the updated tiled ground.
    this.multiMaterial = new BABYLON.MultiMaterial(
      "TileMapMaterial", this.scene);

    for (let v = 0; v < state.rows; ++v) {
      for (let u = 0; u < state.cols; ++u) {
        const i = (state.rows - v - 1) * state.cols + u;
        if (state.tile[i]) {
          const image = new BABYLON.StandardMaterial(state.tile[i], this.scene);
          image.emissiveTexture = new BABYLON.Texture(state.tile[i], this.scene);
          image.emissiveTexture.uscale = 1;
          // Invert the v (Y) axis because the tile map has inverted Y.
          image.emissiveTexture.vscale = -1;
          // Asnchronously load the image texture.
          image.emissiveTexture.isBlocking = false;
          this.multiMaterial.subMaterials.push(image);
        } else {
          this.multiMaterial.subMaterials.push(this.pureBlack);
        }
      }
    }

    // 2. Create the tiled ground.
    this.tiledGround = new BABYLON.Mesh.CreateTiledGround(
      "TileMap", state.origin.x, -(state.origin.y + state.height),
      state.origin.x + state.width, -state.origin.y,
      { h: state.rows, w: state.cols }, // Number of tiles along x and z (Y).
      { h: 2, w: 2 }, // Precisions for each tile.
      this.scene);

    // 3. Apply the multi material to the tiled ground.
    this.tiledGround.material = this.multiMaterial;

    const numTiles = state.rows * state.cols;
    const verticesCount = this.tiledGround.getTotalVertices();
    const tileIndicesLength = this.tiledGround.getIndices().length / numTiles;
    
    // Distribute the multi material into sub meshes.
    this.tiledGround.subMeshes = [];
    for (let i = 0; i < numTiles; ++i) {
      this.tiledGround.subMeshes.push(new BABYLON.SubMesh(
        i, 0, verticesCount, tileIndicesLength * i, 
        tileIndicesLength, this.tiledGround));
    }
  }
}

 

 

Link to comment
Share on other sites

Hello and welcome!

1. Yes you can. You can provide the current mesh to the new TiledGRound constructor (last parameter). This way the new TiledGround will just update the old one and no instantiation will be required

2. Definitely!

3. Funnily it was the first idea that came to my mind when I started reading your topic. If 1. here does not work then it is clearly something you can try

 

From a more general standpoint: JavaScript only support one thread. Which means that heavy operation like creating a texture can block the rendering for a long time. Enough to notice laggyness. With that in mind, it will always be faster to update a resource already created than creating a new one

Link to comment
Share on other sites

Thanks a lot for the prompt reply, Deltakosh! These advises are very helpful!

About the the first comment on the TiledGround constructor, I am not sure whether my understanding is correct. To achieve that I think what I should do is to set `canBeRegenerated` to true, and provide the last parameter (previous TiledGround mesh), is this correct? I understand this can avoid instantiation again, but since after constructing the TiledGround, the new materials are applied and new sub-meshes are attached, what happens to the previous materials/textures/sub-meshes? Do they get disposed automatically?

Sorry for asking question about the basics. Thanks again! :)

Link to comment
Share on other sites

On 10/19/2017 at 9:18 AM, Deltakosh said:

Hello and welcome!

1. Yes you can. You can provide the current mesh to the new TiledGRound constructor (last parameter). This way the new TiledGround will just update the old one and no instantiation will be required

2. Definitely!

3. Funnily it was the first idea that came to my mind when I started reading your topic. If 1. here does not work then it is clearly something you can try

 

From a more general standpoint: JavaScript only support one thread. Which means that heavy operation like creating a texture can block the rendering for a long time. Enough to notice laggyness. With that in mind, it will always be faster to update a resource already created than creating a new one

Hi Deltakosh,

I have met the identical problem as breakds.

I have checked the document(http://doc.babylonjs.com/how_to/set_shapes#tiled-ground) and the the source code of CreateTiledGround and cannot find input parameter as current mesh.

Can I just update the parameters of tiledGround directly to reuse tiledGround?

e.g

this.tiledGround = new BABYLON.Mesh.CreateTiledGround(...)

...

// Updates the parameter.

this.tiledGround.xmin = 5

 

Thank you.

 

Link to comment
Share on other sites

1 hour ago, Deltakosh said:

Check last parameter:

http://doc.babylonjs.com/classes/3.1/tiledground#new-tiledground-classes-3-1-tiledground-id-scene-xmin-zmin-xmax-zmax-subdivisions-precision-canberegenerated-mesh-

 

You will just need to update the paramters like you did and call the constructor again 

Hi Deltakosh,

 

Do you mean I do it like

// Creating

this.tiledGround = new BABYLON.Mesh.CreateTiledGround(id, xmin, zmin, xmax, zmax, subdivisons, precision, true);

...

...

// Updating

this.tiledGround.xmin = new_xmin;

this.tiledGround.zmin = new_zmin;

this.tiledGround.xmax = new_xmax;

this.tiledGround.zmax = new_zmax;

this.tiledGround.subdivisions = new_subdivisions;

this.tiledGround = new BABYLON.Mesh.CreateTiledGround();

Link to comment
Share on other sites

Almost :)

// Creating

this.tiledGround = new BABYLON.Mesh.CreateTiledGround(id, xmin, zmin, xmax, zmax, subdivisons, precision, true);

...

...

// Updating

this.tiledGround = new BABYLON.Mesh.CreateTiledGround(id, new_xmin, new_zmin, new_xmax, new_zmax, new_subdivisions, precision, true, this.tiledGround);

 

Link to comment
Share on other sites

3 minutes ago, Deltakosh said:

Almost :)


// Creating

this.tiledGround = new BABYLON.Mesh.CreateTiledGround(id, xmin, zmin, xmax, zmax, subdivisons, precision, true);

...

...

// Updating

this.tiledGround = new BABYLON.Mesh.CreateTiledGround(id, new_xmin, new_zmin, new_xmax, new_zmax, new_subdivisions, precision, true, this.tiledGround);

 

Thank you so much Deltakosh!

I will try it.

Link to comment
Share on other sites

21 minutes ago, Deltakosh said:

Almost :)


// Creating

this.tiledGround = new BABYLON.Mesh.CreateTiledGround(id, xmin, zmin, xmax, zmax, subdivisons, precision, true);

...

...

// Updating

this.tiledGround = new BABYLON.Mesh.CreateTiledGround(id, new_xmin, new_zmin, new_xmax, new_zmax, new_subdivisions, precision, true, this.tiledGround);

 

Hi Deltakosh,

I am current using tiledground to display a map. And I would like to only display the surrounding map of a moving object, say, a car. So I would like to redraw the submeshes of this tiledground frequently.

I do it in this way:

while (...){
  this.tiledGround.subMeshes = [];

// push new submeshes.

  for (...) {

   this.tiredGround.subMeshes.push(new BABYLON.SubMesh(...))

  }

}

But I find the previous submeshes are still there!

So my question is that how to dispose previous submeshes?

 

Thank you!

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...