Code archives/3D Graphics - Misc/Realtime Procedural Planet Generator
This code has been declared by its author to be Public Domain code.
Download source code
| This is my own planet texture generator. It is a fake, but a very fast one with nice results. It calculates a unique texture by blending 4 pre-rendered 3D Perlin Noise planets together using image filters. And it is done within the main loop and calculates only a few pixels per loop, so it consumes nearly no CPU power and the planet can be calculated on-the-fly, e.g. while moving toward a planet or a solar system. In this demo, i am using a 512x256 texture, but it is even possible to create 1024x512 or 2048x1024 textures in realtime (the pre-rendering takes a lot of time but it is worth waiting for the results!). The planet texture maps perfect on a sphere because it is distorted to the poles. In the demo, press SPACE to calculate a new surface texture and use the mouse and the arrow keys to move while it is forming. IMPORTANT: first you must create the pre-rendered planet images by using this code (execute it in the same directory as the planet generator code): prepareplanet.bb Function DBInt( x# ) If x >= 0 Return Floor(x) Else Return Ceil(x) End If End Function xs%=512 ys%=256 start=MilliSecs() Graphics xs,ys,32,2 ; create arrays For perlin generator Dim s#(15,2) Dim r#(63,63,63) For planet=0 To 9 Text 0,0,"Generating Planet "+(planet+1)+"/10" Flip ; prepare the perlin prepare_perlin(planet+1,0.75) ; multisampling controller multi% = 1 milti% = multi - 1 malt# = multi ^ 2 malt# = 1 / malt# xsize1% = xs ysize1% = ys ; get the base sizes xsize2% = xsize1 * multi ysize2% = ysize1 * multi ; get the scalers xscaler1% = 360 yscaler1% = 180 xscaler2# = xscaler1*1.0 / xsize2 yscaler2# = yscaler1*1.0 / ysize2 width# = 2 ; piece details pxs% = xs-1 pys% = ys-1 xof% = 0 yof% = 0 SetBuffer FrontBuffer() image=CreateImage(xs,ys) ib=ImageBuffer(image) LockBuffer ib min=255 max=0 ; loop the y position For posx% = 0 To pxs Step 1 ; loop the x For posy% = 0 To pys Step 1 ; reset colours g = 0 b = 0 ; get the scaled sizes xp1% = (posx + xof) * multi yp1% = (posy + yof) * multi ; do the multisampling For posa% = 0 To milti ; get the baring around the sphere ba# = xp1 + posa ba# = (ba# + 0.5) * xscaler2# xp2# = Cos(ba#) zp2# = Sin(ba#) For poss% = 0 To milti ; get the pitch around the sphere pa# = yp1 + poss pa# = ((pa# + 0.5) * yscaler2#) po# = Sin(pa#) * width# ; get the positions in the space x# = (xp2# * po#) + 5 y# = (Cos(pa#) * width#) + 5 z# = (zp2# * po#) + 5 ; get the perlin result For that part h = DBInt(perl(x#,y#,z#,6) * 255) ; cap the value If h => 255 Then h = 255 If h <= 0 Then h = 0 If h>max Then max=h If h<min Then min=h ; failsafe quit If KeyHit(1) Then End Next Next ; scale down the colours c=c*malt# ; get the colour rgb=h*$10000+h*$100+h ; put a dot there WritePixelFast posx,posy,rgb,ib Next Next For posx = 0 To pxs Step 1 For posy = 0 To pys Step 1 rgb=ReadPixelFast(posx,posy,ib) r1=(rgb And $ff0000)/$10000 h=Normalize(r1,min,max,0,255) rgb=h*$10000+h*$100+h WritePixelFast posx,posy,rgb,ib Next Next UnlockBuffer ib ende=MilliSecs() cls DrawImage image,0,0 SaveBankImage(image,"planet"+planet+".img",xs,ys) FreeImage image Next End ; perlin Function Function perl#(x#,y#,z#,octaves%) ; make sure the pass value is zerod h# = 0 ; shift octaves down to Input works from 1 but system works from 0 octaves=octaves-1 ; make sue octaves are an ecceptable value If octaves <= 0 Then octaves = 0 If octaves => 15 Then octaves = 15 ; loop the octaves For oct% = 0 To octaves ; grab the frequency And amplitude For this fre# = s#(oct,0) amp# = s#(oct,1) ; convert the co-ordinates into steps xx = DBInt(x# * fre#) yy = DBInt(y# * fre#) zz = DBInt(z# * fre#) ; get the inbetween co-ords xb# = sine((x# * fre#) - flo(xx)) yb# = sine((y# * fre#) - flo(yy)) zb# = sine((z# * fre#) - flo(zz)) xa# = 1 - xb# ya# = 1 - yb# za# = 1 - zb# ; get the values For the 8 corners v000# = vil(xx,yy,zz) * xa# * ya# * za# v100# = vil(xx+1,yy,zz) * xb# * ya# * za# v010# = vil(xx,yy+1,zz) * xa# * yb# * za# v001# = vil(xx,yy,zz+1) * xa# * ya# * zb# v101# = vil(xx+1,yy,zz+1) * xb# * ya# * zb# v110# = vil(xx+1,yy+1,zz) * xb# * yb# * za# v011# = vil(xx,yy+1,zz+1) * xa# * yb# * zb# v111# = vil(xx+1,yy+1,zz+1) * xb# * yb# * zb# ; add it on h#=h#+(v000# + v100# + v010# + v001# + v101# + v110# + v011# + v111#) * amp# Next ; scale it down h# = h# * s#(octaves,2) Return h# End Function ; Function to get the random value of a co-ordinate Function vil#(x,y,z) ; control edges If x < 0 Then x = x - (DBInt((x / 64) - 1) * 64) Else x = x - (DBInt(x/64) * 64) If y < 0 Then y = y - (DBInt((y / 64) - 1) * 64) Else y = y - (DBInt(y/64) * 64) If z < 0 Then z = z - (DBInt((z / 64) - 1) * 64) Else z = z - (DBInt(z/64) * 64) ; get the number v# = r#(x,y,z) Return v# End Function ; Return an integer as a floating point Function flo#(a) b# = a*1.0 Return b# End Function ; Function to turn a straight 0 - 1 into a sine curved 0 - 1 Function sine#(v#) ; perform the change v# = (1 - Cos(v# * 180)) * 0.5 Return v End Function ; Function to prepare Data For perlin noise Function prepare_perlin(seed,persistance#) ; set the seed value SeedRnd seed ; create seed Data For x = 0 To 63 For y = 0 To 63 For z = 0 To 63 zz# = Rnd(10000) r#(x,y,z) = (zz# * 0.0001) Next Next Next ; prepare octave Data For i = 0 To 15 ; work out the frequence of the octave s#(i,0) = 2 ^ i ; get the amplitude s#(i,1) = persistance# ^ i ; work out the maximum amplitude of s#(i,2) = 0.0 For j = 0 To i s#(i,2)=s#(i,2)+s#(j,1) Next s#(i,2) = 1.0 / s#(i,2) Next End Function Function Normalize#(value#=128.0,value_min#=0.0,value_max#=255.0,norm_min#=0.0,norm_max#=1.0) Return ((value-value_min)/(value_max-value_min))*(norm_max-norm_min)+norm_min End Function Function SaveBankImage(image%,filename$,width%,height%) Local x%,y%,rgb%,h%,offset% Local bank%=CreateBank(width*height) Local buffer%=ImageBuffer(image) LockBuffer buffer For x=0 To width-1 For y=0 To height-1 rgb=ReadPixelFast(x,y,buffer) h=(rgb And $ff0000)/$10000 offset=(y*width)+x PokeByte bank,offset,h Next Next UnlockBuffer buffer Local f%=WriteFile(filename$) WriteBytes (bank,f,0,BankSize(bank)) CloseFile f End Function |
; Realtime Procedural Planet Generator ; by Krischan webmaster(at)jaas.de ; ; creates a planet out of a combination of 4 pre-rendered planets out of 10 and three blending filters ; it is possible to create ten thousands of unique planet surfaces in 50-100 milliseconds! ; this is done by calculating the planet step by step so it doesn't consume lots of power and whole ; solar systems can be created while moving towards the system or even the planet Graphics3D 800,600,32,2 ; Declarations Dim GradientR%(0),GradientG%(0),GradientB%(0),Prozent%(0),Rot%(0),Gruen%(0),Blau%(0) Dim images%(9) Dim Array%(0) Global maxx%=512 Global maxy%=256 Global max%=maxx*maxy Global pixelstep%=16384 Global movespeed#=0.1 ; Randomize SeedRnd MilliSecs() ; Create Color Gradient Restore ClassMT : CreateGradient(9,255) ; Read source images For i%=0 To 9 : images(i)=LoadBankImage("planet"+i+".img",maxx,maxy) : Next ; Create random array CreateRandomArray(max) ; Create target texture output=CreateTexture(maxx,maxy,16+32) texbuff=TextureBuffer(output) ; Base color of texture (water) SetBuffer texbuff Color 17, 82,112 Rect 0,0,maxx,maxy,1 Color 255,255,255 SetBuffer BackBuffer() ; Planet planet=CreateSphere(32) ScaleEntity planet,10,10,10 PositionEntity planet,0,0,10 EntityFX planet,2 EntityTexture planet,output,0,1 TextureBlend output,2 EntityShininess planet,0.25 ; Light light=CreateLight(1) AmbientLight 8,8,8 ; Camera camera=CreateCamera() PositionEntity camera,20,0,0 CameraRange camera,0.1,1000 ; Cursor centered, cam points to planet MoveMouse GraphicsWidth()/2,GraphicsHeight()/2 HidePointer() PointEntity camera,planet ; World creation flag newworld=True ; Time measurement start time=MilliSecs() ; Main Loop While Not KeyHit(1) ; FPS measurement FPS_C=FPS_C+1 : If fms<MilliSecs() Then fms=MilliSecs()+1000 : FPS=FPS_C : FPS_C=0 ; Frame tweening Tween#=Float(MilliSecs()-FrameTime)/10.0 : FrameTime=MilliSecs() ; Simple Steering mxs#=MouseXSpeed() mys#=MouseYSpeed() RotateEntity camera,EntityPitch(camera)+(mys#/5),EntityYaw(camera)-(mxs#/5),0 If KeyDown(200) Then MoveEntity camera,0,0,movespeed*Tween If KeyDown(208) Then MoveEntity camera,0,0,-movespeed*Tween MoveMouse GraphicsWidth()/2,GraphicsHeight()/2 ; Current time ms=MilliSecs() ; SPACE and Newworld-Flag False? create a new map! If KeyHit(57) Then ; Measure start time starttime=ms ; Reset pixel counters i=0 water%=0 land%=0 ; Select 4 planet maps as base map$=ZERO(Rand(0,9999),4) ; Extract Planet Codes i1=Int(Left(map,1)) i2=Int(Mid(map,1,1)) i2=Int(Mid(map,2,1)) i4=Int(Right(map,1)) ; set Newworld-Flag True newworld=True EndIf ; Turn Planet and Clouds a little bit TurnEntity planet,0,-0.1*Tween,0 ; SPACE, 30ms gone and Newworld-Flag true? go on! If ms>time And newworld Then roundstart=ms ; Increase time time=ms+30 ; Calculate number of pixels to set adder%=(pixelstep/Tween) ; Start/End for current loop start=i ende=i+adder ; End greater than pixels? end=pixels If ende>max-1 Then ende=max-1 ; Lock Texturebuffer LockBuffer texbuff ; Current Pixel loop For j=start To ende ; Pixelincrement i=i+1 ; Pixel greater than pixel amount? End! If i>max-1 Then newworld=False Goto skip EndIf ; Calculate Pixel X/Y-Position from the randomized array y=Int(Array(i)/maxx) x=Array(i)-(y*maxx) ; get current pixels from bank offset=(y*maxx)+x r1=PeekByte(images(i1),offset) r2=PeekByte(images(i2),offset) r3=PeekByte(images(i3),offset) r4=PeekByte(images(i4),offset) ; Mix with Image Filter r=Average(r1,r2) r=Lighten(r,r3) r=HardLight(r,r4) ; Legend of usesful Filter combinations: ; ; S = Softlight ; M = Multiply ; L = Lighten ; A = Average ; H = Hardlight ; O = Overlay ; E = exclusion ; D = Difference ; N = Negation ; ; SSM = ~ 99% Land ; SSS = 25-75% Land ; SSL = 75-25% Land, flat ; ALH = 35-65% Land, continental ; AMM = 100% Land, mountaineous ; OLS = 25-75% Land ; OLC = <10% Land, oceancic ; MMM = 100% Land, snowy mountains ; EDN = different, earthlike, Archipelagos ; MNS = different, earthlike, Archipelagos ; Above 128: below water, otherwise Land If r>=128 Then water=water+1 Else land=land+1 ; Target Color from Gradient rgb=GradientR(r)*$10000+GradientG(r)*$100+GradientB(r) ; Write to Texturebuffer WritePixelFast x,y,rgb,texbuff Next .skip endtime=MilliSecs() ; Unlock Texturebuffer UnlockBuffer texbuff ; Calculate amount of used time midtime=(midtime+(endtime-roundstart))/2.0 EndIf RenderWorld ; Statistics Text 0, 0,"Planet Source Maps: "+map Text 0, 15,"Pixels blended....: "+(i*100)/max+"%" Text 0, 30,"Transition Time...: "+(endtime-starttime)+"ms" Text 0, 45,"Used ms per cycle.: "+midtime+"ms" Text 0, 60,"Pixels per cycle..: "+adder Text 0, 75,"Water coverage....: "+(water*100.0)/max+"%" Text 0, 90,"Land coverage.....: "+(land*100.0)/max+"%" Text 0,105,"FPS...............: "+FPS Text 0,120,"Tris rendered.....: "+TrisRendered() Flip 0 Wend End ; Soft Light Filter Function SoftLight(a%,b%) Local c% c=a*b Shr 8 Return (c+a*(255-((255-a)*(255-b) Shr 8)-c) Shr 8) End Function ; Hard Light Filter Function HardLight(a%,b%) If b<128 Then Return (a*b) Shr 7 Else Return 255-((255-b)*(255-a) Shr 7) End Function ; Difference Filter Function Difference(a%,b%) Return Abs(a-b) End Function ; Multiply Filter Function Multiply(a%,b%) Return (a*b) Shr 8 End Function ; Average Filter Function Average(a%,b%) Return (a+b) Shr 1 End Function ; Screen Filter Function Screen(a%,b%) Return 255-((255-a)*(255-b) Shr 8) End Function ; Lighten Filter Function Lighten(a%,b%) If a>b Then Return a Else Return b End Function ; Darken Filter Function Darken(a%,b%) If a<b Then Return a Else Return b End Function ; Negative Filter Function Negation(a%,b%) Return 255-Abs(255-a-b) End Function ; Exclusion Filter Function Exclusion(a%,b%) Return a+b-(a*b Shr 7) End Function ; Overlay Filter Function Overlay(a%,b%) If a<128 Then Return (a*b) Shr 7 Else Return 255-((255-a)*(255-b) Shr 7) End Function ; Color Burn Filter Function ColorDodge(a%,b%) If b=255 Then Return 255 Else Local c%=Floor((a Shl 8)/(255-b)) If c>255 Then Return 255 Else Return c EndIf End Function ; Create Gradient Function CreateGradient(colors%,steps%) Dim GradientR%(steps),GradientG%(steps),GradientB%(steps),Prozent%(colors),Rot%(colors),Gruen%(colors),Blau%(colors) Local i%,pos1%,pos2%,pdiff% Local rdiff%,gdiff%,bdiff% Local rstep#,gstep#,bstep# Local counter%=1 ; read color codes For i=1 To colors : Read Prozent(i),Rot(i),Gruen(i),Blau(i) : Next ; calculate gradient While counter<colors ; transform percent value into step position pos1=Prozent(counter)*steps/100 pos2=Prozent(counter+1)*steps/100 ; calculate position difference pdiff=pos2-pos1 ; calculate color difference rdiff%=Rot(counter)-Rot(counter+1) gdiff%=Gruen(counter)-Gruen(counter+1) bdiff%=Blau(counter)-Blau(counter+1) ; calculate color steps rstep#=rdiff*1.0/pdiff gstep#=gdiff*1.0/pdiff bstep#=bdiff*1.0/pdiff ; calculate "in-between" color codes For i=0 To pdiff GradientR(pos1+i)=Int(Rot(counter)-(rstep*i)) GradientG(pos1+i)=Int(Gruen(counter)-(gstep*i)) GradientB(pos1+i)=Int(Blau(counter)-(bstep*i)) Next ; increment counter counter=counter+1 Wend End Function ; create a random array where each value appears only once Function CreateRandomArray(size%) ; Redim Array Dim Array(size) ; Fill with values For i = 0 To size-1 : Array(i) = i : Next ; play dice For N% = 0 To size-2 M% = Rand( N%, size - 1) Z% = Array(N%) Array(N%) = Array(M%) Array(M%) = Z% Next End Function ; Fill a String with prefix Zeros Function ZERO$(number%,lenght%=2) Local r$="" For i=1 To lenght-Len(Str(number)) r$=r$+"0" Next Return r$+Str(number) End Function ; Load a raw Image Function LoadBankImage(filename$,width%,height%) Local f%=OpenFile(filename$) Local bank%=CreateBank(width*height) ReadBytes(bank,f,0,BankSize(bank)) CloseFile f Return bank End Function ; Color codes for an earthlike Planet .ClassMT Data 0,255,255,255 Data 5,179,179,179 Data 10,153,143, 92 Data 25,115,128, 77 Data 48, 42,102, 41 Data 50, 69,108,118 Data 52, 17, 82,112 Data 75, 9, 62, 92 Data 100, 2, 43, 68 |