Import sidesign.minib3d

AppTitle = "Galaxy Demo by Krischan [www.extrasolar.de]"

Global swidth:Int = 1024
Global sheight:Int = 600
Global sfull:Int = 2
Global stars:Int = 100000
Global spiralarms:Int = 4.0
Global spread:Float = 20.0
Global rotation:Float = 4.0

If AppArgs.length > 1 Then

	If Int(AppArgs[1]) > 0 Then swidth = Int(AppArgs[1])

End If

If AppArgs.length > 2 Then

	If Int(AppArgs[2]) > 0 Then sheight = Int(AppArgs[2])

End If

If AppArgs.length > 3 Then

	If Int(AppArgs[3]) > 0 Then sfull = 1 Else sfull = 2

End If

If AppArgs.length > 4 Then

	If Int(AppArgs[4]) > 0 Then Stars = Int(AppArgs[4])

End If

If AppArgs.length > 5 Then

	If Int(AppArgs[5]) > 1 Then spiralarms = Int(AppArgs[5])

End If

If AppArgs.length > 6 Then

	If Float(AppArgs[6]) > 0 Then spread = Float(AppArgs[6])

End If

If AppArgs.length > 7 Then

	If Float(AppArgs[7]) > 0.0 Then Rotation = Float(AppArgs[7])

End If

' ----------------------------------------------------------------------------
' 3D Graphics starts here
' ----------------------------------------------------------------------------
Graphics3D swidth, sheight, 32, sfull

'ClearTextureFilters()

Const TurnSpeed:Float = 4.00
Const RollSpeed:Float = 0.50
Const CameraSpeed:Float = 0.01

'Const Stars:Int = 100000		' number of stars
'Const Spiralarms:Int = 4		' number of spiral arms
'Const Spread:Float = 20.0		' star spread
'Const Rotation:Float = 4.0		' how many spiral rotations
Const Range:Float = 100.0		' milkyway radius

Const QuatToEulerAccuracy:Double = 0.00001

Global width:Int = GraphicsWidth()
Global Height:Int = GraphicsHeight()

Global TIMER:TTimer = CreateTimer(60)
Global Scale:Float = WIDTH / 3.0	' star scale

Global meshes:Int[256]

Global total:Int, vis:Int

Local cam:TCamera, galaxy:Int, tex:TTexture, i:Int, g:Int

Type quad

	Global List:TList = CreateList()
	Method New()
		ListAddLast(List, Self)
	End Method
    
	Field surf:Int
	Field x:Float, y:Float, z:Float
	Field v:Int
	Field scale:Float
	Field r:Int, g:Int, b:Int
	Field lastdist:Float
    
End Type

tex = CreateSunTexture(256, 128, 128, 128)
TextureBlend tex, 3

' init galaxy
galaxy = InitGalaxy(Stars, Spiralarms, Spread, Rotation, Range)

For i = 0 To galaxy
    
	If meshes[i] > 0 Then
		
		EntityFX meshes[i], 1 + 2' + 2 + 32
		'EntityBlend meshes[i], 3
		EntityTexture meshes[i], tex
		
	EndIf
    
Next

' init camera
cam = CreateCamera()
CameraRange cam, 0.1, 1000
PositionEntity cam, 80, 54, 0
PointEntity cam, meshes[0]

' update galaxy once
Local cx:Float = EntityX(cam)
Local cy:Float = EntityY(cam)
Local cz:Float = EntityZ(cam)
Local q:quad, d:Float, s:Float

For q:quad = EachIn quad.List

	' adaptive size
	d = Distance3D(q.x, q.y, q.z, cx, cy, cz)
	s = (q.Scale / d) + (d / Scale)
	If s < d / Scale Then s = d / Scale
    
	' align single surface quads to cam
	UpdateQuad(q:quad, s, cam)
	q.lastdist = d
    
Next

Local sprite:TMesh = CreateQuad(Null)
Local texture1:TTexture = LoadTexture("textures/milkyway.jpg")
ScaleEntity sprite, 128, 128, 128
EntityTexture sprite, texture1
RotateMesh sprite, -90, 0, 0
EntityFX sprite, 1 + 16
EntityBlend sprite, 3

Local detail:TMesh = CreateQuad(Null)
Local texture2:TTexture = LoadTexture("textures/milkyway_detail.jpg", 2)
ScaleEntity detail, 128, 128, 128
EntityTexture detail, texture2
RotateMesh detail, -90, 0, 0
EntityFX detail, 1 + 16
'PositionEntity detail, 0, 0.1, 0
'ScaleTexture texture2, 1.0 / 8, 1.0 / 8
EntityBlend detail, 3

'HideEntity sprite
'HideEntity detail

MoveMouse width / 2, Height / 2

' main loop
While Not AppTerminate()

	Cls
	
	' Escape = Terminate
	If KeyHit(KEY_ESCAPE) Then End
	
	Movement(cam, 1)
	
	Local ms:Int, me:Int
    
	ms = MilliSecs()
	g = g + 1
	If g >= 10 Then g = 0
	UpdateGalaxy(cam, Scale, g)
	me = MilliSecs() - ms
	
	Local a:Float = 0.0
	Local y:Float = EntityY(cam)
	If y < 10 And y > - 10 Then a = 1 - (Abs(y) / 10.0)
	'EntityAlpha detail, a
	
	Local a2:Float = Normalize(Abs(y), 0, 75, 0, 1)
	If a2 < 0.125 Then a2 = 0.125
	
	EntityAlpha sprite, a2
	
	'RotateEntity cam, 45, 90, 0
	
	RenderWorld
    
	WaitTimer TIMER
    
	'Text 0, 0, vis + " Stars updated [" + total + " Stars total] " + me + "ms"
	'Text 0, 20, EntityY(Cam)
    
	Flip 0
    
Wend

End

Function Movement(cam:TCamera, sensitivity:Float = 1.0)
	
	Local roll:Float, cz:Float, tx:Float, ty:Float, multi:Int = 1
	
	cz = (KeyDown(KEY_W) - KeyDown(KEY_S)) * CameraSpeed
	roll = (KeyDown(KEY_A) - KeyDown(KEY_D)) * RollSpeed
	If KeyDown(KEY_Q) Or MouseDown(1) Then multi = 10
	If KeyDown(KEY_E) Or MouseDown(2) Then multi = multi * 10
	
	tx = Normalize(MouseX(), 0, GraphicsWidth(), 1, -1)
	ty = Normalize(MouseY(), 0, GraphicsHeight(), -1, 1)
	
	If ty < 0 Then ty = (Abs(ty) ^ sensitivity) * -1 Else ty = ty ^ sensitivity
	If tx < 0 Then tx = (Abs(tx) ^ sensitivity) * -1 Else tx = tx ^ sensitivity
	
	TurnCam cam, ty * TurnSpeed, tx * TurnSpeed, roll * TurnSpeed
	MoveEntity cam, 0, 0, cz * multi
	
End Function

' update quads in single surface mesh
Function UpdateGalaxy(cam:TEntity, scale:Float = 256.0, part:Int)
    
	Local cx:Float = EntityX(cam)
	Local cy:Float = EntityY(cam)
	Local cz:Float = EntityZ(cam)
    
	Local q:quad, d:Float, s:Float, steps:Int, i:Int, start:Int, ende:Int
    
	total = 0
	vis = 0
    
	steps = Stars / 10
    
	For q:quad = EachIn quad.List
        
		' set start and end
		start = part * steps
		ende = (part + 1) * steps
        
		' check only current part or nearby stars
		If i >= start And i < ende Or q.lastdist < 5 Then
            
			TFormPoint q.x, q.y, q.z, 0, cam
            
			' in front of cam?
			If TFormedZ() > 0 Then
				
				' adaptive size
				d:Float = Distance3D(q.x, q.y, q.z, cx, cy, cz)
				s:Float = (q.scale / d) + (d / scale)
				If s < d / scale Then s = d / scale
				
				s = 1.0 / d
                If s < d / Scale Then s = d / Scale
				
				If s > 0.1 Then s = 0.1
				
				' store last distance calculation
				q.lastdist = d
				
				' align single surface quads To cam
				UpdateQuad(q:quad, s, cam)
				
				vis = vis + 1
                
			EndIf
			
		EndIf
        
		i = i + 1
        
		total = total + 1
        
	Next
    
End Function

' Create galaxy
Function InitGalaxy:Int(stars:Int = 10000, arms:Int = 4, spread:Float = 40.0, rot:Float = 2.0, range:Float = 100.0)
    
	Local mesh:TMesh = CreateMesh()
	Local surf:TSurface = CreateSurface(mesh)
	Local q:quad
	Local angle:Float, dist:Float, turb:Float
	Local i:Int, col:Float, multi:Float, lum:Float, bulge:Int, counter:Int, surfs:Int = 1, m:Int = 0
    
	meshes[0] = mesh
    
	For i = 1 To Stars
        
		q:quad = New quad
        
		' color
		col = Rnd(1)
		If col > 0.90 And col <= 1.00 Then
			q.r = 255
			q.g = 0
			q.b = 0
		EndIf
	
		If col > 0.70 And col <= 0.90 Then
			q.r = 255
			q.g = 255
			q.b = 0
		EndIf
	
		If col > 0.50 And col <= 0.70 Then
			q.r = 0
			q.g = 0
			q.b = 255
		EndIf
	
		If col > 0.00 And col <= 0.50 Then
			q.r = 255
			q.g = 255
			q.b = 255
		EndIf
        
		' angle
		angle = Int(Floor(i * 1.0 / (Stars / arms))) * (360.0 / arms)
        
		' center / arm relation
		If Rnd(1) > 0.5 Then
			multi = Rnd(0.1, 1)
		Else
			multi = Rnd(1, 2)
		EndIf
        
		' distance And turbulence
		dist = Rnd(0, Range) * Rnd(1, Rnd(Rnd(multi)))
		turb = Rnd(0, Rnd(Spread))
		If Rnd(1) > 0.5 Then turb = -turb
        
		' more red/yellow Stars in bulge
		lum = Rnd(1)
		If dist < Range / 2 * lum Then
            
			If lum > 0.75 Then
                
				q.r = 255
				q.g = 0
				q.b = 0        ' red Stars
                
			Else If lum > 0.5 Then
                
				q.r = 255
				q.g = 255
				q.b = 0    ' yellow Stars
                
			EndIf
            
		Else
            
			If lum > 0.75 Then
                
				q.r = 0
				q.g = 0
				q.b = 255        ' blue Stars
                
		
			Else If lum > 0.5 Then
                
				q.r = 255
				q.g = 255
				q.b = 255    ' white Stars
                
			EndIf
            
		EndIf
        
		' star position x/z
		q.x = dist * Cos(angle + (dist * rot)) + Rnd(Rnd(Rnd(-Spread)), Rnd(Rnd(Spread)))
		q.z = dist * Sin(angle + (dist * rot)) + Rnd(Rnd(Rnd(-Spread)), Rnd(Rnd(Spread)))
        
		' star position y
		bulge = Normalize(Distance2D(q.x, q.z, 0, 0), 0, Range / 2.0, 0, 180) / 2.0
		If bulge > 90 Then bulge = 90
		q.y = (Cos(bulge) * Rnd(Rnd(-Spread), Rnd(Spread)) / 2.0) + (turb / 10.0)
		       
		' Scale
		q.Scale = Rnd(0.01, Rnd(0.02, Rnd(0.04, 0.08)))
        
		' Create New surface If too many vertices
		If counter + 4 > 32000 Then
            
			If surfs + 1 > 3 Then
				m = m + 1
				mesh = CreateMesh()
				meshes[m] = mesh
				surfs = 0
			EndIf
			
			surf = CreateSurface(mesh)
			counter = 0
			surfs = surfs + 1
            
		EndIf
        
		q.surf = surf
        
		' add vertices
		q.v = AddVertex(q.surf, 0, 0, 0, 0, 0)
		AddVertex(q.surf, 0, 0, 0, 1, 0)
		AddVertex(q.surf, 0, 0, 0, 1, 1)
		AddVertex(q.surf, 0, 0, 0, 0, 1)
        
		' color vertices
		VertexColor q.surf, q.v + 0, q.r, q.g, q.b
		VertexColor q.surf, q.v + 1, q.r, q.g, q.b
		VertexColor q.surf, q.v + 2, q.r, q.g, q.b
		VertexColor q.surf, q.v + 3, q.r, q.g, q.b
        
		' add triangles
		AddTriangle(q.surf, q.v, q.v + 1, q.v + 2)
		AddTriangle(q.surf, q.v, q.v + 2, q.v + 3)
        
		' vertex counter
		counter = counter + 4
        
	Next
    
	Return m
    
End Function

' align single surface quad To cam
Function UpdateQuad(q:quad, s:Float, target:TEntity)
    
	Local x1:Float, y1:Float, z1:Float, x2:Float, y2:Float, z2:Float
    
	TFormVector - s, 0, 0, target, 0
	x1 = TFormedX()
	y1 = TFormedY()
	z1 = TFormedZ()
    
	TFormVector 0, -s, 0, target, 0
	x2 = TFormedX()
	y2 = TFormedY()
	z2 = TFormedZ()
    
	VertexCoords q.surf, q.v + 0, q.x - x1 - x2, q.y - y1 - y2, q.z - z1 - z2
	VertexCoords q.surf, q.v + 1, q.x - x1 + x2, q.y - y1 + y2, q.z - z1 + z2
	VertexCoords q.surf, q.v + 2, q.x + x1 + x2, q.y + y1 + y2, q.z + z1 + z2
	VertexCoords q.surf, q.v + 3, q.x + x1 - x2, q.y + y1 - y2, q.z + z1 - z2
    
End Function

' calculate 2D Distance
Function Distance2D:Float(x1:Float, y1:Float, x2:Float, y2:Float)
    
	Local x:Float = x1 - x2
	Local y:Float = y1 - y2
    
	Return Sqr((x * x) + (y * y))
    
End Function

' calucate 3D Distance
Function Distance3D:Float(x1:Float, y1:Float, z1:Float, x2:Float, y2:Float, z2:Float)
    
	Local x:Float = x1 - x2
	Local y:Float = y1 - y2
	Local z:Float = z1 - z2
    
	Return Sqr((x * x) + (y * y) + (z * z))
    
End Function

' Normalize a value
Function Normalize:Float(value:Float = 128.0, vmin:Float = 0.0, vmax:Float = 255.0, nmin:Float = 0.0, nmax:Float = 1.0)
    
	Return ((value:Float - vmin:Float) / (vmax:Float - vmin:Float)) * (nmax:Float - nmin:Float) + nmin:Float
    
End Function

' Create a stunning sun texture
Function CreateSunTexture:TTexture(size:Int = 512, r:Int = 255, g:Int = 255, b:Int = 255)

	Local i:Float, j:Int, col:Int, rgb:Int

	Local tempcam:TCamera, tempsun:TPivot, tex:TTexture
	Local pixmap:TPixmap = CreatePixmap(size, size, PF_RGBA8888)
	Local source:TPixmap
		
	For j = 0 To (size / 2) - 1
        
		col = 255 - Normalize(j, 0, (size / 2.0) - 1, 0, 255)
		If col > 255 Then col = 255
		rgb = col * $1000000 + col * $10000 + col * $100 + col
        
		For i = 0 To 360 Step 0.1
            
			WritePixel(pixmap, (size / 2) + (Sin(i) * j), (size / 2) + (Cos(i) * j), rgb)
            
		Next
        
	Next
	
	SavePixmapPNG(pixmap, "textures/sun1.png")
	
	tex = LoadTexture("textures/sun1.png", 2)
    
	' temp camera
	tempcam = CreateCamera()
	CameraRange tempcam, 1, WIDTH * 2
    
	' temp pivot
	tempsun = CreatePivot()
    
	' Create 4 layers
	CreateQuad(tempsun, size / 4.0, tex, 3, 1 + 8 + 16, r * 1.00, g * 1.00, b * 1.00, 1.00)
	CreateQuad(tempsun, size / 1.5, tex, 3, 1 + 8 + 16, r * 1.00, g * 1.00, b * 1.00, 1.00)
	CreateQuad(tempsun, size / 1.2, tex, 3, 1 + 8 + 16, r * 0.75, g * 0.75, b * 0.50, 0.75)
	CreateQuad(tempsun, size / 1.0, tex, 3, 1 + 8 + 16, r * 0.50, g * 0.50, b * 0.50, 0.50)
    
	PositionEntity tempsun, 0, 0, WIDTH
    
	' render it
	RenderWorld
	RenderWorld	
        
	source = GrabPixmap(0, 0, WIDTH, HEIGHT)
     
	' grab image
	For x = 0 To size - 1
        
		For y = 0 To size - 1
            
			xx = (WIDTH / 2) - (size / 2) + x
			yy = (HEIGHT / 2) - (size / 2) + y
            
			rgb = ReadPixel(source, xx, yy)
            
			r = (rgb & $ff0000) / $10000
			g = (rgb & $ff00) / $100
			b = (rgb & $ff)
            
			' rgb
			rgb = (r + g + b) / 3 * $1000000 + r * $10000 + g * $100 + b
            
			WritePixel(pixmap, x, y, rgb)
            
		Next
        
	Next
    
	SavePixmapPNG(pixmap, "textures/sun2.png")
	
	' Load texture
	tex = LoadTexture("textures/sun2.png", 2)
    
	' Delete pivot and temp cam
	FreeEntity tempsun
	FreeEntity tempcam
    
	Return tex
    
End Function

' advanced quad creation
Function CreateQuad:Tmesh(parent:TEntity, scale:Float = 1.0, tex:TTexture = Null, blend:Int = False, fx:Int = False, r:Int = 255, g:Int = 255, b:Int = 255, a:Float = 1.0)
    
	Local mesh:TMesh = CreateMesh()
	Local surf:TSurface = CreateSurface(mesh)
	Local v0:Int, v1:Int, v2:Int, v3:Int
    
	v0 = AddVertex(surf, 1, 1, 0, 0, 0)
	v1 = AddVertex(surf, -1, 1, 0, 1, 0)
	v2 = AddVertex(surf, -1, -1, 0, 1, 1)
	v3 = AddVertex(surf, 1, -1, 0, 0, 1)
    
	AddTriangle surf, v0, v1, v2
	AddTriangle surf, v0, v2, v3
    
	If parent Then EntityParent Mesh, parent
	If fx Then EntityFX Mesh, fx
	If tex Then EntityTexture Mesh, tex
	If blend Then EntityBlend Mesh, blend
    
	EntityColor Mesh, r, g, b
	EntityAlpha Mesh, a
    
	VertexColor surf, v0, r, g, b, a
	VertexColor surf, v1, r, g, b, a
	VertexColor surf, v2, r, g, b, a
	VertexColor surf, v3, r, g, b, a
    
	ScaleEntity Mesh, Scale, Scale, Scale
    
	Return Mesh
    
End Function

Function TurnCam(Ent:TEntity, X:Float, Y:Float, Z:Float, Glob:Int = False)

	Local Pitch:Float = 0.0
	Local Yaw:Float = 0.0
	Local Roll:Float = 0.0
	
	Local Quat:TQuaternion = EulerToQuat( 0.0, 0.0, 0.0 )
	Local Turn_Quat:TQuaternion = EulerToQuat( 0.0, 0.0, 0.0 )
	
	If Glob=False
	
		Quat = EulerToQuat( EntityPitch( Ent, True ), EntityYaw( Ent, True ), EntityRoll( Ent, True ) )
		Turn_Quat = EulerToQuat( X, Y, Z )
		Quat = MultiplyQuats( Quat, Turn_Quat )
		Quat = NormalizeQuat( Quat )
		QuatToEuler2( Quat.x, Quat.y, Quat.z, Quat.w, Pitch, Yaw, Roll )
		RotateEntity Ent, Pitch, Yaw, Roll
	Else
	
		RotateEntity Ent, EntityPitch( Ent )+X, EntityYaw( Ent )+Y, EntityRoll( Ent )+Z
		
	EndIf
	
End Function

Function EulerToQuat:TQuaternion(pitch:Float,yaw:Float,roll:Float)

	Local cr:Float=Cos(-roll/2.0)
	Local cp:Float=Cos(pitch/2.0)
	Local cy:Float=Cos(yaw/2.0)
	Local sr:Float=Sin(-roll/2.0)
	Local sp:Float=Sin(pitch/2.0)
	Local sy:Float=Sin(yaw/2.0)
	Local cpcy:Float=cp*cy
	Local spsy:Float=sp*sy
	Local spcy:Float=sp*cy
	Local cpsy:Float=cp*sy
	
	Local q:TQuaternion=New TQuaternion
	
	q.w:Float=cr*cpcy+sr*spsy
	q.x:Float=sr*cpcy-cr*spsy
	q.y:Float=cr*spcy+sr*cpsy
	q.z:Float=cr*cpsy-sr*spcy
	
	Return q
	
End Function

Function QuatToEuler2(x:Float,y:Float,z:Float,w:Float,pitch:Float Var,yaw:Float Var,roll:Float Var)

	Local sint:Float=(2.0*w*y)-(2.0*x*z)
	Local cost_temp:Float=1.0-(sint*sint)
	Local cost:Float

	If Abs(cost_temp)>QuatToEulerAccuracy

		cost=Sqr(cost_temp)

		Else

		cost=0.0

	EndIf

	Local sinv:Float,cosv:Float,sinf:Float,cosf:Float
	
	If Abs(cost)>QuatToEulerAccuracy
	
		sinv=((2.0*y*z)+(2.0*w*x))/cost
		cosv=(1.0-(2.0*x*x)-(2.0*y*y))/cost
		sinf=((2.0*x*y)+(2.0*w*z))/cost
		cosf=(1.0-(2.0*y*y)-(2.0*z*z))/cost
		
	Else
		
		sinv=(2.0*w*x)-(2.0*y*z)
		cosv=1.0-(2.0*x*x)-(2.0*z*z)
		sinf=0.0
		cosf=1.0
		
	EndIf
	
	pitch=ATan2(sint,cost)
	yaw=ATan2(sinf,cosf)
	roll=-ATan2(sinv,cosv)
	
End Function

Function MultiplyQuats:TQuaternion(q1:TQuaternion,q2:TQuaternion)

	Local q:TQuaternion=New TQuaternion
	
	q.w = q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z
	q.x = q1.w*q2.x + q1.x*q2.w + q1.y*q2.z - q1.z*q2.y
	q.y = q1.w*q2.y + q1.y*q2.w + q1.z*q2.x - q1.x*q2.z
	q.z = q1.w*q2.z + q1.z*q2.w + q1.x*q2.y - q1.y*q2.x

	Return q

End Function

Function NormalizeQuat:TQuaternion(q:TQuaternion)

	Local uv:Float=Sqr(q.w*q.w+q.x*q.x+q.y*q.y+q.z*q.z)

	q.w=q.w/uv
	q.x=q.x/uv
	q.y=q.y/uv
	q.z=q.z/uv

	Return q

End Function