panda3d how create planet with relief ?

Hello, i starting with panda3d and my goal would be to create a planet.

may be i need to create sphere with heightmap, no ?
i read this documentation related this :
panda3d.org/manual/index.ph … sics_World

i can create sphere with earth attraction, but i can not to add relief with TerrainMesh
panda3d.org/manual/index.ph … errainMesh

how can i instanciate terrain_node with my sphere, or it’s possible to create sphere terrain_node and apply ode physic ?

thanks for advance for your help.

From what I (think I) understand, heightmap implementations in Panda are designed for 2D surfaces only, including ShaderTerrainMesh at this point in time. For spherical terrains you may have to write your own code (that is what I ended up doing so far, anyway).

You have to decide on a mapping method though to translate 2D UV coordinates from the textures, including the heightmap, into a spherical surface. Each method has pros and cons and don’t forget what kind of UVs you will be having to work with, with whichever method you choose.

Most folks seem to recommend the spherified cube / cube sphere method. It is said to have the advantages of minimally distorted texture mappings that are relatively easy to work with in 2D texture space (emphasis on ‘relatively’). Each of the “square” sides can be implemented as a quad tree for culling as well.

For physics geometry, I think you have to use triangle meshes for collision detection. No other method is flexible enough, as far as I can tell. You should also think carefully before choosing ODE over Bullet physics. Bullet is significantly faster at certain types of collision tests and is more actively developed and (in my limited experience) better maintained. But in the past, others have said ODE has more thorough documentation (not sure if that is still true though).

thank you for your reply.

thin, I thought panda3d could make me a spherical plane natively :cry:
I’m starting with panda3d and i have no idea how to do it :neutral_face:

For physic engine, i can use Bullet, thank you for your advice

Looking at the documentation of Bullet, for handle collision in a ShaderTerrainMesh i must use BulletHeightfieldShape.
i found example here :
[BulletHeightfieldShape problems with latest panda builds)

Well BulletHeightFieldShape I believe will only scale terrain on one axis, so it may not be a perfect solution for a spherical surface like a planet’s. That is, I do not think it is designed to “wrap around” the curvature of a planet without modification or a workaround.

If you find it cannot produce a spherical enough surface for your project’s needs, you might consider using a BulletTriangleMeshShape instead.

ok.
I’ve been searching for 1 week, but I can not create a sphere with relief but i found maybe librairy for this.

I found library or create procedural sphere :
bitbucket.org/soos/panda-proced … cedural.py

i have sphere but relief not appear correctly, this is my code :

from __future__ import print_function

import os
import sys
from panda3d.core import Vec3, load_prc_file_data, ShaderTerrainMesh
from direct.showbase.ShowBase import ShowBase


from panda3d.core import *
from pandac.PandaModules import *
from panda3d.bullet import BulletRigidBodyNode  
from panda3d.bullet import BulletTriangleMesh
from panda3d.bullet import BulletTriangleMeshShape
from panda3d.bullet import BulletDebugNode 
from panda3d.bullet import BulletWorld

from pandac.PandaModules import *
from math import *

def empty(prefix, points = False):
	path = NodePath(prefix + '_path')
	node = GeomNode(prefix + '_node')
	path.attachNewNode(node)

	gvd = GeomVertexData('gvd', GeomVertexFormat.getV3(), Geom.UHStatic)
	geom = Geom(gvd)
	gvw = GeomVertexWriter(gvd, 'vertex')
	node.addGeom(geom)
	if points:
		prim = GeomPoints(Geom.UHStatic)
	else:
		prim = GeomTriangles(Geom.UHStatic)
	return (gvw, prim, geom, path)

def IcoSphere(radius, subdivisions):
	(gvw, prim, geom, path) = empty('ico')

	verts = []
	
	phi = .5*(1.+sqrt(5.))
	invnorm = 1/sqrt(phi*phi+1)

	verts.append(Vec3(-1,  phi, 0) * invnorm)   #0
	verts.append(Vec3( 1,  phi, 0) * invnorm)   #1
	verts.append(Vec3(0,   1,  -phi) * invnorm) #2
	verts.append(Vec3(0,   1,   phi) * invnorm) #3
	verts.append(Vec3(-phi,0,  -1) * invnorm)   #4
	verts.append(Vec3(-phi,0,   1) * invnorm)   #5
	verts.append(Vec3( phi,0,  -1) * invnorm)   #6
	verts.append(Vec3( phi,0,   1) * invnorm)   #7
	verts.append(Vec3(0,   -1, -phi) * invnorm) #8
	verts.append(Vec3(0,   -1,  phi) * invnorm) #9
	verts.append(Vec3(-1,  -phi,0) * invnorm)   #10
	verts.append(Vec3( 1,  -phi,0) * invnorm)   #11

	faces = [
		0,1,2,
		0,3,1,
		0,4,5,
		1,7,6,
		1,6,2,
		1,3,7,
		0,2,4,
		0,5,3,
		2,6,8,
		2,8,4,
		3,5,9,
		3,9,7,
		11,6,7,
		10,5,4,
		10,4,8,
		10,9,5,
		11,8,6,
		11,7,9,
		10,8,11,
		10,11,9
	]

	size = 60

	# Step 2 : tessellate
	for subdivision in range(0,subdivisions):
		size*=4;
		newFaces = []
		for i in range(0,int(size/12)):
			i1 = faces[i*3]
			i2 = faces[i*3+1]
			i3 = faces[i*3+2]
			i12 = len(verts)
			i23 = i12+1
			i13 = i12+2
			v1 = verts[i1]
			v2 = verts[i2]
			v3 = verts[i3]
			# make 1 vertice at the center of each edge and project it onto the sphere
			vt = v1+v2
			vt.normalize()
			verts.append(vt)
			vt = v2+v3
			vt.normalize()
			verts.append(vt)
			vt = v1+v3
			vt.normalize()
			verts.append(vt)
			# now recreate indices
			newFaces.append(i1)
			newFaces.append(i12)
			newFaces.append(i13)
			newFaces.append(i2)
			newFaces.append(i23)
			newFaces.append(i12)
			newFaces.append(i3)
			newFaces.append(i13)
			newFaces.append(i23)
			newFaces.append(i12)
			newFaces.append(i23)
			newFaces.append(i13)
		faces = newFaces
	
	for i in range(0,len(verts)):
		gvw.addData3f(VBase3(verts[i]))
	for i in range(0, int(len(faces)/3)):
		prim.addVertices(faces[i*3],faces[i*3+1],faces[i*3+2])

	prim.closePrimitive()
	geom.addPrimitive(prim)

	data = []
	data.append(path)
	data.append(prim)
	data.append(geom)
	return data


class Application(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        base.cam.setPos(0, -50, 0)
        
        debugNode = BulletDebugNode('Debug')   
        debugNP = render.attachNewNode(debugNode)   
        debugNP.show()

        world = BulletWorld()     
        world.setDebugNode(debugNP.node())
        world.setGravity(Vec3(0, 0, -9.81))

        planete = IcoSphere(radius=1, subdivisions=3)
        model = planete[0]
        model.reparentTo(self.render)
        
        heightfield = loader.loadTexture("heightfield.png")
        
        terrain_node = ShaderTerrainMesh()
        terrain_node.heightfield = heightfield
        terrain_node.target_triangle_width = 6.0
        terrain_node.generate()

        terrain_n = model.attach_new_node(terrain_node)

        
        mesh = BulletTriangleMesh()
        mesh.addGeom(planete[2])
        shape = BulletTriangleMeshShape(mesh, dynamic = False)
        
        nodeBox = BulletRigidBodyNode('Panda')
        nodeBox.addShape(shape)
        nodeBox.setMass(0)
        nodeControlBox = render.attachNewNode(nodeBox)
        world.attachRigidBody(nodeBox)
        
Application().run()

can you help me ?

Well the code you have there uses a default ShaderTerrainMesh for the rendered mesh. I don’t have any direct experience with ShaderTerrainMesh but it seems to be designed for adding relief to a flat surface, not a spherical surface like that of a planet.

So you should get very strange looking results by using ShaderTerrainMesh to represent a planet. Unless you fairly heavily modified it to handle relief mapping of a spherical surface, as it’s creator, Tobias Springer, seemed to indicate in that post I linked to earlier. But the code you posted appears to make no modifications to it (my guess is whoever wrote it, never finished it).

I think you will have to write your own planet drawing code from scratch in python (or c++ if you prefer) because no “out of the box” solution yet exists for Panda3D to do this for you.

ok, i think that i found a solution.
I create sphere with lot of terrain !

i have question, how can i change position and rotation of a terrain ?
In my code, i want to generate 2 terrains for example.

heightmap.jpg :
img42.com/MnHpn

from panda3d.core import *
from direct.showbase.ShowBase import ShowBase

class MyApp(ShowBase):

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

        #terrain1
        self.terrain = GeoMipTerrain("map_test")
        self.terrain.setHeightfield("resources/heightmap.jpg")
        
        self.terrain.setBlockSize(32)
        self.terrain.setNear(40)
        self.terrain.setFar(100)
        self.terrain.setFocalPoint(base.camera)
        

        self.terrain.getRoot().reparentTo(render)

        terrainScale = 0.5
        self.terrain.getRoot().setScale(terrainScale, terrainScale, terrainScale*300)

        self.terrain.setBruteforce(True)
        self.terrain.generate()

        tex = loader.loadTexture('resources/heightmap.jpg')
        self.terrain.getRoot().setTexture(tex, 1)
        self.terrain.getRoot().setTwoSided(True)
        
        
        #terrain2
        self.terrain2 = GeoMipTerrain("map_test")
        self.terrain2.setHeightfield("resources/heightmap.jpg")
        
        self.terrain2.setBlockSize(32)
        self.terrain2.setNear(40)
        self.terrain2.setFar(100)
        

        self.terrain2.getRoot().reparentTo(render)

        terrainScale = 0.5
        self.terrain2.getRoot().setScale(terrainScale, terrainScale, terrainScale*300)

        self.terrain2.setBruteforce(True)
        self.terrain2.generate()

        tex = loader.loadTexture('resources/heightmap.jpg')
        self.terrain2.getRoot().setTexture(tex, 1)
        self.terrain2.getRoot().setTwoSided(True)


app = MyApp()
app.run()

You need to store the returned NodePath you get when you call get_root() on your terrain to easily change its position and rotation.

So instead of this:

    self.terrain.getRoot().reparentTo(render)

Do the following:

    self.terrain_node = self.terrain.get_root()
    self.terrain_node.reparent_to(render)
    self.terrain_node.set_pos(x, y, z)
    self.terrain_node.set_hpr(heading, pitch, roll)

deus thank you for your help !

i have other problem, i can not instantiate 2 terrain why ?

from panda3d.core import *
from direct.showbase.ShowBase import ShowBase


def Createterrain(id):
    terrain = GeoMipTerrain("map_test"+str(id))
    terrain.setHeightfield("resources/heightmap.jpg")
    
    terrain.setBlockSize(32)
    terrain.setNear(40)
    terrain.setFar(100)

    terrainScale = 0.5
    terrain.getRoot().setScale(terrainScale, terrainScale, terrainScale*300)

    terrain.setBruteforce(True)
    terrain.generate()

    tex = loader.loadTexture('resources/heightmap.jpg')
    terrain.getRoot().setTexture(tex, 1)
    terrain.getRoot().setTwoSided(True)
    return terrain
    
    
    
class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)
        
        position_list=[[0,0,-500], [0,0,0]]
        rotation_list=[[0,0,0], [0,90,0]]
        terrain_list=[]

        for i in range(0,1):
            terrain_list.append(Createterrain(i))
            
            
        for i in range(0,1):

            self.terrain_node = terrain_list[i].get_root()
            self.terrain_node.reparent_to(render)
            self.terrain_node.set_pos(position_list[i][0], position_list[i][1], position_list[i][2])
            self.terrain_node.set_hpr(rotation_list[i][0], rotation_list[i][1], rotation_list[i][2])


app = MyApp()
app.run()

Because the second parameter to range() is not an inclusive boundary. range(0, 1) generates a sequence with only the number 0:

>>> range(0, 1)
[0]

Did you mean range(2) ?

yes sorry, i don’t see my error… :blush:

i finish to create cube with relief but it’s not same sphere (i see that for create planete maybe easy solution is to create cube and transform to sphere)

have you suggestion for help me ?

from panda3d.core import *
from direct.showbase.ShowBase import ShowBase
import direct.directbase.DirectStart
from panda3d.core import Vec3
from panda3d.bullet import BulletWorld
from panda3d.bullet import BulletSphereShape
from panda3d.bullet import BulletRigidBodyNode
from panda3d.bullet import BulletBoxShape


def Createterrain(id):
    terrain = GeoMipTerrain("map_test"+str(id))
    terrain.setHeightfield("resources/heightmap3.jpg")
    
    terrain.setBlockSize(32)
    terrain.setNear(40)
    terrain.setFar(100)

    terrainScale = 0.5
    terrain.getRoot().setScale(terrainScale, terrainScale, terrainScale*300)

    terrain.setBruteforce(True)
    terrain.generate()

    tex = loader.loadTexture('resources/heightmap3.jpg')
    terrain.getRoot().setTexture(tex, 1)
    terrain.getRoot().setTwoSided(True)
    return terrain
    
    
    
class MyApp(ShowBase):

    def __init__(self):
        #ShowBase.__init__(self)
        
        base.cam.setPos(0, 0, 1500)
        base.cam.lookAt(0, 0, 0)
        
        
        # World
        world = BulletWorld()
        world.setGravity(Vec3(0, 0, -9.81))
         
        # Plane
        shape = BulletSphereShape(512)
        
        node = BulletRigidBodyNode('Ground')
        node.addShape(shape)
        np = render.attachNewNode(node)
        world.attachRigidBody(node)
        np.setPos(0, 0, 0)

        number_of_face=6
        position_list=[[0,0,500], [0,0,0], [500,0,0], [0,500,500], [0,500,0], [500,0,0]]
        rotation_list=[[0,0,0], [0,90,0], [0,0,180], [0,-90,0], [90,90,180], [90,90,0]]
        terrain_list=[]

        #Create planete
        for i in range(0, number_of_face):
            terrain_list.append(Createterrain(i))
            
            
        for i in range(0, number_of_face):

            terrain_node = terrain_list[i].get_root()
            terrain_node.reparent_to(render)
            terrain_node.set_pos(position_list[i][0], position_list[i][1], position_list[i][2])
            terrain_node.set_hpr(rotation_list[i][0], rotation_list[i][1], rotation_list[i][2])
            
            
            
        # Box
        shape = BulletBoxShape(Vec3(0.5, 0.5, 0.5))
        node = BulletRigidBodyNode('Box')
        node.setMass(1.0)
        node.addShape(shape)
        np = render.attachNewNode(node)
        np.setPos(0, 0, 1400)
        np.setScale(10)
        world.attachRigidBody(node)
        model = loader.loadModel('models/box.egg')
        model.flattenLight()
        model.reparentTo(np)
            

         
        # Update
        def update(task):
          dt = globalClock.getDt()
          world.doPhysics(dt)
          return task.cont
         
        taskMgr.add(update, 'update')
        run()
        


app = MyApp()
app.run()

I do not know if the subject interests someone but I found better solution :slight_smile:
I still do not see how to generate a sphere with relief but i found other solution for generate terrain, solution more performante !

import direct.directbase.DirectStart
from pandac.PandaModules import *

terrain = GeoMipTerrain("mySimpleTerrain")
terrain.setHeightfield(Filename("models/heightmap.jpg"))
terrain.getRoot().reparentTo(render)
terrain.getRoot().setSz(600)
terrain.generate()

# Add a task to keep updating the terrain
def update(task):
    terrain.update()
    return task.cont
taskMgr.add(update, "update")


light = DirectionalLight('sun')
light.setColor(VBase4(1.0, 1.0, 1.0, 1))
lightnode = render.attachNewNode(light)
lightnode.setHpr(0, -70, 0)
render.setLight(lightnode)

base.accept('w', base.toggleWireframe)

run()

terrain genration is more lot of speed.

The engine seems unable to generate a sphere with refiel but I think it should be possible to transform a cube into a sphere with a heightmap.

thank you huitre39 for your solution.

but you have same problem that me.

Again, the problem you both are having is that you are trying to use features designed to represent a relatively flat terrain surface, to instead represent a whole spherical planet. GeoMipTerrain and ShaderTerrainMesh were not designed to represent whole planets.

To find a solution that would work better for you, it would help if you were each more clear about what exactly you are trying to do with a planet.
Is the planet just a prop that sits in the sky and the player only sees one side of and never get’s close to?
Is the planet a life-sized, thousands-of-kilometers-wide environment the player will walk around on?
Or something in between? (If so, please specify.)

i want a real planet with thousands-of-kilometers-wide environment.

=> In this environnement i want that player can walk or run with ralph for example (with detect colision with relief).
=> I want to apply textures in my terain
=> I want to can add water and 3d objects in this planet (grass, tree…)
=> i want to have a “atmosphere” with 3d cloud (like in flat surface)
=> I want this planet to revolve around a star

I know how to do this on a flat environment (with excellent example can be found in this forum), but I have no idea how to do on a sphere surface.

I would just like a simple demonstration with just a planet and relief/mountain (and may be how to apply water in sphere surface ?), with collision (i know that bullet easy support collision with terrain in flat surface, but i don’t no in sphere surface)

On a cosmic scale a Earth like planet is as smooth as a billiard ball so you might as well render it without any displacement. From a human perspective the curvature of a planet is so small that you can render the terrain flat.
It’s not practical to have details smaller then a pixel and 100 000 000 larger then what could be seen on the screen.
If you need a planet as seen from orbit - make a sphere and add some bump map to it (normal, parallax occlusion, etc), for anything walking on the surface - a flat terrain with a heightmap displacement is the best option.

Using 6 glued ‘heightmaps terrains’ to make a planet is asking for trouble, I’d advise against it.

Ok, but how can I manage the transition between the orbit and the surface of the planet to be realistic ?

Just like any other Level Of Detail setup - fade out the planet and fade in the terrain, just like they do it in No Man’s Sky: youtu.be/h0WtN6cyYLU?t=58s

I misspoke
my question is how one gives the illusion of having a spherical surface with flat terrain ? because you say :

a flat terrain with a heightmap displacement is the best option.

i know how to create lod terrain in panda, like this :

from panda3d.core import Vec3, load_prc_file_data, ShaderTerrainMesh
from direct.showbase.ShowBase import ShowBase


from panda3d.core import *


class Application(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        terrain = GeoMipTerrain("myDynamicTerrain")
        terrain.setHeightfield("yourHeightField.jpg")

        terrain.setBlockSize(32)
        terrain.setNear(40)
        terrain.setFar(100)
        terrain.setFocalPoint(base.camera)

        root = terrain.getRoot()
        root.reparentTo(render)
        root.setSz(100)

        terrain.generate()

        def updateTask(task):
          terrain.update()
          return task.cont
        taskMgr.add(updateTask, "update")

        
Application().run()

but how can we put this flat terrain in a sphere so that it is realistic?

The feature set you are describing is still cutting edge technology, which only a handful of games have or are working towards developing. Building such technology with Panda is surely technically possible, but you will have to write most of it yourself, using knowledge that probably doesn’t yet exist in the Panda community.

You will have to use only or primarily the lower level features of panda to Procedurally Generate Content. You might in the end also have to write some parts of it in C++ to get fast enough performance.