Simplex Noise

Previous topic - Next topic

fivesprites

GLBasic implementation of Simplex noise.  2D and 3D noise are supported.  I'll update with 4D when I get time.

Also provided is very basic demo to show how Simplex noise can be used to affect the velocities of particles.

Left mouse button to create particles, right to clear the screen.  Spacebar to toggle the screen hold effect on/off.

I'm sure you guys can put this to better use (generating funky heightmap data etc) and I can't wait to see the results :)

Cheers,

Andy

Moru

Thanks! I always wanted some noise-functions, now I can finally try out some things :-)

bigsofty

Very interesting. I had to look up Simplex Noise and it looks that it could be very handy indeed! Thank you Andy!  :booze:
Cheers,

Ian.

"It is practically impossible to teach good programming style to students that have had prior exposure to BASIC.  As potential programmers, they are mentally mutilated beyond hope of regeneration."
(E. W. Dijkstra)

mentalthink

Hi very intersting and nice... this can be very ineresting for make pencils por some paint app, in fact seems somthing like accrilict pinture...

Very cool the effect.

Wampus

Thanks. Very much.  =D

Quote from: fivesprites on 2013-Mar-11

I'm sure you guys can put this to better use (generating funky heightmap data etc) and I can't wait to see the results :)


Nor me. :)

erico

Really pretty stuff! :good:

fuzzy70

#6
Had a play around with the 2D noise today & added some fBM (Fractional Brownian Motion) to roughen things up a bit.

The code is far from optimal (esp the isometric part) as it was merely knocked up in less than an hour for playing purposes & there might be variables which have no use. Also the colour is mapped as a single range (black to red) as I haven't got round to finishing my gradient routine as yet. Uncomment the
Code (glbasic) Select
map#[x][y] = simplex.noise2D#(x2,y2) part & comment out the fBM line directly below to see the difference & just generally play around with it.

Hopefully the variables are pretty self explanatory but I will more than likely tidy it all up at some stage. It's not really something that should be done in real time but rather pre calculated if you want a large landscape etc.

Enjoy  =D

Lee

Code (glbasic) Select
GLOBAL simplex AS TSimplex
GLOBAL map#[]
GLOBAL counter%
GLOBAL mapsize%=48
GLOBAL tilewidth%=16
GLOBAL tileheight%=tilewidth%/2

LOCAL x%,y%,x2#=0,y2#=0
LOCAL timer_start%,timer_end%
LOCAL incamount#=0.04,scroll#=0.0
LOCAL low#=0,high#=0,loop%

DIM map%[mapsize%][mapsize%]

SETSCREEN 640,480,FALSE

simplex.initialise()


FOR loop=0 TO 1000

timer_start=GETTIMERALL()

FOR x=0 TO mapsize%-1

FOR y=0 TO mapsize%-1

//map#[x][y] = simplex.noise2D#(x2,y2)
fBM(x,y,x2,y2,6,2,2.0,0.65)
IF map[x][y]<low# THEN low#=map[x][y]
IF map[x][y]>high# THEN high#=map[x][y]
INC x2,incamount#

NEXT

x2=0+scroll#
INC y2,incamount#

NEXT

y2=0

INC scroll#,0.01

drawmap(30)

timer_end=GETTIMERALL()-timer_start

PRINT timer_end,0,0
PRINT "Low= "+low,0,10
PRINT "High="+high,0,20

SHOWSCREEN

NEXT

KEYWAIT



FUNCTION drawmap: scale#

LOCAL x%,y%,colour%
LOCAL w2 = tilewidth / 2
LOCAL h2 = tileheight /2
LOCAL xoff%=(640/2)-(tilewidth/2)
LOCAL yoff%=(480/2)-((mapsize%*tileheight)/2)

STARTPOLY -1,2

FOR x=0 TO mapsize%-2

FOR y=0 TO mapsize%-2

colour% = ((map#[x][y])+1)*128
IF colour% <=0 THEN colour%=0
IF colour% >=255 THEN colour%=255

POLYVECTOR xoff+((x-y)*w2)  ,yoff%+((x+y)*h2)    - (map#[x][y]*scale)    ,0,0,RGB(colour%,0,0)
POLYVECTOR xoff+((x-1-y)*w2),yoff%+((x+y+1)*h2)  - (map#[x][y+1]*scale)  ,0,0,RGB(colour%,0,0)
POLYVECTOR xoff+((x+1-y)*w2),yoff%+((x+1+y)*h2)  - (map#[x+1][y]*scale)  ,0,0,RGB(colour%,0,0)
POLYVECTOR xoff+((x-y)*w2)  ,yoff%+((x+1+y+1)*h2)- (map#[x+1][y+1]*scale),0,0,RGB(colour%,0,0)

POLYNEWSTRIP

NEXT

NEXT

    ENDPOLY
   
ENDFUNCTION


FUNCTION fBM: x%,y%,nx#,ny#,octaves%,hgrid%,lacunarity#,gain#
// Taken & adapted from the following http://code.google.com/p/fractalterraingeneration/wiki/Fractional_Brownian_Motion

LOCAL total# = 0.0
LOCAL frequency# = 1.0/hgrid
LOCAL amplitude# = gain#
               
FOR i = 0 TO octaves

        total = total + simplex.noise2D#(nx * frequency, ny * frequency) * amplitude         
        frequency = frequency * lacunarity
        amplitude = amplitude * gain

NEXT
                       
map[x][y]=total  //now that we have the value, put it in

//Octaves are how many layers you are putting together.
//IF you start with big features, the number of octaves determines how detailed the map will look.

//The frequency of a layer is how many points fit into the space you've created.
//So FOR the mountain scale, you only need a few points, but at the rock scale you may need hundreds of points.
//IN the code above, I start with a small frequency (which equates TO large features) AND move TO
//higher frequencies which produce smaller details.

//The amplitude is how tall the features should be.
//Frequency determines the width of features, amplitude determines the height.
//Each octave the amplitude shrinks, meaning small features are also short.
//this doesn't have TO be the CASE, but FOR this CASE it makes pleasing maps.

//Lacunarity is what makes the frequency grow.
//Each octave the frequency is multiplied by the lacunarity.
//I use a lacunarity of 2.0, however values of 1.8715 OR 2.1042 can help TO reduce artifacts IN some algorithms.
//A lacunarity of 2.0 means that the frequency doubles each octave,
//so IF the first octave had 3 points the second would have 6, THEN 12, THEN 24, etc.
//this is used almost exclusively, partly because octaves IN music double IN frequency.
//Other values are perfectly acceptable, but the results will vary.

//Gain, also called persistence, is what makes the amplitude shrink (OR NOT shrink).
//Each octave the amplitude is multiplied by the gain. I use a gain of 0.65.
//IF it is higher THEN the amplitude will barely shrink, AND maps get crazy.
//Too low AND the details become miniscule, AND the map looks washed out.
//However, most use 1/lacunarity. Since the standard FOR lacunarity is 2.0,
//the standard FOR the gain is 0.5.
//Noise that has a gain of 0.5 AND a lacunarity of 2.0 is referred TO AS 1/f noise, AND is the industry standard.
ENDFUNCTION

// --------------------------------------------------------
//
// Project: Simplex
//
// Version: 1.0
//
// Desc.:   Implementation of Simplex noise for GLBasic
//          Based on C++ implementation by Stefan Gustavson Simplex noise
//
// Author:  Andy White
//          http://www.fivesprites.com
//
// Date:    Friday, June 18, 2012
//
// NOTE: Make sure you call initialise() :)
//
// --------------------------------------------------------


TYPE TSimplex

p[] AS short
perm[] AS short
permMod12[] AS short

grad3[] AS TSxGrad
grad4[] AS TSxGrad

F2#
G2#
F3#
G3#
F4#
G4#

FUNCTION noise2D#: xin#, yin#
LOCAL n0#, n1#, n2#
LOCAL s# = (xin + yin) * self.F2
LOCAL i% = FastFloor(xin+s)
LOCAL j% = FastFloor(yin+s)
LOCAL t# = (i + j) * self.G2
LOCAL X0# = i - t
LOCAL Y0# = j - t
LOCAL xx0# = xin - X0
LOCAL yy0# = yin - Y0

LOCAL i1%, j1%

IF (xx0 > yy0)
i1 = 1
j1 = 0
ELSE
i1 = 0
j1 = 1
ENDIF

LOCAL x1# = xx0 - i1 + self.G2
LOCAL y1# = yy0 - j1 + self.G2
LOCAL x2# = xx0 - 1.0 + 2.0 * self.G2
LOCAL y2# = yy0 - 1.0 + 2.0 * self.G2

LOCAL ii% = bAND(i, 255)
LOCAL jj% = bAND(j, 255)
LOCAL gi0% = self.permMod12[ii + self.perm[jj]]
LOCAL gi1% = self.permMod12[ii + i1 + self.perm[jj+j1]]
LOCAL gi2% = self.permMod12[ii + 1 + self.perm[jj + 1]]

LOCAL t0# = 0.5 - xx0 * xx0 - yy0 * yy0
IF (t0 < 0)
n0 = 0.0
ELSE
t0 = t0 * t0
n0 = t0 * t0 * Dot2(self.grad3[gi0], xx0, yy0)
ENDIF

LOCAL t1# = 0.5 - x1 * x1 - y1 * y1
IF (t1 < 0)
n1 = 0.0
ELSE
t1 = t1 * t1
n1 = t1 * t1 * Dot2(self.grad3[gi1], x1, y1)
ENDIF

LOCAL t2# = 0.5 - x2 * x2 - y2 * y2
IF (t2 < 0)
n2 = 0.0
ELSE
t2 = t2 * t2
n2 = t2 * t2 * Dot2(self.grad3[gi2], x2, y2)
ENDIF

RETURN 70.0 * (n0 + n1 + n2)

ENDFUNCTION

FUNCTION noise3D#: xin#, yin#, zin#
LOCAL n0#, n1#, n2#, n3#
LOCAL s# = (xin + yin + zin) * self.F3
LOCAL i% = FastFloor(xin + s)
LOCAL j% = FastFloor(yin + s)
LOCAL k% = FastFloor(zin + s)
LOCAL t# = (i + j + k) * self.G3
LOCAL X0# = i - t
LOCAL Y0# = j - t
LOCAL Z0# = k - t
LOCAL xx0# = xin - X0
LOCAL yy0# = yin - Y0
LOCAL zz0# = zin - Z0
LOCAL i1%, j1%, k1%
LOCAL i2%, j2%, k2%

IF (xx0 >= yy0)
IF (yy0 >= zz0)
i1 = 1
j1 = 0
k1 = 0
i2 = 1
j2 = 1
k2 = 0
ELSEIF (xx0 >= zz0)
i1 = 1
j1 = 0
k1 = 0
i2 = 1
j2 = 0
k2 = 1
ELSE
i1 = 0
j1 = 0
k1 = 1
i2 = 1
j2 = 0
k2 = 1
ENDIF
ELSE
IF (yy0 < zz0)
i1 = 0
j1 = 0
k1 = 1
i2 = 0
j2 = 1
k2 = 1
ELSEIF (xx0 < zz0)
i1 = 0
j1 = 1
k1 = 0
i2 = 0
j2 = 1
k2 = 1
ELSE
i1 = 0
j1 = 1
k1 = 0
i2 = 1
j2 = 1
k2 = 0
ENDIF
ENDIF

LOCAL x1# = xx0 - i1 + self.G3
LOCAL y1# = yy0 - j1 + self.G3
LOCAL z1# = zz0 - k1 + self.G3

LOCAL x2# = xx0 - i2 + 2.0 * self.G3
LOCAL y2# = yy0 - j2 + 2.0 * self.G3
LOCAL z2# = zz0 - k2 + 2.0 * self.G3

LOCAL x3# = xx0 - 1.0 + 3.0 * self.G3
LOCAL y3# = yy0 - 1.0 + 3.0 * self.G3
LOCAL z3# = zz0 - 1.0 + 3.0 * self.G3

LOCAL ii% = bAND(i, 255)
LOCAL jj% = bAND(j, 255)
LOCAL kk% = bAND(k, 255)

LOCAL gi0% = self.permMod12[ii+self.perm[jj+self.perm[kk]]]
LOCAL gi1% = self.permMod12[ii+i1+self.perm[jj+j1+self.perm[kk+k1]]]
LOCAL gi2% = self.permMod12[ii+i2+self.perm[jj+j2+self.perm[kk+k2]]]
LOCAL gi3% = self.permMod12[ii+1+self.perm[jj+1+self.perm[kk+1]]]

LOCAL t0# = 0.6 - xx0 * xx0 - yy0 * yy0 - zz0 * zz0
IF (t0 < 0)
n0 = 0.0
ELSE
t0 = t0 * t0
n0 = t0 * t0 * Dot3(self.grad3[gi0], xx0, yy0, zz0)
ENDIF

LOCAL t1# = 0.6 - x1 * x1 - y1*y1 - z1*z1
IF (t1 < 0)
n1 = 0.0
ELSE
t1 = t1 * t1
n1 = t1 * t1 * Dot3(self.grad3[gi1], x1, y1, z1)
ENDIF

LOCAL t2# = 0.6 - x2 * x2 - y2*y2 - z2*z2
IF (t2 < 0)
n2 = 0.0
ELSE
t2 = t2 * t2
n2 = t2 * t2 * Dot3(self.grad3[gi2], x2, y2, z2)
ENDIF

LOCAL t3# = 0.6 - x3 * x3 - y3 * y3 - z3 * z3
IF (t3 < 0)
n3 = 0.0
ELSE
t3 = t3 * t3
n3 = t3 * t3 * Dot3(self.grad3[gi3], x3, y3, z3)
ENDIF

RETURN 32.0 * (n0 + n1 + n2 + n3)
ENDFUNCTION

FUNCTION SumOctave#: numIterations%, x#, y#, persistence#, scale#, low#, high#
LOCAL maxAmp# = 0.0
LOCAL amp# = 0.0
LOCAL freq# = scale
LOCAL noise# = 0.0

FOR i%=0 TO numIterations-1
noise = noise + noise2D(x * freq, y * freq) * amp
maxAmp = maxAmp + amp
amp = amp * persistence
freq = freq * 2
NEXT

noise = noise / maxAmp
noise = noise * (high-low) / 2.0 + (high + low) / 2.0

RETURN noise
ENDFUNCTION

FUNCTION Dot2#: g AS TSxGrad, x#, y#
RETURN g.x * x + g.y * y
ENDFUNCTION


FUNCTION Dot3#: g AS TSxGrad, x#, y#, z#
RETURN (g.x * x) + (g.y * y) + (g.z * z)
ENDFUNCTION


FUNCTION Dot4#: g AS TSxGrad, x#, y#, z#, w#
RETURN g.x * x + g.y * y + g.z * z + g.w * w
ENDFUNCTION

FUNCTION initialise:

DEBUG "Initialising Simplex noise...  "

self.F2 = 0.5 * (SQR(3.0) - 1.0)
self.G2 = (3.0 - SQR(3.0)) / 6.0
self.F3 = 1.0 / 3.0
self.G3 = 1.0 / 6.0
self.F4 = (SQR(5.0) - 1.0) / 4.0
self.G4 = (5.0 - SQR(5.0)) / 20.0

LOCAL g30  AS TSxGrad
LOCAL g31  AS TSxGrad
LOCAL g32  AS TSxGrad
LOCAL g33  AS TSxGrad
LOCAL g34  AS TSxGrad
LOCAL g35  AS TSxGrad
LOCAL g36  AS TSxGrad
LOCAL g37  AS TSxGrad
LOCAL g38  AS TSxGrad
LOCAL g39  AS TSxGrad
LOCAL g310 AS TSxGrad
LOCAL g311 AS TSxGrad

g30.init ( 1,  1,  0)
g31.init (-1,  1,  0)
g32.init ( 1, -1,  0)
g33.init (-1, -1,  0)

g34.init ( 1,  0,  1)
g35.init (-1,  0,  1)
g36.init ( 1,  0, -1)
g37.init (-1,  0, -1)

g38.init ( 0,  1,  1)
g39.init ( 0, -1,  1)
g310.init( 0,  1, -1)
g311.init( 0, -1, -1)

REDIM self.grad3[12]
self.grad3[ 0] = g30
self.grad3[ 1] = g31
self.grad3[ 2] = g32
self.grad3[ 3] = g33
self.grad3[ 4] = g34
self.grad3[ 5] = g35
self.grad3[ 6] = g36
self.grad3[ 7] = g37
self.grad3[ 8] = g38
self.grad3[ 9] = g39
self.grad3[10] = g310
self.grad3[11] = g311

LOCAL g40  AS TSxGrad
LOCAL g41  AS TSxGrad
LOCAL g42  AS TSxGrad
LOCAL g43  AS TSxGrad
LOCAL g44  AS TSxGrad
LOCAL g45  AS TSxGrad
LOCAL g46  AS TSxGrad
LOCAL g47  AS TSxGrad
LOCAL g48  AS TSxGrad
LOCAL g49  AS TSxGrad
LOCAL g410 AS TSxGrad
LOCAL g411 AS TSxGrad
LOCAL g412 AS TSxGrad
LOCAL g413 AS TSxGrad
LOCAL g414 AS TSxGrad
LOCAL g415 AS TSxGrad
LOCAL g416 AS TSxGrad
LOCAL g417 AS TSxGrad
LOCAL g418 AS TSxGrad
LOCAL g419 AS TSxGrad
LOCAL g420 AS TSxGrad
LOCAL g421 AS TSxGrad
LOCAL g422 AS TSxGrad
LOCAL g423 AS TSxGrad
LOCAL g424 AS TSxGrad
LOCAL g425 AS TSxGrad
LOCAL g426 AS TSxGrad
LOCAL g427 AS TSxGrad
LOCAL g428 AS TSxGrad
LOCAL g429 AS TSxGrad
LOCAL g430 AS TSxGrad
LOCAL g431 AS TSxGrad

g40.init ( 0,  1,  1,  1)
g41.init ( 0,  1,  1, -1)
g42.init ( 0,  1, -1,  1)
g43.init ( 0,  1, -1, -1)

g44.init ( 0, -1,  1,  1)
g45.init ( 0, -1,  1, -1)
g46.init ( 0, -1, -1,  1)
g47.init ( 0, -1, -1, -1)

g48.init ( 1,  0,  1,  1)
g49.init ( 1,  0,  1, -1)
g410.init( 1,  0, -1,  1)
g411.init( 1,  0, -1, -1)

g412.init(-1,  0,  1,  1)
g413.init(-1,  0,  1, -1)
g414.init(-1,  0, -1,  1)
g415.init(-1,  0, -1, -1)

g416.init( 1,  1,  0,  1)
g417.init( 1,  1,  0, -1)
g418.init( 1, -1,  0,  1)
g419.init( 1, -1,  0, -1)

g420.init(-1,  1,  0,  1)
g421.init(-1,  1,  0, -1)
g422.init(-1, -1,  0,  1)
g423.init(-1, -1,  0, -1)

g424.init( 1,  1,  1,  0)
g425.init( 1,  1, -1,  0)
g426.init( 1, -1,  1,  0)
g427.init( 1, -1, -1,  0)

g428.init(-1,  1,  1,  0)
g429.init(-1,  1, -1,  0)
g430.init(-1, -1,  1,  0)
g431.init(-1, -1, -1,  0)

REDIM self.grad4[32]
self.grad4[ 0] = g40
self.grad4[ 1] = g41
self.grad4[ 2] = g42
self.grad4[ 3] = g43
self.grad4[ 4] = g44
self.grad4[ 5] = g45
self.grad4[ 6] = g46
self.grad4[ 7] = g47
self.grad4[ 8] = g48
self.grad4[ 9] = g49
self.grad4[10] = g410
self.grad4[11] = g411
self.grad4[12] = g412
self.grad4[13] = g413
self.grad4[14] = g414
self.grad4[15] = g415
self.grad4[16] = g416
self.grad4[17] = g417
self.grad4[18] = g418
self.grad4[19] = g419
self.grad4[20] = g420
self.grad4[21] = g421
self.grad4[22] = g422
self.grad4[23] = g423
self.grad4[24] = g424
self.grad4[25] = g425
self.grad4[26] = g426
self.grad4[27] = g427
self.grad4[28] = g428
self.grad4[29] = g429
self.grad4[30] = g430
self.grad4[31] = g431

DIMDATA self.p[], _
151,160,137,91,90,15, _
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, _
190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, _
88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166, _
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, _
102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196, _
135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123, _
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, _
223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9, _
129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228, _
251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107, _
49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254, _
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180

REDIM self.perm[512]
REDIM self.permMod12[512]
FOR i%=0 TO 511
self.perm[i] = self.p[bAND(i, 255)]
self.permMod12[i] = MOD(self.perm[i], 12)
NEXT

DEBUG "[DONE]\n"

ENDFUNCTION

ENDTYPE


TYPE TSxGrad

x#
y#
z#
w#

FUNCTION init: x#, y#, z#, w#=0.0
self.x = x
self.y = y
self.z = z
self.w = w
ENDFUNCTION


ENDTYPE


FUNCTION FastFloor%: x#
LOCAL i_x% = x
IF (x < i_x)
RETURN i_x - 1
ELSE
RETURN i_x
ENDIF
ENDFUNCTION
"Why don't you just make ten louder and make ten be the top number and make that a little louder?"
- "These go to eleven."

This Is Spinal Tap (1984)

mentalthink

Ey Fuzzy this code it's real nice... I test with a lot of subdivisions and makeing the cell more little to 16x16 and runs well---

Really for implement like a bump , gepmetry or displacement... I think use a image usign this technique can be possible, looking white and black parts of the image...

Very interesting Sniipet, thanks a lot  :booze:

fuzzy70

Your welcome. Creating a flat 2D greyscale image is very easy for heightmaps/displacement maps etc & as the code stands creating a terrain in GLB is easily done as the data is in the map array. I only done a quick isometric view as I have not played around at all with GLB's 3D commands so quickly knocked out a routine to visualise the output in iso.

After I get my gradient routine finished which will work pretty much the same as most graphic programs version in that you can add various points of colour & it interpolates the colours in-between then I am going to play with the 3D noise I think along with the 3D parts of GLB hopefully.

Lee
"Why don't you just make ten louder and make ten be the top number and make that a little louder?"
- "These go to eleven."

This Is Spinal Tap (1984)

Ian Price

I could have done with this nearly 30 years ago!

I wrote an isometric "game" on my Amstrad CPC, where a tank traversed a 3D landscape, like the one shown in the bottom picture, all created in hand-inputted data. It worked well, but took forever to implement (and was pretty slow). Think Populous but with a tank, 10 years before Populous. Once I had the tank moving on the landscape my mission was completed and it was soon abandoned. I often think of it.
I came. I saw. I played.

mentalthink

QuoteI could have done with this nearly 30 years ago!

I wrote an isometric "game" on my Amstrad CPC, where a tank traversed a 3D landscape, like the one shown in the bottom picture, all created in hand-inputted data. It worked well, but took forever to implement (and was pretty slow). Think Populous but with a tank, 10 years before Populous. Once I had the tank moving on the landscape my mission was completed and it was soon abandoned. I often think of it.

You have the .DSK ?¿  =D =D, I love to see this... I love the 3D in CPC...

fuzzy70

I wrote quite a few isometric programs back in the 8bit day's, both knight lore style & wireframe style. I used faultline type generation for landscapes which was pretty quick tbh, not realtime quick but a few seconds for an array of 64x64 which was a few screens worth.

My isometric skills are really rusty but writing that has made me think about brushing up on it again.

Lee


Sent from my C6603 using Tapatalk

"Why don't you just make ten louder and make ten be the top number and make that a little louder?"
- "These go to eleven."

This Is Spinal Tap (1984)

Ian Price

Quote from: mentalthink on 2013-Dec-23
QuoteI could have done with this nearly 30 years ago!

I wrote an isometric "game" on my Amstrad CPC, where a tank traversed a 3D landscape, like the one shown in the bottom picture, all created in hand-inputted data. It worked well, but took forever to implement (and was pretty slow). Think Populous but with a tank, 10 years before Populous. Once I had the tank moving on the landscape my mission was completed and it was soon abandoned. I often think of it.

You have the .DSK ?¿  =D =D, I love to see this... I love the 3D in CPC...

No. I've lost pretty much everything I ever did on CPC, Amiga and early pc. It was all either give away, sold, swapped or thrown out. Cimputer deaths killed much of my DOS days development. It's scattered around the internet, but I no longer have it. :(
I came. I saw. I played.