Accretion & Evolution of Matter in a 2D Universe

Share your advanced PureBasic knowledge/code with the community.
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 536
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Accretion & Evolution of Matter in a 2D Universe

Post by BasicallyPure »

Watch stars form, evolve, and die in this graphic animation.
You should be able to waste many hours of your life watching this. :D

A star has just exploded
Image

Windows only
edit: 8.16.2018 made a small change, WaitWindowEvent(1) instead of WindowEvent().
edit: 8.17.2018 improved collision detection, added more comments in code, streamlined some code.
edit: 8.18.2018 improved gravity interaction of smaller objects, added more comments.
edit: 8.26.2018 improved collision detection so drawing circles on invisible screen is not required.
this made a significant reduction in CPU usage.
edit: 1.28.2019 added secondary ejections from collision events.

Code: Select all

; MatterEvolution.pb
; Accretion and evolution of matter, graphic animation
; Author: BasicallyPure
; License: free
; Compiler: PureBasic 5.62
; tested on windows 7
; Date: 1.28.2019
; Version: 1.8
; Forum topic: https://www.purebasic.fr/english/viewtopic.php?f=12&t=71235

EnableExplicit

#ScreenColor   = $000000 ; screen background color
#NovaMass      = 70      ; do not change this!!!
#Tau           = #PI * 2
#Gravity       = 0.45    ; gravitational constant
#GravRange     = 450     ; max range for gravity calculations
#imgBackground = 0

Structure objectParameters
   x.d    ; x location
   y.d    ; y location
   xVel.d ; x velocity
   yVel.d ; y velocity
   mass.i
EndStructure

If ExamineDesktops()
   Global DH = DesktopHeight(0)
   Global DW = DesktopWidth(0)
   Global mid_X = DW/2 - 1
   Global mid_Y = DH/2 - 1
   Global ObjectCount = DW*DH / 8000 ; determine ammount of matter in universe
   Global Dim MidC.i(70) ; array for midpoint color of stars
   Global Dim Object.objectParameters(ObjectCount)
Else
   End
EndIf

Declare INIT_GUI()
Declare MAIN_LOOP()
Declare MAKE_STAR(sprNum, mass)
Declare NOVA(index,strikeForce)
Declare STAR_COLOR(mass)
Declare START_EXPANSION()
Declare VERIFY(result, message$)

Macro RndFloat
   ; produce a random float number (0 < number < 1)
   ((Random(2147483645)+1) / 2147483647)
EndMacro

Macro Distance(x1,x2,y1,y2)
   ; distance between two points
   Sqr((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1))
EndMacro

INIT_GUI()
START_EXPANSION()
MAIN_LOOP()

End

;{ ---------------------- procedures --------------------------

Procedure INIT_GUI()
   Protected s, x, y,yp, event
   
      VERIFY(InitSprite(),"InitSprite()")
      VERIFY(InitKeyboard(),"InitKeyboard()")
      VERIFY(UsePNGImageDecoder(),"UsePNGImageDecoder")
      VERIFY(OpenWindow(0,0,0,DW,DH,"Matter Evolution",#PB_Window_BorderLess),"OpenWindow()")
      VERIFY(OpenWindowedScreen(WindowID(0),0,0,DW,DH),"OpenWindowedScreen()")
      VERIFY(CreatePopupMenu(0),"CreatePopupMenu()")
         MenuItem(1,"pause/resume")
         MenuItem(2,"restart")
         MenuItem(3,"quit")
      
      ;{ ------- sprites for non-stellar objects -------
      CreateSprite(1,3,3)
      TransparentSpriteColor(1, #ScreenColor)
      midC(1) = $FF4040
      StartDrawing(SpriteOutput(1))
         Box(0,0,3,3,#ScreenColor)
         Circle(1,1,1,midC(1))
         Plot(1,1,#ScreenColor)
      StopDrawing()
      
      CreateSprite(2,5,5)
      TransparentSpriteColor(2, #ScreenColor)
      midC(2) = $FF7050
      StartDrawing(SpriteOutput(2))
         Box(0,0,5,5,#ScreenColor)
         Circle(2,2,2,midC(2))
          Plot(2,2,#ScreenColor)
      StopDrawing()
      
      CreateSprite(3,7,7)
      TransparentSpriteColor(3, #ScreenColor)
      midC(3) = $FFA060
      StartDrawing(SpriteOutput(3))
         Box(0,0,7,7,#ScreenColor)
         Circle(3,3,3,midC(3))
          Plot(3,3,#ScreenColor)
      StopDrawing()
      
      ;} ----------------------------------------------- 
      
      For s = 4 To 70 : MAKE_STAR(s, s) : Next s ; create all star sprites
      
      LoadFont(1, "Arial", 24)
      LoadFont(2, "Arial", 18)
      LoadImage(#imgBackground,"background.png")
      
      ClearScreen(#ScreenColor)
      StartDrawing(ScreenOutput())
         If IsImage(#imgBackground) : DrawImage(ImageID(#imgBackground),0,0) : EndIf
         
         DrawingFont(FontID(1))
         FrontColor($FFF6A2)
         y = 10
         DrawText(10,y,"Accretion and Evolution of matter in a 2D universe.",$A2FDFF)
         y + TextHeight("A") + 10
         
         DrawingFont(FontID(2))
         DrawText(10,y,"Keyboard: 'Esc' = quit")
         y = y + TextHeight("A") + 5
         DrawText(10,y,"Keyboard: 'Space' = pause / unpause")
         y = y + TextHeight("A") + 5
         DrawText(10,y,"Keyboard: 'Enter' = restart the simulation")
         y = y + TextHeight("A") + 5
         y = y + TextHeight("A") + 5
         DrawText(10,y,"Mouse: 'Right click' = menu")
         y = y + TextHeight("A") + 5
         y = y + TextHeight("A") + 5
         DrawText(10,y,"Press any key or click mouse to continue...",$BEA2FF)
         yp = y +TextHeight("A") + 15
      StopDrawing()
      
      x = 20
      For s = 1 To 28
         y = yp + (SpriteHeight(28) - SpriteHeight(s))/2
         DisplayTransparentSprite(s,x,y)
         x + SpriteWidth(s) + 10
      Next s
      
      x = 20 : yp = y + SpriteHeight(29)
      For s = 29 To 42
         y = yp + (SpriteHeight(42) - SpriteHeight(s))/2
         DisplayTransparentSprite(s,x,y)
         x + SpriteWidth(s) + 5
      Next s
      
      x = 20 : yp = y + SpriteHeight(43)
      For s = 43 To 53
         y = yp + (SpriteHeight(53) - SpriteHeight(s))/2
         DisplayTransparentSprite(s,x,y)
         x + SpriteWidth(s) + 5
      Next s
      
      x = 20 : yp = y + SpriteHeight(54)
      For s = 54 To 62
         y = yp + (SpriteHeight(62) - SpriteHeight(s))/2
         DisplayTransparentSprite(s,x,y)
         x + SpriteWidth(s) + 5
      Next s
      
      x = 20 : yp = y + SpriteHeight(63)
      For s = 63 To 70
         y = yp + (SpriteHeight(70) - SpriteHeight(s))/2
         DisplayTransparentSprite(s,x,y)
         x + SpriteWidth(s) + 5
      Next s
      
      FlipBuffers()
      
      Repeat
         Repeat
            event = WaitWindowEvent(1)
            If event = #WM_LBUTTONUP
               Break 2
            EndIf
         Until event = 0
         
         ExamineKeyboard()
         If KeyboardReleased(#PB_Key_Escape) : End : EndIf
      Until KeyboardReleased(#PB_Key_All)
      
      FreeFont(1) : FreeFont(2)
EndProcedure

Procedure MAIN_LOOP()
   Protected Event, Quit, pause = #False, hCursor, timerActive = #False
   Protected d.d, f.d, dir.d, cdf.d, sdf.d, m, n, r, StrikeMass
   Protected SFX.d, SFY.d, SF.d
   Protected lim_X = DW - 1, lim_Y = DH - 1
   Protected blinkMass = #NovaMass * 0.9
   Protected holdoff = ObjectCount * 90
   
   Macro StartCursorCountdown()
      AddWindowTimer(0,0,4000) : timerActive = #True
   EndMacro
   
   StartCursorCountdown()
   
   Repeat
      
      ;{ window event loop
      Repeat
         Event = WindowEvent()
         Select Event
            Case #WM_MOUSEMOVE
               If timerActive = #False
                  StartCursorCountdown()
               EndIf
            Case #PB_Event_Timer
               hCursor = SetCursor_(#Null)
               RemoveWindowTimer(0,0)
               timerActive = #False
            Case #PB_Event_CloseWindow
               Quit = #True
            Case #PB_Event_RightClick
               DisplayPopupMenu(0, WindowID(0))
            Case #PB_Event_LeftClick
               StartCursorCountdown()
            Case #PB_Event_Menu
               Select EventMenu()
                  Case 1 ; pause
                     pause ! 1
                     StartCursorCountdown()
                  Case 2 ; restart the simulation
                     holdoff = ObjectCount * 120
                     START_EXPANSION()
                     pause = #False
                     StartCursorCountdown()
                  Case 3
                     Quit = #True
               EndSelect
         EndSelect
      Until Not Event : ;}
      
      If pause = #False
         With Object(n)
            ClearScreen(#ScreenColor)
            
            For n = 0 To ObjectCount  ; draw active sprites
               If \mass ; ignore massless objects
                  If \mass > blinkMass ; indicates a star is unstable
                     m = Random(70, 60)
                     DisplayTransparentSprite(m,\x-m,\y-m)
                  Else
                     DisplayTransparentSprite(\mass,\x-\mass,\y-\mass)
                  EndIf
               EndIf
            Next n
            
            StartDrawing(ScreenOutput())
               For n = 0 To ObjectCount ; collision detection loop
                  If \mass
                     If holdoff ; delay collision detection on startup
                        holdoff - 1
                     ElseIf Point(\x,\y) <> #ScreenColor ; a possible collision
                        
                        For m = 0 To ObjectCount ; find the object of collision
                           If Object(m)\mass     ; objects with mass 0 are ignored
                              r = (\mass + Object(m)\mass) ; sum of objects radii
                              d = Distance(\x, Object(m)\x, \y, Object(m)\y)
                              
                              If r > d And d > 0 ; process collision
                                 
                                 If Object(m)\mass > \mass ;locate to the object With largest mass
                                    \x = Object(m)\x
                                    \y = Object(m)\y
                                    StrikeMass = \mass
                                 Else
                                    StrikeMass = Object(m)\mass
                                 EndIf
                                    
                                 ; calculate strike force
                                 SFX = (StrikeMass * Pow(Object(n)\xVel - Object(m)\xVel,2)) / 2
                                 SFY = (StrikeMass * Pow(Object(n)\yVel - Object(m)\yVel,2)) / 2
                                 SF  = Sqr(SFX*SFX + SFY*SFY)
                                 If SF > StrikeMass : SF = StrikeMass : EndIf
                                 
                                 ; momentum is conserved during collisions
                                 \xVel = \xVel*(\mass/r) + Object(m)\xVel*(Object(m)\mass/r)
                                 \yVel = \yVel*(\mass/r) + Object(m)\yVel*(Object(m)\mass/r)
                                 
                                 \mass = \mass + Object(m)\mass ; mass is conserved during collisions
                                 Object(m)\mass = 0 ; merged object becomes invisible ghost particle
                                 
                                 If \mass >= #NovaMass ; the star explodes
                                    NOVA(n, 0)
                                 ElseIf SF >= 0.5         ; partial fragmentation
                                    NOVA(n, SF)
                                 EndIf
                                 
                                 Break ; finished with collision event
                              EndIf
                              
                           EndIf
                        Next m
                        
                     EndIf
                  EndIf
               Next n
               
               For n = 0 To ObjectCount
                  If \mass
                     Plot(\x, \y, MidC(\mass)) ; color correction for midpoint of sprite
                     
                     \x + \xVel : \y + \yVel ; update object positions
                     
                     ; handle screen boundary crossings
                     If \x > lim_X : \x = lim_X - Int(\x) : EndIf
                     If \x < 0     : \x = lim_X + Int(\x) : EndIf
                     If \y > lim_Y : \y = lim_Y - Int(\y) : EndIf
                     If \y < 0     : \y = lim_Y + Int(\y) : EndIf
                     
                     If \mass > 3 ; do gravity calculation
                        For m = 0 To ObjectCount
                           If m <> n And Object(m)\mass <> 0
                              d = Distance(\x, Object(m)\x, \y, Object(m)\y)
                              If d < #GravRange ; no calculations beyond this distance
                                 f = (#Gravity * \mass * Object(m)\mass) / (d*d) ; force between objects
                                 If Object(m)\mass < 4 : f * 2 : EndIf
                                 
                                 dir = ATan2(\x - Object(m)\x, \y - Object(m)\y) ; force direction
                                 
                                 cdf = Cos(dir) * f ; cosine direction * force
                                 sdf = Sin(dir) * f ; sine direction * force
                                 
                                 ; adjust velocity of object 1
                                 \xVel - (cdf / \mass)
                                 \yVel - (sdf / \mass)
                                 
                                 ; adjust velocity of object 2
                                 Object(m)\xVel + (cdf / Object(m)\mass)
                                 Object(m)\yVel + (sdf / Object(m)\mass)
                                 
                              EndIf
                           EndIf
                        Next m
                     EndIf ; end gravity calculation
                     
                  EndIf
               Next n
               
               
            StopDrawing()
            
         EndWith
      EndIf
      
      FlipBuffers()
      
      ExamineKeyboard()
      If KeyboardReleased(#PB_Key_Space) : pause ! 1    : EndIf ; toggle pause
      If KeyboardPushed(#PB_Key_Escape)  : Quit = #True : EndIf
      If KeyboardReleased(#PB_Key_Return): PostEvent(#PB_Event_Menu,0,2) : EndIf
   Until Quit = #True
   
EndProcedure

Procedure MAKE_STAR(sprNum, mass)
   Protected c, centerColor, starColor, radius = mass
   
   c = STAR_COLOR(mass)
   centerColor = (c >> 24)
   centerColor = centerColor | centerColor<<8 | centerColor<<16
   starColor = c & $FFFFFF
   
   MidC(sprNum) = centerColor
   
   CreateSprite(sprNum,radius*2+1,radius*2+1)
   TransparentSpriteColor(sprNum, #ScreenColor)
   
   StartDrawing(SpriteOutput(sprNum))
      DrawingMode(#PB_2DDrawing_AllChannels)
      Box(0,0,SpriteWidth(sprNum),SpriteWidth(sprNum),#ScreenColor)
      FrontColor(#ScreenColor)
      BackColor(centerColor) ;center of star
      DrawingMode(#PB_2DDrawing_Gradient)
      CircularGradient(mass, mass, radius)
      GradientColor(0.6,starColor) ; main star color
      Circle(mass,mass,radius,#ScreenColor) ; color has no effect
      DrawingMode(#PB_2DDrawing_Default)
      Plot(mass,mass,#ScreenColor)
   StopDrawing()
   
EndProcedure

Procedure NOVA(index, strikeForce)
   ; do nova explosion or collision ejections
   Protected mass = Object(index)\mass, speedFactor.d = 0.5, rMltp.d = 30, cRadius.i
   Protected x = Object(index)\x
   Protected y = Object(index)\y
   Protected lim_X = DW-1, lim_Y = DH-1
   Protected d.d, s.d, n.i = 0
   
   If strikeForce <> 0 ; collision ejections
      cRadius = mass
      mass = strikeForce
      speedFactor = 0.30
      Object(index)\mass - mass
      If Object(index)\mass < 0 : Object(index)\mass = 0 : EndIf
   Else ; full nova explosion
      Object(index)\mass = 0
   EndIf
   
   While mass > 0
      With Object(n)
         If \mass = 0
            \x = x
            \y = y
            d = RndFloat * #Tau ; initial direction
            s = speedFactor + RndFloat  ; initial speed
            \xVel = s * Sin(d)
            \yVel = s * Cos(d)
            \mass = 1
            
            If strikeForce <> 0
               rMltp = cRadius
            EndIf
            
            \x + \xVel * rMltp
            If \x > lim_X : \x = Int(\x) - lim_X : EndIf
            If \x < 0     : \x = lim_X + Int(\x) : EndIf
            
            \y + \yVel * rMltp
            If \y > lim_Y : \y = Int(\y) - lim_Y : EndIf
            If \y < 0     : \y = lim_Y + Int(\y) : EndIf
            
            mass - 1
         EndIf
         n + 1
      EndWith
   Wend
   
EndProcedure

Procedure STAR_COLOR(mass)
   ; produces 70 possible star colors
   ; 7 classes of stars with 10 sub-classes each
   ; mass should range from 1 to #NovaMass
   
   Protected color, cv = $FF - 70
   Protected value.d
   Protected RefMass.d = #NovaMass / 70
   
   mass = mass / RefMass
   value.d = mass * (RefMass / #NovaMass)
   
   If mass > $FF : mass = $FF : EndIf
   If cv < 0 : cv = 0 : EndIf

   If     value <= 1/7 ; type M
      color = $0000AE + ($000709 * (Round(value * 70, #PB_Round_Nearest) -1))
   ElseIf value <= 2/7 ; type K
      color = $003FFF + ($000B00 * Round((value - 1/7) * 70, #PB_Round_Nearest))
   ElseIf value <= 3/7 ; type G
      color = $00ADFF + ($000800 * Round((value - 2/7) * 70, #PB_Round_Nearest))
   ElseIf value <= 4/7 ; type F
      color = $00FDFF + ($0C0000 * Round((value - 3/7) * 70, #PB_Round_Nearest))
   ElseIf value <= 5/7 ; type A
      color = $78FDFF + ($0C0000 * Round((value - 4.0/7) * 70, #PB_Round_Nearest))
   ElseIf value <= 6/7 ; type B
      color = $FFFFFF - ($000404 * (Round((value - 5/7) * 70, #PB_Round_Nearest) -1))
   Else                ; type O
      color = $FFDBDB - ($000A0A * Round((value - 6/7) * 70, #PB_Round_Nearest))
   EndIf
   
   cv = cv + (70 * value)
   
   ProcedureReturn color | (cv << 24)
EndProcedure

Procedure START_EXPANSION()
   ; set all objects in motion
   Protected n, d.d, s.d
   For n = 0 To ObjectCount
      Object(n)\x = mid_X
      Object(n)\y = mid_Y
      d = RndFloat * #Tau       ; initial direction
      s = 0.1 + RndFloat * 1.4  ; initial speed
      Object(n)\xVel = s * Sin(d)
      Object(n)\yVel = s * Cos(d)
      Object(n)\mass = 1
      Object(n)\x + Object(n)\xVel * 25
      Object(n)\y + Object(n)\yVel * 25
   Next
EndProcedure

Procedure VERIFY(result, message$)
   If result = 0
      MessageRequester("Error!", message$ + " has failed.")
      End
   Else
      ProcedureReturn result
   EndIf
EndProcedure : ;}
Last edited by BasicallyPure on Tue Jan 29, 2019 6:25 am, edited 10 times in total.
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
User avatar
Zebuddi123
Enthusiast
Enthusiast
Posts: 794
Joined: Wed Feb 01, 2012 3:30 pm
Location: Nottinghamshire UK
Contact:

Re: Accretion & Evolution of Matter in a 2D Universe

Post by Zebuddi123 »

Hi to All. Nice :) couldnt be bothered to change to windows :0 so here it is for linux :) let it run folks watch the stars capturing :)

Zebuddi.

Code: Select all

; MatterEvolution.pb
; Accretion and evolution of matter, graphic animation
; Author: BasicallyPure
; License: free
; Compiler: PureBasic 5.62
; tested on windows 7
; Date: 8.16.2018
; Version: 1.0
; Forum topic: https://www.purebasic.fr/english/viewtopic.php?f=12&t=71235

EnableExplicit

CompilerIf #PB_Compiler_OS = #PB_OS_Windows
  Macro CURSOR_ARROW : #IDC_ARROW : EndMacro
  Macro CURSOR_BUSY : #IDC_WAIT : EndMacro
 
CompilerElseIf #PB_Compiler_OS = #PB_OS_MacOS
  Macro CURSOR_ARROW : #kThemeArrowCursor : EndMacro
  Macro CURSOR_BUSY : #kThemeWatchCursor : EndMacro
  ImportC ""
    SetThemeCursor(CursorType.L)
  EndImport
 
CompilerElseIf #PB_Compiler_OS = #PB_OS_Linux
  Global *Cursor.GdkCursor
  Macro CURSOR_ARROW : #GDK_ARROW : EndMacro
  Macro CURSOR_BUSY : #GDK_WATCH : EndMacro
  ImportC ""
    gtk_widget_get_window(*widget.GtkWidget)
  EndImport
CompilerEndIf 


Procedure SetCursor(hWnd.i, CursorId.i)
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    SetClassLong_(hWnd, #GCL_HCURSOR, LoadCursor_(0, CursorId))
  CompilerElseIf #PB_Compiler_OS = #PB_OS_MacOS
    SetThemeCursor(CursorId)
  CompilerElseIf #PB_Compiler_OS = #PB_OS_Linux
     *Cursor= gdk_cursor_new_(CursorID)
     If *Cursor
        gdk_window_set_cursor_(gtk_widget_get_window(WindowID(0)), *Cursor)
     EndIf
  CompilerEndIf
EndProcedure

#ScreenColor   = $000000 ; screen background color
#ParticleColor = $FF4040 ; default color for small objects
#NovaMass      = 70      ; do not change this!!!
#Tau           = #PI * 2
#Gravity       = 0.35    ; gravitational constant
#GravRange     = 500     ; max range for gravity calculations

Structure objectParameters
   x.d ; x location
   y.d ; y location
   xVel.d ; x velocity
   yVel.d ; y velocity
   mass.i
   color.i
EndStructure

If ExamineDesktops()
   Global DH = DesktopHeight(0)
   Global DW = DesktopWidth(0)
   Global mid_X = DW/2 - 1
   Global mid_Y = DH/2 - 1
   Global ObjectCount = DW*DH / 6000
   Global Dim Object.objectParameters(ObjectCount)
Else
   End
EndIf

Declare INIT_GUI()
Declare MAIN_LOOP()
Declare MAKE_STAR(sprNum, mass)
Declare NOVA(index)
Declare RESTART(Array Object.objectParameters(1))
Declare STAR_COLOR(mass)
Declare VERIFY(result, message$)

Macro RndFloat
   ; produce a random float number (0 < number < 1)
   ((Random(2147483645)+1) / 2147483647)
EndMacro

Macro Distance(x1,x2,y1,y2)
   ; distance between two points
   Sqr(Pow(x2-x1,2) + Pow(y2-y1,2))
EndMacro

Macro Force(M1,M2,r)
   ; gravitational force between two objects
   (#Gravity * M1 * M2) / Pow(r,2)
EndMacro

INIT_GUI()
RESTART(Object())
MAIN_LOOP()

End

;{ ---------------------- procedures --------------------------

Procedure INIT_GUI()
   Protected s, y, event
   
      VERIFY(InitSprite(),"InitSprite()")
      VERIFY(InitKeyboard(),"InitKeyboard()")
      VERIFY(OpenWindow(0,0,0,DW,DH,"Matter Evolution",#PB_Window_BorderLess),"OpenWindow()")
      VERIFY(OpenWindowedScreen(WindowID(0),0,0,DW,DH),"OpenWindowedScreen()")
      VERIFY(CreatePopupMenu(0),"CreatePopupMenu()")
         MenuItem(1,"pause/resume")
         MenuItem(2,"restart")
         MenuItem(3,"quit")
     
      CreateSprite(1,3,3)
      StartDrawing(SpriteOutput(1))
         Circle(1,1,1,#ParticleColor)
      StopDrawing()
     
      CreateSprite(2,5,5)
      StartDrawing(SpriteOutput(2))
         Circle(2,2,2,#ParticleColor)
      StopDrawing()
     
      For s = 3 To 70 : MAKE_STAR(s, s) : Next s
     
      LoadFont(1, "Arial", 36)
      LoadFont(2, "Arial", 24)
     
      ClearScreen(#ScreenColor)
      StartDrawing(ScreenOutput())
         DrawingFont(FontID(1))
         FrontColor($FFF6A2)
         y = 10
         DrawText(10,y,"Accretion and Evolution of matter in a 2D universe.",$A2FDFF)
         y + TextHeight("A") + 10
         
         DrawingFont(FontID(2))
         DrawText(10,y,"Keyboard: 'Esc' = quit")
         y = y + TextHeight("A") + 5
         DrawText(10,y,"Keyboard: 'Space' = pause / unpause")
         y = y + TextHeight("A") + 5
         DrawText(10,y,"Keyboard: 'Enter' = restart from beginning")
         y = y + TextHeight("A") + 5
         y = y + TextHeight("A") + 5
         DrawText(10,y,"Mouse: 'Right click' = menu")
         y = y + TextHeight("A") + 5
         y = y + TextHeight("A") + 5
         DrawText(10,y,"Press any key or click mouse to continue...",$BEA2FF)
      StopDrawing()
      FlipBuffers()
     
      Repeat
         Repeat
            event = WaitWindowEvent()
            If EventType() = #PB_EventType_LeftClick
            	Debug "lbup"
               Break 2
            EndIf
         Until event = 0
         
         ExamineKeyboard()
         If KeyboardReleased(#PB_Key_Escape) : End : EndIf
      Until KeyboardReleased(#PB_Key_All)
     
EndProcedure

Procedure MAIN_LOOP()
   Protected Event, Quit, pause = #False, hCursor, timerActive = #False
   Protected d.d, f.d, dir.d, i, m, n, q
   Protected lim_X = DW - 1, lim_Y = DH - 1
   Protected blinkMass = #NovaMass * 0.9
   Protected collision = #False, holdoff = ObjectCount * 120
   
   Macro StartCursorCountdown()
      AddWindowTimer(0,0,4000) : timerActive = #True
   EndMacro
   
   StartCursorCountdown()
   
   Repeat
      ;{ window event loop
      Repeat
         Event = WindowEvent()
         Select Event
         	Case #PB_EventType_MouseMove
         		Debug "mouse move"
               If timerActive = #False
                  StartCursorCountdown()
               EndIf
            Case #PB_Event_Timer
               hCursor = SetCursor(0, -2)
               RemoveWindowTimer(0,0)
               timerActive = #False
            Case #PB_Event_CloseWindow
               Quit = #True
            Case #PB_Event_RightClick
               DisplayPopupMenu(0, WindowID(0))
            Case #PB_Event_LeftClick
               StartCursorCountdown()
            Case #PB_Event_Menu
               Select EventMenu()
                  Case 1 ; pause
                     pause ! 1
                     StartCursorCountdown()
                  Case 2 ; restart
                     holdoff = ObjectCount * 120
                     RESTART(Object())
                     pause = #False
                     StartCursorCountdown()
                  Case 3
                     Quit = #True
               EndSelect
         EndSelect
      Until Not Event : ;}
     
      If pause = #False
         ClearScreen(#ScreenColor)
         
         With Object(n)
           
            StartDrawing(ScreenOutput())
               
               For n = 0 To ObjectCount
                  If \mass
                     If holdoff
                        holdoff - 1
                     ElseIf Point(\x,\y) <> #ScreenColor ; a collision event
                        For m = 0 To n-1                 ; find the object of collision
                           If Object(m)\mass
                              q = (\mass + Object(m)\mass)
                             
                              If q > Distance(\x, Object(m)\x, \y, Object(m)\y) ; process collision
                                 collision = #True
                                 \xVel = \xVel*(\mass/q) + Object(m)\xVel*(Object(m)\mass/q)
                                 \yVel = \yVel*(\mass/q) + Object(m)\yVel*(Object(m)\mass/q)
                                 \mass + Object(m)\mass
                                 If \mass >= #NovaMass
                                    If Object(m)\mass > \mass - Object(m)\mass
                                       \x = Object(m)\x
                                       \y = Object(m)\y
                                    EndIf
                                    NOVA(n) ;: holdoff = ObjectCount * 30
                                 EndIf
                                 \color = STAR_COLOR(\mass) & $FFFFFF
                                 \x = Object(m)\x
                                 \y = Object(m)\y
                                 Object(m)\mass = 0
                                 Break
                              EndIf
                             
                           EndIf
                        Next m
                     EndIf
                     
                     If \mass > blinkMass : \color = $CFEFA7 + Random($301058) : EndIf
                     Circle(\x, \y, 0.5+\mass*0.6666, \color)
                     
                     \x + \xVel
                     \y + \yVel
                     If \x > lim_X : \x = lim_X - Int(\x) : EndIf
                     If \x < 0     : \x = Int(\x) + lim_X : EndIf
                     If \y > lim_Y : \y = lim_Y - Int(\y) : EndIf
                     If \y < 0     : \y = Int(\y) + lim_Y : EndIf
                  EndIf
                 
                  If \mass > 4 ; do gravity calculation
                     For i = 0 To ObjectCount
                        If i <> n And Object(i)\mass <> 0
                           d = Distance(\x, Object(i)\x, \y, Object(i)\y)
                           If d < #GravRange ; no calculations beyond this distance
                              f = Force(\mass, Object(i)\mass, d) ; force between objects
                              dir = ATan2(\x - Object(i)\x, \y - Object(i)\y) ; force direction
                             
                              ; adjust velocity of object 1
                              Object(i)\xVel + (Cos(dir) * f) / Object(i)\mass
                              Object(i)\yVel + (Sin(dir) * f) / Object(i)\mass
                              ; adjust velocity of object 2
                              \xVel - (Cos(dir) * f) / \mass
                              \yVel - (Sin(dir) * f) / \mass
                             
                           EndIf
                        EndIf
                     Next i
                  EndIf ; end gravity calculation
                 
               Next n
               
            StopDrawing()
           
            ; ------- remove this block of code to stop using sprites ----
            ClearScreen(#ScreenColor)
           
            For n = 0 To ObjectCount  ; sprite animation loop
               If \mass
                  If \mass > blinkMass
                     m = Random(70, 60)
                     DisplayTransparentSprite(m,\x-m,\y-m)
                  Else
                     DisplayTransparentSprite(\mass,\x-\mass,\y-\mass)
                  EndIf
               EndIf
            Next n
            ; -------------------------------------------------------------
   
            FlipBuffers()
           
         EndWith
         
         If collision : collision = #False
            SortStructuredArray(Object(),#PB_Sort_Descending,OffsetOf(objectParameters\mass),TypeOf(objectParameters\mass))
         EndIf
      EndIf
     
      ExamineKeyboard()
      If KeyboardReleased(#PB_Key_Space) : pause ! 1    : EndIf
      If KeyboardPushed(#PB_Key_Escape)  : Quit = #True : EndIf
      If KeyboardReleased(#PB_Key_Return): PostEvent(#PB_Event_Menu,0,2) : EndIf
   Until Quit = #True
   
EndProcedure

Procedure MAKE_STAR(sprNum, mass)
   Protected c, centerColor, starColor, radius = mass + 3
   
   c = STAR_COLOR(mass)
   centerColor = (c >> 24)
   centerColor = centerColor | centerColor<<8 | centerColor<<16
   starColor = c & $FFFFFF
   
   CreateSprite(sprNum,radius*2,radius*2)
   
   StartDrawing(SpriteOutput(sprNum))
      DrawingMode(#PB_2DDrawing_AllChannels)
      Box(0,0,SpriteWidth(sprNum),SpriteWidth(sprNum),0)
      FrontColor(#ScreenColor)
      BackColor(centerColor) ;center of star
      DrawingMode(#PB_2DDrawing_Gradient)
      CircularGradient(radius-1, radius-1, radius)
      GradientColor(0.6,starColor) ; main star color
      Circle(radius-1,radius-1,radius,$0) ; color has no effect
   StopDrawing()
   
EndProcedure

Procedure NOVA(index)
   ; do nova explosion
   Protected mass = Object(index)\mass
   Protected x = Object(index)\x
   Protected y = Object(index)\y
   Protected lim_X = DW-1, lim_Y = DH-1
   Protected d.d, s.d, n.i = 0
   
   Object(index)\mass = 0
   
   While mass > 0
      With Object(n)
         If \mass = 0
            \x = x
            \y = y
            d = RndFloat * #Tau       ; initial direction
            s = 0.5 + RndFloat * 1.0  ; initial speed
            \xVel = s * Sin(d)
            \yVel = s * Cos(d)
            \mass = 1
           
            \x + \xVel * 30
            If \x > lim_X : \x = Int(\x) - lim_X : EndIf
            If \x < 0     : \x = lim_X + Int(\x) : EndIf
           
            \y + \yVel * 30
            If \y > lim_Y : \y = Int(\y) - lim_Y : EndIf
            If \y < 0     : \y = lim_Y + Int(\y) : EndIf
           
            \color = #ParticleColor
            mass - 1
         EndIf
         n + 1
      EndWith
   Wend
   
EndProcedure

Procedure RESTART(Array Object.objectParameters(1))
   Protected n, d.d, s.d
   For n = 0 To ObjectCount
      Object(n)\x = mid_X
      Object(n)\y = mid_Y
      d = RndFloat * #Tau       ; initial direction
      s = 0.1 + RndFloat * 1.4  ; initial speed
      Object(n)\xVel = s * Sin(d)
      Object(n)\yVel = s * Cos(d)
      Object(n)\mass = 1
      Object(n)\x + Object(n)\xVel * 20
      Object(n)\y + Object(n)\yVel * 20
      Object(n)\color = #ParticleColor
   Next
EndProcedure

Procedure STAR_COLOR(mass)
   ; produces 70 possible star colors
   ; 7 classes of stars with 10 sub-classes each
   ; mass should range from 1 to #NovaMass
   
   Protected color, cv = $FF - 70
   Protected value.d
   Protected RefMass.d = #NovaMass / 70
   
   mass = mass / RefMass
   value.d = mass * (RefMass / #NovaMass)
   
   If mass > $FF : mass = $FF : EndIf
   If cv < 0 : cv = 0 : EndIf

   If     value <= 1/7 ; type M
      color = $0000AE + ($000709 * (Round(value * 70, #PB_Round_Nearest) -1))
   ElseIf value <= 2/7 ; type K
      color = $003FFF + ($000B00 * Round((value - 1/7) * 70, #PB_Round_Nearest))
   ElseIf value <= 3/7 ; type G
      color = $00ADFF + ($000800 * Round((value - 2/7) * 70, #PB_Round_Nearest))
   ElseIf value <= 4/7 ; type F
      color = $00FDFF + ($0C0000 * Round((value - 3/7) * 70, #PB_Round_Nearest))
   ElseIf value <= 5/7 ; type A
      color = $78FDFF + ($0C0000 * Round((value - 4.0/7) * 70, #PB_Round_Nearest))
   ElseIf value <= 6/7 ; type B
      color = $FFFFFF - ($000404 * (Round((value - 5/7) * 70, #PB_Round_Nearest) -1))
   Else                ; type O
      color = $FFDBDB - ($000A0A * Round((value - 6/7) * 70, #PB_Round_Nearest))
   EndIf
   
   cv = cv + (70 * value)
   
   ProcedureReturn color | (cv << 24)
EndProcedure

Procedure VERIFY(result, message$)
   If result = 0
      MessageRequester("Error!", message$ + " has failed.")
      End
   Else
      ProcedureReturn result
   EndIf
EndProcedure : ;}
malleo, caput, bang. Ego, comprehendunt in tempore
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 536
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Re: Accretion & Evolution of Matter in a 2D Universe

Post by BasicallyPure »

Hi Zebuddi123, thanks for the assist.

I checked your code with Linux Mint 17.1 Cinnamon 64-bit.
It runs but very slowly, about 10 times slower than on windows.
This was the problem I saw when I tried to implement this for Linux.
I never could find the reason why it was so slow.

I noticed the text message screen does not appear on startup using your code.
After some experimentation I made a couple of changes to the INIT_GUI() procedure.
Now the text is displayed on startup as it should be and keypress on keyboard continues as it should.
I still get no mouse click detection but it's not really needed because the keyboard gives duplicate functions.

I see you found a mistake I made where I used WindowEvent() where I should have used WaitWindowEvent(1).
Thanks for that. I will change that in my windows code.

This is the improved INIT_GUI() procedure for Linux.

Code: Select all

Procedure INIT_GUI()
   Protected s, y, event
   
      VERIFY(InitSprite(),"InitSprite()")
      VERIFY(InitKeyboard(),"InitKeyboard()")
      VERIFY(OpenWindow(0,0,0,DW,DH,"Matter Evolution",#PB_Window_BorderLess),"OpenWindow()")
      VERIFY(OpenWindowedScreen(WindowID(0),0,0,DW,DH),"OpenWindowedScreen()")
      VERIFY(CreatePopupMenu(0),"CreatePopupMenu()")
         MenuItem(1,"pause/resume")
         MenuItem(2,"restart")
         MenuItem(3,"quit")
     
      CreateSprite(1,3,3)
      StartDrawing(SpriteOutput(1))
         Circle(1,1,1,#ParticleColor)
      StopDrawing()
     
      CreateSprite(2,5,5)
      StartDrawing(SpriteOutput(2))
         Circle(2,2,2,#ParticleColor)
      StopDrawing()
     
      For s = 3 To 70 : MAKE_STAR(s, s) : Next s
     
      LoadFont(1, "Arial", 36)
      LoadFont(2, "Arial", 24)
      
      While WindowEvent() : Wend ;<--- added this
      FlipBuffers()              ;<--- added this
      
      ClearScreen(#ScreenColor)
      StartDrawing(ScreenOutput())
         DrawingFont(FontID(1))
         FrontColor($FFF6A2)
         y = 10
         DrawText(10,y,"Accretion and Evolution of matter in a 2D universe.",$A2FDFF)
         y + TextHeight("A") + 10
         
         DrawingFont(FontID(2))
         DrawText(10,y,"Keyboard: 'Esc' = quit")
         y = y + TextHeight("A") + 5
         DrawText(10,y,"Keyboard: 'Space' = pause / unpause")
         y = y + TextHeight("A") + 5
         DrawText(10,y,"Keyboard: 'Enter' = restart from beginning")
         y = y + TextHeight("A") + 5
         y = y + TextHeight("A") + 5
         DrawText(10,y,"Mouse: 'Right click' = menu")
         y = y + TextHeight("A") + 5
         y = y + TextHeight("A") + 5
         DrawText(10,y,"Press any key or click mouse to continue...",$BEA2FF)
      StopDrawing()
      FlipBuffers()
     
      Repeat
         Repeat
            event = WaitWindowEvent(1) ;<-- changed this
            If EventType() = #PB_EventType_LeftClick
               ;Debug "lbup"
               Break 2
            EndIf
         Until event = 0
         
         ExamineKeyboard()
         If KeyboardReleased(#PB_Key_Escape) : End : EndIf
      Until KeyboardPushed(#PB_Key_All)
     
EndProcedure
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
User avatar
STARGÅTE
Addict
Addict
Posts: 2089
Joined: Thu Jan 10, 2008 1:30 pm
Location: Germany, Glienicke
Contact:

Re: Accretion & Evolution of Matter in a 2D Universe

Post by STARGÅTE »

Nice simulation.
Just a comment from my side:
As I see it right, your 2D universe has periodic boundary conditions (everything which flies out of the screen comes back on the other side).
I think it would be consistent, if you apply this behavior also to your gravity calculation, that an object on the far right hand side interacts with an object on the far left hand side.
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Lizard - Script language for symbolic calculations and moreTypeface - Sprite-based font include/module
User avatar
RSBasic
Moderator
Moderator
Posts: 1218
Joined: Thu Dec 31, 2009 11:05 pm
Location: Gernsbach (Germany)
Contact:

Re: Accretion & Evolution of Matter in a 2D Universe

Post by RSBasic »

Nice
Image
Image
User avatar
mk-soft
Always Here
Always Here
Posts: 5408
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Accretion & Evolution of Matter in a 2D Universe

Post by mk-soft »

Cool :wink:
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

Re: Accretion & Evolution of Matter in a 2D Universe

Post by Mijikai »

Nice thanks for sharing.
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5353
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Accretion & Evolution of Matter in a 2D Universe

Post by Kwai chang caine »

Very nice explosion of colors :shock:
Works very well on W10 x86 / v5.62 x86
Thanks for sharing 8)
ImageThe happiness is a road...
Not a destination
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 536
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Re: Accretion & Evolution of Matter in a 2D Universe

Post by BasicallyPure »

Try out the latest version, I have made a few tweaks to the code.

@STARGATE
You are correct in your observation, gravity does not cross screen boundaries.
I had considered doing what you suggest but the ammount of extra calculations seemed excessive.
It would probably use a lot more CPU time. I'm not sure though because I never tested it.
If you want to give it a try the place would be in the 'Main_Loop' procedure.
Look for the comment 'do gravity calculation'.

Thanks to everyone else for the feedback.

If anyone wants to customize this to their own preferences here are some possibilities.

First a basic explanation of how this works.

The universe starts with a fixed ammount of objects each with one mass unit.
The number of objects never changes but they do merge and exchange mass during collisions.
When an object gives up it's mass in a collision it becomes an invisible massless ghost object.
Later when a star explodes the ghost objects are given back one mass unit and become visible again.
The number of restored objects is equal to the mass of the star that exploded.

Momentum is conserved during collisions so this is why objects change speed and direction after a collision.

Objects interact with each other under the influence of gravity.
Some compromises were made to reduce the number of calculations required.
During an iteration of the animation loop objects with mass less than 5 do not trigger a gravity calculation.
Any object with a mass greater than 4 will produce a gravity calculation with any other object that is less distant than the specified maximum range
for gravity calculations.
This will influence all objects within range even those that have mass less than 5.
This range limit is something you can easily change by using the constant #GravRange.

Another change you can easily make is the number of total objects in the universe.
When you see this line 33 'Global ObjectCount = DW*DH / 7000' make 7000 larger to reduce the number of objects or smaller to increase the number.

Another obvious change you can make is to change the gravitaional constant #Gravity.

Collision detection was one of the harder problems to solve for this simulation.
It is performed by using a hidden screen that is drawn with a circle for each visible object.
Before the circle is drawn Point(x,y) is used to see if the point is other than the background color.
If the color is not the background color then a collision has occured and an iteration is performed with all other objects.
If the distance is less than the objects diameter then that is the object of collision.
For collision detection to work properly it is necessary to sort the object array after every collision.
The larger objects must be drawn first or collisions will be missed and problems arise.
If you want to see the process in action remove the block of code in the 'Main_Loop' that displayes the sprites.
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
User avatar
STARGÅTE
Addict
Addict
Posts: 2089
Joined: Thu Jan 10, 2008 1:30 pm
Location: Germany, Glienicke
Contact:

Re: Accretion & Evolution of Matter in a 2D Universe

Post by STARGÅTE »

You are correct in your observation, gravity does not cross screen boundaries.
I had considered doing what you suggest but the ammount of extra calculations seemed excessive.
You need not more calculations. What you can do ist the following:
You have to subtract the positions of two objects, as you do it already.
Now you check is the difference (for X) is larger than half of the screen, because than the way over the bondary is shorter.
So you add just the screen width to the position of the object and do the gravity stuff (same for Y).

An other point is:
You can make your code much faster if you skip ATan2, Cos and Sin.

Currently you use already vectors you add the velocity on the position.

Code: Select all

\x + \xVel : \y + \yVel ; update object positions
You can do this also for the gravity force, instead of you angle calculation.
Just an impression:

Code: Select all

xDirection.f = Object(m)\x - Object(n)\x
yDirection.f = Object(m)\y - Object(n)\y
Distance3.f = Pow(xDirection*xDirection+yDirection*yDirection, 1.5)
xForce.f = (#Gravity * M1 * M2 * xDirection) / Distance3
yForce.f = (#Gravity * M1 * M2 * yDirection) / Distance3
;...
\xVel - xForce/mass; check the sign ..
\yVel - yForce/mass
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Lizard - Script language for symbolic calculations and moreTypeface - Sprite-based font include/module
Post Reply