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