Heightmap Toolbox 0.3.6
(the core Heightmap class)
hmap03.6.bmx:
(the core Heightmap class)
hmap03.6.bmx:
Rem HeightMap Toolbox V0.3.6 August 2008 PURPOSE 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 lockedLine UTILITY CLASS TSimpleVector setXYZ normalise add sub crossProduct dotProduct End Rem SuperStrict Import BRL.Random Import BRL.PNGLoader Import BRL.JPGLoader Import BRL.TGALoader Import BRL.BMPLoader Import BRL.FileSystem Const STYLE_DISCREET:Int = 0 Const STYLE_BLENDED:Int = 1 Const METHOD_STICKY:Int = 0 Const METHOD_ROLLING:Int = 1 Type 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 EndMethod End Type Type 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 Function End Type