Ooops
January 15, 2021, 06:39:02 PM

Author Topic: [bmx] Heightmap Toolbox V0.3.6 by impixi [ 1+ years ago ]  (Read 456 times)

Offline BlitzBot

  • Jr. Member
  • **
  • Posts: 1
[bmx] Heightmap Toolbox V0.3.6 by impixi [ 1+ years ago ]
« on: June 29, 2017, 12:28:38 AM »
Title : Heightmap Toolbox V0.3.6
Author : impixi
Posted : 1+ years ago

Description : A self-contained class of heightmap creation, processing, exporting, and importing utilities.

Code :
Code: BlitzMax
  1. See below...


Comments :


impixi(Posted 1+ years ago)

 Heightmap Toolbox 0.3.6(the core Heightmap class)hmap03.6.bmx:
Code: [Select]
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




impixi(Posted 1+ years ago)

 test1.bmx:
Code: [Select]
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 class
Import "hmap03.6.bmx"

'Set the application title
AppTitle = "Heightmap Generation and Processing Example"

'(#) Set the screen resolution
Graphics 800, 600, 0

'Set the blend mode
SetBlend(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)

Cls

drawWait()

'(*) 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 object
HMap1.renderToPixmap(Pm1, 0, 0)

'(#) Draw the pixmap onto the backbuffer
DrawPixmap(Pm1, 0, 0)

Flip

' ------------- MAIN LOOP -------------

While Not KeyHit(KEY_ESCAPE)

handleKeys()
drawScreen()

Delay 1

Wend

End

' ------------- 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
EndIf


End Function

Function drawWait()

DrawText "Please wait...", 0, 0
Flip

EndFunction

Function drawScreen()

Cls
drawMenu()
DrawPixmap(Pm1, 0, 0)
Flip

EndFunction

Function 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, 400

End Function
test2.bmx:
Code: [Select]
Rem

Heightmap Toolbox Test #2 - Blending example.

End Rem


' ------------- INITIALISATION -------------

SuperStrict

Import "hmap03.6.bmx"

AppTitle = "Blending Example"

Graphics 800, 600, 0
SetBlend(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, 0
HMap2.renderToPixmap Pm2, 0, 0

' ------------- MAIN LOOP -------------

While Not KeyHit(KEY_ESCAPE)

handleKeys()
drawScreen()

Delay 1

Wend

End

' ------------- 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
EndIf


End Function

Function drawWait()

DrawText "Please wait...", 0, 0
Flip

EndFunction

Function drawScreen()

Cls
drawMenu()
DrawPixmap(Pm1, 0, 0)
DrawPixmap(Pm2, Pm1.Width, 0)
DrawPixmap(PmR, Pm1.Width + Pm2.Width, 0)
Flip

EndFunction

Function 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, 360

End Function



impixi(Posted 1+ years ago)

 test3.bmx:
Code: [Select]
Rem

Heightmap Toolbox Test #3 - Brush example.

NOTE: A 'brush' in HeightMap Toolbox is just a small THeightMap instance.

End Rem

' ------------- INITIALISATION -------------

SuperStrict

Import "hmap03.6.bmx"

AppTitle = "Brush Example"

Graphics 800, 600, 0

Global 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 ALPHABLEND
SetAlpha 1.0

HideMouse

' ------------- 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 1

Wend

End
test4.bmx:
Code: [Select]
Rem

Heightmap Toolbox Test #4 - Shadow Map example.

End Rem

' ------------- INITIALISATION -------------

SuperStrict

Import "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 TSimpleVector
Sun.setXYZ(1, 2, -1)

HMap1.renderShadowsToPixmap(PmShadow, Sun)

' ------------- MAIN LOOP -------------

While Not KeyHit(KEY_ESCAPE)

handleKeys()
drawScreen()

Delay(1)

Wend

End

' ------------- 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

EndIf

End Function

Function drawWait()

DrawText "Please wait...", 0, 0
Flip

EndFunction

Function drawScreen()

Cls
drawMenu()
DrawPixmap(Pm1, 0, 0)
DrawPixmap(pmShadow, Pm1.Width, 0)
Flip

EndFunction

Function 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



impixi(Posted 1+ years ago)

 test5.bmx:
Code: [Select]
Rem

Heightmap Toolbox Test #5 - Color Map example.

End Rem

' ------------- INITIALISATION -------------

SuperStrict

Import "hmap03.6.bmx"

AppTitle = "Color Map Example"

Graphics 800, 600, 0
SetBlend 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 1

Wend

End

' ------------- 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

EndIf

End Function

Function drawWait()

DrawText "Please wait...", 0, 0
Flip

EndFunction

Function drawScreen()

Cls
drawMenu()
DrawPixmap PmColor, 0, 0
DrawPixmap pmColor2, PmColor.width + 5, 0
Flip

EndFunction

Function 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
test6.bmx:[code]
Rem

   Heightmap Toolbox Test #6 - Texture Map example.
   
End Rem

' ------------- INITIALISATION -------------

SuperStrict

Import "hmap03.6.bmx"

AppTitle = "Texture Map Example"

Graphics 800, 600, 0
SetBlend(SOLIDBLEND)
   
Global HMap1:THeightMap = THeightMap.Create(256, 256)

Global PmTexture:TPixmap = CreatePixmap(HMap1.Width, HMap1.Height, PF_RGB888)
Global Pmtexture2:TPixmap = CreatePixmap(HMap1.Width, HMap1.Height, PF_RGB888)

genSampleTextures()

SetColor 255, 255, 255

Global TextureURLs:String[] = ["texture1.png", "texture2.png", "texture3.png", "texture4.png"]

drawWait()

HMap1.generateHillMap(MilliSecs(), 1000, (HMap1.Width * 0.25), True)
HMap1.renderToTexturePixmap(PmTexture, 0, 0, TextureURLs, STYLE_DISCREET)
HMap1.renderToTexturePixmap(PmTexture2, 0, 0, TextureURLs, STYLE_BLENDED)

' ------------- MAIN LOOP -------------

While Not KeyHit(KEY_ESCAPE)

   handleKeys()
   drawScreen()

   Delay 1

Wend

End

' ------------- FUNCTIONS -------------

Function handleKeys()

   If KeyHit(KEY_1)
      drawWait()
      HMap1.generateHillMap(MilliSecs(), 1000, (HMap1.Width * 0.25), True)
      HMap1.renderToTexturePixmap(PmTexture, 0, 0, TextureURLs, STYLE_DISCREET)
      HMap1.renderToTexturePixmap(PmTexture2, 0, 0, TextureURLs, STYLE_BLENDED)
      Return
   EndIf

   If KeyHit(KEY_F1)
      drawWait()
      If SavePixmapPNG(PmTexture, "tex.png") Then ..
         Notify("tex.png saved.") Else Notify("tex.png could Not be saved.")
      If SavePixmapPNG(PmTexture2, "tex2.png") Then ..
         Notify("tex2.png saved.") Else Notify("tex2.png could Not be saved.")   
      Return
   EndIf

End Function

Function drawWait()

   DrawText "Please wait...", 0, 0
   Flip
   
EndFunction

Function drawScreen()

   Cls
   drawMenu()
   DrawPixmap PmTexture, 0, 0
   DrawPixmap PmTexture2, PmTexture.width + 5, 0
   Flip
   
EndFunction

Function drawMenu()

   DrawText "Press a key (ESC quits):", 5, 270
   DrawText "------------------------", 5, 285
   DrawText "(1) Generate heightmap and texture maps", 5, 300
   DrawText "(F1) Export PNGs - tex.png, tex2.png", 5, 320
   
End Function


Function genSampleTextures()

   SetBlend SOLIDBLEND
   DrawText "Generating sample textures...", 0, 0
   Flip

   Cls
   For Local x:Int = 0 To HMap1.width - 1
      For Local z:Int = 0 To HMap1.height - 1
         Local v:Byte = Rand(128, 196)
         SetColor 88, 88, v
         Plot x, z
      Next
   Next
   SavePixmapPNG(GrabPixmap(0, 0, HMap1.width, HMap1.height), "texture1.png", 9)

   Cls
   For Local x:Int = 0 To HMap1.width - 1
      For Local z:Int = 0 To HMap1.height - 1
         SetColor 0, Rand(128, 196), 0
         Plot x, z
      Next
   Next
   SavePixmapPNG(GrabPixmap(0, 0, HMap1.width, HMap1.height), "texture2.png", 9)

   Cls
   For Local x:Int = 0 To HMap1.width - 1
      For Local z:Int = 0 To HMap1.h

 

SimplePortal 2.3.6 © 2008-2014, SimplePortal