TGC Codebase Backup



Noise Library Part 2: NoiseUtils.dba by The Sab

31st Oct 2010 10:11
Summary

Noise Utilities to be used with NoiseGen



Description

Noise Utilities Instructions

NoiseUtils_Setup must be called my your main program and after NoiseGen_Setup.

Here is an example of how all this works:

`*****************************************
#include "NoiseGen.dba"
#include "NoiseUtils.dba"

#constant DBIMAGE 1 `A Dark Basic image number

set display mode 640, 480, 32
set cursor 0, 300

NoiseGen_Setup()
NoiseUtils_Setup()

mod1 = Create_Module(MOD_PERLIN)
Set_OctaveCount(mod1, 10)
Set_NoiseQuality(mod1, QUALITY_BEST)

mod2 = Create_Module(MOD_SCALEBIAS)
Set_Source0(mod2, mod1)
Set_Scale(mod2, 0.9)

print "Generating Noise Map..."
noise = NoiseMap_Create(SHAPE_PLANE)
NoiseMap_SetSourceModule(noise, mod2)
NoiseMap_SetSize(noise, 256, 256)
NoiseMap_SetBounds(noise, 0.0, 4.0, 1.0, 5.0)
NoiseMap_Build(noise)

print "Building Gradients..."
grad = Gradient_Create()
Gradient_Clear(grad)
Gradient_AddPoint(grad, -1.0, rgb(0, 0, 0))
Gradient_AddPoint(grad, 1.0, rgb(255, 255, 255))
Gradient_AddPoint(grad, 0.0, rgb(128, 0, 0))

print "Rendering Image...This takes some time..."
renderer = Renderer_Create()
Renderer_SetSourceNoiseMap(renderer, noise)
Renderer_SetDestImage(renderer, DBIMAGE)
Renderer_SetGradient(renderer, grad)
Renderer_Render(renderer)

print "Writing Image to file..."
writer = Writer_Create()
Writer_SetDestFilename(writer, "testImage.bmp")
Writer_SetSourceImage(writer, DBIMAGE)
Writer_WriteDestFile(writer)

print "Process Complete"

paste image DBIMAGE, 0, 0

wait key

end

`*****************************************

Now, here are all the Utility functions, and what they do. For the Module functions,
please see the GenReadme.txt.

NoiseMaps:

n_index = NoiseMap_Create(shape_constant)
Shapes come in three flavors: SHAPE_PLANE noise is evenly distributed between defined borders
SHAPE_CYLINDER seamless cylinderical map
SHAPE_SPHERE seamless sphere map

NoiseMap_SetSize(n_index, width, height) Sets the size of the noisemap array, and the resulting image if rendered. Width and height should be integers.

NoiseMap_SetBounds(index, lowerXBound, upperXBound, lowerYBound, upperYBound) Sets the area of the given shape to be processed. All values are floating point.
SHAPE_PLANE - All bounds mark an area on an arbitrary plane
SHAPE_CYLINDER - XBounds are in degrees from -180.0 to 180.0. YBounds are arbitrary
SHAPE_SPHERE - XBounds are West to East: -180.0 to 180.0. YBounds are North to South: -90.0 to 90.0 (I know it seams backwards, but the image is drawn from top to bottom). Defining borders less than the max will effectively zoom in on the selected area.

NoiseMap_SetSourceModule(n_index, m_index) Sets which noise module the noise map will draw its values from.

NoiseMap_SetDestNMap(n_index, nm_index) ***Not implimented due to technical problems*** Currently only one noiseMap array can exist at a time. Since DBPro cannot instance arrays, I am having trouble finding an elegant way of fixing this.

NoiseMap_EnableSeamless(n_index, boolean) SHAPE_PLANE only. When this is set to true, the noisemap will be generated as a seamless repeating texture.

NoiseMap_Build(n_index) This function will fill the noiseMap array with values. ***When this function is called, it will overwrite all noiseMaps previously generated. If you have more than one noiseMap defined, process them into images before building the next noiseMap.

NoiseMap_GetValue(n_index, x, y) Will retrieve the value at the given x, y coordinates. Currently it will only retrieve the value of the last noiseMap built.

NoiseMap_GetWidth(n_index) Retrieves the width and height of the noiseMap
NoiseMap_GetHeight(n_index)

NoiseMap_GetLowerXBound(n_index) Retrieves the bounds of the given shape from which the noiseMap will be filled.
NoiseMap_GetUpperXBound(n_index)
NoiseMap_GetLowerYBound(n_index)
NoiseMap_GetUpperYBound(n_index)

NoiseMap_IsSeamlessEnabled(n_index) SHAPE_PLANE only. Returns whether the noiseMap will be seamless along the borders.


Color Gradients:

g_index = Gradient_Create() Creates a color gradient. By default, this is a grayscale gradient. Due to typical array problems, there is a hard cap of 255 gradients, each with a max of 255 points. So don't go crazy.

Gradient_Clear(g_index) Empties the given gradient.

Gradient_AddPoint(g_index, position_value, color_value) Adds a gradient point to the gradient. Position_value should be a floating point, color should be a dword (DBPro's rgb(r, g, b) function is mighty handy here). The position_value is typically in the -1.0..1.0 range to match the noisemaps. All values outside of the supplied range will be clamped to the nearest color value.

Gradient_GetColor(g_index, position_value) This function is used internally, but you can use it too if you want to figure out what color a given value will produce. Position_value can be any floating point number, not just positions previously assigned. The color returned will be interpolated.

Gradient_GetPointCount(g_index) Returns the number of gradient points in the current gradient.

Gradient_BuildGrayscale(g_index) Builds a grayscale gradient for you with -1.0 as black, 1.0 as white. This is the default gradient.

Gradient_BuildTerrain(g_index) Builds a color gradient to simulate oceans, shallows, beaches, grasslands, hills, mountains, and snowy peaks.


Image Renderers:

r_index = Renderer_Create() Creates an image renderer.

Renderer_SetSourceNoiseMap(r_index, n_index) Assigns a previously defined noiseMap to the renderer. Technically, this doesn't do much, since it can only draw the last noiseMap built, but it is required to put it in here anyway.

Renderer_SetDestImage(r_index, image_number) Assigns a DBPro image to the renderer. If an image already exists with that number, it will be erased. You have been warned.

Renderer_EnableLight(r_index, boolean) If set to true, lighting effects will be applied to the image. This does not change any values in the noiseMap, just how the image is rendered.

Renderer_EnableWrap(r_index, boolean) This will only matter if light is enabled. If set to true, lighting and shading effects will be wrapped around the borders of the image. Otherwise, they won't. If you are going to tile the image, or are making a cylinder or sphere map, you should turn this on.

Renderer_SetBackground(r_index, b_index) Not fully implemented yet, as alpha channels work a little different in DBPro then they do in C++. When implemented, you can have a background image assigned and the map will be blended over it.

Renderer_SetLightAzimuth(r_index, float) Default 45.0. Sets the direction of the light source. 0.0 is directly east, 90.0 is directly south. I know it seams a little backwards, but the original code draws the images from bottom to top, whereas I draw mine from top to bottom. I will correct it later.

Renderer_SetLightElev(r_index, float) Default 45.0. Sets the elevation of the light source. 90.0 is directly over-head, 0.0 is directly on the azimuth.

Renderer_SetLightBrightness(r_index, float) Default 1.0. Sets how bright the light source is.

Renderer_SetLightColor(r_index, dword) Default rgb(255, 255, 255). Sets the color of the light source.

Renderer_SetLightContrast(r_index, float) Default 1.0. Sets the contrast of the image. Higher darker shadows.

Renderer_SetLightIntensity(r_index, float) Not implemented... kinda. This variable exists in the original code, but is not used in any calculations. An independant intesity variable is used however, but it is the sum total of all the light effects used for a given pixel, and so is not a constant value.

Renderer_EnableDisplay(r_index, boolean) Default false. If set to true, the image will be displayed on screen as it is being rendererd.

Renderer_SetScreenCoords(r_index, x, y) If display is enabled, these are the coordinates that the image will be displayed at on the screen.

Renderer_IsLightEnabled(r_index) Returns 0 if it is not enabled, 1 if it is.
Renderer_IsWrapEnabled(r_index)
Renderer_IsDisplayEnabled(r_index)

Renderer_GetLightAzimuth(r_index) Standard batch of Get functions. The return the values assigned to those parameters.
Renderer_GetLightElev(r_index)
Renderer_GetLightBrightness(r_index)
Renderer_GetLightColor(r_index)
Renderer_GetLightContrast(r_index)
Renderer_GetScreenX(r_index)
Renderer_GetScreenY(r_index)

Renderer_Render(r_index) This renders the noiseMap to the supplied DBPro image using all gradients and lighting options. *Note: DBPro cannot write pixels directly to an image, so I have it search out for an available DBPro bitmap to write to as a buffer. Once it is complete, it captures the image from the bitmap buffer and then frees the buffer. At least one DBPro bitmap must be available for this function to work. DBPro has 32 of them.

Renderer_RenderNormalMap(r_index) This renders the noiseMap as just a grayscale, ignoring all lighting and gradients. This is useful for making bumpmaps, and is also much faster than using a grayscale gradient with the normal Render function. At least when it is working. Right now it makes a bluish purple image. I am pretty sure it has something to do with how DBPro uses the alpha channel, but I am still working the bugs out of this one.


Image Writers:

w_index = Writer_Create() Creates an image writer.

Writer_SetDestFilename(w_index, filename string) Sets the name of the image file to create. Must be a .bmp, .dds, .jpg, or .dib. You can also include a directory path in the string.

Writer_SetSourceImage(w_index, image_number) Tells the writer which image to write. Ideally the same image number you fed to the renderer, but the writer doesn't really care. It will write any image given to it to a file for you.

Writer_GetDestFilename(w_index) Returns the string you hopefully provided to it earlier.

Writer_WriteDestFile(w_index) This function actually creates the image file. If the file already exists, it will be overwritten with no warning.



Code
                                    ` This code was downloaded from The Game Creators
                                    ` It is reproduced here with full permission
                                    ` http://www.thegamecreators.com
                                    
                                    #include "Models.dba"
#include "Math.dba"

#constant SHAPE_PLANE 0
#constant SHAPE_CYLINDER 1
#constant SHAPE_SPHERE 2

#constant I_MAX 1.0

type gradientType
   pos as float
   color as dword
endtype

type noiseType
   shape as integer
   width as integer
   height as integer
   bound1 as float
   bound2 as float
   bound3 as float
   bound4 as float
   isSeamless as boolean
   sourceModule as integer
   nMap as integer
endtype

type rendererType
   sourceNoiseMap as integer
   destImage as integer
   isLightEnabled as boolean
   isWrapEnabled as boolean
   lightAzimuth as float
   lightBrightness as float
   lightColor as dword
   lightContrast as float
   lightElev as float
   lightIntensity as float
   background as integer
   cosAzimuth as float
   cosElev as float
   gradient as integer
   recalcLightValues as boolean
   sinAzimuth as float
   sinElev as float
   bumpHeight as float
   screenX as integer
   screenY as integer
   display as boolean
endtype

type writerType
   destFilename as string
   sourceImage as integer
endtype

function NoiseUtils_Setup()
   global gradientCount as integer
   global noiseMapCount as integer
   global renderesCount as integer
   global writersCount as integer

   global planeModel as integer
   global cylinderModel as integer
   global sphereModel as integer

   global dim noiseMaps() as noiseType
   global dim renderers() as rendererType
   global dim writers() as writerType
   global dim gradients(255, 255) as gradientType
   global dim gradientPointCount(255) as integer

   global dim noiseMap() as float

   gradientCount = 0
   noiseMapCount = 0
   renderesCount = 0
   writersCount = 0

   `Create the noise modules for future use
   planeModel = Create_Module(MOD_PLANE)
   cylinderModel = Create_Module(MOD_CYLINDER)
   sphereModel = Create_Module(MOD_SPHERE)
endfunction

function Gradient_Create()
   local result as integer
   if gradientCount <= 255
      result = gradientCount
      inc gradientCount
   else
      ExceptionInvalidParam(gradientCount)
   endif
   Gradient_BuildGrayscale(result)
endfunction result

function Gradient_Clear(g_index as integer)
   for i = 0 to gradientPointCount(g_index)
      gradients(g_index, i).pos = 0
      gradients(g_index, i).color = 0
   next i
   gradientPointCount(g_index) = 0
endfunction

function Gradient_AddPoint(g_index as integer, gradientPos as float, gradientColor as dword)
  `Find the insertion point for the new gradient point and insert the new
  `gradient point at that insertion point.  The gradient point array will
  `remain sorted by gradient position.
   insertionPos = Gradient_FindInsertionPos(g_index, gradientPos)
   Gradient_InsertAtPos(g_index, insertionPos, gradientPos, gradientColor)
endfunction

function Gradient_FindInsertionPos(g_index as integer, gradientPos as float)
   insertionPos as integer
   if gradientPointCount(g_index) = 0
      insertionPos = 0
      exitfunction insertionPos
   endif

   insertionPos = gradientPointCount(g_index)
   for i = 0 to gradientPointCount(g_index) - 1
      if gradientPos < gradients(g_index, i).pos
         `We found the array index in which to insert the new gradient point.
         `Exit now.
         insertionPos = i
         exitfunction insertionPos
      else
         if gradientPos = gradients(g_index, i).pos
            `Each gradient point is required to contain a unique gradient
            `position, so throw an exception.
            ExceptionInvalidParam(g_index)
         endif
      endif
   next i
endfunction insertionPos

function Gradient_InsertAtPos(g_index as integer, insertionPos as integer, gradientPos as float, gradientColor as dword)
   `Make room for the new gradient point at the specified insertion position
   `within the gradient point array.  The insertion position is determined by
   `the gradient point's position; the gradient points must be sorted by
   `gradient position within that array.
   if gradientPointCount(g_index) > 0
      for i = gradientPointCount(g_index) to insertionPos + 1 step -1
         gradients(g_index, i).pos = gradients(g_index, i - 1).pos
         gradients(g_index, i).color = gradients(g_index, i - 1).color
      next i
   endif

   `Now that we've made room for the new gradient point within the array, add
   `the new gradient point.
   gradients(g_index, insertionPos).pos = gradientPos
   gradients(g_index, insertionPos).color = gradientColor
   inc gradientPointCount(g_index)
endfunction

function Gradient_GetColor(g_index as integer, gradientPos as float)
   local result as dword

   if gradientPointCount(g_index) >= 2
      `Find the first element in the gradient point array that has a gradient
      `position larger than the gradient position passed to this method.
      for indexPos = 0 to gradientPointCount(g_index) - 1
         if gradientPos < gradients(g_index, indexPos).pos
            exit
         endif
      next indexPos

      `Find the two nearest gradient points so that we can perform linear
      `interpolation on the color.
      index0 = ClampValue(indexPos - 1, 0, gradientPointCount(g_index) - 1)
      index1 = ClampValue(indexPos    , 0, gradientPointCount(g_index) - 1)

      `If some gradient points are missing (which occurs if the gradient
      `position passed to this method is greater than the largest gradient
      `position or less than the smallest gradient position in the array), get
      `the corresponding gradient color of the nearest gradient point and exit
      `now.
      if index0 = index1
         result = gradients(g_index, index1).color
         exitfunction result
      endif

      `Compute the alpha value used for linear interpolation.
      input0# = gradients(g_index, index0).pos
      input1# = gradients(g_index, index1).pos
      alpha# = (gradientPos - input0#) / (input1# - input0#)

      `Now perform the linear interpolation given the alpha value.
      color0 = gradients(g_index, index0).color
      color1 = gradients(g_index, index1).color
      result = LinearInterpColor(color0, color1, alpha#)
   else
      `throw exception
   endif
endfunction result

function Gradient_GetPointCount(g_index as integer)
   local result as integer
   result = gradientPointCount(g_index)
endfunction result

function Gradient_BuildGrayscale(g_index as integer)
   Gradient_Clear(g_index)
   Gradient_AddPoint(g_index, -1.0, rgb(  0,   0,   0))
   Gradient_AddPoint(g_index,  1.0, rgb(255, 255, 255))
endfunction

function Gradient_BuildTerrain(g_index as integer)
   Gradient_Clear(g_index)
   Gradient_AddPoint(g_index, -1.00, rgb(  0,   0, 128))
   Gradient_AddPoint(g_index, -0.20, rgb( 32,  64, 128))
   Gradient_AddPoint(g_index, -0.04, rgb( 64,  96, 192))
   Gradient_AddPoint(g_index, -0.02, rgb(192, 192, 128))
   Gradient_AddPoint(g_index,  0.00, rgb(  0, 192,   0))
   Gradient_AddPoint(g_index,  0.25, rgb(192, 192,   0))
   Gradient_AddPoint(g_index,  0.50, rgb(160,  96,  64))
   Gradient_AddPoint(g_index,  0.75, rgb(128, 255, 255))
   Gradient_AddPoint(g_index,  1.00, rgb(255, 255, 255))
endfunction

function NoiseMap_Create(shape as integer)
   local result as integer
   array insert at bottom noiseMaps()
   noiseMaps(noiseMapCount).shape = shape
   result = noiseMapCount
   inc noiseMapCount
   NoiseMap_Setup(result)
endfunction result

function NoiseMap_Setup(n_index as integer)
   noiseMaps(n_index).width = 0
   noiseMaps(n_index).height = 0
   noiseMaps(n_index).bound1 = 0.0
   noiseMaps(n_index).bound2 = 0.0
   noiseMaps(n_index).bound3 = 0.0
   noiseMaps(n_index).bound4 = 0.0
   noiseMaps(n_index).isSeamless = FALSE
   noiseMaps(n_index).sourcemodule = -1
   noiseMaps(n_index).nMap = -1
endfunction

function NoiseMap_GetValue(n_index as integer, x as integer, y as integer)
   local result as float
   result = noiseMap(x, y)
endfunction result

function NoiseMap_GetWidth(n_index as integer)
   local result as integer
   result = noiseMaps(n_index).width
endfunction result

function NoiseMap_GetHeight(n_index as integer)
   local result as integer
   result = noiseMaps(n_index).height
endfunction result

function NoiseMap_GetLowerAngleBound(n_index as integer)
   local result as float
   result = noiseMaps(n_index).bound1
endfunction result

function NoiseMap_GetLowerHeightBound(n_index as integer)
   local result as float
   result = noiseMaps(n_index).bound3
endfunction result

function NoiseMap_GetUpperAngleBound(n_index as integer)
   local result as float
   result = noiseMaps(n_index).bound2
endfunction result

function NoiseMap_GetUpperHeightBound(n_index as integer)
   local result as float
   result = noiseMaps(n_index).bound4
endfunction result

function NoiseMap_GetLowerXBound(n_index as integer)
   local result as float
   result = noiseMaps(n_index).bound1
endfunction result

function NoiseMap_GetLowerZBound(n_index as integer)
   local result as float
   result = noiseMaps(n_index).bound3
endfunction result

function NoiseMap_GetUpperXBound(n_index as integer)
   local result as float
   result = noiseMaps(n_index).bound2
endfunction result

function NoiseMap_GetUpperZBound(n_index as integer)
   local result as float
   result = noiseMaps(n_index).bound4
endfunction result

function NoiseMap_IsSeamlessEnabled(n_index as integer)
   local result as boolean
   result = noiseMaps(n_index).isSeamless
endfunction result

function NoiseMap_GetEastLonBound(n_index as integer)
   local result as boolean
   result = noiseMaps(n_index).bound2
endfunction result

function NoiseMap_GetNorthLatBound(n_index as integer)
   local result as boolean
   result = noiseMaps(n_index).bound3
endfunction result

function NoiseMap_GetSouthLatBound(n_index as integer)
   local result as boolean
   result = noiseMaps(n_index).bound4
endfunction result

function NoiseMap_GetWestLonBound(n_index as integer)
   local result as boolean
   result = noiseMaps(n_index).bound1
endfunction result

function NoiseMap_SetSize(n_index as integer, width as integer, height as integer)
   noiseMaps(n_index).width = width
   noiseMaps(n_index).height = height
endfunction

function NoiseMap_SetBounds(n_index as integer, b1 as float, b2 as float, b3 as float, b4 as float)
   noiseMaps(n_index).bound1 = b1
   noiseMaps(n_index).bound2 = b2
   noiseMaps(n_index).bound3 = b3
   noiseMaps(n_index).bound4 = b4
endfunction

function NoiseMap_SetSourceModule(n_index as integer, m_index as integer)
   noiseMaps(n_index).sourceModule = m_index
endfunction

function NoiseMap_SetDestNMap(n_index as integer, nm_index as integer)
   noiseMaps(n_index).nMap = nm_index
endfunction

function NoiseMap_EnableSeamless(n_index as integer, toggle as boolean)
   noiseMaps(n_index).isSeamless = toggle
endfunction

function NoiseMap_Build(n_index as integer)
   select noiseMaps(n_index).shape
      case SHAPE_PLANE:
         NoiseMap_BuildPlane(n_index)
      endcase
      case SHAPE_CYLINDER:
         NoiseMap_BuildCylinder(n_index)
      endcase
      case SHAPE_SPHERE:
         NoiseMap_BuildSphere(n_index)
      endcase
   endselect
endfunction

function NoiseMap_BuildCylinder(n_index as integer)
   if noiseMaps(n_index).bound2 <= noiseMaps(n_index).bound1 or noiseMaps(n_index).bound4 <= noiseMaps(n_index).bound3 or noiseMaps(n_index).width <= 0 or noiseMaps(n_index).height <= 0 or noiseMaps(n_index).sourceModule = -1
      ExceptionInvalidParam(n_index)
   else
      `Resize the destination noise map so that it can store the new output
      `values from the source model.
      width = noiseMaps(n_index).width
      height = noiseMaps(n_index).height
      dim noiseMap(width, height) as float

      `Load the source module into the cylinder model
      Set_Source0(cylinderModel, noiseMaps(n_index).sourceModule)

      angleExtent# = noiseMaps(n_index).bound2 - noiseMaps(n_index).bound1
      heightExtent# = noiseMaps(n_index).bound4 - noiseMaps(n_index).bound3
      xDelta# = angleExtent# / width
      yDelta# = heightExtent# / height
      curAngle# = noiseMaps(n_index).bound1
      curHeight# = noiseMaps(n_index).bound3

      `Fill every point in the noise map with the output values from the model.
      for y = 0 to height - 1
         curAngle# = noiseMaps(n_index).bound1
         for x = 0 to width - 1
            noiseMap(x, y) = Cylinder_GetValue(cylinderModel, curAngle#, curHeight#)
            curAngle# = curAngle# + xDelta#
         next x
         curHeight# = curHeight# + yDelta#
      next y
   endif
endfunction

function NoiseMap_BuildPlane(n_index as integer)
   if noiseMaps(n_index).bound2 <= noiseMaps(n_index).bound1 or noiseMaps(n_index).bound4 <= noiseMaps(n_index).bound3 or noiseMaps(n_index).width <= 0 or noiseMaps(n_index).height <= 0 or noiseMaps(n_index).sourceModule = -1
      ExceptionInvalidParam(n_index)
   else
      `Resize the destination noise map so that it can store the new output
      `values from the source model.
      width = noiseMaps(n_index).width
      height = noiseMaps(n_index).height
      dim noiseMap(width, height) as float

      `Load the source module into the plane model
      Set_Source0(planeModel, noiseMaps(n_index).sourceModule)

      xExtent# = noiseMaps(n_index).bound2 - noiseMaps(n_index).bound1
      zExtent# = noiseMaps(n_index).bound4 - noiseMaps(n_index).bound3
      xDelta# = xExtent# / width
      zDelta# = zExtent# / height
      xCur# = noiseMaps(n_index).bound1
      zCur# = noiseMaps(n_index).bound3
      `Fill every point in the noise map with the output values from the model.
      for z = 0 to height - 1
         xCur# = noiseMaps(n_index).bound1
        for x = 0 to width - 1
            finalValue as float
            if not noiseMaps(n_index).isSeamless
               finalValue = Plane_GetValue(planeModel, xCur#, zCur#)
            else
               swValue# = Plane_GetValue(planeModel, xCur#           , zCur#           )
               seValue# = Plane_GetValue(planeModel, xCur# + xExtent#, zCur#           )
               nwValue# = Plane_GetValue(planeModel, xCur#           , zCur# + zExtent#)
               neValue# = Plane_GetValue(planeModel, xCur# + xExtent#, zCur# + zExtent#)
               xBlend# = 1.0 - ((xCur# - noiseMaps(n_index).bound1) / xExtent#)
               zBlend# = 1.0 - ((zCur# - noiseMaps(n_index).bound3) / zExtent#)
               z0# = LinearInterp(swValue#, seValue#, xBlend#)
               z1# = LinearInterp(nwValue#, neValue#, xBlend#)
               finalValue = LinearInterp(z0#, z1#, zBlend#)
            endif
            noiseMap(x, z) = finalValue
            xCur# = xCur# + xDelta#
         next x
         zCur# = zCur# + zDelta#
      next z
   endif
endfunction

function NoiseMap_BuildSphere(n_index as integer)
   if noiseMaps(n_index).bound2 <= noiseMaps(n_index).bound1 or noiseMaps(n_index).bound4 <= noiseMaps(n_index).bound3 or noiseMaps(n_index).width <= 0 or noiseMaps(n_index).height <= 0 or noiseMaps(n_index).sourceModule = -1
      ExceptionInvalidParam(n_index)
   else
      `Resize the destination noise map so that it can store the new output
      `values from the source model.
      width = noiseMaps(n_index).width
      height = noiseMaps(n_index).height
      dim noiseMap(width, height) as float

      `Load the source module into the sphere model
      Set_Source0(sphereModel, noiseMaps(n_index).sourceModule)

      lonExtent# = noiseMaps(n_index).bound2 - noiseMaps(n_index).bound1
      latExtent# = noiseMaps(n_index).bound4 - noiseMaps(n_index).bound3
      xDelta# = lonExtent# / width
      yDelta# = latExtent# / height
      curLon# = noiseMaps(n_index).bound1
      curLat# = noiseMaps(n_index).bound3

      `Fill every point in the noise map with the output values from the model.
      for y = 0 to height - 1
         curLon# = noiseMaps(n_index).bound1
         for x = 0 to width - 1
            noiseMap(x, y) = Sphere_GetValue(sphereModel, curLat#, curLon#)
            curLon# = curLon# + xDelta#
         next x
         curLat# = curLat# + yDelta#
      next y
   endif
endfunction

function Renderer_Create()
   local result as integer
   array insert at bottom renderers()
   result = rendererCount
   inc rendererCount
   Renderer_Setup(result)
endfunction result

function Renderer_Setup(r_index as integer)
   renderers(r_index).isLightEnabled = 0
   renderers(r_index).isWrapEnabled = 0
   renderers(r_index).gradient = 0
   renderers(r_index).lightAzimuth = 45.0
   renderers(r_index).lightBrightness = 1.0
   renderers(r_index).lightColor = rgb(255, 255, 255)
   renderers(r_index).lightContrast = 1.0
   renderers(r_index).lightElev = 45.0
   renderers(r_index).lightIntensity = 1.0
   renderers(r_index).background = -1
   renderers(r_index).sourceNoiseMap = -1
   renderers(r_index).destImage = -1
   renderers(r_index).recalcLightValues = 1
   renderers(r_index).bumpHeight = 1.0
   renderers(r_index).screenX = 0
   renderers(r_index).screenY = 0
   renderers(r_index).isDisplayEnabled = 0
endfunction

function Renderer_GetValue(r_index as integer, x as integer, y as integer)
   local result as dword
endfunction result

function Renderer_SetValue(r_index as integer, x as integer, y as integer, color as dword)
endfunction

function Renderer_EnableLight(r_index as integer, toggle as boolean)
   renderers(r_index).isLightEnabled = toggle
endfunction

function Renderer_EnableWrap(r_index as integer, toggle as boolean)
   renderers(r_index).isWrapEnabled = toggle
endfunction

function Renderer_SetGradient(r_index as integer, g_index as integer)
   renderers(r_index).gradient = g_index
endfunction

function Renderer_GetLightAzimuth(r_index as integer)
   local result as float
   result = renderers(r_index).lightAzimuth
endfunction result

function Renderer_GetLightBrightness(r_index as integer)
   local result as float
   result = renderers(r_index).lightBrightness
endfunction result

function Renderer_GetLightColor(r_index as integer)
   local result as dword
   result = renderers(r_index).lightColor
endfunction result

function Renderer_GetLightContrast(r_index as integer)
   local result as float
   result = renderers(r_index).lightContrast
endfunction result

function Renderer_GetLightElev(r_index as integer)
   local result as float
   result = renderers(r_index).lightElev
endfunction result

function Renderer_GetLightIntensity(r_index as integer)
   local result as float
   result = renderers(r_index).lightIntensity
endfunction result

function Renderer_IsLightEnabled(r_index as integer)
   local result as boolean
   result = renderers(r_index).isLightEnabled
endfunction result

function Renderer_IsWrapEnabled(r_index as integer)
   local result as boolean
   result = renderers(r_index).isWrapEnabled
endfunction result

function Renderer_GetScreenX(r_index as integer)
   local result as integer
   result = renderers(r_index).screenX
endfunction result

function Renderer_GetScreenY(r_index as integer)
   local result as integer
   result = renderers(r_index).screenY
endfunction result

function Renderer_IsDisplayEnabled(r_index as integer)
   local result as boolean
   result = renderers(r_index).isDisplayEnabled
endfunction result

function Renderer_Render(r_index as integer)
   if renderers(r_index).sourceNoiseMap = -1 or renderers(r_index).destImage = -1 or noiseMaps(renderers(r_index).sourceNoiseMap).width <= 0 or noiseMaps(renderers(r_index).sourceNoiseMap).height <= 0 or gradientPointCount(renderers(r_index).gradient) < 2
      `throw exception
   else
      width = noiseMaps(renderers(r_index).sourceNoiseMap).width
      height = noiseMaps(renderers(r_index).sourceNoiseMap).height

      `Dark Basic cannot alter images directly, so we need to make a bitmap.
      `Find the first available empty bitmap.
      global bmap
      bmap = 0
      for i = 1 to 32
         if not bitmap exist(i)
            bmap = i
            exit
         endif
      next i

      if bmap = 0
         `throw exception
      endif
      create bitmap bmap, width, height
      set current bitmap bmap

      `If a background image was provided, make sure it is the same size the
      `source noise map.  Paste it onto the Dark Basic bitmap for processing
      if renderers(r_index).background <> -1
         paste image renderers(r_index).background, 0, 0
      endif

      backColor as dword
      destColor as dword
      for y = 0 to height - 1
         for x = 0 to width - 1
            `Get the color based on the value at the current point in the noise
            `map.
            destColor = Gradient_GetColor(renderers(r_index).gradient, noiseMap(x, y))

            `If lighting is enabled, calculate the light intensity based on the
            `rate of change at the current point in the noise map.
            lightIntensity as float
            if renderers(r_index).isLightEnabled
               `Calculate the positions of the current point's four-neighbors.
               if renderers(r_index).isWrapEnabled
                  if x = 0
                     xLeftOffset  = width - 1
                     xRightOffset = 1
                  else
                     if x = width - 1
                        xLeftOffset  = -1
                        xRightOffset = width - 1
                     else
                        xLeftOffset  = -1
                        xRightOffset = 1
                     endif
                  endif
                  if y = 0
                     yDownOffset = 1
                     yUpOffset   = height - 1
                  else
                     if y = height - 1
                        yDownOffset = -(height - 1)
                        yUpOffset   = -1
                     else
                        yDownOffset = 1
                        yUpOffset   = -1
                     endif
                  endif
               else
                  if x = 0
                     xLeftOffset  = 0
                     xRightOffset = 1
                  else
                     if x = width - 1
                        xLeftOffset  = -1
                        xRightOffset = 0
                     else
                        xLeftOffset  = -1
                        xRightOffset = 1
                     endif
                  endif
                  if y = 0
                     yDownOffset = 1
                     yUpOffset   = 0
                  else
                     if y = height - 1
                        yDownOffset = 0
                        yUpOffset   = -1
                     else
                        yDownOffset = 1
                        yUpOffset   = -1
                     endif
                  endif
               endif
               `yDownOffset = yDownOffset * width
               `yUpOffset   = yUpOffset   * width

               `Get the noise value of the current point in the source noise map
               `and the noise values of its four-neighbors.
               nc# = noisemap(x, y)
               nl# = noisemap(x + xLeftOffset,  y)
               nr# = noisemap(x + xRightOffset, y)
               nd# = noisemap(x,  y + yDownOffset)
               nu# = noisemap(x,  y + yUpOffset)

               `Now we can calculate the lighting intensity.
               lightIntensity = Renderer_CalcLightIntensity(r_index, nc#, nl#, nr#, nd#, nu#)
               lightIntensity = lightIntensity * renderers(r_index).lightBrightness
            else
               `These values will apply no lighting to the destination image.
               lightIntensity = 1.0
            endif

            `Get the current background color from the background image.
            if renderers(r_index).background <> -1
               backColor = point(x, y)
            else
               backColor = 0
            endif

            `Blend the destination color, background color, and the light
            `intensity together, then update the destination image with that
            `color.
            finalColor = Renderer_CalcDestColor(r_index, destColor, backColor, lightIntensity)
            dot x, y, finalColor
            if renderers(r_index).isDisplayEnabled
               copy bitmap bmap, 0, 0, width, height, 0, renderers(r_index).screenX, renderers(r_index).screenY, renderers(r_index).screenX + width, renderers(r_index).screenY + height
            endif
         next x
      next y

      if image exist(renderers(r_index).destImage)
         delete image renderers(r_index).destImage
      endif
      get image renderers(r_index).destImage, 0, 0, width, height, 1

      delete bitmap bmap
      set current bitmap 0
   endif
endfunction

function Renderer_SetBackground(r_index as integer, b_index as integer)
   renderers(r_index).background = b_index
endfunction

function Renderer_SetLightAzimuth(r_index as integer azimuth as float)
   renderers(r_index).lightAzimuth = azimuth
   renderers(r_index).recalcLightValues = 1
endfunction

function Renderer_SetLightBrightness(r_index as integer bright as float)
   renderers(r_index).lightBrightness = bright
endfunction

function Renderer_SetLightColor(r_index as integer color as dword)
   renderers(r_index).lightColor = color
endfunction

function Renderer_SetLightContrast(r_index as integer contrast as float)
   renderers(r_index).lightContrast = contrast
endfunction

function Renderer_SetLightElev(r_index as integer elev as float)
   renderers(r_index).lightElev = elev
   renderers(r_index).recalcLightValues = 1
endfunction

function Renderer_SetLightIntensity(r_index as integer intensity as float)
   renderers(r_index).lightIntensity = intensity
endfunction

function Renderer_SetSourceNoiseMap(r_index as integer n_index as integer)
   renderers(r_index).sourceNoiseMap = n_index
endfunction

function Renderer_SetDestImage(r_index as integer i_index as integer)
   renderers(r_index).destImage = i_index
endfunction

function Renderer_SetScreenCoords(r_index as integer, x as integer, y as integer)
   renderers(r_index).screenX = x
   renderers(r_index).screenY = y
endfunction

function Renderer_EnableDisplay(r_index as integer, toggle as boolean)
   renderers(r_index).isDisplayEnabled = toggle
endfunction

function Renderer_CalcDestColor(r_index as integer, sourceColor as dword, backgroundColor as dword, lightValue as float)
   `Translators note: Dark Basic colors do not carry an alpha
   `channel, so blending a sourceColor and a backgroundColor
   `can't actually happen with this implementation.  I have
   `left the code intact from the original, but the
   `sourceColor will completely override the backColor.
   local result as dword
   sourceRed#   = rgbr(sourceColor) / 255.0
   sourceGreen# = rgbg(sourceColor) / 255.0
   sourceBlue#  = rgbb(sourceColor) / 255.0
   sourceAlpha# = 1.0
   backgroundRed#   = rgbr(backgroundColor) / 255.0
   backgroundGreen# = rgbg(backgroundColor) / 255.0
   backgroundBlue#  = rgbb(backgroundColor) / 255.0

   `First, blend the source color to the background color using the alpha
   `of the source color.
   red#   = LinearInterp(backgroundRed#  , sourceRed#  , sourceAlpha#)
   green# = LinearInterp(backgroundGreen#, sourceGreen#, sourceAlpha#)
   blue#  = LinearInterp(backgroundBlue# , sourceBlue# , sourceAlpha#)

   if renderers(r_index).isLightEnabled
      `Now calculate the light color.
      lightRed#   = lightValue * rgbr(renderers(r_index).lightColor) / 255.0
      lightGreen# = lightValue * rgbg(renderers(r_index).lightColor) / 255.0
      lightBlue#  = lightValue * rgbb(renderers(r_index).lightColor) / 255.0

      `Apply the light color to the new color.
      red#   = red#   * lightRed#
      green# = green# * lightGreen#
      blue#  = blue#  * lightBlue#
   endif

   `Clamp the color channels to the (0..1) range.
   if red# < 0.0
      red# = 0.0
   endif
   if red# > 1.0
      red# = 1.0
   endif
   if green# < 0.0
      green# = 0.0
   endif
   if green# > 1.0
      green# = 1.0
   endif
   if blue# < 0.0
      blue# = 0.0
   endif
   if blue# > 1.0
      blue# = 1.0
   endif

   `Rescale the color channels to the noise::uint8 (0..255) range and return
   `the new color.
   result = rgb(red# * 255, green# * 255, blue# * 255)
endfunction result

function Renderer_CalcLightIntensity(r_index as integer, center as float, left as float, right as float, down as float, up as float)
   local result as float
   `Recalculate the sine and cosine of the various light values if
   `necessary so it does not have to be calculated each time this method is
   `called.
   if renderers(r_index).recalcLightValues
      renderers(r_index).cosAzimuth = cos(renderers(r_index).lightAzimuth)
      renderers(r_index).sinAzimuth = sin(renderers(r_index).lightAzimuth)
      renderers(r_index).cosElev = cos(renderers(r_index).lightElev)
      renderers(r_index).sinElev = sin(renderers(r_index).lightElev)
      renderers(r_index).recalcLightValues = 0
   endif

   `Now do the lighting calculations.
   io# = I_MAX * SQRT_2 * renderers(r_index).sinElev / 2.0
   ix# = (I_MAX - io#) * renderers(r_index).lightContrast * SQRT_2 * renderers(r_index).cosElev * renderers(r_index).cosAzimuth
   iy# = (I_MAX - io#) * renderers(r_index).lightContrast * SQRT_2 * renderers(r_index).cosElev * renderers(r_index).sinAzimuth
   intensity# = (ix# * (left - right) + iy# * (up - down) + io#)
   if intensity# < 0.0
      intensity# = 0.0
   endif
   result = intensity#
endfunction result

function Renderer_RenderNormalMap(r_index as integer)
   if renderers(r_index).sourceNoiseMap = -1 or renderers(r_index).destImage = -1 or noiseMaps(renderers(r_index).sourceNoiseMap).width <= 0 or noiseMaps(renderers(r_index).sourceNoiseMap).height <=0
      `throw exception
   else
      width = noiseMaps(renderers(r_index).sourceNoiseMap).width
      height = noiseMaps(renderers(r_index).sourceNoiseMap).height

      `Dark Basic cannot alter images directly, so we need to make a bitmap.
      `Find the first available empty bitmap.
      bmap = 0
      for i = 1 to 32
         if not bitmap exist(i)
            bmap = i
            exit
         endif
      next i

      if bmap = 0
         `throw exception
      endif

      create bitmap bmap, width, height
      set current bitmap bmap

      for y = 0 to height - 1
         for x = 0 to width - 1
            `Calculate the positions of the current point's right and down
            `neighbors.
            if renderers(r_index).isWrapEnabled
               if x = width - 1
                  xRightOffset = -(width - 1)
               else
                  xRightOffset = 1
               endif
               if y = height - 1
                  yDownOffset = -(height - 1)
               else
                  yDownOffset = 1
               endif
            else
               if x = width - 1
                  xRightOffset = 0
               else
                  xRightOffset = 1
               endif
               if y = height - 1
                  yDownOffset = 0
               else
                  yDownOffset = 1
               endif
            endif
            `Get the noise value of the current point in the source noise map
            `and the noise values of its right and up neighbors.
            nc# = noiseMap(x, y)
            nr# = noiseMap(x + xRightOffset, y)
            nd# = noiseMap(x, y + yDownOffset)

            `Calculate the normal product.
            finalColor = Renderer_CalcNormalColor(r_index, nc#, nr#, nd#, renderers(r_index).bumpHeight)
            dot x, y, finalColor
         next x
      next y

      if image exist(renderers(r_index).destImage)
         delete image renderers(r_index).destImage
      endif
      get image renderers(r_index).destImage, 0, 0, width, height, 1
      delete bitmap bmap
      set current bitmap 0
   endif
endfunction

function Renderer_GetBumpHeight(r_index as integer)
   local result as float
   result = renderers(r_index).bumpHeight
endfunction result

function Renderer_SetBumpHeight(r_index as integer, bumpHeight as float)
   renderers(r_index).bumpHeight = bumpHeight
endfunction

function Renderer_CalcNormalColor(r_index as integer, nc as float, nr as float, nd as float, bumpHeight as float)
   local result as dword
   `Calculate the surface normal.
   nc = nc * bumpHeight
   nr = nr * bumpHeight
   nd = nd * bumpHeight
   ncr# = nc - nr
   ncd# = nc - nd
   d# = sqrt((ncd# ^ 2.0) + (ncr# ^ 2.0) + 1.0)
   vxc# = (nc - nr) / d#
   vyc# = (nc - nd) / d#
   vzc# = 1.0 / d#

   `Map the normal range from the (-1.0 .. +1.0) range to the (0 .. 255)
   `range.
   xc = floor(vxc# + 1.0) * 127.5
   yc = floor(vyc# + 1.0) * 127.5
   zc = floor(vzc# + 1.0) * 127.5
   result = rgb(xc, yc, zc)
endfunction result

function Writer_Create()
   local result as integer
   array insert at bottom writers()
   result = writerCount
   inc writerCount
   Writer_Setup(result)
endfunction result

function Writer_Setup(w_index as integer)
   writers(w_index).destFilename = ""
   writers(w_index).sourceImage = -1
endfunction

function Writer_GetDestFilename(w_index as integer)
   local result as string
   result = writers(w_index).destFilename
endfunction result

function Writer_SetDestFilename(w_index as integer, filename as string)
   writers(w_index).destFilename = filename
endfunction

function Writer_SetSourceImage(w_index as integer, i_index as integer)
   writers(w_index).sourceImage = i_index
endfunction

function Writer_WriteDestFile(w_index as integer)
   if image exist(writers(w_index).sourceImage)
      if file exist(writers(w_index).destFilename)
         delete file writers(w_index).destFilename
      endif
      save image writers(w_index).destFilename, writers(w_index).sourceImage
   else
      print "no image"
      wait key
      `throw exception
   endif
endfunction

function BlendChannel(channel0 as byte, channel1 as byte, alpha as float)
   local result as byte
   c0# = channel0 / 255.0
   c1# = channel1 / 255.0
   result = ((c1# * alpha) + (c0# * (1.0 - alpha))) * 255.0
endfunction result

function LinearInterpColor(color0 as dword, color1 as dword, alpha as float)
   local result as dword
   outRed   = BlendChannel(rgbr(color0), rgbr(color1), alpha)
   outGreen = BlendChannel(rgbg(color0), rgbg(color1), alpha)
   outBlue  = BlendChannel(rgbb(color0), rgbb(color1), alpha)
   result = rgb(outRed, outGreen, outBlue)
endfunction result