NaaLaa

Full Version: Mordor
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
It takes a while to generate the images and the meshes.



Code:
include "s3d.n7"

#win32
#mem256000000

' Display settings.
constant RES = 360
constant SCALE = 1
constant FULLSCREEN = 1
constant DISPLAY_FPS = 1
constant FOV = 65
constant MOUSE_SENS = 0.5

' Fog color.
constant FOG_R = 48, FOG_G = 56, FOG_B = 80

visible vLastTick = 0

randomize 139

aspect = screenw()/screenh()
set window "Heightmap", RES*screenw()/screenh(), RES, FULLSCREEN, SCALE
set redraw off

S3D_SetView(primary, rad(FOV), 0.1, 32)

water1Image = NoiseImage(128, 128, 3, 3, 1)
ApplyTint(water1Image, 128, 0, 0, 1, 1, 1)
water2Image = NoiseImage(128, 128, 2, 2, 1)
ApplyTint(water2Image, 255, 255, 0, 1, 1, 1)
ApplyTint(water2Image, 255, 32, 0, 1, 0.25, 0)
ApplyContrast(water2Image, 2, 2, 2)

waterTexture = createimage(128, 128)
watera = 0


' Create three different textures for three height levels.
w = 64
h = 64
diffs = [
        NoiseImage(w, h, 4, 4, 2),
        NoiseImage(w, h, 4, 4, 2),
        NoiseImage(w, h, 4, 4, 2)]
tmp = NoiseImage(w, h, 2, 2, 4)
bm = Bumpmap(tmp, 0.05)
bm2 = Bumpmap(tmp, 0.25)
free image tmp
ApplyTint(diffs[0], 128, 16, 0, 1, 1, 1)
ApplyTint(diffs[1], 64, 48, 32, 1, 1, 1)
ApplyTint(diffs[2], 128, 96, 64, 1, 1, 1)
ApplyBumpmap(diffs[0], bm, 1, 1, 1)
ApplyBumpmap(diffs[1], bm2, 1, 1, 1)
ApplyBumpmap(diffs[2], bm2, 1, 1, 1)
free image bm
free image bm2

' Eh ... create all possible combinations of these three textures blended from the four corners.
combos = dim(3, 3, 3, 3)
for tl = 0 to 2  for tr = 0 to 2  for bl = 0 to 2  for br = 0 to 2
    tlimg = diffs[tl];  trimg = diffs[tr];  blimg = diffs[bl];  brimg = diffs[br]
    img = createimage(w, h)
    combos[tl][tr][bl][br] = img
    set image img
    for y = 0 to h - 1
        ny = y/h
        for x = 0 to w - 1
            nx = x/w
            set color BilerpV(pixel(tlimg, x, y), pixel(trimg, x, y),
                    pixel(blimg, x, y), pixel(brimg, x, y),
                    nx, ny)
            set pixel x, y
        next
    next
    set image primary
next

' Create heightmap image.
'heightmapImg = NoiseImage(512, 512, 32, 32, 4)
heightmapImg = NoiseImage(256, 256, 16, 16, 4)
' Create heightmap.
waterLevel = 6
hm = CreateHeightmap(heightmapImg, 16, combos, [0, 7.5, 10], waterTexture, waterLevel)

' Camera position.
camX = width(heightmapImg)/2 + 0.5
camY = 0
camZ = height(heightmapImg)/2 + 0.5
' Yaw and pitch for camera rotation.
camYaw = 0
camPitch = 0
' The camera bobs up and down when the user moves.
camBobAngle = 0
camBobEffect = 0

set mouse off
set mouse width(primary)/2, height(primary)/2

while not keydown(KEY_ESCAPE, true)
    dt = DeltaTime()
   
    mdx = mouserelx()
    mdy = mouserely()
    set mouse width(primary)/2, height(primary)/2
   
    camYaw = camYaw + mdx*MOUSE_SENS
    camPitch = min(max(camPitch - mdy*MOUSE_SENS, -60), 60)
   
    dx = 0; dz = 0
    if keydown(KEY_W)
        dx = dx + sin(rad(camYaw))
        dz = dz + cos(rad(camYaw))
    endif
    if keydown(KEY_S)
        dx = dx - sin(rad(camYaw))
        dz = dz - cos(rad(camYaw))
    endif
    if keydown(KEY_A)
        dx = dx + sin(rad(camYaw - 90))
        dz = dz + cos(rad(camYaw - 90))
    endif
    if keydown(KEY_D)
        dx = dx + sin(rad(camYaw + 90))
        dz = dz + cos(rad(camYaw + 90))
    endif
    if dx or dz
        camBobAngle = camBobAngle + 500*dt
        k = 2*dt/sqr(dx*dx + dz*dz)
        camX = camX + k*dx
        camZ = camZ + k*dz
        camBobEffect = min(camBobEffect + 4*dt, 1)
    else
        camBobEffect = max(camBobEffect - 4*dt, 0)
    endif

    camY = min(hm.GetY(camX, camZ), -waterLevel + 0.25) - 0.75

    watera = watera + 0.5*dt
    set image waterTexture, false
    w = width(water1Image);  h = height(water1Image)
    set color 255, 255, 255
    for y = -1 to 1  for x = -1 to 1  draw image water1Image, floor(x*w + cos(watera)*0.5*w), floor(y*h + sin(watera)*0.5*h)
    set color 255, 255, 255, 192
    set additive true
    for y = -1 to 1  for x = -1 to 1  draw image water2Image, floor(x*w - cos(watera)*0.5*w), floor(y*h - sin(watera)*0.5*h)
    set additive false
    set image primary

    set color FOG_R, FOG_G, FOG_B
    cls

    S3D_Clear()

    S3D_SetDepthBuffer(S3D_Z_BUFFER)
    S3D_RotateX(rad(-camPitch))
    S3D_RotateY(rad(-camYaw))
    S3D_Translate(-camX, -camY + 0.04*camBobEffect*sin(rad(camBobAngle)), -camZ)

    viewDX = sin(rad(camYaw))
    viewDY = cos(rad(camYaw))
    chunkSize = 8
    ix = floor(camX/chunkSize);  iy = floor(camZ/chunkSize)
    dist = 4
    fovc = cos(rad(FOV*aspect)*0.5)
    for gy = iy - dist to iy + dist  for gx = ix - dist to ix + dist
        if gx = ix and gy = iy
            inside = true
        else
            inside = false
            for gdy = 0 to 1  for gdx = 0 to 1
                dx = (gx + gdx)*chunkSize - camX;  dy = (gy + gdy)*chunkSize - camZ
                d = sqr(dx*dx + dy*dy)
                if d > 0
                    dx = dx/d;  dy = dy/d
                    dp = dx*viewDX + dy*viewDY
                    if dp >= fovc
                        inside = true
                        break
                    endif
                endif
            next
        endif
        if inside
          if gx >= 0 and gx < sizeof(hm.meshes) - 1 and gy >= 0 and gy < sizeof(hm.meshes[0])
              S3D_Mesh(hm.meshes[gx][gy], 0)
          endif
        endif
    next
   
    S3D_RenderFog(FOG_R, FOG_G, FOG_B, false)
   
    set color 255, 255, 255
    set caret 0, 0
    wln "Position: (" + str(camX, 0, 2) + ", " + str(camY, 0, 2) + ", " + str(camZ, 0, 2) + ")"
    wln
    wln "Move with WASD keys"
    wln "Look around with the mouse"
    wln
    wln "Press Esc to quit"

    DisplayFps(dt)
   
    redraw
    fwait 60
wend

' CreateHeightmap
' ---------------
' Create heightmap mesh and data from image.
function CreateHeightmap(img, maxHeight, levelTextures, levels, waterTexture, waterLevel)
    assert image(img), "CreateHeightmap: invalid image"

    mapw = width(img);  maph = height(img)

    ' For heights and normals.
    data = fill([], mapw, maph)
   
    ' Calculate height for each tile in the xz plane based on the color of the pixels in img.
    set image img
    for z = 0 to maph - 1
        for x = 0 to mapw - 1  data[x][z].h = -maxHeight*pixel(x, z)[0]/255
    next
    set image primary
   
    ' A tile is represented by two triangles. Calculate the normals of these triangles, as they're
    ' needed for calculating the y coordinate at any position within a tile.
    a = dim(3); b = dim(3); c = dim(3); n = dim(3)
    for z = 0 to maph - 2  for x = 0 to mapw - 2
        a[0] = 0; a[1] = data[x][z + 1].h - data[x][z].h; a[2] = 1
        b[0] = 1; b[1] = data[x + 1][z + 1].h - data[x][z].h; b[2] = 1
        c[0] = 1; c[1] = data[x + 1][z].h - data[x][z].h; c[2] = 0
        CrossProduct(n, a, b)
        Normalize(n)
        data[x][z].nLx = n[0]
        data[x][z].nLy = n[1]
        data[x][z].nLz = n[2]
        CrossProduct(n, b, c)
        Normalize(n)
        data[x][z].nRx = n[0]
        data[x][z].nRy = n[1]
        data[x][z].nRz = n[2]
    next
   
 
    ' Build the meshes.
    chunkSize = 8
    meshes = dim(ceil(mapw/chunkSize), ceil(maph/chunkSize))
    'meshes = []
   
    waterY = -waterLevel
    z = 0; zi = 0
    while z <= maph - 2
        x = 0
        xi = 0
        while x <= mapw - 2
            meshes[xi][zi] = S3D_BeginMesh()
            S3D_Color3(255, 255, 255)
            S3D_Begin(S3D_TRIANGLES)
            for chunkz = z to min(z + chunkSize - 1, maph - 2)
                voffs = (chunkz%2)*0.5
                for chunkx = x to min(x + chunkSize - 1, mapw - 2)
                    uoffs = (chunkx%2)*0.5
                    htl = data[chunkx][chunkz].h
                    htr = data[chunkx + 1][chunkz].h
                    hbl = data[chunkx][chunkz + 1].h
                    hbr = data[chunkx + 1][chunkz + 1].h
                   
                    for i = sizeof(levels) - 1 to 0  if |htl| > levels[i]  break;  ttl = max(i, 0)
                    for i = sizeof(levels) - 1 to 0  if |htr| > levels[i]  break;  ttr = max(i, 0)
                    for i = sizeof(levels) - 1 to 0  if |hbl| > levels[i]  break;  tbl = max(i, 0)
                    for i = sizeof(levels) - 1 to 0  if |hbr| > levels[i]  break;  tbr = max(i, 0)
                    tex = levelTextures[ttl][ttr][tbl][tbr]
                 
                    if htl < waterY or htr < waterY or hbl < waterY or hbr < waterY
                        S3D_Texture(tex)
                        S3D_Vertex5(chunkx, data[chunkx][chunkz].h, chunkz, 0, 0)
                        S3D_Vertex5(chunkx + 1, data[chunkx + 1][chunkz + 1].h, chunkz + 1, 1, 1)
                        S3D_Vertex5(chunkx + 1, data[chunkx + 1][chunkz].h, chunkz, 1, 0)
                        S3D_Vertex5(chunkx, data[chunkx][chunkz].h, chunkz, 0, 0)
                        S3D_Vertex5(chunkx, data[chunkx][chunkz + 1].h, chunkz + 1, 0, 1)
                        S3D_Vertex5(chunkx + 1, data[chunkx + 1][chunkz + 1].h, chunkz + 1, 1, 1)
                    endif
                    if htl > waterY or htr > waterY or hbl > waterY or hbr > waterY
                        S3D_Texture(waterTexture)
                        S3D_Vertex5(chunkx, waterY, chunkz, uoffs, voffs)
                        S3D_Vertex5(chunkx + 1, waterY, chunkz + 1, uoffs + 0.5, voffs + 0.5)
                        S3D_Vertex5(chunkx + 1, waterY, chunkz, uoffs + 0.5, voffs)
                        S3D_Vertex5(chunkx, waterY, chunkz, uoffs, voffs)
                        S3D_Vertex5(chunkx, waterY, chunkz + 1, uoffs, voffs + 0.5)
                        S3D_Vertex5(chunkx + 1, waterY, chunkz + 1, uoffs + 0.5, voffs + 0.5)
                    endif
                next
            next
            S3D_End()
            S3D_EndMesh()
            x = x + chunkSize
            xi = xi + 1
        wend
        z = z + chunkSize
        zi = zi + 1
    wend
   
    hm = []
    hm.meshes = meshes
    hm.data = data
   
    ' GetY
    ' ----
    hm.GetY = function(x, z)
        ' Tile.
        ix = min(max(floor(x), 0), sizeof(.data) - 2)
        iz = min(max(floor(z), 0), sizeof(.data[0]) - 2)
        ' Coordinates within tile.
        x = x - float(ix)
        z = z - float(iz)
        ' Which triangle?
        if x < z
            y = .data[ix][iz].h -
                    (.data[ix][iz].nLx*x + .data[ix][iz].nLz*z)/.data[ix][iz].nLy
        else
            x = x - 1
            z = z - 1
            y = .data[ix + 1][iz + 1].h -
                    (.data[ix][iz].nRx*x + .data[ix][iz].nRz*z)/.data[ix][iz].nRy
        endif
        return y
    endfunc
   
    return hm
endfunc

' CrossProduct
' ------------
' Set dst to a x b.
function CrossProduct(dst, a, b)
    dst[0] = a[1]*b[2] - a[2]*b[1]
    dst[1] = a[2]*b[0] - a[0]*b[2]
    dst[2] = a[0]*b[1] - a[1]*b[0]   
endfunc

' Normalize
' ---------
' Normalize vector a.
function Normalize(a)
    k = 1/sqr(a[0]*a[0] + a[1]*a[1] + a[2]*a[2])
    a[0] = k*a[0]; a[1] = k*a[1]; a[2] = k*a[2]
endfunc

' DeltaTime
' ---------
' Return delta time in seconds since last call.
function DeltaTime()
    t = clock()
    dt = (min(t - vLastTick, 100))/1000
    vLastTick = t
    return dt
endfunc

' DisplayFps
' ----------
' Display number of frames per second in bottom right corner based on delta time in seconds.
function DisplayFps(dt)
    if DISPLAY_FPS
        set caret width(primary) - fwidth(" "), height(primary) - fheight()
        set justification right
        set color 0, 255, 0
        write "FPS: " + str(round(1/dt))
        set justification left
    endif
endfunc

' Bilerp
' ------
' Return bilinear interpolation of four values.
function Bilerp(tl, tr, bl, br, x, y)
    return (1 - y)*((1 - x)*tl + x*tr) + y*((1 - x)*bl + x*br)
endfunc

' BilerpV
' -------
' Return bilinear interpolation of four arrays.
function BilerpV(tl, tr, bl, br, x, y)
    c = sizeof(tl)
    v = dim(c)
    ix = 1 - x;  iy = 1 - y
    for i = 0 to c - 1  v[i] = iy*(ix*tl[i] + x*tr[i]) + y*(ix*bl[i] + x*br[i])
    return v
endfunc

' Bumpmap
' -------
' Treat the red color channel of image img as height data and create a bumpmap. normalZ determines
' the roughness of the surface. A low value creates a rougher looking surface, and probably you'll
' usually want to keep the value in the range [0.1 .. 1]. The function returns a new image, but
' this image is actually a normalmap, where a 3d normal is coded into the rgb data of each pixel.
function Bumpmap(img, normalZ)
    assert normalZ > 0, "CreateBumpmap: z must be larger than 0"
    w = width(img);  h = height(img)
    map = createimage(w, h)
    set image map, false
    for y = 0 to h - 1  for x = 0 to w - 1
        nx = (Red(pixeli(img, (x + 1)%w, y)) - Red(pixeli(img, (x - 1)%w, y)))/255
        ny = (Red(pixeli(img, x, (y + 1)%h)) - Red(pixeli(img, x, (y - 1)%h)))/255
        nz = normalZ
        k = 128/sqr(nx*nx + ny*ny + nz*nz)
        nx = 128 + nx*k;  ny = 128 + ny*k;  nz = 128 + nz*k
        set color nx, ny, nz
        set pixel x, y
    next
    set image primary
    return map
endfunc

' ApplyBumpmap
' ------------
' Apply bumpmap bmap, created with CreateBumpmap, to img using the light vector (lightDx lightDy
' lightDz).
function ApplyBumpmap(img, bmap, lightDx, lightDy, lightDz)
    w = width(img);  h = height(img);  wbmap = width(bmap);  hbmap = height(bmap)
    s = sqr(lightDx*lightDx + lightDy*lightDy + lightDz*lightDz)
    lightDx = lightDx/s;  lightDy = lightDy/s;  lightDz = lightDz/s
    set image img, false
    ' Same size?
    if wbmap = w and hbmap = h
        for y = 0 to h - 1  for x = 0 to w - 1
            c = pixeli(bmap, x, y)
            nx = (Red(c) - 128)/128
            ny = (Green(c) - 128)/128
            nz = (Blue(c) - 128)/128
            i = max(nx*lightDx + ny*lightDy + nz*lightDz, 0)
            c = pixeli(x, y)
            set color Red(c)*i, Green(c)*i, Blue(c)*i
            set pixel x, y
        next
    else
        ' Use bilinear interpolation of normals.
        sx = wbmap/w;  sy = hbmap/h
        for y = 0 to h - 1  for x = 0 to w - 1
            xl = sx*x;  xf = xl - floor(xl);  xr = (xl + 1)%wbmap
            yt = sy*y;  yf = yt - floor(yt);  yb = (yt + 1)%hbmap
            tl = pixel(bmap, xl, yt);  tr = pixel(bmap, xr, yt)
            bl = pixel(bmap, xl, yb);  br = pixel(bmap, xr, yb)
            n = BilerpV(tl, tr, bl, br, xf, yf)
            n[0] = (n[0] - 128)/128
            n[1] = (n[1] - 128)/128
            n[2] = (n[2] - 128)/128
            ' We SHOULD normalize n, but ... meh.
            'k = 1/sqr(n[0]*n[0] + n[1]*n[1] + n[2]*n[2])
            'n[0] = n[0]*k;  n[1] = n[1]*k;  n[2] = n[2]*k
            i = max(n[0]*lightDx + n[1]*lightDy + n[2]*lightDz, 0)
            c = pixeli(x, y)
            set color Red(c)*i, Green(c)*i, Blue(c)*i
            set pixel x, y
        next
    endif
    set image primary
endfunc

' ApplyContrast
' -------------
function ApplyContrast(img, r, g, b)
    set image img, false
    for y = 0 to height(img) - 1  for x = 0 to width(img) - 1
        c = pixel(x, y)
        set color 128 + (c[0] - 128)*r, 128 + (c[1] - 128)*g, 128 + (c[2] - 128)*b
        set pixel x, y
    next
    set image primary
endfunc

' ApplyTint
' ---------
function ApplyTint(img, r, g, b, shadows, midtones, highlights)
    r = r/255;  g = g/255;  b = b/255
    filter = []
    for i = 0 to 255
        filter[i] = max(128 - i, 0)*shadows/128 + max(i - 128, 0)*highlights/128 +
                (128 - |128 - i|)*midtones/128
    next
    set image img, false
    for y = 0 to height(img) - 1  for x = 0 to width(img) - 1
        c = pixel(x, y)
        i = int(c[0] + c[1] + c[2])/3
        fi = filter[i];  ifi = 1 - fi
        set color fi*c[0]*r + ifi*c[0],
                fi*c[1]*g + ifi*c[1],
                fi*c[2]*b + ifi*c[2]
        set pixel x, y
    next
    set image primary
endfunc

' BoxBlur
' -------
' Box blur image.
function BoxBlur(img, rx, ry)
    rx = max(int(rx), 0); ry = max(int(ry), 0)
    set image img
    w = width(img); h = height(img)
    data = dim(w, h)

    ' Blur vertically
    for y = 0 to h - 1  for x = 0 to w - 1  data[x][y] = pixeli(img, x, y)
    count = ry*2 + 1
    for x = 0 to w - 1
        sr = 0; sg = 0; sb = 0; sa = 0
        for y = -ry to ry
            p = data[x][y%h];
            sr = sr + Red(p); sg = sg + Green(p); sb = sb + Blue(p); sa = sa + Alpha(p)
        next
        for y = 0 to h - 1
            set color sr/count, sg/count, sb/count, sa/count
            set pixel x, y
            p = data[x][(y - ry)%h]
            sr = sr - Red(p); sg = sg - Green(p); sb = sb - Blue(p); sa = sa - Alpha(p)
            p = data[x][(y + ry + 1)%h]
            sr = sr + Red(p); sg = sg + Green(p); sb = sb + Blue(p); sa = sa + Alpha(p)
        next
    next
    ' Blur horizontally.
    for y = 0 to h - 1  for x = 0 to w - 1  data[x][y] = pixeli(img, x, y)
    count = rx*2 + 1
    for y = 0 to h - 1
        sr = 0; sg = 0; sb = 0; sa = 0
        for x = -rx to rx
            p = data[x%w][y]
            sr = sr + Red(p); sg = sg + Green(p); sb = sb + Blue(p); sa = sa + Alpha(p)
        next
        for x = 0 to w - 1
            set color sr/count, sg/count, sb/count, sa/count
            set pixel x, y
            p = data[(x - rx)%w][y]
            sr = sr - Red(p); sg = sg - Green(p); sb = sb - Blue(p); sa = sa - Alpha(p)
            p = data[(x + rx + 1)%w][y]
            sr = sr + Red(p); sg = sg + Green(p); sb = sb + Blue(p); sa = sa + Alpha(p)
        next
    next
    set image primary
endfunc

' Blur
' ----
function Blur(img, r)
    if r <= 0  return
    for i = 1 to r  BoxBlur(img, 1, 1)
endfunc

' NoiseImage
' ----------
' Create noise image using something that is maybe perlin noise.
function NoiseImage(w, h, resX, resY, details)
    details = max(details, 1)
    octaves = []
    for i = 1 to details
        octaves[sizeof(octaves)] = CloudMap(resX, resY)
        resX = resX*2;  resY = resY*2
    next
    img = createimage(w, h)
    set image img
    for y = 0 to h - 1  for x = 0 to w - 1
        nx = x/w;  ny = y/h
        v = 0;  amp = 1
        for i = 0 to sizeof(octaves) - 1
            v = v + octaves[i].ValueAt(nx, ny)*amp
            amp = amp*0.5
        next
        v = 255*(0.5 + 0.5*v)
        set color v, v, v
        set pixel x, y
    next   
    set image primary
    return img
endfunc

' CloudMap
' --------
function CloudMap(resX, resY)
    map = []
 
    ' Create grid of random unit vectors.
    map.m = dim(resX, resY)
    for y = 0 to resY - 1  for x = 0 to resX - 1
        a = rnd()*PI*2
        map.m[x][y] = [x: cos(a), y: sin(a)]
    next
 
    ' ValueAt
    ' -------
    ' Return value, [-1..1] at coordinates (x, y), The size of the cloud is 1x1, but the coordinates
    ' are wrapped and the cloud is always seamless.
    map.ValueAt = function(x, y)
        m = this.m
        w = sizeof(m);  h = sizeof(m[0])
        x = (x*w)%w;  y = (y*h)%h
        x0 = floor(x);  y0 = floor(y)
        x1 = (x0 + 1)%w;  y1 = (y0 + 1)%h
        fx = x - x0;  fy = y - y0
        tl = -fx*m[x0][y0].x - fy*m[x0][y0].y
        tr = (1 - fx)*m[x1][y0].x - fy*m[x1][y0].y
        bl = -fx*m[x0][y1].x + (1 - fy)*m[x0][y1].y
        br = (1 - fx)*m[x1][y1].x + (1 - fy)*m[x1][y1].y
        return Bilinear(tl, tr, bl, br, Quad(0, 1, fx), Quad(0, 1, fy))
     
        ' Bilinear
        ' --------
        function Bilinear(tl, tr, bl, br, x, y)
            ix = 1 - x
            return (1 - y)*(ix*tl + x*tr) + y*(ix*bl + x*br)
        endfunc

        ' Quad
        ' ----
        function Quad(a, b, p)
            return a - 3*a*p^2 + 2*a*p^3 + 3*b*p^2 - 2*b*p^3
        endfunc
    endfunc
 
    return map
endfunc

' Pixeli helpers.
function Alpha(c);  return int(c/16777216);  endfunc
function Red(c);  return int((c/65536))%256;  endfunc
function Green(c);  return int((c/256))%256;  endfunc
function Blue(c);  return c%256;  endfunc
function ToRGB(r, g, b);  return 255*16777216 + r*65536 + g*256 + b;  endfunc
function ToRGBA(r, g, b, a);  return a*16777216 + r*65536 + g*256 + b;  endfunc

' RNG
' ---
' Return a random number generator. It can be useful if you need multiple independent series,
' especially if one or more of them needs to be fixed.
function RNG(seed)
    r = []
   
    ' SetSeet
    ' -------
    r.SetSeed = function(seed)
        this.s = max(int(seed), 1)
    endfunc
   
    ' Next
    ' ----
    ' Return next random number in the range [0..2147483646].
    r.Next = function()
        this.s = int((16807*this.s + 12345)%2147483647)
        return this.s
    endfunc
   
    ' Int
    ' ---
    ' Return a random int in the range [0..n - 1], negative if n is negative.
    r.Int = function(n)
        n = floor(n)
        if n > 0  return this.Next()%n
        elseif n < 0  return -this.Next()%(-n)
        else  return 0
    endfunc
   
    ' Float
    ' -----
    ' Return a random float in the range [0..1].
    r.Float = function()
        return this.Next()/2147483647
    endfunc
   
    ' Range
    ' -----
    ' Return a random int in the range [rmin..rmax], works like.
    r.Range = function(rmin, rmax)
        rmin = int(rmin);  rmax = int(rmax)
        if rmin > rmax
            tmp = rmin;  rmin = rmax;  rmax = tmp
        endif
        return floor((this.Next()/2147483647)*(rmax - rmin + 1) + rmin)
    endfunc
   
    r.SetSeed(seed)
   
    return r
endfunc
"A while", you say? Nah... A few seconds and ran at 63fps. Very smooth. Perhaps a "sizzle" when walking across the lava? lol

Very nicely done!!
(05-08-2026, 08:04 PM)johnno56 Wrote: [ -> ]"A while", you say? Nah... A few seconds and ran at 63fps. Very smooth. Perhaps a "sizzle" when walking across the lava? lol

Very nicely done!!

I locked the fps at 60, you can unlock it by replacing "fwait 60" with "wait 1"
I'm curious how that 'heightmap' lava is actually animated. I'll have to dig into the code and see how it works - thanks for sharing it !
I'm just glad that after trekking through Mordor, the Land of Shadow, I didn't actually run into any monsters or erupting volcanoes...lol
Cool... "wait 1" produced 111 to 125 fps....