Code archives/3D Graphics - Misc/Spacegame tech demo

This code has been declared by its author to be Public Domain code.

Download source code

Spacegame tech demo by Krischan2009
This is a small demo made of parts from my current project. It demonstrates a simple Freelancer-like steering and a planet with atmosphere glow. When you get close to the planet the atmosphere effect shows up and it even includes a check if you are at the dayside or nightside and fades the stars at the dayside. The glowing ring always keeps the same size as the planet. Have fun!

Steering: Mouse, Arrows
Red Cross: Flight direction
Green Box: Target direction
SPACE = Wireframe on/off
AppTitle "Planet Atmosphere Glow Demo"
; by Krischan webmaster(at)jaas.de

; Constants
Const ScreenWidth%			= 800			; Screen width
Const ScreenHeight%			= 600			; Screen height
Const ColorDepth%			= 32			; Color depth
Const ScreenMode%			= 2				; Screen Mode
Const MouseSpeed#			= 0.1			; Mousespeed
Const CameraSmoothness#		= 10			; Smoothness of movement
Const Scale#				= 12.756		; Approx. the diameter of earth (1 unit = 1000km)
Const GameTime%				= 20			; Game speed
Const CursorSize%			= 33			; Size of cursor and crosshair image
Const TurnSpeed#			= 2.0			; Turnspeed of player
Const RollSpeed#			= 1.0			; Roll speed
Const MaxRollAngle#			= 20.0			; Maximum roll angle
Const RingDetail%			= 60			; Number of segments

; Glow Color scheme
Const R1%=192,G1%=224,B1%=255				; Surface near color
Const R2%=128,G2%=160,B2%=255				; Surface far color

; Variables
Global CameraSpeed#			= Scale/10.0	; Movement speed
Global TargetDistance#		= 100.0*Scale	; Mousetarget distance to player
Global FrameTime			= MilliSecs()	; Initialize Frame Timer
Global Period%				= 1000/75.0		; Calc frame period (here: 75FPS)

; help variables and objects
Global MX%,MY%								; Mouse position
Global Player%,MouseTarget%,Cam%,Ship%		; Player
Global Cursor%,Cross%						; Images
Global Planet%,Light%,Sun%,StarBox%			; Scene objects
Global GlowPivot%							; Pivot of Glow
Global Glow1%,Glow2%						; Glow rings
Global GlowScale#							; Glow scale indicator
Global Tween#								; Frame tween
Global MoveSpeed#							; movespeed in km/h

; Init 3D
Graphics3D ScreenWidth,ScreenHeight,ColorDepth,ScreenMode

; Init Player and Scene
InitPlayer()
InitScene()

; Center mouse and hide pointer
MoveMouse ScreenWidth/2,ScreenHeight/2
HidePointer

; Main loop
While Not KeyHit(1)
	
	; Frame tweening
	Tween#=Float(MilliSecs()-FrameTime)/Float(GameTime) : FrameTime=MilliSecs()
	
	; SPACE = Wireframe
	If KeyHit(57) Then wf%=1-wf : WireFrame wf
	
	; Get Distance player to planet
	Local distance#=EntityDistance(Cam,Planet)
	
	; Update atmosphere glow
	UpdateAtmosphere(distance)
	
	; Attach Starbox to camera
	PositionEntity StarBox,EntityX(Player),EntityY(Player),EntityZ(Player)
	
	;Sun points To Player
	PointEntity Sun,Player
	
	UpdateWorld
	RenderWorld
	
	;Move Player
	Movement(GlowScale,distance)
	
	; Draw cursor and crosshair by chasing the mousetarget
	CameraProject Cam,EntityX(MouseTarget,1),EntityY(MouseTarget,1),EntityZ(MouseTarget,1)
	DrawImage Cursor,MX-(CursorSize/2),MY-(CursorSize/2)
	DrawImage Cross,ProjectedX()-(CursorSize/2),ProjectedY()-(CursorSize/2)
	
	; Statistics
	Text 0, 0,"Triangles rendered....: "+TrisRendered()
	Text 0,15,"Distance To Surface...: "+Int((distance-Scale)*1000)+" km"
	Text 0,30,"Move speed............: "+MoveSpeed+" km/h"
	
	Flip 0
	
Wend

End

; The functions that creates the mesh and surface for a ring
Function CreateRing(fx%=0,blend%=0)
	
	Local a1#,a2#,a3#,a4#,angle%
	Local v0%,v1%,v2%,v3%
	
	Local mesh=CreateMesh()
	Local surf=CreateSurface(mesh)
	
	; Ring FX
	If fx>0 Then EntityFX mesh,fx
	If blend>0 Then EntityBlend mesh,blend
	
	Return mesh
	
End Function

; Re-creates the ring vertices and triangles with different values
Function UpdateRing(mesh%,radius1#=1.0,radius2#=2.0,segments%=360,r1%=255,g1%=255,b1%=255,alpha1#=1.0,r2%=0,g2%=0,b2%=0,alpha2#=1.0,scale#=0.0)
	
	Local a1#,a2#,angle%
	Local v0%,v1%,v2%,v3%,v%
	
	; get and clear the surface
	Local surf=GetSurface(mesh,1)
	ClearSurface surf,1,1
	
	; Limit segments
	If segments>360 Then segments=360
	
	; Create ring
	For angle=0 To segments
		
		a1=angle*360.0/segments
		a2=angle*360.0/segments+180.0/segments
		
		; Calc vertex points
		v0=AddVertex(surf,radius1*Cos(a1),radius1*Sin(a1),0,0,0)
		v1=AddVertex(surf,radius2*Cos(a2),radius2*Sin(a2),scale,1,1)
		
		; Color
		VertexColor surf,v0,r1,g1,b1,alpha1
		VertexColor surf,v1,r2,g2,b2,alpha2
		
	Next
	
	; Create Triangles
	For v=0 To CountVertices(surf)-3
		
		AddTriangle(surf,v,v+1,v+2)
		
	Next
	
	Return mesh
	
End Function

; Initialize player, camera
Function InitPlayer()
	
	Local i%
	
	; Player pivot
	Player=CreatePivot()
	PositionEntity Player,-Scale*2,0,Scale*2
	
	; Ship mesh
	Ship=CreateCube(Player)
	EntityFX Ship,1
	ScaleEntity Ship,0.2,0.05,0.2
	PositionEntity Ship,0,-0.5,1.5
	EntityOrder Ship,-1000
	
	; Mousetarget in space
	MouseTarget=CreatePivot(Ship)
	MoveEntity MouseTarget,0,0,TargetDistance
	
	; Camera
	Cam=CreateCamera(Player)
	PositionEntity Cam,0,0,0
	CameraRange Cam,0.01,1000*Scale
	
	; Create cursor image
	Cursor=CreateImage(CursorSize,CursorSize)
	SetBuffer ImageBuffer(Cursor)
	For i=0 To 2
		Color 0,Int(255.0/(1+i)),0
		Rect i,i,CursorSize-(2*i),CursorSize-(2*i),0
	Next
	
	; Create crosshair image
	Cross=CreateImage(CursorSize,CursorSize)
	SetBuffer ImageBuffer(Cross)
	Color 255,0,0
	Line (CursorSize-1)/2.0,0,(CursorSize-1)/2.0,CursorSize
	Line 0,(CursorSize-1)/2.0,CursorSize,(CursorSize-1)/2.0
	
	; reset buffer and color
	SetBuffer BackBuffer()
	Color 255,255,255
	
End Function

; Initialize scene
Function InitScene()
	
	Local startex%,i%,col%,rgb%
	
	; Planet
	Planet=CreateSphere(60)
	ScaleEntity Planet,Scale,Scale,Scale
	EntityColor Planet,32,192,64
	
	; Directional Sunlight
	Light=CreateLight(1)
	PositionEntity Light,0,0,-Scale*200
	LightRange Light,200*Scale
	AmbientLight 16,16,16
	
	; Sun
	Sun=CreateQuad()
	ScaleEntity Sun,Scale*10,Scale*10,Scale*10
	EntityFX Sun,1
	EntityColor Sun,255,255,192
	EntityParent Sun,Light
	PositionEntity Sun,0,0,0
	EntityBlend Sun,3
	EntityTexture Sun,CreateSunTexture()
	
	; Simple Starbox
	StarBox=CreateCube()
	startex=CreateTexture(1024,1024)
	LockBuffer TextureBuffer(startex)
	For i=1 To 1000
		col=Rand(0,255)
		rgb=col*$10000+col*$100+col
		WritePixelFast Rand(0,1023),Rand(0,1023),rgb,TextureBuffer(startex)
	Next
	UnlockBuffer TextureBuffer(startex)
	EntityTexture StarBox,startex
	ScaleEntity StarBox,10,10,10
	EntityOrder StarBox,1
	EntityFX StarBox,1
	FlipMesh StarBox
	
	; Glow
	GlowPivot=CreatePivot()
	Glow1=CreateRing(1+2+16+32,1)
	Glow2=CreateRing(1+2+16+32,3)
	EntityParent Glow1,GlowPivot
	EntityParent Glow2,GlowPivot
	EntityOrder Glow1,1
	EntityOrder Glow2,1
	
	; Player points to planet first
	PointEntity Player,Planet
	TurnEntity Player,0,-20,-90
	
End Function

; Update atmosphere glow and background color
Function UpdateAtmosphere(distance#)
	
	Local s2#,s2d#,aa#,bb#,cc#
	Local angle#,intensity#,clscol#,horizon#
	
	; Calculate ring scale with the help of two right-angled triangles and trigonometry
	s2#=Scale^2
	s2d#=s2/distance
	GlowScale#=(Sqr(s2+(Scale/Tan(90-(90-(90-ATan(Sqr((distance-(s2d))*(s2d))/(distance-(s2d)))))))^2))/Scale
	
	; Calculcate the sun light angle (1 = exactly between sun and planet, 0 = exactly behind the planet)
	aa#=EntityDistance(Cam,Planet)
	bb#=EntityDistance(Planet,Light)
	cc#=EntityDistance(Cam,Light)
	angle#=ACos((aa^2+cc^2-bb^2)/(2*aa*cc))/180.0
	If angle>1 Then angle=1
	If angle<0 Then angle=0
	
	; Calculate the glow intensity according to sun light angle and distance to planet
	intensity#=1-(1.0/Exp(GlowScale*angle))
	If intensity<0 Then intensity=0
	If intensity>1 Then intensity=1
	
	; Calculate the horizon glow scale multiplicator
	horizon#=1-(1.2/Exp(GlowScale*angle/2.0))
	If horizon<0 Then horizon=0
	If horizon>1 Then horizon=1
	
	; Update ring intensity according to sun light angle and distance to planet
	UpdateRing(Glow1,0.6*Scale,0.01*Scale,RingDetail,R1*intensity,G1*intensity,B1*intensity,angle,R2*(angle-intensity),G2*(angle-intensity),B2*(angle-intensity),   0,Scale/3.0)
	UpdateRing(Glow2,(1.0-(horizon/10.0))*Scale,(1.05+(horizon/10.0))*Scale,RingDetail,R1*intensity,G1*intensity,B1*intensity,1.0-horizon,R2*angle,G2*angle,B2*angle,0,horizon*2)
	
	; Scale the rings and always point to player
	ScaleEntity GlowPivot,GlowScale,GlowScale,GlowScale
	PointEntity GlowPivot,Player
	
	; Calculate the background color to simulate atmosphere penetration
	clscol#=(1-(5.0/Exp(GlowScale*angle/2.0)))*intensity
	If clscol<0 Then clscol=0
	If clscol>1 Then clscol=1
	CameraClsColor Cam,R2*clscol,G2*clscol,B2*clscol
	
	; Change Starbox alpha
	EntityAlpha StarBox,1-clscol
	
End Function

; Player movement
Function Movement(scale#,distance#)
	
	Local roll#,mox#,moz#,cx#,cz#
	Local t1#,t2#
	
	; get mouse position
	MX=MouseX()
	MY=MouseY()
	
	; Movement with speed limit
	cx=(KeyDown(205)-KeyDown(203))*CameraSpeed
	cz=(KeyDown(200)-KeyDown(208))*CameraSpeed
	
	; Arrow left/right = roll
	If KeyDown(203) Then roll=RollSpeed
	If KeyDown(205) Then roll=-RollSpeed
	
	; Normalize Mouse position (-1 to +1)
	t1=Normalize(MY,0,ScreenHeight,-1,1)
	t2=Normalize(MX,0,ScreenWidth,1,-1)
	
	; Slower cursor movement in the center of the screen
	;If t1<0 Then t1=(Abs(t1)^2.0)*-1 Else t1=t1^2.0
	;If t2<0 Then t2=(Abs(t2)^2.0)*-1 Else t2=t2^2.0
	
	; Rotate ship mesh and turn player pivot
	RotateEntity Ship,t1*MaxRollAngle,t2*MaxRollAngle,t2*MaxRollAngle*2
	TurnEntity Player,t1*TurnSpeed*Tween,t2*TurnSpeed*Tween,roll*TurnSpeed*Tween
	
	; Move the player forward/backward
	MoveEntity Player,0,0,(cz*1.0/(scale)^3)*Tween
	
	; Calculate actual movespeed
	MoveSpeed#=(cz*1.0/(scale)^3)*3600000
	
End Function

; Normalize a value
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 CreateQuad()
	
	; Create mesh and surface
	Local mesh%=CreateMesh()
	Local surf%=CreateSurface(mesh)
	
	; Add vertices
	Local v0%=AddVertex(surf,  1.0,  1.0, 0.0, 0.0, 0.0 )	; upper left
	Local v1%=AddVertex(surf, -1.0,  1.0, 0.0, 1.0, 0.0 )	; upper right
	Local v2%=AddVertex(surf, -1.0, -1.0, 0.0, 1.0, 1.0 )	; lower right
	Local v3%=AddVertex(surf,  1.0, -1.0, 0.0, 0.0, 1.0 )	; lower left
	
	; Connect vertices
	AddTriangle surf,v0,v1,v2
	AddTriangle surf,v0,v2,v3
	
	Return mesh
	
End Function

; Create a simple sun texture
Function CreateSunTexture()
	
	Local tex%=CreateTexture(512,512,2)
	Local tb%=TextureBuffer(tex)
	
	Local i#,j%,col%,rgb%
	
	SetBuffer tb
	LockBuffer tb
	
	; Intensity steps
	For j=0 To 255
		
		; Exponential falloff
		col=Int((1.0/Exp(j*0.075))*100000000)
		If col>255 Then col=255
		rgb=col*$1000000+col*$10000+col*$100+col
		
		; Draw circles
		For i=0 To 360 Step 0.1
			WritePixelFast 256+(Sin(i)*j),256+(Cos(i)*j),rgb,tb
		Next
		
	Next
	
	UnlockBuffer tb
	SetBuffer BackBuffer()
	
	Return tex
	
End Function

Comments

CakeMonitor2009
This is pure gold! Only a little bit of tweaking and it is the exact control system I was developing. You have saved me soooo much work. The glow and lighting system for the sun and planets is also exceptional imo and has given me inspiration for overcoming various problems I will shortly be facing.

You will be fully credited ofc when my project hits the shelves.


Krischan2009
Yeah use it, it is public domain. My own engine will be even more advanced than this, but this demo shows the basic idea behind it. There is only a little bug in the very basic function to determine if you are between the planet and the sun or behind the planet: if you are exactly behind the planet and facing the sun, my triangle calculation fails. You need to limit the very small angle numbers or the glow will begin to flicker for a small angle range:

Failing Line:
If angle>1 Then angle=1
If angle<0 Then angle=0

Patch:
If angle Then lastangle#=angle#
Local s2#,s2d#,aa#,bb#,cc#

...

If Not angle And lastangle<0.5 Then angle=0.01
If Not angle And lastangle>0.5 Then angle=0.99
If angle<0.01 Then angle=0.01
If angle>0.99 Then angle=0.99

This is not a 100% solution but the numbers are so small that you wouldn't notice the difference.

To see the bug just change the player position line in the Function "Initplayer" to this and move backwards without moving the mouse, the ring will begin to flicker or even disappear:
PositionEntity Player,0,0,Scale*3



CakeMonitor2009
Hello again Christian,

I've been testing this quite a bit and have noticed one other tiny issue. When you get very close to the surface of the planet (about 10km), you can see what i think is one the glow surfaces from behind. It obscures the planet surface.

Hopefully this image I put on imageShack works...



[URL]http://img231.imageshack.us/my.php?image=atmosissuejl9.jpg[/URL]

Have you too had this issue and any ideas on a fix?


ps by the way, when you say that this is a small portion of your project, you make me drool and I cannot help but feel that my contribution to the space game genre will be strictly inferior to yours! Even so, I shall trudge on and wish you all the best with your project.


Krischan2009
Hi CakeMonitor. Yeah I know this issue, it's just the first value of the CameraRange which is not small enough anymore. You are so close to the planet that the camera can't project it, adjust it to a lower value to "fix" the issue.

In my project I plan to "auto-land" the ship when you reached about 10km height (and to fake the landing with some cloud and atmosphere entry fx), so I don't care. You could try to auto-update the camerarange depending on the distance to the surface, but it is not designed for that.

And thanks for the wishes - I am a biiiiiig Starflight 1 fan (Binary Systems, 1986) and I think I am able to warp this type of game with my own additions into 3D space with Blitz. At this moment I am working on a realtime planet generator using banks and blendings, you can take a look at it here:

http://www.blitzbasic.com/codearcs/codearcs.php?code=2416

Generate your unique Class M planet in 200ms! Still in an early stage but able to generate ten thousands of unique surfaces out of 10 images!


Krischan2009
Well, here is an update with a whole solar system. I had some problems with the correct distances but I think I solved it - could be better but it is working and sufficient as an example. Have fun.

Steering:
Mouse= Target direction
Arrows = move forward/backward and roll
LMB = 10x Zoom
RMB = move 10x faster
SPACE = Wireframe

Demo screenshot:


Pimped screenshot, uses 99% of the code below:





_PJ_2009
It's looking absolutley beautiful, and very smooth/fast too!

I dunnoif it's important for your use, though your
p\radius=(4+3*2^(i-1))*1000
is very similar.

Unless you want astronomically accurate mean distances for your planets, a quick method (especially if static) is to use the Titus-Bode approximation:

Function Titus_Bode_Distance#(Planet_Number%)
Return 0.4+(0.3*(2^(Planet_Number-2)))
End Function


You also need to assume a value for dwarf-planets etc. such as the asteroid field between Mars and Jupiter to maintain consistency, so the 'Planet_Number' list should be:

1 - Mercury
2 - Venus
3 - Earth
4 - Mars
5 - (Asteroids)
6 - Jupiter
7 - Saturn
8 - Uranus
9 - Pluto

Note the exception of Neptune, due to its greatly obliquatwed orbit and interference from Pluto and other TNO gravitational effects.

The approximation still gives excellent results quickly.


Code Archives Forum