What should be the generic solution to plot a heatmap on ground?

Hey, I’m stuck with this again with plotting the heatmap. So, I already asked this question previously here Previous Question

But, I’m trying to find out the generic solution so that whatever sphere points we create it should render the heatmap correctly at the sphere point location regardless the ground size. So, previously the guy suggested me a workaround trick to change the ground size and position. I’m using simpleheatmap.js to plot the heatmap.

So, here is my current playground where I’m testing - Heatmap

As we can see it plots the heatmaps at the top left corner, instead it should be plotted at the sphere points location. The heatmap is plotted only when I pass the x, and z coordinates divided by 10 otherwise it doesn’t plot. I don’t understand why it is happening.

What I’ve tried so far -

  • Tried to convert the heatmap point to ground coordinates
var worldPoint = new Vector3(x, 0, z);
var localPoint = Vector3.TransformCoordinates(worldPoint, ground.getWorldMatrix());

but got the same points as I have.

  • Tried to convert the sphere vector3 points to screen points but no luck as it changes as we drag/move
var boundingBox = sphere.getBoundingInfo().boundingBox;
          // Get the center of the bounding box
          var center = boundingBox.centerWorld;
          var coordinates = Vector3.Project(center,
              Matrix.Identity(),
              scene.getTransformMatrix(),
              camera.viewport.toGlobal(
                  engine.getRenderWidth(),
                  engine.getRenderHeight(),
              )
          );
  • If you see, I’ve listened to the scene.onPointerUp event and tried to plot the point manually on the sphere point, if you uncomment and check it does but I didn’t understand cause the pickup points are different than the actual points
scene.onPointerUp = function(p,pick){
         
        var pickResult = scene.pick(
            scene.pointerX,
            scene.pointerY
        );
        
        var { x, y } = pickResult.getTextureCoordinates();     

        console.log(x, y)
        
        try {                
            const heatmapVal = randomIntFromInterval(1,100)

            dataset.push([x, y, Math.round( heatmapVal / 10 )]);
            const heat = simpleheat('babylonjs-container', textureContext).data(dataset).max(60);
            heat.draw(0.5);                    
        } catch (error) {
            console.log('error', error)
        }
    };  
  • So, again what I thought is to manually fire the pointer-up event by passing the 2d points and listen to the event. But that also doesn’t work as the event is not dispatching. Not understand what I’m doing wrong.
  var event = new PointerEvent('pointerup', {
                pointerId: 1, // Use a unique pointer ID
                pointerType: 'pointerup',
                clientX: x, // Set the clientX position of the pointer
                clientY: z, // Set the clientY position of the pointer
            });
            
            // Dispatch the event on the canvas element
            canvas.dispatchEvent(event);
            scene.onPointerObservable.notifyObservers(event);

I’ve been searching and reading a lot for 2 days but didn’t find anything which can help me to go ahead. Please correct me on what I’m doing wrong or what is the better way to plot the heatmap on sphere location.

It seems your texture is not generated correctly? It is:

image

If the colored area should not be in the top left corner, then it’s a problem with the simpleheat class I guess?

@Evgeni_Popov, I think I agree with you that the texture is not generated correctly cause even If I give bigger values to x and y of heatmap data it doesn’t plot the heatmap. I tried giving bigger values like 150, 75, 255, and 350 to check but it doesn’t plot. Even though the texture size is 512 and diffuse with ground material.

 textureGround = new BABYLON.DynamicTexture("dynamic texture", 512, scene);   
    textureContext = textureGround.getContext();          
    
    var materialGround = new BABYLON.StandardMaterial("Mat", scene);    				
    materialGround.diffuseTexture = textureGround;
    ground.material = materialGround;

How can I debug my texture that to know what’s the problem?

Hey, @Evgeni_Popov, I’m able to achieve what I want, finally! Here is the playground - Achieved Result

So, as you said -

If the colored area should not be in the top left corner, then it’s a problem with the simpleheat class I guess?

I’ve checked the simple heat class and tried to play around with the draw method

// draw a grayscale heatmap by putting a blurred circle at each data point
            for (var i = 0, len = this._data.length, p; i < len; i++) {
                p = this._data[i];
                var centerX = p[0] * size.width;
                var centerY = size.height - p[1] * size.height;
                // console.log(p, centerX, centerY, this._r)
                ctx.globalAlpha = Math.min(Math.max(p[2] / this._max, minOpacity === undefined ? 0.05 : minOpacity), 1);                
                ctx.drawImage(this._circle, centerX - this._r, centerY - this._r);
            }

So, what I did is first I convert the world point to screen point using this -

    const sphere = scene.getNodeById(property);
                var boundingBox = sphere.getBoundingInfo().boundingBox;
                // Get the center of the bounding box
                var center = boundingBox.centerWorld;
                var coordinates = Vector3.Project(sphere.position,
                    Matrix.Identity(),
                    scene.getTransformMatrix(),
                    camera.viewport.toGlobal(
                        engine.getRenderWidth(),
                        engine.getRenderHeight(),
                    )
                );

And I’ve passed the x and y to the heatmap data and did some change on draw method -

// draw a grayscale heatmap by putting a blurred circle at each data point
            for (var i = 0, len = this._data.length, p; i < len; i++) {
                p = this._data[i];
                var centerX = p[0] * size.width;
                var centerY = size.height - p[1] * size.height;
                // console.log(p, centerX, centerY, this._r)
                ctx.globalAlpha = Math.min(Math.max(p[2] / this._max, minOpacity === undefined ? 0.05 : minOpacity), 1);                
                ctx.drawImage(this._circle, p[0], p[1]);
            }

So, as I played around with this and tried to check with some points first only so I realized the texture is generated correctly. Also, the draw method of the simple heat class is also working fine.

So, as continuing with some calculations and looking into scene.onPointerUp event there we have this var { x, y } = pickResult.getTextureCoordinates(); which gives the texture cordinates and that’s why if I manually ploting the heatmap on pointer up it correctly plots so, tried to find the intersect point again between spheres and the ground using Ray and scene.pickWithRay like this -

 var ray = new Ray(sphere.position, new Vector3(0, -1, 0));
            // Find the intersection point between the ray and the ground plane
            var pickInfo = scene.pickWithRay(ray, function(mesh) {
                return mesh.name === "ground";
            });
if (pickInfo.hit) {
                console.log('yes')                
                dataset.push([pickInfo.getTextureCoordinates().x, pickInfo.getTextureCoordinates().y, Math.round( heatmapVal / 10 )]);
            }

So, I got the pickInfo.getTextureCoordinates() from this and then I pass this to heatmap data that plot correctly how I want.

One thing I didn’t understand now for my further work or the actual case that I’m trying to achieve is I want to create the heightmap via passing this heatmap texture as an image on CreateGroundFromHeightMap, but this textureGround.url giving me the null while passing it to that function also as I console.log.

console.log(textureGround.url)
 var ground = MeshBuilder.CreateGroundFromHeightMap("gdhm", textureGround.url, {
            width:3, 
            height:3, 
            subdivisions: 500, 
            maxHeight: -1
        }, scene, false);

Am I doing something wrong or accessing the textureGround.url at wrong place or wrong time. My final usecase is this only to create the heightmap dynamically using the data we plot as sphere point and create heatmap on base of that so we can pass that to CreateGroundFromHeightMap function to create a heightmap(kind of a continent).

Let me know what you think. Thanks!!

@here, well, I’m able to get the URL also, but not sure if it is correct or not. That’s the Final Playground, and the heightmap is rendering like this -

I’m not sure that the base64url that gives the image is correct or not cause as I’m seeing the it looks like this -

The discretized heat is not there, it is something like grayscale. It should be like the below, so on the rendered heatmap I can have some levels/subdivisions like we have in heightmap example playground here Example from Doc

This code in draw (which colorizes the texture):

createImageBitmap(colored).then((result) => {
    ctx.drawImage(result, 0, 0);
    textureGround.update();
});

is asynchronous, so when you do:

heat.draw(0.5); 
var ground = new BABYLON.MeshBuilder.CreateGroundFromHeightMap(
    "gdhm", textureGround.getContext().canvas.toDataURL(), {

textureGround.getContext().canvas.toDataURL() gets executed before the colorization takes place. You should wait for the code to be executed:

1 Like

Oh, thanks! I got it!