RemHeightMap ToolboxV0.3.6August 2008PURPOSE A self-contained class of heightmap creation, processing, exporting, and importing utilities.RECENT CHANGES 0.3.5 * Removed COLORMAP_DISCREET constant - Superceded by new STYLE_DISCREET constant * Removed COLORMAP_BLENDED constant - Superceded by new STYLE_BLENDED constant * Removed TEXTUREMAP_DISCREET constant - Superceded by new STYLE_DISCREET constant * Removed TEXTUREMAP_BLENDED constant - Superceded by new STYLE_BLENDED constant * Removed resize method - inappropriate, superceded by the new Create function * Removed blendMapsRep function - superfluous * Removed createParticleSMap method - superceded by generateParticleMap * Removed createParticleRMap method - superceded by generateParticleMap * Added STYLE_DISCREET constant * Added STYLE_BLENDED constant * Added METHOD_STICKY constant * Added METHOD_ROLLING constant * Added Create function - more appropriate and convenient than resize * Added generate ParticleMap method - combines both previous particle algorthims and adds some extra algorithm customization * Modified renderToTexturePixmap - now accepts pre-existing pixmap arrays or urls * Renamed createRandomMap to generateRandomMap - more appropriate name * Renamed createHillMap to generateHillMap - more appropriate name * Renamed createFaultMap to generateFaultMap - more appropriate name * Renamed createPerlinMap to generatePerlinMap - more appropriate name * Renamed createMPDMap to generateMPDMap - more appropriate name * Renamed TVector class to TSimpleVector - minimize potential naming conflicts with other imported code 0.3.6 * Removed exportPNG - superfluous * Removed exportColorPNG - superfluous * Removed exportTexturePNG - superfluous * Removed exportShadowsPNG - superfluous * Modified generatePerlinMap algorithm - simplified: one less parameter METHODS AND FUNCTIONS: create clone generateRandomMap generateHillMap generateParticleMap generateFaultMap generatePerlinMap generateMPDMap recurseMPD MPD fill normalise smoothen flatten makeCoast invert islandise lowerCenter importPNG importImageFile importBin importPixmap exportBin blendMapsAdd blendMapsSub blendMapsHi blendMapsLo blendBrushRaise blendBrushLower blendBrushHi blendBrushLo renderToPixmap renderRectToPixmap renderToColorPixmap renderRectToColorPixmap renderToTexturePixmap renderShadowsToPixmap lockedLineUTILITY CLASS TSimpleVector setXYZ normalise add sub crossProduct dotProduct End RemSuperStrictImport BRL.RandomImport BRL.PNGLoaderImport BRL.JPGLoaderImport BRL.TGALoaderImport BRL.BMPLoaderImport BRL.FileSystemConst STYLE_DISCREET:Int = 0Const STYLE_BLENDED:Int = 1Const METHOD_STICKY:Int = 0Const METHOD_ROLLING:Int = 1Type THeightMap Field Width:Int = 128 'Width (units) > 0 Field Height:Int = 128 'Height (units) > 0 Field Map:Float[Width, Height] 'Matrix of height values (floats) >=0.0 <=1.0 normalised Function Create:THeightMap(w:Int = 256, h:Int = 256) Rem A convenience function that creates a new blank THeightmap an returns the result. w: width. > 0. Higher = slower performance And increased memory consumption. h: height. > 0. Higher = slower performance And increased memory consumption. Returns a new THeightMap instance. EndRem If Not ((w > 0) And (h > 0)) Return Null Local hm:THeightMap = New THeightMap hm.Width = w hm.Height = h hm.Map = New Float[hm.Width, hm.Height] Return hm EndFunction Method clone:THeightMap() Rem Copies / clones the THeightMap instance and returns the result as a new object. Returns the clone as a THeightMap instance. EndRem Local cl:THeightMap = Create(Width, Height) For Local x:Int = 0 To cl.Width - 1 For Local z:Int = 0 To cl.Height - 1 cl.Map[x, z] = Map[x, z] Next Next Return cl EndMethod Method fill(val:Float = 0.0) Rem Set all heights To a specific value. val: value To fill with. Use 0.0 To clear the map. EndRem For Local x:Int = 0 To Width - 1 For Local z:Int = 0 To Height - 1 Map[x, z] = val Next Next End Method Method generateRandomMap(seed:Int = 0) Rem Generate a map of random heights between 0.0 and 1.0. seed: random seed. Use Millisecs() to generate a different result every time. End Rem SeedRnd(seed) For Local x:Int = 0 To Width - 1 For Local z:Int = 0 To Height - 1 Map[x, z] = Rnd() Next Next End Method Method generateHillMap(seed:Int = 0, numhills:Int = 1000, maxhsize:Int, island:Int = False) Rem Generate a map using the "hill algorithm". seed: random seed. Use Millisecs() to generate a different result every time. numhills: number of hills. (>1000) is best. Minimum 1. maxhsize: maximum hill radius. For best results, value should be significantly less than the map's shortest dimension. island: generate an island-like result. True/False End Rem SeedRnd(seed) fill(0.0) 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() End Method Method generateParticleMap(seed:Int = 0, particles:Int = 500, clusters:Int = 1000, mthd:Int = METHOD_STICKY, vals:Int[] = Null) Rem seed: random seed. Use Millisecs() to generate a different result every time. particles: number of particles to calculate per cluster. Minimum 1. clusters: number of clusters: higher number gives greater map density. Minimum 1. mthd: algorithm method: METHOD_STICKY or METHOD_ROLLING. values: algorithm variation: array length should be 4. For best results use integers between -4 and 4. End Rem If vals = Null Then vals = [1, -1, 1, -1] If vals.length < 4 Then vals = [1, -1, 1, -1] Local disp:Float = 1.0 SeedRnd(seed) fill(0.0) If mthd = METHOD_STICKY For Local m:Int = 0 To (clusters - 1) Local x:Int = Rand(0, Width - 1) Local z:Int = Rand(0, Height - 1) For Local n:Int = 0 To (particles - 1) If ((x < Width) And (z < Height) And (x >= 0) And (z >= 0)) Then Map[x, z] :+ disp Select Rand(0, 3) Case 0 x :+ vals[0] Case 1 x :+ vals[1] Case 2 z :+ vals[2] Case 3 z :+ vals[3] End Select Next Next Else If mthd = METHOD_ROLLING For Local m:Int = 0 To (clusters - 1) Local x:Int = Rand(0, Width - 1) Local z:Int = Rand(0, Height - 1) For Local n:Int = 0 To (particles - 1) If ((x < Width) And (z < Height) And (x >= 0) And (z >= 0)) Local y:Float = Map[x, z] + disp Map[x, z] = y Local startx:Int = x - 1 If startx < 0 startx = 0 Local endx:Int = x + 1 If endx > Width endx = Width Local startz:Int = z - 1 If startz < 0 startz = 0 Local endz:Int = z + 1 If endz > Height endz = Height For Local x2:Int = startx To (endx - 1) For Local z2:Int = startz To (endz - 1) Local y2:Float = Map[x2, z2] If y2 < y Map[x2, z2] = y2 + disp Next Next EndIf Select Rand(0, 3) Case 0 x :+ vals[0] Case 1 x :+ vals[1] Case 2 z :+ vals[2] Case 3 z :+ vals[3] End Select Next Next EndIf normalise() EndMethod Method generateFaultMap(seed:Int = 0, realism:Int = 500) Rem Generate a map using the "fault line" algorithm. seed: random seed. Use Millisecs() To generate a different result every time. realism: number of fault lines: higher number (> 500) gives greater realism. Minimum 1. End Rem SeedRnd(seed) fill(0.0) Local d:Float = Sqr((Width * Width) + (Height * Height)) Local disp:Float = 10.0 For Local n:Int = 1 To realism Local v:Float = Rnd(1,359) Local a:Float = Sin(v) Local b:Float = Cos(v) Local c:Float = (RndFloat() * d) - (d / 2) For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) If (((a * x) + (b * z) - c) > 0.0) Map[x, z] :+ disp Else Map[x, z] :- disp Next Next Next normalise() End Method Method generatePerlinMap(seed:Int = 0, multiplier:Float = 2.0) Rem Generate a map using the "perlin noise" alogorithm. PORTED and MODIFIED code from the blitzbasic.com code archives. **** ORIGINAL B3D CODE AUTHOR: Shawn C. Swift **** NOTE: This method will only work properly on maps with equal Width and Height dimensions that are a power of two: eg 256 x 256. seed: random seed. Use Millisecs() to generate a different result every time. multiplier: algorithm variation. End Rem If Not(Width = Height) Return SeedRnd(seed) Local size:Int = Width Local NoiseMapSize:Int = Floor(size / 2) Local max_height:Float = 1.0 Local NoiseMap:Float[NoiseMapSize + 1, NoiseMapSize + 1] For Local x:Int = 0 To (size - 1) For Local z:Int = 0 To (size - 1) Map[x, z] = RndFloat() Next Next max_height :* multiplier# Repeat For Local Noise_Z:Int = 0 To NoiseMapSize For Local Noise_X:Int = 0 To NoiseMapSize NoiseMap[Noise_X, Noise_Z] = RndFloat() * max_height Next Next Local ScaleDifference:Int = size / NoiseMapSize Local StepSize:Float = 1.0 / ScaleDifference For Local Noise_Z:Int = 0 To (NoiseMapSize - 1) For Local Noise_X:Int = 0 To (NoiseMapSize - 1) Local N1:Float = NoiseMap[Noise_X, Noise_Z] Local N2:Float = NoiseMap[(Noise_X + 1), Noise_Z] Local N3:Float = NoiseMap[Noise_X, (Noise_Z + 1)] Local N4:Float = NoiseMap[(Noise_X + 1), (Noise_Z + 1)] Local Hx:Int = Noise_X * ScaleDifference Local Hz:Int = Noise_Z * ScaleDifference Local Iy:Float = 0.0 For Local Height_Z:Int = 0 To (ScaleDifference - 1) Local ICy:Float = 1.0 - ((Cos(Iy * 180.0) + 1.0) / 2.0) Local Ix:Float = 0.0 For Local Height_X:Int = 0 To (ScaleDifference - 1) Local ICx:Float = 1.0 - ((Cos(Ix * 180.0) + 1.0) / 2.0) Local Na:Float = N1 * (1.0 - ICx) Local Nb:Float = N2 * ICx Local Nc:Float = N3 * (1.0 - ICx) Local Nd:Float = N4 * ICx Local y:Float = Map[(Hx + Height_X), (Hz + Height_Z)] Map[(Hx + Height_X), (Hz + Height_Z)] = (y + (Na + Nb) * (1.0 - ICy) + (Nc + Nd) * ICy) Ix = Ix + StepSize Next Iy = Iy + StepSize Next Next Next NoiseMapSize :* 0.5 max_height :* multiplier Until (NoiseMapSize < 1) normalise() End Method Rem generateMPDMap(seed, grain#) recurseMPD(x0, y0, x2, y2, grain#, level#) mpd#(x0, y0, x1, y1, x2, y2, grain#, level#) PORTED and MODIFIED code from the BlitzCoder.com code database. **** ORIGINAL B3D CODE AUTHOR: Simon Wetterlind 13-08-2002 **** **** v.0.9.3 - Revised: 18-11-2002 by Simon Wetterlind. **** These methods are used to generate a terrain map using the Midpoint Displacement (ie Diamond Square / Plasma) fractal algorithm. grain# is the "graininess" i.e. the influence of randomness. seed: random seed. Use Millisecs() to generate a different result every time. End Rem Method generateMPDMap(seed:Int = 0, grain:Float = 0.5) fill(-1.0) SeedRnd(seed) Map[0, 0] = RndFloat() Map[(Width - 1), 0] = RndFloat() Map[0, (Height - 1)]= RndFloat() Map[(Width - 1), (Height - 1)] = RndFloat() recurseMPD(0, 0, Width - 1, Height - 1, grain, 1.0) normalise() End Method Method recurseMPD(x0:Int, z0:Int, x2:Int, z2:Int, grain:Float, level:Float) If (x2 - x0 < 2) And (z2 - z0 < 2) Then Return Local v:Float, i:Float ' change the calculation of level# To (possibly) achieve ' strange results level = 2.0 * level Local x1:Int = Ceil((x0 + x2) / 2.0) Local z1:Int = Ceil((z0 + z2) / 2.0) v = Map[x1, z0] If v = -1.0 v = MPD(x0, z0, x1, z0, x2, z0, grain, level) i = v v = Map[x2, z1] If v = -1.0 v = MPD(x2, z0, x2, z1, x2, z2, grain, level) i :+ v v = Map[x1, z2] If v = -1.0 v = MPD(x0, z2, x1, z2, x2, z2, grain, level) i :+ v v = Map[x0, z1] If v = -1.0 v = MPD(x0, z0, x0, z1, x0, z2, grain, level) i :+ v If Map[x1, z1] = -1.0 Map[x1, z1] = i / 4.0 + Rnd(-grain, grain) / level RecurseMPD(x0, z0, x1, z1, grain, level) RecurseMPD(x1, z0, x2, z1, grain, level) RecurseMPD(x1, z1, x2, z2, grain, level) RecurseMPD(x0, z1, x1, z2, grain, level) End Method Method MPD:Float(x0:Int, z0:Int, x1:Int, z1:Int, x2:Int, z2:Int, grain:Float, level:Float) Local r:Float = 0.0 r :+ (Map[x0, z0] + Map[x2, z2]) / 2.0 If r < 0.0 r = 0.0 If r > 1.0 r = 1.0 Map[x1, z1] = r Return r End Method Method normalise() Rem Normalise map values to between 0.0 and 1.0 End Rem 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 End Method Method smoothen(k:Float = 0.5) Rem Smooth the map values. Some maps require more smoothing than others to create realistic terrains. k: smoothing factor. Minumum 0.0 (extreme). Maxumum 1.0 (no smoothing). End Rem For Local x:Int = 1 To (Width - 1) For Local z:Int = 0 To (Height - 1) Map[x, z] = Map[(x - 1), z] * (1 - k) + Map[x, z] * k Next Next For Local x:Int = (Width - 3) To 0 Step -1 For Local z:Int = 0 To (Height - 1) Map[x, z] = Map[(x + 1), z] * (1 - k) + Map[x, z] * k Next Next For Local x:Int = 0 To (Width - 1) For Local z:Int = 1 To (Height - 1) Map[x, z] = Map[x, (z - 1)] * (1 - k) + Map[x, z] * k Next Next For Local x:Int = 0 To (Width - 1) For Local z:Int = (Height - 3) To 0 Step -1 Map[x, z] = Map[x, (z + 1)] * (1 - k) + Map[x, z] * k Next Next End Method Method flatten(value:Float = 2.0) Rem 'Flatten' the map values. value: flattening amount. 1 = no flattening. Minimum 1. Suggested Maximum 4. End Rem For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) Map[x, z] = Map[x, z] ^ value Next Next End Method Method makeCoast(extent:Float = 0.5, depth:Float = 0.5) Rem Create a crude coastline effect. For best results, smoothing should be applied after this process. extent: extent of coastalisation. 0.0 to 1.0 = None To 100%. depth: depth. 0.0 To 1.0 = Shallow to deep. End Rem 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 Local e:Float = (maxv - minv) * extent Local d:Float = (maxv - minv) * depth For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) If Map[x, z] <= (minv + e) Map[x, z] :- d Next Next normalise() End Method Method invert() Rem Invert the map End Rem For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) Map[x, z] = 1.0 - Map[x, z] Next Next EndMethod Method islandise(amt:Float = 0.0) Rem Attempt to turn the heightmap into an island by applying an 'island mask'. amt: Height retention amount. Minimum 0.0, Maximum 1.0. The higher the retention amount the greater the distortion will be. EndRem If amt < 0.0 Then amt = 0.0 If amt > 1.0 Then amt = 1.0 Local mask:THeightMap = Create(Width, Height) mask.generateHillMap(MilliSecs(), 1000, (Width * 0.25), True) For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) Local val:Float = amt + mask.map[x, z] If val > 1.0 Then val = 1.0 Map[x, z] = Map[x, z] * val Next Next mask = Null GCCollect EndMethod Method lowerCenter() Rem Lower the center (basically, the opposite of islandise) EndRem Local mask:THeightMap = Create(Width, Height) mask.generateHillMap(MilliSecs(), 500, (Width * 0.25), True) For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) Map[x, z] = Map[x, z] * (1.0 - mask.map[x, z]) Next Next mask = Null GCCollect EndMethod Method importPNG:Int(url:String) Rem Import from a PNG file. url: source file name. Only the blue component data is used. Returns True if successful. End Rem Local pm:TPixmap = LoadPixmapPNG(url) If Not pm Return False Return importPixmap(pm) End Method Method exportBin:Int(url:String = "test.bin") Rem Save heightmap data as a binary file. File Structure: 1 * Int (Width) 1 * Int (Height) Width * Height * Floats (height values) url: target file name. Returns True if successful. End Rem Local result:Int = CreateFile(url) If Not result Then Return False Local file:TStream = OpenFile(url, False, True) WriteInt(file, Width) WriteInt(file, Height) For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) WriteFloat(file, Map[x, z]) Next Next CloseFile(file) Return True End Method Method importBin:Int(url:String = "test.bin") Rem Load heightmap data from a binary file that was previously saved using exportBin. url: source file name. Returns True if successful. End Rem Local file:TStream = OpenFile(url, True, False) If Not file Return False Width = ReadInt(file) Height = ReadInt(file) Map = New Float[Width, Height] For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) Map[x, z] = ReadFloat(file) Next Next CloseFile(file) Return True End Method Method importImageFile:Int(url:String) Rem Import from a jpg, png, tga or bmp file. url: source file name. Only the blue component data is used. Returns True if successful. End Rem Local pm:TPixmap = LoadPixmap(url) If Not pm Return False Return importPixmap(pm) End Method Method importPixmap:Int(pm:TPixmap) Rem Import from an existing TPixmap. pm: source TPixmap. Only the blue component data is used. Returns True if successful. End Rem If Not pm Return False If (pm.format <> PF_RGB888) Then pm = ConvertPixmap(pm, PF_RGB888) Width = PixmapWidth(pm) Height = PixmapHeight(pm) Map = New Float[Width, Height] For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) Local c:Int = ReadPixel(pm, x, z) c :| c Shr 16 Map[x, z] = $0000 | c Next Next normalise() Return True End Method Function blendMapsAdd(src1:THeightMap, src2:THeightMap, outp:THeightMap) Rem Blends two maps using an additive process. src1: source THeightMap instance #1. src2: source THeightMap instance #2. outp: output THeightMap instance into which the results will be stored. End Rem Local y1:Float, y2:Float For Local x:Int = 0 To (outp.Width - 1) For Local z:Int = 0 To (outp.Height - 1) If (x < src1.Width) And (z < src1.Height) y1 = src1.Map[x, z] Else y1 = 0.0 If (x < src2.Width) And (z < src2.Height) y2 = src2.Map[x, z] Else y2 = 0.0 outp.Map[x, z] = y1 + y2 Next Next outp.normalise() End Function Function blendMapsSub(src1:THeightMap, src2:THeightMap, outp:THeightMap) Rem Blends two maps using a subtractive process. src1: source THeightMap instance #1. src2: source THeightMap instance #2. outp: output THeightMap instance into which the results will be stored. End Rem Local x:Int, z:Int Local y1:Float, y2:Float For Local x:Int = 0 To (outp.Width - 1) For Local z:Int = 0 To (outp.Height - 1) If (x < src1.Width) And (z < src1.Height) y1 = src1.Map[x, z] Else y1 = 0.0 If (x < src2.Width) And (z < src2.Height) y2 = src2.Map[x, z] Else y2 = 0.0 outp.Map[x, z] = y1 - y2 Next Next outp.normalise() End Function Function blendMapsHi(src1:THeightMap, src2:THeightMap, outp:THeightMap) Rem Blends two maps, favouring the highest values. src1: source THeightMap instance #1. src2: source THeightMap instance #2. outp: output THeightMap instance into which the results will be stored. End Rem Local y1:Float, y2:Float For Local x:Int = 0 To (outp.Width - 1) For Local z:Int = 0 To (outp.Height - 1) If (x < src1.Width) And (z < src1.Height) y1 = src1.Map[x, z] Else y1 = 0.0 If (x < src2.Width) And (z < src2.Height) y2 = src2.Map[x, z] Else y2 = 0.0 If y1 > y2 outp.Map[x, z] = y1 Else outp.Map[x, z] = y2 Next Next outp.normalise() End Function Function blendMapsLo(src1:THeightMap, src2:THeightMap, outp:THeightMap) Rem Blends two maps, favouring the lowest values. src1: source THeightMap instance #1. src2: source THeightMap instance #2. outp: output THeightMap instance into which the results will be stored. End Rem Local y1:Float, y2:Float For Local x:Int = 0 To (outp.Width - 1) For Local z:Int = 0 To (outp.Height - 1) If (x < src1.Width) And (z < src1.Height) y1 = src1.Map[x, z] Else y1 = 0.0 If (x < src2.Width) And (z < src2.Height) y2 = src2.Map[x, z] Else y2 = 0.0 If y1 < y2 outp.Map[x, z] = y1 Else outp.Map[x, z] = y2 Next Next outp.normalise() End Function Method renderToPixmap(pm:TPixmap = Null, offx:Int = 0, offz:Int = 0) Rem Render the map to a BlitzMax pixmap object (grey scale style). pm: Destination TPixmap object. Must be the same dimensions as the heightmap. The pixmap format should be PF_RGB888. offx: x offset (pixels) into pm. >=0 and <pm width. offz: z offset (pixels) into pm. >=0 and <pm height. End Rem If Not pm Then Return Local pmw:Int = PixmapWidth(pm) Local pmh:Int = PixmapHeight(pm) If Not ((offx >= 0) And (offx < pmw)) Then Return If Not ((offz >= 0) And (offz < pmh)) Then Return If (pm.format <> PF_RGB888) Then pm = ConvertPixmap(pm, PF_RGB888) For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) If ((offx + x) < pmw) And ((offz + z) < pmh) Local v:Int = Floor(Map[x, z] * 255) WritePixel(pm, (offx + x), (offz + z), v Shl 16 | v Shl 8 | v Shl 0) EndIf Next Next End Method Method renderRectToPixmap(pm:TPixmap = Null, offx:Int = 0, offz:Int = 0, rectx:Int = 0, rectz:Int = 0, rectx2:Int = 0, rectz2:Int = 0) Rem Render a rectangular heightmap section to a pixmap (greyscale style). pm: Destination TPixmap object. The pixmap format should be PF_RGB888. offx: x offset (pixels) into pm. >=0 and <pm width. offz: z offset (pixels) into pm. >=0 and <pm height. rectx: top left x coordinate of heightmap rectz: top left z coordinate of heightmap rectx2: bottom right x coordinate of heightmap rectz2: bottom right z coordinate of heightmap EndRem If Not pm Then Return Local pmw:Int = PixmapWidth(pm) Local pmh:Int = PixmapHeight(pm) If Not ((offx >= 0) And (offx < pmw)) Return If Not ((offz >= 0) And (offz < pmh)) Return If Not ((rectx >= 0) And (rectx < Width)) Return If Not ((rectz >= 0) And (rectz < Height)) Return If Not (rectx2 >= rectx) Return If Not (rectz2 >= rectz) Return If (pm.format <> PF_RGB888) Then pm = ConvertPixmap(pm, PF_RGB888) For Local x:Int = rectx To rectx2 For Local z:Int = rectz To rectz2 If ((offx + x) < pmw) And ((offz + z) < pmh) Local v:Int = Floor(Map[x, z] * 255) WritePixel(pm, (offx + x), (offz + z), v Shl 16 | v Shl 8 | v Shl 0) EndIf Next Next End Method Method renderToColorPixmap(pm:TPixmap = Null, offx:Int = 0, offz:Int = 0, colarr:Int[] = Null, style:Int = STYLE_DISCREET) Rem Render the map to a BlitzMax pixmap object (colormap style). pm: Destination TPixmap object. Must be the same dimensions as the heightmap. The pixmap format should be PF_RGB888. offx: x offset (pixels) into pm. >=0 and <pm width. offz: z offset (pixels) into pm. >=0 and <pm height. colarr: Array of integers - each integer represents a color in RGB format. style: 0 - discrete colors, 1 - blended colors (use constants STYLE_DISCREET, STYLE_BLENDED). End Rem If Not pm Then Return Local pmw:Int = PixmapWidth(pm) Local pmh:Int = PixmapHeight(pm) If Not ((offx >= 0) And (offx < pmw)) Then Return If Not ((offz >= 0) And (offz < pmh)) Then Return If (pm.format <> PF_RGB888) Then pm = ConvertPixmap(pm, PF_RGB888) If (colarr.length <= 1) Or (colarr = Null) Then colarr = [$0000FF, $00FF00, $888888] Local div:Float = 1.0 / Float(colarr.length) Select style Case STYLE_DISCREET For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) If ((offx + x) < pmw) And ((offz + z) < pmh) For Local i:Int = 0 To colarr.length - 1 Local lvl:Float = Float(i) * div If (Map[x, z] >= lvl) Then WritePixel pm, (offx + x), (offz + z), colarr[i] Next EndIf Next Next Case STYLE_BLENDED For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) If ((offx + x) < pmw) And ((offz + z) < pmh) If Map[x,z] > (1.0 - div) WritePixel(pm, (offx + x), (offz + z), colarr[colarr.length - 1]) Else Local val:Float = 0.0 Local r:Int = 0 Local g:Int = 0 Local b:Int = 0 For Local i:Int = 0 To (colarr.length - 1) Local ir:Byte = colarr[i] Local ig:Byte = colarr[i] Shr 8 Local ib:Byte = colarr[i] Shr 16 Local factor:Float = (div - Abs(val - Map[x, z])) / div If factor < 0.0 Then factor = 0.0 Else If factor > 1.0 Then factor = 1.0 r :+ (factor * ir) g :+ (factor * ig) b :+ (factor * ib) val :+ div Next WritePixel(pm, (offx + x), (offz + z), b Shl 16 | g Shl 8 | r) EndIf EndIf Next Next EndSelect EndMethod Method renderRectToColorPixmap(pm:TPixmap, offx:Int = 0, offz:Int = 0, .. rectx:Int = 0, rectz:Int = 0, rectx2:Int = 0, rectz2:Int = 0, colarr:Int[] = Null, style:Int = STYLE_DISCREET) Rem Render a rectangular heightmap section to a BlitzMax pixmap object (colormap style). pm: Destination TPixmap object. The pixmap format should be PF_RGB888. offx: x offset (pixels) into pm. >=0 and <pm width. offz: z offset (pixels) into pm. >=0 and <pm height. rectx: top left x coordinate of heightmap rectz: top left z coordinate of heightmap rectx2: bottom right x coordinate of heightmap rectz2: bottom right z coordinate of heightmap style: 0 - discrete colors, 1 - blended colors (use constants STYLE_DISCREET, STYLE_BLENDED). End Rem If Not pm Then Return Local pmw:Int = PixmapWidth(pm) Local pmh:Int = PixmapHeight(pm) If Not ((offx >= 0) And (offx < pmw)) Return If Not ((offz >= 0) And (offz < pmh)) Return If Not ((rectx >= 0) And (rectx < Width)) Return If Not ((rectz >= 0) And (rectz < Height)) Return If Not (rectx2 >= rectx) Return If Not (rectz2 >= rectz) Return If (pm.format <> PF_RGB888) Then pm = ConvertPixmap(pm, PF_RGB888) If (colarr.length <= 1) Or (colarr = Null) Then colarr = [$0000FF, $00FF00, $888888] Local div:Float = 1.0 / Float(colarr.length) Select style Case STYLE_DISCREET For Local x:Int = rectx To rectx2 For Local z:Int = rectz To rectz2 If ((offx + x) < pmw) And ((offz + z) < pmh) For Local i:Int = 0 To colarr.length - 1 Local lvl:Float = Float(i) * div If (Map[x, z] >= lvl) Then WritePixel pm, (offx + x), (offz + z), colarr[i] Next EndIf Next Next Case STYLE_BLENDED For Local x:Int = rectx To rectx2 For Local z:Int = rectz To rectz2 If ((offx + x) < pmw) And ((offz + z) < pmh) If Map[x,z] > (1.0 - div) WritePixel(pm, (offx + x), (offz + z), colarr[colarr.length - 1]) Else Local val:Float = 0.0 Local r:Int = 0 Local g:Int = 0 Local b:Int = 0 For Local i:Int = 0 To (colarr.length - 1) Local ir:Byte = colarr[i] Local ig:Byte = colarr[i] Shr 8 Local ib:Byte = colarr[i] Shr 16 Local factor:Float = (div - Abs(val - Map[x, z])) / div If factor < 0.0 Then factor = 0.0 Else If factor > 1.0 Then factor = 1.0 r :+ (factor * ir) g :+ (factor * ig) b :+ (factor * ib) val :+ div Next WritePixel(pm, (offx + x), (offz + z), b Shl 16 | g Shl 8 | r) EndIf EndIf Next Next EndSelect EndMethod Method renderToTexturePixmap(pm:TPixmap = Null, offx:Int = 0, offz:Int = 0, arr:Object[] = Null, style:Int = STYLE_DISCREET) Rem Render the heightmap to a BlitzMax pixmap object (using textures). pm: Destination TPixmap object. Must be the same dimensions as the heightmap. The pixmap format should be PF_RGB888. offx: x offset (pixels) into pm. >=0 and <pm width. offz: z offset (pixels) into pm. >=0 and <pm height. arr: Array of urls (file names) OR pixmaps to use for texturing style: 0 - discrete edges, 1 - blended edges (use constants STYLE_DISCREET, STYLE_BLENDED). End Rem If Not pm Then Return Local pmw:Int = PixmapWidth(pm) Local pmh:Int = PixmapHeight(pm) If Not ((offx >= 0) And (offx < pmw)) Then Return If Not ((offz >= 0) And (offz < pmh)) Then Return If (pm.format <> PF_RGB888) Then pm = ConvertPixmap(pm, PF_RGB888) If (arr.length <= 1) Or (arr = Null) Then Return Local textures:TPixmap[arr.length] For Local i:Int = 0 To (textures.length - 1) If String(arr[i]) textures[i] = LoadPixmap(String(arr[i])) Else If TPixmap(arr[i]) Then textures[i] = TPixmap(arr[i]) EndIf If Not textures[i] textures[i] = CreatePixmap(Width, Height, PF_RGB888) Local r:Byte = Rand(64, 255) Local g:Byte = Rand(64, 255) Local b:Byte = Rand(64, 255) For Local x:Int = 0 To PixmapWidth(textures[i]) - 1 For Local z:Int = 0 To PixmapHeight(textures[i]) - 1 WritePixel textures[i], x, z, 0 Shl 24 | b Shl 16 | g Shl 8 | r Next Next EndIf If textures[i].format <> PF_RGB888 Then textures[i] = ConvertPixmap(textures[i], PF_RGB888) If (Width <> PixmapWidth(textures[i])) Or (Height <> PixmapHeight(textures[i])) Then textures[i] = ResizePixmap(textures[i], Width, Height) Next Local div:Float = 1.0 / Float(textures.length) Select style Case STYLE_DISCREET For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) If ((offx + x) < pmw) And ((offz + z) < pmh) For Local i:Int = 0 To textures.length - 1 Local lvl:Float = Float(i) * div If (Map[x, z] >= lvl) Then WritePixel pm, (offx + x), (offz + z), ReadPixel(textures[i], x, z) Next EndIf Next Next Case STYLE_BLENDED For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) If ((offx + x) < pmw) And ((offz + z) < pmh) If Map[x, z] > (1.0 - div) WritePixel pm, (offx + x), (offz + z), ReadPixel(textures[textures.length - 1], x, z) Else Local val:Float = 0.0 Local r:Int = 0 Local g:Int = 0 Local b:Int = 0 For Local i:Int = 0 To (textures.length - 1) Local px:Int = ReadPixel(textures[i], x, z) Local ir:Byte = px Local ig:Byte = px Shr 8 Local ib:Byte = px Shr 16 Local factor:Float = (div - Abs(val - Map[x, z])) / div If factor < 0.0 Then factor = 0.0 Else If factor > 1.0 Then factor = 1.0 r :+ (factor * ir) g :+ (factor * ig) b :+ (factor * ib) val :+ div Next WritePixel(pm, (offx + x), (offz + z), b Shl 16 | g Shl 8 | r) EndIf EndIf Next Next EndSelect For Local i:Int = 0 To (textures.length - 1) textures[i] = Null Next GCCollect EndMethod Method blendBrushRaise(brush:THeightMap, offx:Int = 0, offz:Int = 0, p:Float = 0.1) Rem Blends a brush (ie a small heightmap) with the heightmap using an additive process. brush: brush THeightMap instance. offx: x coordinate in heightmap to start brush blend offz: z coordinate in heightmap to start brush blend p: brush pressure: 0.0 to 1.0. (0% to 100%) End Rem If Not ((offx >= 0) And (offx < Width)) Return If Not ((offz >= 0) And (offz < Height)) Return If (p < 0.0) p = 0.0 Else If (p > 1.0) p = 1.0 For Local x:Int = 0 To (brush.Width - 1) For Local z:Int = 0 To (brush.Height - 1) Local y1:Float = brush.Map[x, z] * p If ((offx + x) < Width) And ((offz + z) < Height) Local y2:Float = Map[offx + x, offz + z] + y1 If (y2 < 0.0) y2 = 0.0 Else If (y2 > 1.0) y2 = 1.0 Map[offx + x, offz + z] = y2 EndIf Next Next End Method Method blendBrushLower(brush:THeightMap, offx:Int = 0, offz:Int = 0, p:Float = 0.1) Rem Blends a brush (ie a small heightmap) with the heightmap using an subtractive process. brush: brush THeightMap instance. offx: x coordinate in heightmap To start brush blend offz: z coordinate in heightmap To start brush blend p: brush pressure: 0.0 To 1.0. (0% To 100%) End Rem If Not ((offx >= 0) And (offx < Width)) Return If Not ((offz >= 0) And (offz < Height)) Return If (p < 0.0) p = 0.0 Else If (p > 1.0) p = 1.0 For Local x:Int = 0 To (brush.Width - 1) For Local z:Int = 0 To (brush.Height - 1) Local y1:Float = brush.Map[x, z] * p If ((offx + x) < Width) And ((offz + z) < Height) Local y2:Float = Map[offx + x, offz + z] - y1 If (y2 < 0.0) Then y2 = 0.0 Else If (y2 > 1.0) Then y2 = 1.0 Map[offx + x, offz + z] = y2 EndIf Next Next End Method Method blendBrushHi(brush:THeightMap, offx:Int = 0, offz:Int = 0, clear:Int = False) Rem Blends a brush (ie a small heightmap) with the heightmap favouring the highest value. brush: brush THeightMap instance. offx: x coordinate in heightmap To start brush blend offz: z coordinate in heightmap To start brush blend clear: clear the values in the brush that were not blended: True/False End Rem If Not ((offx >= 0) And (offx < Width)) Return If Not ((offz >= 0) And (offz < Height)) Return For Local x:Int = 0 To (brush.Width - 1) For Local z:Int = 0 To (brush.Height - 1) If ((offx + x) < Width) And ((offz + z) < Height) If brush.Map[x, z] > Map[offx + x, offz + z] Map[offx + x, offz + z] = brush.Map[x, z] Else If clear Then brush.Map[x, z] = 0.0 EndIf EndIf Next Next End Method Method blendBrushLo(brush:THeightMap, offx:Int = 0, offz:Int = 0, clear:Int = False) Rem Blends a brush (ie a small heightmap) with the heightmap favouring the lowest value. brush: brush THeightMap instance. offx: x coordinate in heightmap To start brush blend offz: z coordinate in heightmap To start brush blend clear: clear the values in the brush that were not blended: True/False End Rem If Not ((offx >= 0) And (offx < Width)) Return If Not ((offz >= 0) And (offz < Height)) Return For Local x:Int = 0 To (brush.Width - 1) For Local z:Int = 0 To (brush.Height - 1) If ((offx + x) < Width) And ((offz + z) < Height) If brush.Map[x, z] < Map[offx + x, offz + z] Map[offx + x, offz + z] = brush.Map[x, z] Else If clear Then brush.Map[x, z] = 1.0 EndIf EndIf Next Next End Method Method renderShadowsToPixmap(pm:TPixmap, sun:TSimpleVector = Null, k:Float = 0.5) Rem PORTED and MODIFIED code from the blitzbasic.com code archives. **** ORIGINAL AUTHOR: Tim Fisher **** Render a lightmap/shadowmap onto a pixmap pm: Destination TPixmap object. The pixmap format should be PF_RGBA8888. sun: Sun's relative position (as a TSimpleVector object) k#: smoothing factor. Minumum 0.0 (extreme). Maxumum 1.0 (no smoothing). End Rem If Not pm Then Return If (pm.format <> PF_RGB888) Then pm = ConvertPixmap(pm, PF_RGB888) Local pmw:Int = PixmapWidth(pm) Local pmh:Int = PixmapHeight(pm) If Not sun Then sun = New TSimpleVector sun.setXYZ(1, 2, -1) EndIf Local tmp1:TSimpleVector = New TSimpleVector Local tmp2:TSimpleVector = New TSimpleVector Local norm:TSimpleVector[Width, Height] For Local i:Int = 0 To Width - 1 For Local j:Int = 0 To Height - 1 norm[i, j] = New TSimpleVector Next Next Local v:TSimpleVector[9] Local f:TSimpleVector[9] For Local i:Int = 0 To (v.length - 1) v[i] = New TSimpleVector f[i] = New TSimpleVector Next For Local i:Int = 1 To (Width - 2) For Local j:Int = 1 To (Height - 2) v[0].setXYZ((i)-Width/2,Map[i, j]*255,(j)-Height/2) v[1].setXYZ((i-1)-Width/2,Map[i-1, j-1]*255,(j-1)-Height/2) v[2].setXYZ((i)-Width/2,Map[i, j-1]*255,(j-1)-Height/2) v[3].setXYZ((i+1)-Width/2,Map[i+1, j-1]*255,(j-1)-Height/2) v[4].setXYZ((i+1)-Width/2,Map[i+1, j]*255,(j)-Height/2) v[5].setXYZ((i+1)-Width/2,Map[i+1, j+1]*255,(j+1)-Height/2) v[6].setXYZ((i)-Width/2,Map[i, j+1]*255,(j+1)-Height/2) v[7].setXYZ((i-1)-Width/2,Map[i-1, j+1]*255,(j+1)-Height/2) v[8].setXYZ((i-1)-Width/2,Map[i-1, j]*255,(j)-Height/2) tmp1.sub(v[8], v[1]) tmp2.sub(v[1], v[0]) tmp1.normalise() tmp2.normalise() f[0].crossProduct(tmp1, tmp2) tmp1.sub(v[2], v[1]) tmp2.sub(v[1], v[0]) tmp1.normalise() tmp2.normalise() f[1].crossProduct(tmp1, tmp2) tmp1.sub(v[3], v[2]) tmp2.sub(v[2], v[0]) tmp1.normalise() tmp2.normalise() f[2].crossProduct(tmp1, tmp2) tmp1.sub(v[4], v[3]) tmp2.sub(v[3], v[0]) tmp1.normalise() tmp2.normalise() f[3].crossProduct(tmp1, tmp2) tmp1.sub(v[6], v[5]) tmp2.sub(v[5], v[0]) tmp1.normalise() tmp2.normalise() f[4].crossProduct(tmp1, tmp2) tmp1.sub(v[0], v[5]) tmp2.sub(v[5], v[4]) tmp1.normalise() tmp2.normalise() f[5].crossProduct(tmp1, tmp2) tmp1.sub(v[6], v[0]) tmp2.sub(v[0], v[7]) tmp1.normalise() tmp2.normalise() f[6].crossProduct(tmp1, tmp2) tmp1.sub(v[0], v[8]) tmp2.sub(v[8], v[7]) tmp1.normalise() tmp2.normalise() f[7].crossProduct(tmp1, tmp2) tmp1.add(f[1], f[2]) tmp2.add(tmp1, f[3]) tmp1.add(tmp2, f[4]) tmp2.add(tmp1, f[6]) tmp1.add(tmp2, f[7]) tmp2.add(tmp1, f[0]) tmp1.add(tmp2, f[5]) tmp1.normalise() norm[i - 1, j - 1].X = tmp1.X * 128 norm[i - 1, j - 1].Y = tmp1.Y * 128 norm[i - 1, j - 1].Z = tmp1.Z * 128 Next Next sun.normalise() Local lmap:Int[, ] = New Int[Width, Height] For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) lmap[x, z] = 0 Next Next For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) Local f:Float = 0.1 tmp1.X = norm[x, z].X tmp1.Y = norm[x, z].Y tmp1.Z = norm[x, z].Z tmp1.normalise() f :+ (0.5 * (1.0 + TSimpleVector.dotProduct(tmp1, sun) )) If f > 1 Then f = 1 If LockedLine(x, z, Map[x, z] * 255, sun) Then f :/ 2 ' shade the shadow If f < 0 Then f = 0 lmap[x, z] = Int(f * 255) Next Next For Local x:Int = 1 To (Width - 1) For Local z:Int = 0 To (Height - 1) lmap[x, z] = lmap[(x - 1), z] * (1 - k) + lmap[x, z] * k Next Next For Local x:Int = (Width - 3) To 0 Step -1 For Local z:Int = 0 To (Height - 1) lmap[x, z] = lmap[(x + 1), z] * (1 - k) + lmap[x, z] * k Next Next For Local x:Int = 0 To (Width - 1) For Local z:Int = 1 To (Height - 1) lmap[x, z] = lmap[x, (z - 1)] * (1 - k) + lmap[x, z] * k Next Next For Local x:Int = 0 To (Width - 1) For Local z:Int = (Height - 3) To 0 Step -1 lmap[x, z] = lmap[x, (z + 1)] * (1 - k) + lmap[x, z] * k Next Next For Local x:Int = 0 To (Width - 1) For Local z:Int = 0 To (Height - 1) WritePixel (pm, x, z, lmap[x, z] Shl 16 | lmap[x, z] Shl 8 | lmap[x, z]) Next Next EndMethod Method LockedLine:Int(x1:Float, y1:Float, z1:Float, sun:TSimpleVector) Rem Utility function used by renderShadowsToPixmap PORTED and MODIFIED code from the blitzbasic.com code archives. **** ORIGINAL AUTHOR: Tim Fisher **** EndRem Local x2:Float = sun.X Local y2:Float = sun.Y Local z2:Float = sun.Z While Not ((x1 > Width - 1) Or (y1 > Height - 1) Or (z1 > 255) Or (x1 < 0) Or (y1 < 0)) If (Int((Map[x1, y1]) * 255) > z1) Then Return True x1 :+ (x2 * 2) y1 :+ (z2 * 2) z1 :+ (y2 * 2) Wend Return False EndMethodEnd TypeType TSimpleVector Rem Utility class used by shadow mapping code. PORTED and MODIFIED code from the blitzbasic.com code archives. **** ORIGINAL AUTHORS: Chroma and Tim Fisher **** EndRem Field tol:Float Field X:Float Field Y:Float Field Z:Float Method New() tol = 0.001 X = 0.0 Y = 0.0 Z = 0.0 EndMethod Method setXYZ(vx:Float = 0.0, vy:Float = 0.0, vz:Float = 0.0) X = vx Y = vy Z = vz EndMethod Method normalise() Local mag:Float = Sqr((X * X) + (Y * Y) + (Z * Z)) X :/ mag Y :/ mag Z :/ mag If (Abs(X) < tol) X = 0.0 If (Abs(Y) < tol) Y = 0.0 If (Abs(Z) < tol) Z = 0.0 EndMethod Method add(v2:TSimpleVector, v3:TSimpleVector) X = v2.X + v3.X Y = v2.Y + v3.Y Z = v2.Z + v3.Z EndMethod Method sub(v2:TSimpleVector, v3:TSimpleVector) X = v2.X - v3.X Y = v2.Y - v3.Y Z = v2.Z - v3.Z EndMethod Method crossProduct(u:TSimpleVector, v:TSimpleVector) X = u.Y * v.Z - u.Z * v.Y Y = -u.X * v.Z + u.Z * v.X Z = u.X * v.Y - u.Y * v.X EndMethod Function dotProduct:Float(u:TSimpleVector, v:TSimpleVector) Return (u.X * v.X) + (u.Y * v.Y) + (u.Z * v.Z) End FunctionEnd Type
Rem Heightmap Toolbox Test #1 - Generation and Processing example. (*) - Indicates an essential step in creating and setting up a heightmap. (#) - Indicates an essential step in rendering a heightmap to the screen.End Rem' ------------- INITIALISATION -------------SuperStrict'(*) Import the core Heightmap Toolbox classImport "hmap03.6.bmx"'Set the application titleAppTitle = "Heightmap Generation and Processing Example"'(#) Set the screen resolutionGraphics 800, 600, 0'Set the blend modeSetBlend(SOLIDBLEND) '(*) Create a new blank THeightMap with dimensions width X height.'For MOST algorithms (all except Perlin) the width and height can be 'dissimilar values (eg 355, 200) but usually the width and height 'are the same as each other and a power of two.Global HMap1:THeightMap = THeightMap.Create(256, 256)'(#) Declare and create a TPixmap object on which to render a greyscale representation of the heightmap.'Note that the pixmap's width and height should be the same as the heightmap's width and height.Global Pm1:TPixmap = CreatePixmap(HMap1.Width, HMap1.Height, PF_RGB888)ClsdrawWait()'(*) Generate a heightmap using the Midpoint Displacement algorithm'There are several algorithms to choose from, some quicker than others, that produce'differing results. See the handleKeys() function below for other methods.HMap1.generateMPDMap(MilliSecs(), 0.5)drawMenu()'(#) Render a greyscale representation of the heightmap to the relevent TPixmap objectHMap1.renderToPixmap(Pm1, 0, 0)'(#) Draw the pixmap onto the backbufferDrawPixmap(Pm1, 0, 0)Flip' ------------- MAIN LOOP -------------While Not KeyHit(KEY_ESCAPE) handleKeys() drawScreen() Delay 1WendEnd' ------------- FUNCTIONS -------------Function handleKeys() If KeyHit(KEY_1) drawWait() HMap1.generateMPDMap(MilliSecs(), 0.5) HMap1.renderToPixmap(Pm1, 0, 0) Return EndIf If KeyHit(KEY_2) drawWait() HMap1.generatePerlinMap(MilliSecs(), 1.7) HMap1.renderToPixmap(Pm1, 0, 0) Return EndIf If KeyHit(KEY_3) drawWait() HMap1.generateHillMap(MilliSecs(), 500, (HMap1.Width * 0.25)) HMap1.renderToPixmap(Pm1, 0, 0) Return EndIf If KeyHit(KEY_4) drawWait() HMap1.generateParticleMap(MilliSecs(), 500, 1000, METHOD_STICKY) HMap1.renderToPixmap(Pm1, 0, 0) Return EndIf If KeyHit(KEY_5) drawWait() HMap1.generateParticleMap(MilliSecs(), 500, 1000, METHOD_ROLLING) HMap1.renderToPixmap(Pm1, 0, 0) Return EndIf If KeyHit(KEY_6) drawWait() HMap1.generateFaultMap(MilliSecs(), 500) HMap1.renderToPixmap(Pm1, 0, 0) Return EndIf If KeyHit(KEY_7) drawWait() HMap1.generateRandomMap(MilliSecs()) HMap1.renderToPixmap(Pm1, 0, 0) Return EndIf If KeyHit(KEY_8) drawWait() HMap1.fill(0.0) HMap1.renderToPixmap(Pm1, 0, 0) Return EndIf If KeyHit(KEY_F1) drawWait() If SavePixmapPNG(pm1, "test.png") Then .. Notify("test.png saved.") Else Notify("test.png could NOT be saved.") Return EndIf If KeyHit(KEY_F2) drawWait() Local result:Int = HMap1.exportBIN("test.bin") If result Notify("test.bin saved.") Else Notify("test.bin could NOT be saved.") Return EndIf If KeyHit(KEY_F5) drawWait() Local result:Int = HMap1.importPNG("test.png") If Not result Notify("test.png could NOT be imported. Check that the file exists.") Pm1 = ResizePixmap(Pm1, HMap1.Width, HMap1.Height) HMap1.renderToPixmap(Pm1, 0, 0) Return EndIf If KeyHit(KEY_F6) drawWait() Local result:Int = HMap1.importBIN("test.bin") If Not result Notify("test.bin could NOT be imported. Check that the file exists.") Pm1 = ResizePixmap(Pm1, HMap1.Width, HMap1.Height) HMap1.renderToPixmap(Pm1, 0, 0) Return EndIf If KeyHit(KEY_S) drawWait() HMap1.smoothen(0.8) HMap1.renderToPixmap(Pm1, 0, 0) Return EndIf If KeyHit(KEY_F) drawWait() HMap1.flatten(1.5) HMap1.renderToPixmap(Pm1, 0, 0) Return EndIf If KeyHit(KEY_L) drawWait() HMap1.lowerCenter() HMap1.renderToPixmap(Pm1, 0, 0) Return EndIf If KeyHit(KEY_C) drawWait() HMap1.makeCoast(0.25, 0.25) HMap1.renderToPixmap(Pm1, 0, 0) Return EndIf If KeyHit(KEY_I) drawWait() HMap1.islandise() HMap1.renderToPixmap(Pm1, 0, 0) Return EndIfEnd FunctionFunction drawWait() DrawText "Please wait...", 0, 0 Flip EndFunctionFunction drawScreen() Cls drawMenu() DrawPixmap(Pm1, 0, 0) Flip EndFunctionFunction drawMenu() DrawText "Press a key (ESC quits):", 5, 270 DrawText "------------------------", 5, 285 DrawText "(1) Midpoint Displacement", 5, 300 DrawText "(2) Perlin", 5, 320 DrawText "(3) Hill", 5, 340 DrawText "(4) Sticky Particle", 5, 360 DrawText "(5) Rolling Particle", 5, 380 DrawText "(6) Faultline", 5, 400 DrawText "(7) Random", 5, 420 DrawText "(8) Clear", 5, 440 DrawText "(S) Smoothen", 250, 300 DrawText "(F) Flatten", 250, 320 DrawText "(L) Lower Center", 250, 340 DrawText "(C) Make Coast", 250, 360 DrawText "(I) Islandise", 250, 380 DrawText "(F1) Export PNG - test.png", 450, 300 DrawText "(F2) Export BIN - test.bin", 450, 320 DrawText "(F5) Import PNG - test.png", 450, 380 DrawText "(F6) Import BIN - test.bin", 450, 400End Function
Rem Heightmap Toolbox Test #2 - Blending example.End Rem' ------------- INITIALISATION -------------SuperStrictImport "hmap03.6.bmx"AppTitle = "Blending Example"Graphics 800, 600, 0SetBlend(SOLIDBLEND)Global HMap1:THeightMap = THeightMap.Create(256, 256)Global HMap2:THeightMap = THeightMap.Create(256, 256)Global HMapR:THeightMap = THeightMap.Create(256, 256)Global Pm1:TPixmap = CreatePixmap(HMap1.Width, HMap1.Height, PF_RGB888)Global Pm2:TPixmap = CreatePixmap(HMap2.Width, HMap2.Height, PF_RGB888)Global PmR:TPixmap = CreatePixmap(HMapR.Width, HMapR.Height, PF_RGB888)drawWait()HMap1.generateHillMap MilliSecs(), 500, (HMap1.Width * 0.25)HMap2.generateParticleMap MilliSecs(), 500, 1000, Rand(METHOD_STICKY, METHOD_ROLLING)HMap1.renderToPixmap Pm1, 0, 0HMap2.renderToPixmap Pm2, 0, 0' ------------- MAIN LOOP -------------While Not KeyHit(KEY_ESCAPE) handleKeys() drawScreen() Delay 1WendEnd' ------------- FUNCTIONS -------------Function handleKeys() If KeyHit(KEY_1) drawWait() THeightMap.BlendMapsAdd(HMap1, HMap2, HMapR) HMapR.renderToPixmap(PmR, 0, 0) Return EndIf If KeyHit(KEY_2) drawWait() THeightMap.BlendMapsSub(HMap1, HMap2, HMapR) HMapR.renderToPixmap(PmR, 0, 0) Return EndIf If KeyHit(KEY_3) drawWait() THeightMap.BlendMapsHi(HMap1, HMap2, HMapR) HMapR.renderToPixmap(PmR, 0, 0) Return EndIf If KeyHit(KEY_4) drawWait() THeightMap.BlendMapsLo(HMap1, HMap2, HMapR) HMapR.renderToPixmap(PmR, 0, 0) Return EndIfEnd FunctionFunction drawWait() DrawText "Please wait...", 0, 0 Flip EndFunctionFunction drawScreen() Cls drawMenu() DrawPixmap(Pm1, 0, 0) DrawPixmap(Pm2, Pm1.Width, 0) DrawPixmap(PmR, Pm1.Width + Pm2.Width, 0) Flip EndFunctionFunction drawMenu() DrawText "Press a key (ESC quits):", 5, 270 DrawText "------------------------", 5, 285 DrawText "(1) Blend Add", 5, 300 DrawText "(2) Blend Sub", 5, 320 DrawText "(3) Blend Hi", 5, 340 DrawText "(4) Blend Lo", 5, 360End Function
Rem Heightmap Toolbox Test #3 - Brush example. NOTE: A 'brush' in HeightMap Toolbox is just a small THeightMap instance.End Rem' ------------- INITIALISATION -------------SuperStrictImport "hmap03.6.bmx"AppTitle = "Brush Example"Graphics 800, 600, 0Global HMap1:THeightMap = THeightMap.Create(256, 256)HMap1.generateMPDMap(MilliSecs(), 0.5)Global Brush:THeightMap = THeightMap.Create(32, 32)Brush.generateHillMap(MilliSecs(), 100, 15, True)Global Pm1:TPixmap = CreatePixmap(HMap1.Width, HMap1.Height, PF_RGB888)HMap1.renderToPixmap(Pm1, 0, 0)Global PmBrush:TPixmap = CreatePixmap(Brush.Width, Brush.Height, PF_RGB888)Brush.renderToPixmap(PmBrush, 0, 0)SetBlend ALPHABLENDSetAlpha 1.0HideMouse' ------------- MAIN LOOP -------------While Not KeyHit(KEY_ESCAPE) Cls DrawText "Left Click - Raise", 5, 300 DrawText "Right Click - Lower", 5, 320 DrawText "[Space] - Highest", 5, 340 DrawText "[Enter] - Lowest", 5, 360 DrawText "I - Invert", 5, 380 Local mx:Int = MouseX() Local my:Int = MouseY() DrawPixmap(Pm1, 0, 0) DrawPixmap(PmBrush, mx, my) If MouseDown(1) If mx >= 0 And mx < HMap1.Width If my >= 0 And my < HMap1.Height HMap1.BlendBrushRaise(Brush, mx, my, 0.05) HMap1.renderRectToPixmap(Pm1, 0, 0, mx, my, (mx + (Brush.Width - 1)), (my + (Brush.Height - 1))) EndIf EndIf EndIf If MouseDown(2) If mx >= 0 And mx < HMap1.Width If my >= 0 And my < HMap1.Height HMap1.BlendBrushLower(Brush, mx, my, 0.05) HMap1.renderRectToPixmap(Pm1, 0, 0, mx, my, (mx + (Brush.Width - 1)), (my + (Brush.Height - 1))) EndIf EndIf EndIf If KeyHit(KEY_SPACE) If mx >= 0 And mx < HMap1.Width If my >= 0 And my < HMap1.Height HMap1.BlendBrushHi(Brush, mx, my) HMap1.renderRectToPixmap(Pm1, 0, 0, mx, my, (mx + (Brush.Width - 1)), (my + (Brush.Height - 1))) EndIf EndIf EndIf If KeyHit(KEY_ENTER) If mx >= 0 And mx < HMap1.Width If my >= 0 And my < HMap1.Height HMap1.BlendBrushLo(Brush, mx, my) HMap1.renderRectToPixmap(Pm1, 0, 0, mx, my, (mx + (Brush.Width - 1)), (my + (Brush.Height - 1))) EndIf EndIf EndIf If KeyHit(KEY_I) Brush.invert() Brush.RenderToPixmap(PmBrush) EndIf Flip Delay 1WendEnd
Rem Heightmap Toolbox Test #4 - Shadow Map example.End Rem' ------------- INITIALISATION -------------SuperStrictImport "hmap03.6.bmx"AppTitle = "Shadow Map Example"Graphics 800, 600, 0 drawWait()Global HMap1:THeightMap = THeightMap.Create(256, 256)HMap1.generateHillMap(MilliSecs(), 500, (HMap1.Width * 0.25))Global Pm1:TPixmap = CreatePixmap(HMap1.Width, HMap1.Height, PF_RGB888)HMap1.renderToPixmap(Pm1, 0, 0)Global PmShadow:TPixmap = CreatePixmap(HMap1.Width, HMap1.Height, PF_RGB888)Global Sun:TSimpleVector = New TSimpleVectorSun.setXYZ(1, 2, -1)HMap1.renderShadowsToPixmap(PmShadow, Sun)' ------------- MAIN LOOP -------------While Not KeyHit(KEY_ESCAPE) handleKeys() drawScreen() Delay(1)WendEnd' ------------- FUNCTIONS -------------Function handleKeys() If KeyHit(KEY_1) drawWait() HMap1.generateHillMap(MilliSecs(), 500, (HMap1.Width * 0.25)) HMap1.renderToPixmap(Pm1, 0, 0) HMap1.renderShadowsToPixmap(PmShadow, Sun) Return EndIf If KeyHit(KEY_F1) drawWait() If SavePixmapPNG(PmShadow, "shadows.png") Then .. Notify("shadows.png saved.") Else Notify("shadows.png could NOT be saved.") If SavePixmapPNG(Pm1, "test.png") Then .. Notify("test.png saved.") Else Notify("test.png could NOT be saved.") Return EndIfEnd FunctionFunction drawWait() DrawText "Please wait...", 0, 0 Flip EndFunctionFunction drawScreen() Cls drawMenu() DrawPixmap(Pm1, 0, 0) DrawPixmap(pmShadow, Pm1.Width, 0) Flip EndFunctionFunction drawMenu() DrawText "Press a key (ESC quits):", 5, 270 DrawText "------------------------", 5, 285 DrawText "(1) Generate heightmap and shadow map", 5, 300 DrawText "(F1) Export PNGs - test.png & shadows.png", 5, 320 End Function
Rem Heightmap Toolbox Test #5 - Color Map example. End Rem' ------------- INITIALISATION -------------SuperStrictImport "hmap03.6.bmx"AppTitle = "Color Map Example"Graphics 800, 600, 0SetBlend SOLIDBLEND Global HMap1:THeightMap = THeightMap.Create(256, 256)Global PmColor:TPixmap = CreatePixmap(HMap1.Width, HMap1.Height, PF_RGB888)Global PmColor2:TPixmap = CreatePixmap(HMap1.Width, HMap1.Height, PF_RGB888)Global ColorArray:Int[] = [$0000FF, $80FF00, $00FF00, $888888, $AAAAAA, $FFFFFF]drawWait()HMap1.generateHillMap(MilliSecs(), 1000, (HMap1.Width * 0.25), True)HMap1.renderToColorPixmap(PmColor, 0, 0, ColorArray, STYLE_DISCREET)HMap1.renderToColorPixmap(PmColor2, 0, 0, ColorArray, STYLE_BLENDED)' ------------- MAIN LOOP -------------While Not KeyHit(KEY_ESCAPE) handleKeys() drawScreen() Delay 1WendEnd' ------------- FUNCTIONS -------------Function handleKeys() If KeyHit(KEY_1) drawWait() HMap1.generateHillMap(MilliSecs(), 1000, (HMap1.Width * 0.25), True) HMap1.renderToColorPixmap(PmColor, 0, 0, ColorArray, STYLE_DISCREET) HMap1.renderToColorPixmap(PmColor2, 0, 0, ColorArray, STYLE_BLENDED) Return EndIf If KeyHit(KEY_F1) drawWait() If SavePixmapPNG(PMColor, "testc.png") Then .. Notify("testc.png saved.") Else Notify("testc.png could Not be saved.") If SavePixmapPNG(PMColor2, "testc2.png") Then .. Notify("testc2.png saved.") Else Notify("testc2.png could NOT be saved.") Return EndIfEnd FunctionFunction drawWait() DrawText "Please wait...", 0, 0 Flip EndFunctionFunction drawScreen() Cls drawMenu() DrawPixmap PmColor, 0, 0 DrawPixmap pmColor2, PmColor.width + 5, 0 Flip EndFunctionFunction drawMenu() DrawText "Press a key (ESC quits):", 5, 270 DrawText "------------------------", 5, 285 DrawText "(1) Generate heightmap and color maps", 5, 300 DrawText "(F1) Export PNGs - testc.png, testc2.png", 5, 320 End Function