[SOLVED] GeoMipTerrain - Seam issues - Borderstitch

Hi guys,

Having an issue with seams in regards to turning on borderstitch with GeoMipTerrain.

When I turn on borderstitch, I get some horrible merged vertices:

The two images are actually just mirrors of the same image. Additionally, the pixels around the edge are copied to match. So this SHOULD resolve any issues with the images themselves.

When border stitch is off, it looks pretty good, but still sort of obvious where the terrains come together:

The seam is less noticeable if you bump down the block size, but that just increases the vertice count…

Any ideas? Anybody used this before successfully? Or is this a bug?

Heres the code/assests to generate two terrains and try to stitch them together.

The two heightmaps:

from pandac.PandaModules import loadPrcFileData

loadPrcFileData('', 'threading-model /Draw')
loadPrcFileData('', 'sync-video 0'); 

import direct.directbase.DirectStart
from pandac.PandaModules import *
from pandac.PandaModules import GeoMipTerrain
from direct.task import Task

class Terrain:

	def __init__(self, heightfield, scale, pos, name, minlevel):

		self.heightfield = heightfield
		self.scale = scale
		self.pos = pos
		self.name = name
		self.tile = ''
		self.minlevel = minlevel

		# Our heightmap
		self.tex = loader.loadTexture(heightfield)

		# Our colour texture
		self.ctex = loader.loadTexture(heightfield)

		# Stops edge bleeding on colour texture
		self.ctex.setWrapU(Texture.WMClamp)
		self.ctex.setWrapV(Texture.WMClamp)

		# Doesn't really help for the heightmap though...
		#self.tex.setWrapU(Texture.WMClamp)
		#self.tex.setWrapV(Texture.WMClamp)

		# Would setting these filters fix stitching?
		#self.tex.setMagfilter(Texture.FTNearest)
		#self.tex.setMinfilter(Texture.FTNearest)



class TiledTerrain:

	def __init__(self): 

		# An array of all our Terrain objects
		self.TerrainPieces = []

		# Whats the size of each tile?  Heightfield - 1
		self.terrainSize = 256
		self.updateTime = 0

		# Lets add a whole bunch of tiles
		# Heightmap / ZScale / Position / Name / Minlevel
		self.TerrainPieces.append(Terrain('1.png', 50, Vec3(0,0,0), '1', 1))
		self.TerrainPieces.append(Terrain('2.png', 50, Vec3(0,self.terrainSize,0), '2', 1))
		#self.TerrainPieces.append(Terrain('3.jpg', 50, Vec3(0,self.terrainSize*2,0), '3'))
		#self.TerrainPieces.append(Terrain('4.jpg', 50, Vec3(self.terrainSize,0,0), '4'))
		#self.TerrainPieces.append(Terrain('5.jpg', 50, Vec3(self.terrainSize,self.terrainSize,0), '5'))
		#self.TerrainPieces.append(Terrain('6.jpg', 50, Vec3(self.terrainSize,self.terrainSize*2,0), '6'))
		#self.TerrainPieces.append(Terrain('7.jpg', 50, Vec3(self.terrainSize*2,0,0), '7'))
		#self.TerrainPieces.append(Terrain('8.jpg', 50, Vec3(self.terrainSize*2,self.terrainSize,0), '8'))
		#self.TerrainPieces.append(Terrain('9.jpg', 50, Vec3(self.terrainSize*2,self.terrainSize*2,0), '9'))

		for terrain in self.TerrainPieces:
			
			t = GeoMipTerrain(terrain.name)
			t.setHeightfield(Filename(terrain.heightfield))

			# Try to stitch together borders - broken?
			# If your heightmaps have a few copied data around the edge, you get a sort of seamless appearance
			t.setBorderStitching(True)

			t.clearColorMap()
			t.getRoot().setTexture(terrain.ctex) 
			t.getRoot().setSz(terrain.scale)			

			# Only really needed if we were to call update
			#t.setFocalPoint(base.camera)

			t.setBlockSize(64)
			t.setMinLevel(terrain.minlevel)

			t.setBruteforce(1)

			# Move and rotate to get correctly positioned
			t.getRoot().setPos(terrain.pos)
			t.getRoot().setH(90)			

			# Lets show and generate the terrain
			t.getRoot().reparentTo(render)      
			t.generate()

			# Lets set two sided, so we can see whats going on
			t.getRoot().setTwoSided(True)

			# Save a reference back into our array
			terrain.tile = t

		#Perform an update every second
		#Even at high min level and framerate, there is a noticeable jolt when the update is run
		#myTask = taskMgr.doMethodLater(1, self.update, 'tickTask')   

	def update(self, task):

		for terrain in self.TerrainPieces:

			# Setting autoflatten seems to reset things shaders, textures, scaling etc
			# So if we want autoflatten, we need to reapply all those settings
			#terrain.tile.setAutoFlatten(GeoMipTerrain.AFMMedium) 
			#terrain.tile.getRoot().setSz(terrain.scale)
			#terrain.tile.getRoot().setH(90)
			#terrain.tile.getRoot().setPos(terrain.pos)
			#terrain.tile.clearColorMap()
			#terrain.tile.getRoot().setTexture(terrain.tex)
			#terrain.tile.setBlockSize(128)	

			#Need to reset the focal point		
			terrain.tile.setFocalPoint(base.camera)

			terrain.tile.update()			

		return task.again


class World:

	def __init__(self): 

		base.camLens.setNear(16)
		base.camLens.setFar(10000)
		base.setFrameRateMeter(True)

		#render.setRenderModeWireframe()

		# Create some basic lighting so we can see some shading
		dlight = DirectionalLight('dlight')
		dlight.setColor(VBase4(0.8, 0.8, 0.6, 1))
		dlnp = render.attachNewNode(dlight)
		dlnp.setHpr(0, -60, 0)
		render.setLight(dlnp)

		alight = AmbientLight('alight')
		alight.setColor(VBase4(0.1, 0.1, 0.2, 1))
		alnp = render.attachNewNode(alight)
		render.setLight(alnp)

		# Create out terrain
		# Todo - pass a variable to turn on and off update calls or bruteforce
		terrain = TiledTerrain()


if __name__ == '__main__':
	myworld = World()
	run() 

You should enable wireframe to better see what’s going on. You’ll probably need to adjust the LOD settings to have a higher quality near the edges.

Instead of using minlevel you could also consider downscaling your terrain.

Ahh, thanks, wireframe mode did help.

It seemed to help balancing blocksize and minlevel. (It looks like the stitching is almost done by block size, and if theres two many vertices per block, it’ll merge them in weird places)

A good match, 16 blocksize 3 minlevel:

To show a bad match, 64 blocksize with 2 minlevel:

To get a nice balance, blocksize of 8 and minlevel 2 of worked well (But thats fairly high res).

As suggested elsewhere, it still is pretty mandatory that you have the same edge pixels on each heightmap for it to stitch correctly.