Code archives/3D Graphics - Mesh/Mesh LoadTerrain

This code has been declared by its author to be Public Domain code.

Download source code

Mesh LoadTerrain by Ross C
(Posted 21 years ago)
This function is exactly like the LoadTerrain command in blitz, except it uses a blitz mesh instead of a blitz terrain. There's no LOD, but it should be alot faster in many circumstances. Same syntax as the Blitz function. Also centres the mesh, unlike the original function.

EDIT<<< I have redone this routine, as it had many errors and inaccurate workings:

- It will now properly apply the heightmap without a strange seem appearing.
- I have added the functionality to adjust the Y scale.
- You can load three textures, and state flags, and they will be applied.
- The UV mapping has been fixed now.
- The function returns the mesh back, which i don't think it did before?

Global terrain = load_terrain("heightmap.bmp",0.5); load the terrain half scale (since Red pixel colours highest value is 255, the highest value will now be 128.)


To load a texture and apply it to the terrain.

Global terrain = load_terrain("heightmap.bmp",0.5,"terrain_texture.bmp",1+16+32); load the terrain, and apply a texture, with the clamping flags and colour flag. Note, you must clamp the texture if it encompasses the whole mesh, or it's edges will bleed to the other side.
; Y position is calculated by: scale * pixel colour on loaded image.

; PLEASE NOTE: You must clamp the texture your applying if it is spread across the whole mesh.
;              If you don't, it will bleed across to the opposite side. Clamp with flags 16 + 32
Function load_terrain(heightmap$,y_scale#=1,texture1$="",t_flag1 = 1,texture2$="",t_flag2 = 1,texture3$="",t_flag3 = 1)


	temp = LoadImage(heightmap$)
	If temp = 0 Then Return 0
	
	x = ImageWidth(temp)
	DebugLog(" image width = "+x)
	y = ImageHeight(temp)

	mesh = CreateMesh()
	surf = CreateSurface(mesh)

	For ly = 0 To y-1 ; your doing -1 because the image pixels start at 1 and the vertex
					  ; indices start at 0
		For lx = 0 To x-1
			AddVertex surf,lx , 0, -ly, Float(lx)/(x-1), Float(ly)/(y-1)
		Next
	Next
	RenderWorld
	
	AddTriangle surf, 0, 65,64
	AddTriangle surf, 0, 1 ,65
	For ly = 0 To y-2
		For lx = 0 To x-2 ; leave the vertex counting an extra unit short, as the code below reaches
						  ; reaches one unit ahead to create the triangles

			; below is to simply explain the creation process and order of the triangles.
			; Triangles must be created in clockwise order, facing the camera, to be seen.
			current_y = ly*y
			next_y = (ly+1)*y
			current_x = lx
			next_x = lx+1
			;create the quad
			AddTriangle surf, current_y+current_x, next_y+next_x    , next_y + current_x
			AddTriangle surf, current_y+current_x, current_y+next_x , next_y + next_x
		Next
	Next


	PositionMesh mesh, -x/2.0,0,y/2.0 ; centre the mesh on the world axis.

	SetBuffer ImageBuffer(temp)
	For ly = 0 To y-1
		For lx = 0 To x-1
			GetColor lx,ly
			index = (ly*y) + lx
			VertexCoords surf, index , VertexX(surf,index), ColorRed()*y_scale, VertexZ(surf,index)
		Next
	Next
	SetBuffer BackBuffer()
	
	UpdateNormals mesh
	
	If texture1 <> "" Then
		t1 = LoadTexture(texture1,t_flag1)
		If t1 = 0 Then RuntimeError(" Error in load_terrain() function: Texture1 not found from filename given")
		EntityTexture mesh,t1,0,0
	End If
	If texture2 <> "" Then
		t2 = LoadTexture(texture2,t_flag2)
		If t2 = 0 Then RuntimeError(" Error in load_terrain() function: Texture2 not found from filename given")
		EntityTexture mesh,t2,0,1
	End If
	If texture3 <> "" Then
		t3 = LoadTexture(texture3,t_flag3)
		If t3 = 0 Then RuntimeError(" Error in load_terrain() function: Texture3 not found from filename given")
		EntityTexture mesh,t3,0,2
	End If
		
	
	Return mesh
	
End Function

Comments

Hambone
(Posted 21 years ago)
Very nice. Very clear. When I have a little time I may extend this further to something like this:

Global terrain = load_terrain("heightmap.bmp, texture.bmp,lightmap.bmp")

ShadowTurtle
(Posted 21 years ago)
You mean:

Global terrain = load_terrain("heightmap.bmp", "texture.bmp", "lightmap.bmp")

Function load_terrain(file$, texfile$ = "", lightfile$ = "")
...
End Function

Hambone
(Posted 21 years ago)
Yeah,

Exactly like that. Your English is fine. Far better than the gibberish that I type most of the time :)

Thanks,

Allan

Ross C
(Posted 21 years ago)
Well, the texture co-ords are set, so, it should be a doodle to set up :o)

Chroma
(Posted 21 years ago)
Got it working but the texture coords are out of whack. =(

Chroma
(Posted 21 years ago)
It also loads the height map inverted...

EDIT: Change this line and it will load the height map correctly.

GetColor lx,y-ly


Also add this to the end of the function:
Return mesh


Chroma
(Posted 21 years ago)
Altered it to:
LoadTerrain3( "hmap.bmp","tmap.bmp","lmap.bmp")


Now the heightmap is loaded correctly and also the texture coordinates are set. The function automatically scales the texture to fit the terrain. Next on my list for this function is to have it break the terrain up into manageable segments that can be used with PVS and also define the detail level of the terrains so you can cap each segment at say, 500.

Function LoadTerrain3(hmap$,tmap$=0,lmap$=0)

	; load the heightmap
	temp = LoadImage(hmap$)
	If temp = 0 Then RuntimeError "Heightmap image "+hmap$+" does not exist." : Return 0
	
	; store heightmap dimensions
	x = ImageWidth(temp)
	y = ImageHeight(temp)

	; load texture and lightmaps
	tmap = LoadTexture(tmap$)
	lmap = LoadTexture(lmap$)
	
	; auto scale the textures to the right size
	If tmap ScaleTexture tmap,x,y
	If lmap	ScaleTexture lmap,x,y

	; start building the terrain
	mesh = CreateMesh()
	surf = CreateSurface(mesh)

	; create some verts for the terrain
	For ly = 0 To y
		For lx = 0 To x
			AddVertex surf,lx,0,ly,1.0/lx,1.0/ly
		Next
	Next
	RenderWorld
	
	; connect the verts with faces
	For ly = 0 To y-1
		For lx = 0 To x-1
			AddTriangle surf,lx+((x+1)*ly),lx+((x+1)*ly)+(x+1),(lx+1)+((x+1)*ly)
			AddTriangle surf,(lx+1)+((x+1)*ly),lx+((x+1)*ly)+(x+1),(lx+1)+((x+1)*ly)+(x+1)
		Next
	Next
	
	; position the terrain to center 0,0,0
	PositionMesh mesh, -x/2.0,0,-y/2.0

	; alter vertice height to match the heightmap red channel
	SetBuffer ImageBuffer(temp)
	For lx = 0 To x
		For ly = 0 To y
			GetColor lx,y-ly
			index = lx + ((x+1)*ly)
			VertexCoords surf, index , VertexX(surf,index), ColorRed()/20.0,VertexZ(surf,index)
			; set the terrain texture coordinates
			VertexTexCoords surf,index,lx,-ly 
		Next
	Next
	SetBuffer BackBuffer()
	
	; update the terrain normals so lighting will look correct
	UpdateNormals mesh
	
	; apply texture map to index 0
	If tmap EntityTexture mesh,tmap,0,0
		
	; apply lightmap to index 1 with flag 2
	If lmap EntityTexture mesh,lmap,0,1 : TextureBlend lmap,2
	
	Return Mesh
End Function


Chroma
(Posted 21 years ago)
Hmm...new bug.

The far right and bottom vertices aren't effected by the hmap. They stay at 0,0,0 even if the right wall is painted pure white...

ckob
(Posted 20 years ago)
not sure why but when I try this it crashes on renderworld.

Stevie G
(Posted 20 years ago)
Your heightmap is probably too big .. creating too many poly's or verts per surface. I think the limit is 64k verts and 32k polys - higher on some cards.

Stevie

ckob
(Posted 20 years ago)
256 X 256 ?

ShadowTurtle
(Posted 20 years ago)
from Chroma:
>> The far right and bottom vertices aren't effected by
>> the hmap. They stay at 0,0,0 even if the right wall
>> is painted pure white...

from ckob:
>> not sure why but when I try this it crashes on
>> renderworld.

In an other situation i am haved the same problem. Any vertice from triangle does not exists. Blitz debugger does it nothin register and so it make bugs.

You must integrate border-checking. As sample this would be set on a vertice in mesh:
; from: AddTriangle surf,lx+((x+1)*ly),lx+((x+1)*ly)+(x+1),(lx+1)+((x+1)*ly)

vertice = lx+((x+1)*ly)


So you must test:
If lx+((x+1)*ly) > CountVertices(surf) then ... End If


... ... or similarly.

On many refers of vertices this would be a long work for(to? sry, my english is bad) testing.

thelizardking
(Posted 20 years ago)
128x128, actually...

thelizardking
(Posted 20 years ago)
128x128, actually...but the function works AWSOME! thnxs ta big10p 4 finding it 4 me...lol thats how lazy i am

p.s. just add ResizeImage temp,128,128 after If temp = 0 Then RuntimeError "Heightmap image "+hmap$+" does not exist." : Return 0

Krischan
(Posted 20 years ago)
The functions works very good. I wrote a complete tech demo which tiles a given heightmap/colormap into smaller pieces and displays it nearly seamless (there is still a bug with the UV coordinates I couldn't locate).

Screenshot


Download with Source and Images

Source:
; Meshterrain Demo, Version 1.001 by Krischan (webmaster@...)
;
; Make sure there are three images in the current folder:
;
; - a Heightmap (standard: 256x256 PNG)
; - a Colormap (standard: 2048x2048 JPG)
; - a detail Texture (standard: 512x512 PNG)
;
; This script will cut the images and display the heightmap/colormap as a tiled Mesh Terrain
;
; Textures and Heightmap generated with Large 3D Terrain SE 2.5 (http://www.bundysoft.com/L3DT)
; Based on a script from Ross C and this thread:
;
; <a href="http://www.blitzbasic.com/codearcs/codearcs.php?code=1609" target="_blank">http://www.blitzbasic.com/codearcs/codearcs.php?code=1609</a>

; Settings
Global speed#	=	0.1			; Walk speed
Global scale#	=	100.00		; map scale
Global tiles	=	16			; how many tiles in X -AND- Y direction? must be power of 2
Global mapsize	=	256			; overview map size
Global range#	=	100			; initial view range (100 = 100% map size)
Global showmap	=	1			; show map
Global fog		=	1			; use fog or not
Global gravity	=	1			; use gravity or not
Global wired	=	0			; show wireframe or not
Global raster	=	0			; show tile raster or not
Global border#	=	0.99		; 1.00 = 100% = walk along the borderline
Global vscale#	=	8.0			; vertical terrain scale divider (increase if using smaller hmaps)
Global ipos		=	50			; initial player y position

AppTitle "Monkey Island Meshterrain Demo 1.001"
Graphics3D 1024,768,32,1

ms=MilliSecs() : SeedRnd(ms)

Dim hm(tiles,tiles),cm(tiles,tiles),tile(tiles,tiles)
Global cam,player,cam_pitch#,cam_yaw#,mvx#,mvy#,mvz#,targetpitch#,targetyaw#,sp#

hmap=LoadImage("heightmap.png")
cmap=LoadImage("colormap.jpg")
detail=LoadTexture("detail.png")

hmw=ImageWidth(hmap) : hmh=ImageHeight(hmap) : hmws=hmw/tiles : hmhs=hmh/tiles
cmw=ImageWidth(cmap) : cmh=ImageHeight(cmap) : cmws=cmw/tiles : cmhs=cmh/tiles

Global sw=hmw*range#

; create textures and heightmap tiles
x=1
Repeat
	y=1 
	Repeat
		cm(x,y)=CreateTexture(cmw/tiles,cmh/tiles,1+16+32)
		hm(x,y)=CreateImage((hmw/tiles)+1,(hmh/tiles)+1)
		y=y+1
	Until y>tiles
	x=x+1
Until x>tiles

; cut heightmap
x=hmws
Repeat
	y=hmhs
	Repeat
		CopyRect(x-hmws,y-hmhs,hmws+1,hmhs+1,0,0,ImageBuffer(hmap),ImageBuffer(hm(x/hmws,y/hmhs)))
	y=y+hmhs
	Until y>hmh
	x=x+hmws
Until x>hmw

; cut colormap
x=cmws
Repeat
	y=cmhs
	Repeat
		CopyRect(x-cmws,y-cmhs,cmws,cmhs,0,0,ImageBuffer(cmap),TextureBuffer(cm(x/cmws,y/cmhs)))
		If raster Then
			SetBuffer TextureBuffer(cm(x/cmws,y/cmhs))
			Color 255,0,0
			Rect(0,0,cmws,cmhs,0)
		EndIf
		y=y+cmhs
	Until y>cmh
	x=x+cmws
Until x>cmw

; resize and free unused images
ResizeImage cmap,mapsize,mapsize
FreeImage(hmap)
mf#=mapsize*1.0/hmw

; create mesh terrain tiles
x=0
Repeat
	y=0
	Repeat
		tx=1+Abs(x)/hmws : ty=1+Abs(y)/hmhs
		tile(tx,ty)=LoadMeshTerrain(hm(tx,ty),cm(tx,ty),detail,tx,ty)
		PositionEntity tile(tx,ty),(x*scale#)-((hmw/2)*scale#),0,(y*scale#)+((hmh/2)*scale#)
		EntityType tile(tx,ty),2
		ScaleEntity tile(tx,ty),scale#,scale#,scale#
		y=y-hmhs
	Until y<(-hmh+hmhs)
	x=x+hmws
Until x>(hmw-hmws)

player=CreatePivot()
PositionEntity player,0,ipos*scale#,0
EntityType player,1
EntityRadius player,1.0*scale#

cam=CreateCamera(player)
PositionEntity cam,0,0,0
CameraZoom cam,2
CameraFogColor cam,100,150,200
CameraClsColor cam,100,150,200
cam_yaw=230
targetyaw=230

AmbientLight 255,255,255

s=Speed(ms)

MoveMouse GraphicsWidth()/2,GraphicsHeight()/2

Collisions 1,2,2,3

; Main loop
While Not KeyHit(1)

	FPS_C=FPS_C+1 
	If ms<MilliSecs() 
		ms=MilliSecs()+1000 
		FPS=FPS_C 
		FPS_C=0 
	EndIf

	old=pos : pos=MouseZSpeed()
	
	; Keys
	If KeyHit(59) Then wired=1-wired		; F1 = Wireframe on/off
	If KeyHit(60) Then gravity=1-gravity	; F2 = Gravity on/off
	If KeyHit(61) Then fog=1-fog			; F3 = Fog on/off
	If KeyHit(28) Then showmap=1-showmap	; Return = Map on/off
	If KeyDown(201) Then sw=sw+hmw/16		; Page up = slow range increase
	If KeyDown(209) Then sw=sw-hmw/16		; Page down = slow range decrease
	
	If old=1 Then sw=sw+hmw					; Mouse Wheel up = fast range increase
	If old=-1 Then sw=sw-hmw				; Mouse Wheel down = fast range decrease
	
	If sw<hmw Then sw=hmw

	PlayerControls()
	
	plx#=EntityX(player) : ply#=EntityY(player) : plz#=EntityZ(player)
	If gravity Then ply#=ply#-(sp#*50)
	If plx#<((-hmw*border#)/2)*scale# Then plx=((-hmw*border#)/2)*scale#
	If plx#>((hmw*border#)/2)*scale# Then plx#=((hmw*border#)/2)*scale#
	If plz#<((-hmh*border#)/2)*scale# Then plz#=((-hmh*border#)/2)*scale#
	If plz#>((hmw*border#)/2)*scale# Then plz#=((hmw*border#)/2)*scale#
	PositionEntity player,plx#,ply#,plz#
	
	CameraFogMode cam,fog
	CameraRange cam,0.1,sw
	CameraFogRange cam,0,sw

	WireFrame wired
	UpdateWorld
	RenderWorld
	
	Color 255,255,255
	Text 0,0,"Tris: "+TrisRendered()+"  FPS: "+FPS+"  Range: "+sw+"  Wireframe: "+wired+"  Gravity: "+gravity+"  Fog: "+fog+"  Build time: "+(s/1000.0)+" sec."
	
	If showmap Then
		Color 0,0,0 : Rect 0,25,mapsize,mapsize,1 : DrawImage cmap,0,25
		Color 255,255,255 : Text 0,12,"X:"+Int(plx#)+" Y:"+Int(ply#)+" Z:"+Int(plz#)+" Pitch: "+cam_pitch+" Yaw: "+cam_yaw
		Color 255,0,0 : Oval((mapsize/2)+(plx#/scale#*mf#)-2,25+(mapsize/2)+(plz#*-1/scale#*mf#)-2,4,4,1)
	EndIf
	
	Flip 0

Wend

End

Function Speed(ms) : s=(MilliSecs()-ms) : Return s : End Function

Function PlayerControls()

	mxspd# = MouseXSpeed()*0.25
	myspd# = MouseYSpeed()*0.25

	MoveMouse GraphicsWidth()/2,GraphicsHeight()/2

	targetpitch = targetpitch + myspd
	targetpitch = ClampValue(targetpitch, -85,85)
	targetyaw = targetyaw - mxspd	

	cam_pitch = cam_pitch + (targetpitch - cam_pitch)/16.0
	cam_yaw = cam_yaw + (targetyaw - cam_yaw)/16.0
	
	RotateEntity player,0,cam_yaw,0
	RotateEntity cam,cam_pitch,0,0
	
	sp#=speed#
	If KeyDown(42) Or KeyDown(54) Then sp#=(speed#*5)	; Shift = Turbo

	If KeyDown(200) Then mvz=mvz+sp#			; Arrow up = forward
	If KeyDown(208) Then mvz=mvz-sp#			; Arrow down = backward
	If KeyDown(205) Then mvx=mvx+sp#			; Arrow right = right
	If KeyDown(203) Then mvx=mvx-sp#			; Arrow left = left
	
	If MouseDown(1) Then mvy=mvy+sp#			; LMB = raise
	If MouseDown(2) Then mvy=mvy-sp#			; RMB = lower
		
	If KeyDown(17) Then mvz=mvz+sp#				; W = forward
	If KeyDown(31) Then mvz=mvz-sp#				; S = backward
	If KeyDown(30) Then mvx=mvx-sp#				; A = left
	If KeyDown(32) Then mvx=mvx+sp#				; D = right

	If KeyDown(16) Then mvy=mvy+sp#				; Q = raise
	If KeyDown(18) Then mvy=mvy-sp#				; E = lower
	
	
	TranslateEntity player,0,mvy,0
	MoveEntity player,mvx,0,mvz

	mvx=mvx/1.01
	mvy=mvy/1.01
	mvz=mvz/1.01
	
End Function

Function ClampValue(Original#, low#, high#)

	If Original<low  Then Return low
	If Original>high Then Return high
	Return Original

End Function


Function LoadMeshTerrain(heightmap,colormap=False,detail=False,tx=0,ty=0)

	lessx=1 : If tx=ti Then lessx=2
	lessy=1 : If ty=ti Then lessy=2

	x = ImageWidth(heightmap)-lessx : y = ImageHeight(heightmap)-lessy

	mesh = CreateMesh() : surf = CreateSurface(mesh)

	For ly = 0 To y
		For lx = 0 To x
			AddVertex surf,lx,0,ly,1.0/lx,1.0/ly
		Next
	Next

	For ly = 0 To y-1
		For lx = 0 To x-1
			AddTriangle surf,lx+((x+1)*ly),lx+((x+1)*ly)+(x+1),(lx+1)+((x+1)*ly)
			AddTriangle surf,(lx+1)+((x+1)*ly),lx+((x+1)*ly)+(x+1),(lx+1)+((x+1)*ly)+(x+1)
		Next
	Next
	
	PositionMesh mesh,0,0,-y

	SetBuffer ImageBuffer(heightmap) : LockBuffer ImageBuffer(heightmap)

	For lx = 0 To x
		For ly = 0 To y
			h=(ReadPixelFast(lx,y-ly) And $ff0000)/$10000
			index = lx + ((x+1)*ly)
			VertexCoords surf,index,VertexX(surf,index),h/vscale#,VertexZ(surf,index)
			VertexTexCoords surf,index,lx,(y-ly)
		Next
	Next

	UnlockBuffer ImageBuffer(heightmap) : SetBuffer BackBuffer()
	
	UpdateNormals mesh
	
	If colormap EntityTexture mesh,colormap,0,0 : ScaleTexture colormap,x,y
	If detail EntityTexture mesh,detail,0,2 : ScaleTexture detail,2,2 : TextureBlend detail,5
			
	Return Mesh
	
End Function


Nexus6
(Posted 20 years ago)
Moved comments to programming.

Ross C
(Posted 19 years ago)
Need to have another look at this... Thanks for tidying it up though :o)

Ross C
(Posted 18 years ago)
UPDATED!

Blitzplotter
(Posted 18 years ago)
Hey Ross, this is very interesting. I did some stuff a while back (ermm 10 years or so ago) that used Vista and DEM files to create landscapes.

It is great to see the type of code necessary to produce such fantastic results, thanks for sharing.

DarkShadowWing
(Posted 18 years ago)
Did anyone fix the UV Coordinate glitch yet? I cant seem to find it.

DarkShadowWing
(Posted 18 years ago)
?

DarkShadowWing
(Posted 18 years ago)
I know u guys proally dont have time to fix it, but here's what i mean by uv coordinate glitch. its some bad geometry.

[href]http://i406.photobucket.com/albums/pp148/DarkShadowWing/badgeom.png[/href]

Code Archives Forum