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 |