Flat Terrain Fixes

I have generated a heightfield map using noise and generated a terrain by Panda 3D but it is completely flat. What am I doing wrong?


from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from direct.actor.Actor import Actor
from panda3d.core import AmbientLight
from panda3d.core import Vec4
from panda3d.core import DirectionalLight,PointLight,PNMImage
from panda3d.core import *
from panda3d.core import GeoMipTerrain,Texture
import os
import noise
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from perlin_noise import PerlinNoise
import cv2


width = 256
height = 256
scale = 0.1 
octaves = 6000
persistence = int(input("Please enter the seed: "))


terrain = np.zeros((height, width))


noise = PerlinNoise(octaves=octaves, seed=persistence)
for y in range(height):
    for x in range(width):
        terrain[y][x] = noise([x * scale, y * scale])


terrain = (terrain - np.min(terrain)) / (np.max(terrain) - np.min(terrain))

plt.imshow(terrain, cmap='gray')
plt.axis("off")


plt.savefig("path to\\HEIGHTFIELD.jpg")

class MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        terrain = GeoMipTerrain("myDynamicTerrain")
        heightfield_image="path//to//heightfield.jpg"
        image=PNMImage(513,513)
        image.write(Filename(heightfield_image))
        grassTexture = self.loader.loadTexture("grass.png")
        grassTexture.setWrapU(Texture.WM_repeat)
        grassTexture.setWrapV(Texture.WM_repeat)
        ts = TextureStage.getDefault()
        terrain.getRoot().setTexture(ts, grassTexture)
        terrain.getRoot().setTexScale(ts, 50)
        
        
        terrain.setBlockSize(32)
        terrain.setNear(40)
        terrain.setFar(100)
        terrain.setFocalPoint(base.camera)
        terrain.setBruteforce(True)
        self.root = terrain.getRoot()
        self.root.reparentTo(self.render)
        self.root.setSz(60)
        
        
        terrain.setHeightfield(image)
        terrain.generate()
        

        def updateTask(task):
            terrain.update()
            return task.cont

        self.taskMgr.add(updateTask, "update")

app = MyApp()
app.run()

Hi, welcome to the forum ! :slight_smile:

In the given code, it seems that you create a blank (or black) PNMImage and write it to disk :

heightfield_image="path//to//heightfield.jpg"
image=PNMImage(513,513)
image.write(Filename(heightfield_image))

And you then load this black image as the heightmap :

terrain.setHeightfield(image)
terrain.generate()

So this is not loading the first noisemap you created and saved with :

plt.savefig("path to\\HEIGHTFIELD.jpg")

You should be able to directly specify the path to the desired image while setting the GeoMipTerrain like so :

terrain.setHeightfield("path to\\HEIGHTFIELD.jpg")

This allow you to get rid of the first part of code i’ve cited (the setting of the PNMImage), as the HEIGHTMAP.png will be loaded on the fly, if it’s the way you choose to go.

If I remove this

image=PNMImage(513,513)
image.write(Filename(heightfield_image))

and directly specifying the heightfield image I am getting this error.

:grutil(error): Failed to load heightfield image C:\Users\DELL\OneDrive\Desktop\PANDA 3D LEARNING\HEIGHTFIELD.jpg!
:grutil(error): No valid heightfield image has been set!

I got the answer as

from direct.showbase.ShowBase import ShowBase
from panda3d.core import GeoMipTerrain, Texture, Filename, TextureStage
from perlin_noise import PerlinNoise
import numpy as np
import matplotlib.pyplot as plt

class MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        width = 256
        height = 256
        scale = 0.1
        octaves = 6000
        persistence = int(input("Please enter the seed: "))

        terrain_data = np.zeros((height, width))

        noise = PerlinNoise(octaves=octaves, seed=persistence)
        for y in range(height):
            for x in range(width):
                terrain_data[y][x] = noise([x * scale, y * scale])

        terrain_data = (terrain_data - np.min(terrain_data)) / (np.max(terrain_data) - np.min(terrain_data))

        plt.imshow(terrain_data, cmap='gray')
        plt.axis("off")
        plt.savefig("HEIGHTFIELD.jpg")

        terrain = GeoMipTerrain("myDynamicTerrain")
        heightfield_image = "HEIGHTFIELD.jpg"

        grassTexture = self.loader.loadTexture("grass.png")
        grassTexture.setWrapU(Texture.WM_repeat)
        grassTexture.setWrapV(Texture.WM_repeat)
        ts = TextureStage.getDefault()
        terrain.getRoot().setTexture(ts, grassTexture)
        terrain.getRoot().setTexScale(ts, 50)

        terrain.setBlockSize(32)
        terrain.setNear(40)
        terrain.setFar(100)
        terrain.setFocalPoint(self.camera)
        terrain.setBruteforce(True)
        self.root = terrain.getRoot()
        self.root.reparentTo(self.render)
        self.root.setSz(60)

        terrain.setHeightfield(heightfield_image)
        terrain.generate()

        def updateTask(task):
            terrain.update()
            return task.cont

        self.taskMgr.add(updateTask, "update")

app = MyApp()
app.run()

If anything, panda has its own noise generators.

https://docs.panda3d.org/1.10/python/reference/panda3d.core.StackedPerlinNoise2#stackedperlinnoise2

from direct.showbase.ShowBase import ShowBase
from panda3d.core import PNMImage, GeoMipTerrain, StackedPerlinNoise2

class TestGeoMipTerrain(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        perlin = StackedPerlinNoise2(0.4, 0.5)

        noise = PNMImage(1025, 1025)
        noise.perlin_noise_fill(perlin)

        self.terrain = GeoMipTerrain("DynamicTerrain")
        self.terrain.set_heightfield(noise)

        self.terrain.set_block_size(32)
        self.terrain.set_near(40)
        self.terrain.set_far(100)
        self.terrain.set_focal_point(base.camera)

        root = self.terrain.get_root()
        root.reparent_to(render)
        root.set_sz(100)

        self.terrain.generate()

        taskMgr.add(self.terrain_update, "update")

    def terrain_update(self, task):
        self.terrain.update()
        return task.cont

game = TestGeoMipTerrain()
game.run()
2 Likes