Terrain?

BlitzMax Modules Forums/MiniB3D/Terrain?

The forum is locked. These are the original blitzbasic.com forums, kept so they can still be read. No new topics, replies or edits can be made here. To join in, head over to the Blitz3D+ forums.

Here are the results of my 'chunking' experiment:


Rem

MiniB3d Chunked Terrain Mesh Test
(Proof of concept / performance test)

Notes:

	* Faster than non-chunked large terrain meshes, but probably not fast enough 
	  for any practical purposes. 512 x 512 heightmaps will reduce minib3d to a crawl.
	
	* The heightmap must have equal dimensions that are power of two (eg 128x128, 256x256, etc).

	* KEYS: WASD, Arrows.
	
	* MOUSE: Right Button
		
EndRem

SuperStrict

Import "c:\temp\minib3d\minib3d.bmx"			' <- Change this to your minib3d path / module

Graphics3D 800, 600

Text 5, 5, "Generating sample height map..."
Flip

createSampleHeightMap("temp_hmap.png", 128, 128, 1000, 32, False)	 

Text 5, 25, "Setting up world and loading terrain..."
Flip

AmbientLight 255, 255, 255

Global Camera:TCamera = CreateCamera()
CameraRange Camera, 0.1, 4000.0
CameraClsColor Camera, 64, 164, 196
	
'**** Terrain Stuff
Local Terrain:TChunkedTerrain = New TChunkedTerrain
Terrain.Load("temp_hmap.png")
Terrain.TextureFromFile("temp_hmap.png", 1) 		' <- Using the heightmap image for the texture also.
Terrain.Scale 40.0, 1000.0, 40.0
Terrain.Position 0.0, 0.0, 0.0
'**** 

PositionEntity Camera, Terrain.X, 1000.0, Terrain.Z

Global MoveSpeed:Float = 2.5

Local old_ms:Int = MilliSecs()
Local renders:Int
Local fps:Int

Global mxs:Float
Global mys:Float

'WireFrame True

While Not KeyDown(KEY_ESCAPE)		

	Cls

	HandleInput
	UpdateWorld
	RenderWorld

	renders :+ 1

	If MilliSecs() -old_ms >= 1000
		old_ms = MilliSecs()
		fps = renders
		renders = 0
	EndIf

	Text 5, 5, "FPS: " + String(fps)
	text 5, 25, EntityX(Camera) + ", " + EntityZ(Camera)

	Flip False
	
	'Delay 1

Wend

FreeEntity Camera
Camera = Null

Terrain.Destroy
Terrain = Null

GCCollect

End


'********** CHUNKED TERRAIN CLASS **********


Type TChunkedTerrain


	Field Size:Int			
	Field ChunkSize:Int
	Field Cols:Int
	Field Rows:Int
	
	Field Pivot:TPivot
	Field Chunks:TMesh[,]
	
	Field X:Float
	Field Y:Float
	Field Z:Float
	
	Field XScale:Float
	Field YScale:Float
	Field ZScale:Float
	
	
	Method New()
	
		X = 0.0
		Y = 0.0
		Z = 0.0
				
		XScale = 1.0
		YScale = 1.0
		ZScale = 1.0
	
	EndMethod
	
	
	Method Destroy()
	
		For Local col:Int = 0 To Cols - 1
			For Local row:Int = 0 To Rows - 1
				FreeEntity Chunks[col, row]
				Chunks[col, row] = Null
			Next
		Next	
	
		FreeEntity Pivot
		
		Pivot = Null
	
		Chunks = Null
		
		GCCollect		
	
	EndMethod
	
	
	'Chunk Size should be less than the heightmap size and a power of two (eg, 16, 32, 64, etc)
	
	Method Load:Int(hmap_url:String, chunk_size:Int = 64)
	
		Local pix:TPixmap = LoadPixmap(hmap_url)
		If Not pix Return False
		
		Size = pix.height
		ChunkSize = chunk_size
		
		Cols = (Size / ChunkSize)
		Rows = (Size / ChunkSize)

		Pivot = CreatePivot()
		
		PositionEntity Pivot, X, Y, Z, True
	
		Chunks = New TMesh[Cols, Rows]
		
		For Local col:Int = 0 To Cols - 1
			For Local row:Int = 0 To Rows - 1
		
				Local px:Int = (col * ChunkSize) - col
				Local pz:Int = (row * ChunkSize) - row
		
				Local hmappm:TPixmap = PixmapWindow(pix, px, pz, ChunkSize + 1, ChunkSize + 1)
				Chunks[col, row] = CreateChunk(hmappm, Pivot)
		
				Local wx:Float = Float(px)
				Local wz:Float = Float(((rows - 1) * chunk_size) - pz)
				PositionEntity Chunks[col, row], wx, 0.0, wz, False

			Next
		Next

		Scale XScale, YScale, ZScale
		
		Return True

	EndMethod


	Method TextureFromFile:Int(tex_url:String = "", flags:Int = 1, frame:Int = 0, index:Int = 0)

		If Not pivot Return False
	
		Local tex:TPixmap = LoadPixmap(tex_url)
		If Not tex Then Return False

		tex = ResizePixmap(tex, Size, Size)

		For Local col:Int = 0 To Cols - 1
			For Local row:Int = 0 To Rows - 1
		
				Local px:Int = (col * ChunkSize) - col
				Local pz:Int = (row * ChunkSize) - row
		
				Local fname:String = "temp_chunk_tex_" + col + "_" + row + ".png"
				Local pm:TPixmap = PixmapWindow(tex, px, pz, ChunkSize + 1, ChunkSize + 1)
				SavePixmapPNG(pm, fname, 9)
				
				Local tex_chunk:TTexture = LoadTexture(fname, flags)
				ScaleTexture tex_chunk, pm.width, pm.height
				EntityTexture chunks[col, row], tex_chunk, frame, index
				
				DeleteFile fname
				
			Next
		Next
		
		Return True

	EndMethod
	

	Method Scale(sx:Float, sy:Float, sz:Float)
	
		If Not Pivot Return
	
		XScale = sx
		YScale = sy
		ZScale = sz
	
		ScaleEntity Pivot, XScale, YScale, ZScale
	
	EndMethod
	
	
	Method Position(wx:Float, wy:Float, wz:Float)
	
		If Not Pivot Return
	
		X = wx
		Y = wy
		Z = wz
		
		PositionEntity Pivot, X, Y, Z		
	
	EndMethod
	
	
	Function CreateChunk:TMesh(pix:TPixmap, parent:TEntity = Null)
	Rem
		* Ported And slightly modified from Blitz3D code:
  		  <a href="http://www.blitzbasic.com/codearcs/codearcs.php?code=1609" target="_blank">http://www.blitzbasic.com/codearcs/codearcs.php?code=1609</a>
	EndRem

		If Not pix Return Null

		Local pixw:Int = PixmapWidth(pix)
		Local pixh:Int = PixmapHeight(pix)

		Local mesh:TMesh = CreateMesh(parent)
		Local surf:TSurface = CreateSurface(mesh)

		For Local ly:Int = 0 To pixh
			For Local lx:Int = 0 To pixw
				AddVertex surf, lx, 0, ly, (1.0 / lx), (1.0 / ly)
			Next
		Next
	
		For Local ly:Int = 1 To (pixw - 2)
			For Local lx:Int = 1 To (pixh - 2)
				AddTriangle surf, lx + ((pixw + 1) * ly), lx + ((pixw + 1) * ly) + (pixw + 1), (lx + 1) + ((pixw + 1) * ly)
				AddTriangle surf, (lx + 1) + ((pixw + 1) * ly), lx + ((pixw + 1) * ly) + (pixw + 1), (lx + 1)+((pixw + 1) * ly) + (pixw + 1)
			Next
		Next

		For Local lx:Int = 0 To (pixw - 1)
			For Local ly:Int = 0 To (pixh - 1)
				Local r:Byte = ReadPixel(pix, lx, pixh - 1 - ly)
				Local index:Int = lx + ((pixw + 1) * ly)
				VertexCoords surf, index, VertexX(surf, index), Float(r) / 255.0, VertexZ(surf, index)
				VertexTexCoords surf, index, lx, -ly 
			Next
		Next

		UpdateNormals mesh
		
		Return Mesh
	
	End Function
	
EndType


'******************************




'****** MISCELLANEOUS FUNCTIONS (NOT required for Chunked Terrain class) *********


Function handleInput()
	
	mxs :+ (MouseXSpeed() / 3.0)
	mys :+ (MouseYSpeed() / 3.0)

	RotateEntity Camera, mys, -mxs, 0

	MoveMouse GraphicsWidth() / 2, GraphicsHeight() / 2
		
	MouseXSpeed()
	MouseYSpeed()
		
	If KeyDown(KEY_UP) Or KeyDown(KEY_W) Or MouseDown(2)
		MoveEntity Camera, 0.0, 0.0, MoveSpeed
	EndIf

	If KeyDown(KEY_DOWN) Or KeyDown(KEY_S) 
		MoveEntity Camera, 0.0, 0.0, -MoveSpeed
	EndIf

	If KeyDown(KEY_D) Then MoveEntity Camera, MoveSpeed, 0.0, 0.0

	If KeyDown(KEY_A) Then MoveEntity Camera, -MoveSpeed, 0.0, 0.0
	
EndFunction	


Function createSampleHeightMap(fname:String, width:Int = 128, height:Int = 128, numhills:Int = 1000, maxhsize:Int = 32, island:Int = True)

	'Generate

	Local Map:Float[width, height]

    SeedRnd(MilliSecs())
  
	If island
		numhills :* 0.2
		maxhsize :* 0.5
	EndIf

    For Local n:Int = 1 To numhills

		Local radius:Int = Rand(0, maxhsize)
		Local centrex:Int = Rand(0, Width - 1)
		Local centrez:Int = Rand(0, Height - 1)
		
		If island
			Local theta:Int = Rand(0, 359)
			Local distx:Int = Rand(0, (Width * 0.5) - radius)
			Local distz:Int = Rand(0, (Height * 0.5) - radius)
	  		centrex = (Width * 0.5) + (Cos(theta) * distx)
	  		centrez = (Height * 0.5) + (Sin(theta) * distz)
		EndIf

    	Local startx:Int = centrex - radius
      	If startx < 0 startx = 0

      	Local endx:Int = centrex + radius
	  	If endx > Width endx = Width

      	Local startz:Int = centrez - radius
      	If startz < 0 startz = 0

      	Local endz:Int = centrez + radius
      	If endz > Height endz = Height

      	For Local x:Int = startx To (endx - 1)
       		For Local z:Int = startz To (endz - 1)
	   			Local y:Float = (radius * radius) - (((x - centrex) * (x - centrex)) + ((z - centrez) * (z - centrez)))
	    		If y > 0.0 Map[x, z] :+ y
	    	Next
	  	Next
    
    Next

	'Normalise
	
	Local minv:Float = 10^38, maxv:Float = 10^-38
  
  	For Local x:Int = 0 To (Width - 1)
    	For Local z:Int = 0 To (Height - 1)
 	 		If Map[x, z] < minv minv = Map[x, z] Else If Map[x, z] > maxv maxv = Map[x, z]
     	Next
   	Next

   	For Local x:Int = 0 To (Width - 1)
     	For Local z:Int = 0 To (Height - 1)
			Map[x, z] = (Map[x, z] - minv) / (maxv - minv)
		Next
  	Next

	'Convert to byte Array
	
	Local arr:Byte[,] = New Byte[width, height]

  	For Local x:Int = 0 To (Width - 1)
    	For Local z:Int = 0 To (Height - 1)

			Local v:Int = Floor(Map[x, z] * 255)

			arr[x, z] = Byte(v)

		Next
	Next
		
	'Render to pixmap and save to file
	
	Local pm:TPixmap = CreatePixmap(width, height, PF_RGB888)

    For Local x:Int = 0 To (Width - 1)
    	For Local z:Int = 0 To (Height - 1)
    		WritePixel(pm, x, z, arr[x, z] Shl 16 | arr[x, z] Shl 8 | arr[x, z])
    	Next
    Next

	SavePixmapPNG(pm, fname, 9)	
	
End Function