I should probably use a chunked mesh terrain anyway. The following BlitzMax code runs at over 1000fps:
It’s an updated version of code I wrote for MiniB3d earlier in the year. I just haven’t figured out how to eliminate the seams between chunks when tiling textures.
SuperStrict Import blitz3d.blitz3dsdk bbBeginBlitz3D() bbGraphics3D 800, 600, 32, 1 bbSetBuffer bbBackBuffer() Local hmap_url:String = "temp_hmap.png" bbAmbientLight 255, 255, 255 Local Camera:Int = bbCreateCamera() bbCameraRange Camera, 0.1, 1000.0 bbCameraClsColor Camera, 64, 164, 196 Local Terrain:TChunkedTerrain = New TChunkedTerrain Local temp:Float[,] = createSampleHeightMap(256, 256, 1000, 32, False, hmap_url) Terrain.CreateMeshes(temp, 64) temp = Null GCCollect 'Terrain.LoadFromFile(hmap_url, 64) 'Terrain.SetUV 32 '<-- BUGGY Terrain.TextureFromPixmap(LoadPixmap(hmap_url), 1, 0, 1) Terrain.Scale 10.0, 100.0, 10.0 Terrain.Position 0.0, 0.0, 0.0 'bbClearTextureFilters 'Terrain.TextureChunk bbLoadTexture("grass.png", 1 + 8), Terrain.GetChunk(0.0, 0.0), 0, 1 bbPositionEntity Camera, Terrain.CentreX(), Terrain.Y, Terrain.CentreZ() Local MoveSpeed:Float = 0.1 Local old_ms:Int, renders:Int, fps:Int Local mxs:Float, mys:Float 'bbWireFrame True While Not bbKeyHit(1) mxs :+ (bbMouseXSpeed() / 3.0) mys :+ (bbMouseYSpeed() / 3.0) bbRotateEntity Camera, mys, -mxs, 0.0 bbMoveMouse bbGraphicsWidth() / 2, bbGraphicsHeight() / 2 bbMouseXSpeed() bbMouseYSpeed() If bbKeyDown(200) Or bbKeyDown(17) Or bbMouseDown(2) Then bbMoveEntity Camera, 0.0, 0.0, MoveSpeed If bbKeyDown(208) Or bbKeyDown(31) Then bbMoveEntity Camera, 0.0, 0.0, -MoveSpeed If bbKeyDown(32) Then bbMoveEntity Camera, MoveSpeed, 0.0, 0.0 If bbKeyDown(30) Then bbMoveEntity Camera, -MoveSpeed, 0.0, 0.0 Local camx:Float = bbEntityX(Camera) Local camz:Float = bbEntityZ(Camera) Local camy:Float = Terrain.MeshY(camx, camz) + 5.0 bbPositionEntity Camera, camx, camy, camz Terrain.DoVisibleChunks camx, camz bbRenderWorld renders :+ 1 If MilliSecs() -old_ms >= 1000 old_ms = MilliSecs() fps = renders renders = 0 EndIf bbText 5, 5, "FPS: " + fps bbFlip False Wend Terrain.Destroy bbEndBlitz3D() End '********** CHUNKED TERRAIN CLASS ********** Type TChunkedTerrain Field ChunkSize:Int Field Chunks:Int[,] Field Cols:Int Field Rows:Int Field X:Float Field Y:Float Field Z:Float Field XScale:Float Field YScale:Float Field ZScale:Float Field XSize:Float Field ZSize: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 If Chunks[col, row] Then bbFreeEntity Chunks[col, row] Chunks[col, row] = Null Next Next Chunks = Null GCCollect EndMethod 'Chunk Size should be less than the heightmap size and a power of two (eg, 16, 32, 64, etc) Method LoadFromFile:Int(hmap_url:String, chunk_size:Int = 64) Local pix:TPixmap = LoadPixmap(hmap_url) If Not pix Return False Local map:Float[,] = New Float[pix.width, pix.height] For Local px:Int = 0 To pix.width - 1 For Local pz:Int = 0 To pix.height - 1 Local r:Byte = ReadPixel(pix, px, pz) map[px, pz] = Float(r) / 255.0 Next Next CreateMeshes map, chunk_size Scale XScale, YScale, ZScale map = Null GCCollect Return True EndMethod Method CreateMeshes(arr:Float[,] Var, chunk_size:Int = 64) If Not arr Then Return Local dims:Int[] = arr.Dimensions() Local map_size:Int If dims[0] < dims[1] Then map_size = dims[0] Else map_size = dims[1] Local map:Float[,] = New Float[map_size, map_size] For Local px:Int = 0 To map_size - 1 For Local pz:Int = 0 To map_size - 1 map[px, (map_size - pz - 1)] = arr[px, pz] Next Next ChunkSize = chunk_size Cols = (map_size / ChunkSize) Rows = (map_size / ChunkSize) Chunks = New Int[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 Chunks[col, row] = CreateChunk(map, px, pz, ChunkSize + 1, ChunkSize + 1) bbEntityPickMode Chunks[col, row], 2 Next Next Position X, Y, Z map = Null GCCollect Return EndMethod Method CreateChunk:Int(map:Float[,] Var, px:Int, pz:Int, w:Int, h:Int, parent:Int = 0) 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 Local mesh:Int = bbCreateMesh(parent) Local surf:Int = bbCreateSurface(mesh) For Local lz:Int = 0 To h For Local lx:Int = 0 To w bbAddVertex surf, lx, 0, lz, (1.0 / lx), (1.0 / lz) Next Next For Local lz:Int = 1 To (h - 2) For Local lx:Int = 1 To (w - 2) bbAddTriangle surf, lx + ((w + 1) * lz), lx + ((w + 1) * lz) + (w + 1), (lx + 1) + ((w + 1) * lz) bbAddTriangle surf, (lx + 1) + ((w + 1) * lz), lx + ((w + 1) * lz) + (w + 1), (lx + 1)+((w + 1) * lz) + (w + 1) Next Next For Local lx:Int = 1 To (w - 1) For Local lz:Int = 1 To (h - 1) Local index:Int = lx + ((w + 1) * lz) bbVertexCoords surf, index, bbVertexX(surf, index), map[px + lx, pz + lz], bbVertexZ(surf, index) bbVertexTexCoords surf, index, lx, -lz Next Next bbUpdateNormals mesh Return Mesh EndMethod '** BUGGY - seams visible between chunks Method SetUV(tile_size:Int = 1) For Local col:Int = 0 To Cols - 1 For Local row:Int = 0 To Rows - 1 Local surf:Int = bbGetSurface(Chunks[col, row], 1) For Local lx:Int = 1 To ChunkSize For Local lz:Int = 1 To ChunkSize Local index:Int = lx + ((ChunkSize + 2) * lz) bbVertexTexCoords surf, index, (lx * tile_size), -(lz * tile_Size) Next Next Next Next EndMethod Method TextureFromFile:Int(tex_url:String = "", flags:Int = 1, frame:Int = 0, index:Int = 0) Local tex:TPixmap = LoadPixmap(tex_url) Return TextureFromPixmap(tex, flags, frame, index) EndMethod Method TextureFromPixmap:Int(tex:TPixmap, flags:Int = 1, frame:Int = 0, index:Int = 0) If Not tex Then Return False If PixmapFormat(tex) <> PF_RGBA8888 Then tex = ConvertPixmap(tex, PF_RGBA8888) tex = YFlipPixmap(ResizePixmap(tex, ChunkSize * Cols, ChunkSize * 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 fname:String = "temp_chunk_tex_" + col + "_" + row + ".png" Local pm:TPixmap = PixmapWindow(tex, px, pz, ChunkSize + 1, ChunkSize + 1) SavePixmapPNG(YFlipPixmap(pm), fname, 9) TextureChunk(bbLoadTexture(fname, flags), Chunks[col, row], frame, index) DeleteFile fname Next Next Return True EndMethod Method TextureChunk(tex:Int, mesh:Int, frame:Int = 0, index:Int = 0) If Not tex Then Return bbScaleTexture tex, ChunkSize + 1, ChunkSize + 1 bbEntityTexture mesh, tex, frame, index EndMethod Method Scale(sx:Float, sy:Float, sz:Float) XScale = sx YScale = sy ZScale = sz For Local c:Int = 0 To Cols - 1 For Local r:Int = 0 To Rows - 1 bbScaleEntity Chunks[c, r], XScale, YScale, ZScale Next Next XSize = ChunkSize * Cols * XScale ZSize = ChunkSize * Rows * ZScale EndMethod Method Position(wx:Float, wy:Float, wz:Float) X = wx Y = wy Z = wz For Local col:Int = 0 To Cols - 1 For Local row:Int = 0 To Rows - 1 Local px:Int = X + (col * ChunkSize * XScale) - (col * XScale) Local pz:Int = Z + (row * ChunkSize * ZScale) - (row * ZScale) bbPositionEntity Chunks[col, row], px - XScale, Y, pz - ZScale Next Next EndMethod Method MeshY:Float(wx:Float = 0.0, wz:Float = 0.0) If (wx < X) Or (wz < Z) Then Return Y If wx > X + (ChunkSize * Cols * XScale) Then Return Y If wz > Z + (ChunkSize * Rows * ZScale) Then Return Y bbLinePick(wx, Y + YScale, wz, 0.0, -(YScale * 2), 0.0) Return bbPickedY() EndMethod Method GetChunk:Int(wx:Float = 0.0, wz:Float = 0.0) If (wx < X) Or (wz < Z) Then Return Null If wx > X + (ChunkSize * Cols * XScale) Then Return Null If wz > Z + (ChunkSize * Rows * ZScale) Then Return Null Local col:Int = (wx - X) / (ChunkSize * XScale) Local row:Int = (wz - Z) / (ChunkSize * ZScale) Return Chunks[col, row] EndMethod Method CentreX:Float() Return X + ((Cols * ChunkSize * XScale) / 2) EndMethod Method CentreZ:Float() Return Z + ((Rows * ChunkSize * ZScale) / 2) EndMethod Method DoVisibleChunks(wx:Float, wz:Float, range:Int = 1) For Local col:Int = 0 To Cols - 1 For Local row:Int = 0 To Rows - 1 bbHideEntity Chunks[col, row] Next Next Local current_col:Int = (wx - X) / (ChunkSize * XScale) Local start_col:Int = current_col - range If start_col < 0 Then start_col = 0 Local end_col:Int = current_col + range If end_col >= Cols Then end_col = Cols - 1 Local current_row:Int = (wz - Z) / (ChunkSize * ZScale) Local start_row:Int = current_row - range If start_row < 0 Then start_row = 0 Local end_row:Int = current_row + range If end_row >= Rows Then end_row = Rows - 1 For Local col:Int = start_col To end_col For Local row:Int = start_row To end_row bbShowEntity Chunks[col, row] Next Next EndMethod EndType '****************************** Function createSampleHeightMap:Float[,](width:Int = 128, height:Int = 128, numhills:Int = 1000, maxhsize:Int = 32, island:Int = True, fname:String = "") '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 If fname 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) EndIf Return Map End Function
It’s an updated version of code I wrote for MiniB3d earlier in the year. I just haven’t figured out how to eliminate the seams between chunks when tiling textures.