accurate getGroundHeight algorithm for GeoMipTerrain

background:
in Panda3D 1.72 (not sure about newer version of Panda), the getElevation function doesn’t return accurate height.
collision detection may be the only way to get accurate value.
unlike collision detection, this algorithm can be called anytime/anywhere in a Python program.

before calling this function, some data must be prepared:

BS=int(blockSize) # GeoMipTerrain blockSize, must be int here
scaleX,scaleY,scaleZ=?? # terrain scale for gfx render, must be float, not int
minX,minY,maxX,maxY=?? # bound box , 4 values in world coordinate
HF=Terrain.heightfield(); tex=Texture(); tex.load(HF)
imgData=tex.getUncompressedRamImage().getData(); imgXSize=HF.getXSize()

this function assumes there is only one terrain object, and the terrain is not rotated:


def	getGroundHeight(worldPos):
	if minX <= worldPos.x < maxX and minY <= worldPos.y < maxY:
	# for simplicity, if at max XY of a terrain, don't run

		posX,posY=( worldPos.x - terrainPos.x ) / scaleX, ( worldPos.y - terrainPos.y ) / scaleY
		# pos in img

		posXint,posYint=int(posX), int(posY)

		X,Y=posX-posXint, posY-posYint		# pos in pixel

		split=abs(bool( (posX-posXint/BS*BS)/BS > 0.5)-bool( (posY-posYint/BS*BS)/BS > 0.5 ))
		# direction of splitting a square

		rows=imgXSize * posYint

		if split:
			if X>1-Y: upper=1; c=posXint + rows + 1 ; b = c + imgXSize ; a=b-1
			else: upper=0; a=posXint + rows ; b , c = a+1 , a + imgXSize
		else:
			if Y>X: upper=1; c=posXint + rows ; a = c + imgXSize ; b = a+1
			else: upper=0; a=posXint + rows ; b= a+1 ; c= b + imgXSize

		d=[ord(imgData[a]),ord(imgData[b]),ord(imgData[c])]; d+=[d[1]-d[0], d[2]-d[1-abs(split-upper)]]

		return scaleZ * (d[3] * X + d[0] + d[4] * (upper - Y * (upper * 2 - 1))) / 255 + terrainPos.z

	return 'no ground'

getElevation returns the result for a “perfect” rendering of the terrain, ie. as if the terrain is rendered at the highest detail setting at the point at which it is sampled. This is usually close enough to what is desired. What does your function do differently, exactly - does it sample in a way that is closer to the actual generated geometry?

i remember when i tested Panda1.72 using getElevation to limit my camera from droping below ground, when i moved the camera (i use custom code to drive the cam, not Panda built-in driver) along the terrain, the camera suddenly moved up at some point, leaving the ground. i also remember other users mentioned about “stair-case” like movement if they use getElevation instead of collision traverser.
that was because getElevation uses surrounding vertices (i don’t remember how many vertices) and linearly interpolate to get the height value. but the actual geometry is formed by triangles which are defined by 3 point. to get accurate height on a triangle, we should use the triangle which a vertical line (defined by XY pos) intersect with.
the algorithm i posted does find the actual triangle then use the 3 points and linearly interpolate. the result is the actual Z pos in the triangle given a certain XY pos. using this can make the camera always stick to the rendered ground, so can it make the roaming ralph stick to the ground, and other uses.
because GeomipTerrain divides the quads in different directions (into triangles) at different pos of a block, the algorithm takes several lines of code to decide which 3 points form the target triangle. hence the “split, upper” variables in the function posted.