Seite 2 von 2

Verfasst: 26.11.2008 12:22
von dllfreak2001
Bällchenterror mit den Konstanten #sw = Screenbreite
#sh = Screenhöhe kann die Auflösung eingestellt werden.
#plc = Bällchenmenge
#pc = "Kollisionspunktemenge"
#pr = Bällchenradius

Dies ist ein unrealistischer aber lustiger Ansatz...

Code: Alles auswählen

InitSprite()
InitKeyboard()
InitMouse()

Procedure.f TRadius(p1x.f, p1y.f, p2x.f, p2y.f)
  ProcedureReturn Sqr((p1x-p2x)*(p1x-p2x)+(p1y-p2y)*(p1y-p2y))
EndProcedure

Procedure.f TAngle(p1x.f, p1y.f, p2x.f, p2y.f)
  Protected angle.f = 0 
  angle = ATan((p1y-p2y)/(p1x-p2x)) 
  If p1x < p2x 
    angle + #PI
  EndIf
  ProcedureReturn angle
EndProcedure


#sw = 1280
#sh = 1024

#pr = 16

#pc = 100

#plc = 60
OpenScreen(#sw,#sh,32,"Mathe")


;   ppx.f = 0
;   
;   ppy.f = 0

Structure Pnt
  x.f
  y.f
EndStructure

Structure Pl
  x.f
  y.f
  xm.f
  ym.f
EndStructure


Dim p.Pnt(#pc)
Dim pl.Pl(#plc)

For n = 0 To #pc 
  p(n)\x = Random(#sw)
  p(n)\y = Random(#sh)
Next

For n = 0 To #plc 
  pl(n)\x = 200+Random(400)
  pl(n)\y = -22 -Random(64)
  pl(n)\xm = (Random(400)-200)/100
  pl(n)\ym = 0
Next

Repeat
  ExamineKeyboard()
  ExamineMouse()
  
    
;   If KeyboardPushed(#PB_Key_Left)
;     ppx - 2
;   ElseIf  KeyboardPushed(#PB_Key_Right) 
;     ppx + 2
;   EndIf
;   If KeyboardPushed(#PB_Key_Up)
;     ppy - 2
;   ElseIf  KeyboardPushed(#PB_Key_Down) 
;     ppy + 2
;   EndIf
;   
  
  
  For m = 0 To #plc
      
      pl(m)\xm = pl(m)\xm/1.0001
      pl(m)\ym = pl(m)\ym +0.05
      
      pl(m)\x =  pl(m)\x + pl(m)\xm
      pl(m)\y =  pl(m)\y + pl(m)\ym
     
      For n = 0 To #plc
       If n <> m 
        If TRadius(pl(m)\x,pl(m)\y,pl(n)\x,pl(n)\y) < #pr*2
          
          ang.f = TAngle(pl(m)\x,pl(m)\y,pl(n)\x,pl(n)\y)
          pow.f = Sqr(pl(m)\xm*pl(m)\xm + pl(m)\ym*pl(m)\ym)
 
        
          pl(n)\x = pl(m)\x - Cos(ang)*#pr*2
          pl(n)\y = pl(m)\y - Sin(ang)*#pr*2
          pl(m)\ym = Sin(ang)*pow/1.5
          pl(m)\xm = Cos(ang)*pow/1.5
        EndIf
       EndIf
       
      
      Next

      For n = 0 To #pc
        If TRadius(pl(m)\x,pl(m)\y,p(n)\x,p(n)\y) < #pr
          
          ang = TAngle(pl(m)\x,pl(m)\y,p(n)\x,p(n)\y)
          pow = Sqr(pl(m)\xm*pl(m)\xm + pl(m)\ym*pl(m)\ym)
        
          pl(m)\x = p(n)\x + Cos(ang)*#pr
          pl(m)\y = p(n)\y + Sin(ang)*#pr
          pl(m)\ym = Sin(ang)*pow/1.1
          pl(m)\xm = Cos(ang)*pow/1.1
        EndIf
      Next

     If pl(m)\x < 0
      pl(m)\x = 0
      pl(m)\xm = - pl(m)\xm/2
     EndIf
     
     If pl(m)\x > #sw
      pl(m)\x = #sw
      pl(m)\xm = - pl(m)\xm/2
     EndIf
     

     
     If pl(m)\y > #sh-#pr
      pl(m)\y = #sh-#pr
      pl(m)\ym = - pl(m)\ym/2
     EndIf    
     
     
  Next

  StartDrawing(ScreenOutput())
    For n = 0 To #pc

      Circle(p(n)\x,p(n)\y,2,RGB(255,255,255))
    Next
    DrawingMode(#PB_2DDrawing_Outlined)
    For m = 0 To #plc
      Circle(pl(m)\x,pl(m)\y,#pr,RGB(255,255,0))
    Next
  StopDrawing()
  
  
  FlipBuffers()
  ClearScreen(0)
Until KeyboardPushed(#PB_Key_Escape)

Verfasst: 26.11.2008 17:36
von PMV
Wirklich cool Stargate :shock:
Nur das Aufschaukeln ist von der Physik her etwas komisch. Fürs Auge aber natürlich schöner:

Default: http://pmv.pytalhost.com/Images/Stargates_Blume.png
O_O: http://pmv.pytalhost.com/Images/Stargates_O_O.png

die anderen eignen sich aber auch sehr gut für Bildschirmschoner :allright:

MFG PMV

Re: Effektcodes und sonstiger Unnütz

Verfasst: 02.08.2010 13:39
von Renovatio
@dllfreak2001

Was dagegen wenn ich einen Teil deines Codes in meinem Spiel verwende?
Wenn nein dann kommste mit in die Liste "Produzenten und Mitwirkende". *hehe*


Viele Grüße

Re: Effektcodes und sonstiger Unnütz

Verfasst: 02.08.2010 15:25
von Mok
...Wie wär's mit PN/PM?

Re: Effektcodes und sonstiger Unnütz

Verfasst: 02.08.2010 16:50
von dllfreak2001
@Renovatio

Wenn du willst.

Re: Effektcodes und sonstiger Unnütz

Verfasst: 30.09.2010 21:00
von dllfreak2001
Das ist eigentlich eine Kollisionsroutine für tile-basierte Level.

#mapw
#maph
#blocksize
#playerrad

...sind selbsterklärend.

Mit der linken Maustaste malt man einen Block in den Raum, mit der rechten löscht man ihn.
Außerdem gibt der Mauscursor an wohin man sich bewegt.
Bewegt wird per WASD wie beim Egoshooter. Ich weiß es gibt besseres aber ich mag solche Bastelsachen und Physikdemos. <)

Code: Alles auswählen

#IsMin = 1
#IsMax = 2

Procedure.f Angle(x1.f, y1.f, x2.f, y2.f)
  wink.f = ATan((y2 - y1) / (x2 - x1)) + #PI
  If x2 = x1 And y2 = y1
    wink = 0 
  ElseIf x2 => x1
    wink + #PI
  EndIf
  ProcedureReturn wink
EndProcedure

Procedure.f Radius(x1.f, y1.f, x2.f, y2.f)
  ProcedureReturn Sqr((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) 
EndProcedure

Procedure.l IfMinL(wert.l, min.l)
  If wert < min
    ProcedureReturn min
  Else
    ProcedureReturn wert
  EndIf
EndProcedure

Procedure.l IfMaxL(wert.l, max.l)
  If wert > max
    ProcedureReturn max
  Else
    ProcedureReturn wert
  EndIf
EndProcedure

Procedure.l Raster(wert.f, bsize.l)
  ProcedureReturn Round(wert / bsize, 0)  
EndProcedure

Procedure.b MinMaxL(wert.l, min.l, max.l)
  If wert < min
    ProcedureReturn #IsMin  
  ElseIf wert > max
    ProcedureReturn #IsMax
  Else
    ProcedureReturn #False
  EndIf
EndProcedure

#mapw = 124
#maph = 112
#blocksize = 64
#playerrad = 24

ExamineDesktops()

screenw = DesktopWidth(0)
screenh = DesktopHeight(0)

#speed = 4

Dim mymap.b(#mapw, #maph)

InitSprite()
InitKeyboard()
InitMouse()

OpenScreen(screenw, screenh, 32, "Kollisiondemo")

CreateSprite(0, #blocksize, #blocksize)
CreateSprite(1, #blocksize, #blocksize)

StartDrawing(SpriteOutput(0))
  DrawingMode(#PB_2DDrawing_Outlined )
  Box(0, 0,  #blocksize, #blocksize, RGB(0, 100, 50))
StopDrawing()

StartDrawing(SpriteOutput(1))
  DrawingMode(#PB_2DDrawing_Default  )
  Box(0, 0,  #blocksize, #blocksize, RGB(0, 250, 100))
StopDrawing()

soffx.l = screenw / 2
soffy.l = screenh / 2
playerx.f = 1220
playery.f = 1220
pdir.f = 0

Repeat
  ExamineKeyboard()
  ExamineMouse()
  
  pdir = Angle(screenw/2, screenh/2, MouseX(), MouseY())

  If KeyboardPushed(#PB_Key_W)
    playerx + Cos(pdir) * #speed
    playery + Sin(pdir) * #speed
  EndIf
  
  If KeyboardPushed(#PB_Key_S)
    playerx - Cos(pdir) * #speed
    playery - Sin(pdir) * #speed
  EndIf
  
  If KeyboardPushed(#PB_Key_A)
    playerx + Cos(pdir - #PI / 2) * #speed
    playery + Sin(pdir - #PI / 2) * #speed
  EndIf
  
  If KeyboardPushed(#PB_Key_D)
    playerx + Cos(pdir + #PI / 2) * #speed
    playery + Sin(pdir + #PI / 2) * #speed
  EndIf
  
  ;Tile auf das der Mauszeiger zeigt
  mpx = Raster(MouseX() - soffx + playerx, #blocksize)
  mpy = Raster(MouseY() - soffy + playery, #blocksize)  
  
  If MinMaxL(mpx, 0, #mapw) = 0 And MinMaxL(mpy, 0, #maph) = 0
    If MouseButton(#PB_MouseButton_Left)
      mymap(mpx, mpy) = #True
    ElseIf MouseButton(#PB_MouseButton_Right)
      mymap(mpx, mpy) = #False
    EndIf  
  EndIf
  
  
  ;Block unten prüfen
  
  tx = Raster(playerx, #blocksize)
  ty = Raster(playery + #playerrad, #blocksize)
  
  If MinMaxL(tx, 0, #mapw) = 0 And MinMaxL(ty, 0, #maph) = 0
    If mymap(tx, ty) = #True
      playery = ty*#blocksize - #playerrad 
    EndIf
  EndIf
  
  ;Block oben prüfen
  
  tx = Raster(playerx, #blocksize)
  ty = Raster(playery - #playerrad, #blocksize)
  
  If MinMaxL(tx, 0, #mapw) = 0 And MinMaxL(ty, 0, #maph) = 0
    If mymap(tx, ty) = #True
      playery = (ty + 1)*#blocksize + #playerrad 
    EndIf
  EndIf
  
  ;Block rechts prüfen
  
  tx = Raster(playerx + #playerrad, #blocksize)
  ty = Raster(playery, #blocksize)
  
  If MinMaxL(tx, 0, #mapw) = 0 And MinMaxL(ty, 0, #maph) = 0
    If mymap(tx, ty) = #True
      playerx = tx*#blocksize - #playerrad 
    EndIf
  EndIf
  
  ;Block links prüfen
  
  tx = Raster(playerx - #playerrad, #blocksize)
  ty = Raster(playery , #blocksize)
  
  If MinMaxL(tx, 0, #mapw) = 0 And MinMaxL(ty, 0, #maph) = 0
    If mymap(tx, ty) = #True
      playerx = (tx + 1)*#blocksize + #playerrad 
    EndIf
  EndIf
  
  ;Untere rechte Ecke prüfen
  
  tx = Raster(playerx - #playerrad, #blocksize)
  ty = Raster(playery - #playerrad , #blocksize)
  
  If MinMaxL(tx, 0, #mapw) = 0 And MinMaxL(ty, 0, #maph) = 0
    If mymap(tx, ty) = #True
      If Radius((tx + 1) * #blocksize, (ty + 1) * #blocksize, playerx, playery) < #playerrad
        wink.f = Angle((tx + 1) * #blocksize, (ty + 1) * #blocksize, playerx, playery)
        playerx = (tx + 1) * #blocksize + Cos(wink) * #playerrad
        playery = (ty + 1) * #blocksize + Sin(wink) * #playerrad
      EndIf
    EndIf
  EndIf
  
  ;Untere linke Ecken prüfen
  
  tx = Raster(playerx + #playerrad, #blocksize)
  ty = Raster(playery - #playerrad , #blocksize)
  
  If MinMaxL(tx, 0, #mapw) = 0 And MinMaxL(ty, 0, #maph) = 0
    If mymap(tx, ty) = #True
      If Radius(tx * #blocksize, (ty + 1) * #blocksize, playerx, playery) < #playerrad
        wink.f = Angle(tx * #blocksize, (ty + 1) * #blocksize, playerx, playery)
        playerx = tx * #blocksize + Cos(wink) * #playerrad
        playery = (ty + 1) * #blocksize + Sin(wink) * #playerrad
      EndIf
    EndIf
  EndIf
  
  ;Obere rechte Ecken prüfen
  
  tx = Raster(playerx - #playerrad, #blocksize)
  ty = Raster(playery + #playerrad , #blocksize)
  
  If MinMaxL(tx, 0, #mapw) = 0 And MinMaxL(ty, 0, #maph) = 0
    If mymap(tx, ty) = #True
      If Radius((tx + 1) * #blocksize, ty * #blocksize, playerx, playery) < #playerrad
        wink.f = Angle((tx + 1) * #blocksize, ty * #blocksize, playerx, playery)
        playerx = (tx + 1) * #blocksize + Cos(wink) * #playerrad
        playery = ty * #blocksize + Sin(wink) * #playerrad
      EndIf
    EndIf
  EndIf
  
  ;Obere linke Ecken prüfen
  tx = Raster(playerx + #playerrad, #blocksize)
  ty = Raster(playery + #playerrad , #blocksize)
  
  If MinMaxL(tx, 0, #mapw) = 0 And MinMaxL(ty, 0, #maph) = 0
    If mymap(tx, ty) = #True
      If Radius(tx * #blocksize, ty * #blocksize, playerx, playery) < #playerrad
        wink.f = Angle(tx * #blocksize, ty * #blocksize, playerx, playery)
        playerx = tx * #blocksize + Cos(wink) * #playerrad
        playery = ty * #blocksize + Sin(wink) * #playerrad
      EndIf
    EndIf
  EndIf 
  
  
  ;Erstes und letztes zu darstellendes Tile ermitteln
  ux = Raster(playerx - soffx, #blocksize)
  uy = Raster(playery - soffy, #blocksize)
    
  ox = Raster(playerx + soffx, #blocksize)
  oy = Raster(playery + soffy, #blocksize)
  
  ;Map darstellen
  If (MinMaxL(ux, 0, #mapw) = 0 Or MinMaxL(ox, 0, #mapw) = 0) And (MinMaxL(uy, 0, #maph) = 0 Or MinMaxL(oy, 0, #maph) = 0)   
    ux = IfMinL(ux, 0)
    uy = IfMinL(uy, 0)
    ox = IfMaxL(ox, #mapw)
    oy = IfMaxL(oy, #maph)
    
    For x = ux To ox : For y = uy To oy
  
        If mymap(x, y) = #False
          DisplaySprite(0, x * #blocksize + soffx - playerx, y * #blocksize + soffy - playery)
        Else
          DisplaySprite(1, x * #blocksize + soffx - playerx, y * #blocksize + soffy - playery)
        EndIf
        
    Next : Next
  EndIf
  
  
  StartDrawing(ScreenOutput())
    DrawText(0, 0, "x = " + StrF(playerx, 1))
    DrawText(0, 16, "y = " + StrF(playery, 1))
    DrawText(0, 32, "wink = " + StrF(pdir, 1))
    DrawText(0, 48, "ux = " + Str(ux) + " uy = " + Str(uy) + " ox = " + Str(ox) + " oy = " + Str(oy))
    
    DrawingMode(#PB_2DDrawing_Outlined ) 

    Circle(soffx, soffy, #playerrad, RGB(100, 150, 0)) ; Der Spieler
    LineXY(soffx, soffy, soffx + Cos(pdir) * #playerrad, soffy + Sin(pdir) * #playerrad, RGB(100, 150, 0))
    
    ; Ein Mauscursor
    Line(MouseX() - 20, MouseY(), 41, 1, RGB(255, 5, 5))
    Line(MouseX(), MouseY() - 20, 1, 41, RGB(255, 5, 5))
    
  StopDrawing()

  FlipBuffers()
  Delay(1)
  ClearScreen(0)
Until KeyboardPushed(#PB_Key_Escape)
CloseScreen()
End

Re: Effektcodes und sonstiger Unnütz

Verfasst: 30.09.2010 22:16
von STARGÅTE
Nett ^^

Hier mal n Code von mir den ich ausgegraben habe ...

Fraktal in 42 zeilen

Code: Alles auswählen

Structure Stick 
  x.f : y.f : Angle.f : Size.f
EndStructure
Global NewList Stick.Stick()

; Rekursionszweig
With Stick()
  AddElement(Stick()) : \Angle = #PI/6      : \Size = 0.8 : \x = Cos(\Angle)*80 : \y = Sin(\Angle)*80
  AddElement(Stick()) : \Angle = #PI-#PI/12 : \Size = 0.7 : \x = Cos(\Angle)*70 : \y = Sin(\Angle)*70
EndWith  

; Rotation
Procedure Rotate(*Stick.Stick, Angle.f)
  Protected x.f = *Stick\x, y.f = *Stick\y
  *Stick\x = Cos(Angle)*x - Sin(Angle)*y
  *Stick\y = Sin(Angle)*x + Cos(Angle)*y
EndProcedure

; Baum  
Procedure CreateTree(x.f, y.f, Angle.f, Size.f, Recursion) 
  Protected Stick.Stick
  ForEach Stick() : With Stick
    \x = Size*Stick()\x : \y = Size*Stick()\y : Rotate(Stick, Angle)
    LineXY(x, y, x+\x, y+\y)   
    If Recursion > 0 : *Stick = @Stick()
      CreateTree(x+\x, y+\y, Angle+Stick()\Angle, Size*Stick()\Size, Recursion-1)
      ChangeCurrentElement(Stick(), *Stick)
    EndIf
  EndWith : Next
EndProcedure

; Bild
CreateImage(0, 800, 600, 32)
StartDrawing(ImageOutput(0))
  DrawingMode(#PB_2DDrawing_AlphaBlend|#PB_2DDrawing_Gradient)
  CircularGradient(400, 200, 400) : GradientColor(0.0,$1000F0F0) : GradientColor(1.0,$1000F000)
  CreateTree(400, 200, -#PI/6, Sqr(3), 16)
StopDrawing()

OpenWindow(0,0,0,ImageWidth(0),ImageHeight(0), "Fractal", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)
ImageGadget(0,0,0,ImageWidth(0), ImageHeight(0), ImageID(0))
Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
Ihr dürft selbstverständlich an den Parametern des Rekursionszweigs "rumspielen".
Nur spielt mit bedacht! kleine Änderungen habe große Wirkung auf Grund der Rekursion!

Unsprünglich wollte ich dafür n echten Editor schreiben mit Rechtzeit Preview usw. - aber ihr wisst ja - Zeit und Lust :freak:

Re: Effektcodes und sonstiger Unnütz

Verfasst: 04.10.2010 13:22
von NicTheQuick
Hab auch gerade etwas sehr sehr altes ausgegraben. Leider beendet PB unter Linux den Screen wohl nicht richtig, womit mein ganzer Desktop im Arsch ist. Aber ich weiß, dass es noch vor zwei Wochen lief.

Code: Alles auswählen

;Erstellt eine Kamera, die 3D-Koordinaten in 2D-Koordinaten umrechnet

#PI      = 3.141592653589793238
#DEG2RAD = #PI / 180

Structure V3D
  x.d
  y.d
  z.d
EndStructure

Structure Camera
  p.V3D
  a.V3D
  x.V3D
  y.V3D
  z.V3D   ;Zeigt in Blickrichtung
  FOV.d
  tFOV.d
  MinX.d
  MaxX.d
  MinY.d
  MaxY.d
  AspX.d
  AspY.d
EndStructure

Procedure Camera_Calc(*Camera.Camera) ;Wird intern aufgerufen
  Protected a_1.d, a_2.d, a_3.d, a_4.d, a_5.d, a_6.d, v_x.V3D, v_y.V3D
  Static Pi2.d = #PI / 2
  
  a_1 = Sin(*Camera\a\y)
  a_2 = Cos(*Camera\a\x)
  a_3 = Cos(*Camera\a\y)
  a_4 = Sin(*Camera\a\x)
  a_5 = Cos(*Camera\a\z)
  a_6 = Sin(*Camera\a\z)
  
  ;Z-Achse
  *Camera\z\x = a_1 * a_2             ; Sin(y) * Cos(x)             0
  *Camera\z\y = a_4                   ; Sin(x)                      0
  *Camera\z\z = a_2 * a_3             ; Cos(x) * Cos(y)             1
  
  ;Y-Achse
  v_y\x = -a_1 * a_4                  ; -Sin(y) * Sin(x)            0
  v_y\y = a_2                         ; Cos(x)                      1
  v_y\z = -a_4 * a_3                  ; -Sin(x) * Cos(y)            0
  
  v_x\x = -*Camera\z\y * v_y\z + *Camera\z\z * v_y\y
  v_x\y = -*Camera\z\z * v_y\x + *Camera\z\x * v_y\z
  v_x\z = -*Camera\z\x * v_y\y + *Camera\z\y * v_y\x
  
  ;Einberechnung des Z-Winkels
  *Camera\x\x = v_x\x * a_5 + v_y\x * a_6
  *Camera\x\y = v_x\y * a_5 + v_y\y * a_6
  *Camera\x\z = v_x\z * a_5 + v_y\z * a_6
  
  *Camera\y\x = v_y\x * a_5 - v_x\x * a_6
  *Camera\y\y = v_y\y * a_5 - v_x\y * a_6
  *Camera\y\z = v_y\z * a_5 - v_x\z * a_6
EndProcedure

Procedure Camera_New() ;Erstellt eine neue Kamera
  Protected *Camera.Camera
  
  *Camera = AllocateMemory(SizeOf(Camera))
  
  If *Camera = 0 : ProcedureReturn #False : EndIf
  
  Camera_Calc(*Camera)
  With *Camera
    \MinX = -1
    \MaxX = 1
    \MinY = -1
    \MaxY = 1
    \AspX = 1
    \AspY = 1
  EndWith
  
  ProcedureReturn *Camera
EndProcedure

Procedure Camera_Set(*Camera.Camera, *Position.V3D, *Angle.V3D) ;Setzt Position und Winkel der Kamera
  If *Position
    *Camera\p\x = *Position\x
    *Camera\p\y = *Position\y
    *Camera\p\z = *Position\z
  EndIf
  
  If *Angle
    *Camera\a\x = *Angle\x * #DEG2RAD
    *Camera\a\y = *Angle\y * #DEG2RAD
    *Camera\a\z = *Angle\z * #DEG2RAD
    Camera_Calc(*Camera)
  EndIf
EndProcedure

Procedure Camera_Add(*Camera.Camera, *Position.V3D, *Angle.V3D) ;Addiert Position und Winkel zur Kamera
  If *Position
    *Camera\p\x + *Position\x
    *Camera\p\y + *Position\y
    *Camera\p\z + *Position\z
  EndIf
  
  If *Angle
    *Camera\a\x + *Angle\x * #DEG2RAD
    *Camera\a\y + *Angle\y * #DEG2RAD
    *Camera\a\z + *Angle\z * #DEG2RAD
    Camera_Calc(*Camera)
  EndIf
EndProcedure

Procedure.d Camera_FOV(*Camera.Camera, FOV.d = -1) ;Setzt "Field of view" der Kamera in Grad (0 < FOV < 180) oder gibt es zurück
  If FOV = -1
    ProcedureReturn *Camera\FOV
  ElseIf FOV > 0 And FOV < 180
    *Camera\FOV = FOV * #DEG2RAD
    *Camera\tFOV = Tan(*Camera\FOV)
  EndIf
EndProcedure
Procedure Camera_Size(*Camera.Camera, MinX.d, MaxX.d, MinY.d, MaxY.d) ;Setzt die maximalen Ausgabekoordinaten
  With *Camera
    \MinX = MinX
    \MaxX = MaxX
    \MinY = MinY
    \MaxY = MaxY
    
    If \MaxX - \MinX > \MaxY - \MinY
      \AspX = 1
      \AspY = (\MaxY - \MinY) / (\MaxX - \MinX)
    ElseIf \MaxX - \MinX = \MaxY - \MinY
      \AspX = 1
      \AspY = 1
    Else
      \AspY = 1
      \AspX = (\MaxX - \MinX) / (\MaxY - \MinY)
    EndIf
  EndWith
EndProcedure

Procedure Camera_Move(*Camera.Camera, *Position.V3D, *Angle.V3D) ;Bewegt die Kamera relativ zu Position und Drehung
  Protected CosZ.d, SinZ.d
  
  If *Position
    *Camera\p\x + *Position\x * *Camera\x\x + *Position\y * *Camera\y\x + *Position\z * *Camera\z\x
    *Camera\p\y + *Position\x * *Camera\x\y + *Position\y * *Camera\y\y + *Position\z * *Camera\z\y
    *Camera\p\z + *Position\x * *Camera\x\z + *Position\y * *Camera\y\z + *Position\z * *Camera\z\z
  EndIf
  
  If *Angle
    *Camera\a\z + *Angle\z * #DEG2RAD
    CosZ = Cos(*Camera\a\z)
    SinZ = Sin(*Camera\a\z)
    *Camera\a\x + (*Angle\x * CosZ + *Angle\y * SinZ) * #DEG2RAD
    *Camera\a\y + (*Angle\y * CosZ - *Angle\x * SinZ) * #DEG2RAD
    Camera_Calc(*Camera)
  EndIf
EndProcedure

Procedure Camera_3Dto2D(*Camera.Camera, *p3d.V3D, *p2d.V3D, *c3d.V3D = 0) ;Rechnet 3D-Punkt in 2D-Punkt um
  Protected v_x.V3D, v_y.V3D, v.V3D, cx.d, cy.d, cz.d
  
  ;Vektor von Kamera zu Punkt
  v\x = *p3d\x - *Camera\p\x
  v\y = *p3d\y - *Camera\p\y
  v\z = *p3d\z - *Camera\p\z
  
  ;Abstand von Kamera auf Z-Achse
  cz = v\x * *Camera\z\x + v\y * *Camera\z\y + v\z * *Camera\z\z
  
  If cz = 0
    cx = 0
    cy = 0
    *p2d\x = 0
    *p2d\y = 0
  Else
    ;Abstand von Kamera auf X- und Y-Achse
    cx = v\x * *Camera\x\x + v\y * *Camera\x\y + v\z * *Camera\x\z
    cy = v\x * *Camera\y\x + v\y * *Camera\y\y + v\z * *Camera\y\z
    
    ;Berechnung des 2D-Punktes auf X- und Y-Achse im Bereich -1 bis 1
    *p2d\x = cx / (cz * *Camera\tFOV) / *Camera\AspX
    *p2d\y = cy / (cz * *Camera\tFOV) / *Camera\AspY
    
    ;Umrechnung des Bereichs auf die maximalen Ausgabekoordinaten
    *p2d\x = *Camera\MinX + (*p2d\x + 1) * 0.5 * (*Camera\MaxX - *Camera\MinX)
    *p2d\y = *Camera\MinY + (*p2d\y + 1) * 0.5 * (*Camera\MaxY - *Camera\MinY)
  EndIf
  
  If *c3d
    *c3d\x = cx
    *c3d\y = cy
    *c3d\z = cz
  EndIf
  
  ;Wenn Punkt hinter Kamera war
  If cz <= 0  : ProcedureReturn #False : EndIf
  
  ;Wenn Punkt außerhalb maximalen Ausgabekoordinaten war
  If *p2d\x < *Camera\MinX Or *p2d\x > *Camera\MaxX : ProcedureReturn #False : EndIf
  If *p2d\y < *Camera\MinY Or *p2d\y > *Camera\MaxY : ProcedureReturn #False : EndIf
  
  ;Wenn Punkt im sichtbaren Bereich
  ProcedureReturn #True
EndProcedure


EnableExplicit

Global width.l = 800, height.l = 600
Global *Camera.Camera, MouseFocus.l
Global NodeDims = 39, DrawLines = 1, SpringD.d = 150, NodeM.d = 0.1, MaxL.d = 100
Global ActTest, deltatime.d

; Structure V3D
  ; x.d
  ; y.d
  ; z.d
; EndStructure
Macro SetV3D(Var, inx, iny, inz)
  Var\x = inx
  Var\y = iny
  Var\z = inz
EndMacro
Procedure.d Min(a.d, b.d)
  If a < b
    ProcedureReturn a
  EndIf
  ProcedureReturn b
EndProcedure
Procedure.d Max(a.d, b.d)
  If a > b
    ProcedureReturn a
  EndIf
  ProcedureReturn b
EndProcedure
Structure C3F
  r.f
  g.f
  b.f
EndStructure
Structure Spring
  length.d    ;Normallänge der Feder bei F=0
  nLength.d   ;Momentante Länge der Feder
  d.d         ;Federkonstante
  F.d
  maxF.d      ;Maximale Kraft, die sie auf Zug aushält
  maxLength.d
  broken.l    ;#True, wenn Feder gerissen
EndStructure
Structure Node
  m.d
  p.V3D
  v.V3D
  a.V3D
  F.V3D
  Fixed.l
  cNodes.l
  *pNodes
  *pSpring
  *pFirst
EndStructure

Procedure.d Distance(*a.V3D, *b.V3D)
  Protected x.d = *b\x - *a\x, y.d = *b\y - *a\y, z.d = *b\z - *a\z
  ProcedureReturn Sqr(x * x + y * y + z * z)
EndProcedure

Global NewList Nodes.Node()
Procedure NewNode(x.d, y.d, z.d, m.d, Fixed.l = #False)
  If AddElement(Nodes())
    Nodes()\m = m
    Nodes()\p\x = x
    Nodes()\p\y = y
    Nodes()\p\z = z
    Nodes()\Fixed = Fixed
    ProcedureReturn @Nodes()
  EndIf
 
  ProcedureReturn #False
EndProcedure
Procedure ClearNodes()
  ForEach Nodes()
    If Nodes()\pNodes : FreeMemory(Nodes()\pNodes) : EndIf
    If Nodes()\pSpring : FreeMemory(Nodes()\pSpring) : EndIf
    If Nodes()\pFirst : FreeMemory(Nodes()\pFirst) : EndIf
  Next
  ClearList(Nodes())
EndProcedure
Global NewList Springs.Spring()
Procedure NewSpring(length.d, d.d)
 
  If AddElement(Springs())
    Springs()\length = length
    Springs()\d = d
   
    ProcedureReturn @Springs()
  EndIf
 
  ProcedureReturn #False
EndProcedure
Procedure ClearSprings()
  ClearList(Springs())
EndProcedure
Procedure SetSpring(*Spring.Spring, maxF.d = 0, maxLength.d = 0, LengthMult.d = 0)
  If maxF > 0
    *Spring\maxF = maxF
    *Spring\maxLength = maxF / *Spring\d
  ElseIf maxLength > 0
    *Spring\maxLength = maxLength
    *Spring\maxF = maxLength * *Spring\d
  ElseIf LengthMult > 0
    *Spring\maxLength = *Spring\length * LengthMult
    *Spring\maxF = *Spring\maxLength * *Spring\d
  EndIf
EndProcedure
Procedure LinkNodes(*Node1.Node, *Node2.Node, d.d = 1, SpringPercent.d = 1)
  Protected a.l, new.l, *pNode.Long, *tmp1, *tmp2, *tmp3, *tmp4, *tmp5, *tmp6
  Protected *Spring.Spring
 
  If *Node1 = *Node2 : ProcedureReturn #False : EndIf
 
  new = #True
  *pNode = *Node1\pNodes
  For a = 1 To *Node1\cNodes
    If *pNode\l = *Node2 : new = #False : Break : EndIf
    *pNode + SizeOf(Long)
  Next
 
  If new
    *Spring = NewSpring(Distance(*Node1\p, *Node2\p) * SpringPercent, d)
    If *Spring = 0 : ProcedureReturn #False : EndIf
   
    *tmp1 = ReAllocateMemory(*Node1\pNodes, (*Node1\cNodes + 1) * SizeOf(Long))
    *tmp2 = ReAllocateMemory(*Node1\pSpring, (*Node1\cNodes + 1) * SizeOf(Long))
    *tmp5 = ReAllocateMemory(*Node1\pFirst, (*Node1\cNodes + 1))
    *tmp3 = ReAllocateMemory(*Node2\pNodes, (*Node2\cNodes + 1) * SizeOf(Long))
    *tmp4 = ReAllocateMemory(*Node2\pSpring, (*Node2\cNodes + 1) * SizeOf(Long))
    *tmp6 = ReAllocateMemory(*Node2\pFirst, (*Node2\cNodes + 1))
    If *tmp1 : *Node1\pNodes = *tmp1 : EndIf
    If *tmp2 : *Node1\pSpring = *tmp2 : EndIf
    If *tmp5 : *Node1\pFirst = *tmp5 : EndIf
    If *tmp3 : *Node2\pNodes = *tmp3 : EndIf
    If *tmp4 : *Node2\pSpring = *tmp4 : EndIf
    If *tmp6 : *Node2\pFirst = *tmp6 : EndIf
   
    If *tmp1 And *tmp2 And *tmp3 And *tmp4 And *tmp5 And *tmp6
      PokeL(*Node1\pNodes + *Node1\cNodes * SizeOf(Long), *Node2)
      PokeL(*Node1\pSpring + *Node1\cNodes * SizeOf(Long), *Spring)
      PokeB(*Node1\pFirst + *Node1\cNodes, 1)
      PokeL(*Node2\pNodes + *Node2\cNodes * SizeOf(Long), *Node1)
      PokeL(*Node2\pSpring + *Node2\cNodes * SizeOf(Long), *Spring)
      PokeB(*Node2\pFirst + *Node2\cNodes, 0)
      *Node1\cNodes + 1
      *Node2\cNodes + 1
     
      ProcedureReturn *Spring
    EndIf
  EndIf
 
  ProcedureReturn #False
EndProcedure

Procedure SetAllSpringsD(d.d)
  ForEach Springs()
    Springs()\d = d
    Springs()\maxF = Springs()\maxLength * Springs()\d
  Next
EndProcedure
Procedure SetAllNodesM(m.d)
  ForEach Nodes()
    Nodes()\m = m
  Next
EndProcedure
Procedure SetAllSpringsL(l.d)
  ForEach Springs()
    SetSpring(@Springs(), 0, 0, l)
  Next
EndProcedure

Global Friction.d = 0.98, Gravity.d = 9.81
Global Energy.d
Procedure CalcFriction(deltatime.d)
  ForEach Nodes()
    With Nodes()
      ;Geschwindigkeit wird durch imaginäre Reibung gebremst
      \F\x = \v\x * \m / deltatime * Friction
      \F\y = \v\y * \m / deltatime * Friction
      \F\z = \v\z * \m / deltatime * Friction
      
      \v\x = 0 : \v\y = 0 : \v\z = 0
    EndWith
  Next
EndProcedure
Procedure CalcForce(*Node.Node, deltatime.d)
  Protected *pSpring.Long, *pNode.Long, a.l, F.V3D, Dist.d, mult.d, *rNode.Node, *rSpring.Spring, W.V3D, s.d
  Protected *pFirst.Byte
  
  *pNode = *Node\pNodes
  *pSpring = *Node\pSpring
  *pFirst = *Node\pFirst
  For a = 1 To *Node\cNodes
    *rNode = *pNode\l
    *rSpring = *pSpring\l
   
    If *rSpring\broken = #False And *pFirst\b = 1
      Dist = Distance(*Node\p, *rNode\p)
      
      mult = (Dist - *rSpring\length) * *rSpring\d * 0.5
      F\x = (*rNode\p\x - *Node\p\x) * mult
      F\y = (*rNode\p\y - *Node\p\y) * mult
      F\z = (*rNode\p\z - *Node\p\z) * mult
     
      *rSpring\nLength = Dist
      If Dist < *rSpring\length
        *rSpring\F = Sqr(F\x * F\x + F\y * F\y + F\z * F\z)
      Else
        *rSpring\F = - Sqr(F\x * F\x + F\y * F\y + F\z * F\z)
      EndIf
      If -*rSpring\F > *rSpring\maxF : *rSpring\broken = #True : EndIf
      If *rSpring\nLength > *rSpring\maxLength : *rSpring\broken = #True : EndIf
      
      If *Node\Fixed = #False
        *Node\F\x + F\x
        *Node\F\y + F\y
        *Node\F\z + F\z
      EndIf
      If *rNode\Fixed = #False
        *rNode\F\x - F\x
        *rNode\F\y - F\y
        *rNode\F\z - F\z
      EndIf
      
      ;Federspannungsenergie
      s = Abs(Dist - *rSpring\length)
      Energy + 0.5 * s * s * *rSpring\d
    EndIf
    
    *pNode + SizeOf(Long)
    *pSpring + SizeOf(Long)
    *pFirst + 1
  Next
  
  ;Bewegungsenergie
  W\x = 0.5 * *Node\v\x * *Node\v\x * *Node\m
  W\y = 0.5 * *Node\v\y * *Node\v\y * *Node\m
  W\z = 0.5 * *Node\v\z * *Node\v\z * *Node\m
  Energy + Sqr(W\x * W\x + W\y * W\y + W\z * W\z)
EndProcedure
Procedure CalcMove(deltatime.d)
  ForEach Nodes()
    With Nodes()
      If \Fixed = #False
        \v\x = \F\x * deltatime / \m
        \v\y = (\F\y + Gravity * \m) * deltatime / \m
        \v\z = \F\z * deltatime / \m
        \p\x + \v\x * deltatime
        \p\y + \v\y * deltatime
        \p\z + \v\z * deltatime
      EndIf
    EndWith
  Next
EndProcedure
Procedure DrawNodes(deltatime.d)
  Protected a.l, *pNode.Long, *rNode.Node, *pSpring.Long, *rSpring.Spring, SpringC.C3F, r.d, p1.V3D, p2.V3D
  Protected speed.d, duration.l, *pFirst.Byte, Drawed.l = 0
  Static time_FPS.l = -1
  
  If time_FPS >= 0
    time_FPS = ElapsedMilliseconds() - time_FPS
    duration = time_FPS
    speed = deltatime * 1000 / time_FPS
  EndIf
  time_FPS = ElapsedMilliseconds()
  
  StartDrawing(ScreenOutput())
    DrawingMode(#PB_2DDrawing_Transparent)
    
    ForEach Nodes()
      *pNode = Nodes()\pNodes
      *pSpring = Nodes()\pSpring
      *pFirst = Nodes()\pFirst
      
      If Camera_3Dto2D(*Camera, Nodes()\p, p1)
        If DrawLines = 1
          For a = 1 To Nodes()\cNodes
            *rNode = *pNode\l
            *rSpring = *pSpring\l
            
            If *rSpring\broken = #False And *pFirst\b = 1
              If Camera_3Dto2D(*Camera, *rNode\p, p2)
                If *rSpring\nLength < *rSpring\length ;Kürzer=Grün, Normal=Blau, Länger=Rot
                  SpringC\r = 0
                  SpringC\g = (*rSpring\length - *rSpring\nLength) * 1.5 / *rSpring\length
                  SpringC\b = 1 - SpringC\g
                ElseIf *rSpring\nLength > *rSpring\length
                  SpringC\r = (*rSpring\nLength - *rSpring\length) * 0.75 / *rSpring\length
                  SpringC\g = 0
                  SpringC\b = 1 - SpringC\r
                Else
                  SpringC\r = 0
                  SpringC\g = 0
                  SpringC\b = 1
                EndIf
                If SpringC\r > 1.0 : SpringC\r = 1.0 : EndIf : If SpringC\r < 0.0 : SpringC\r = 0.0 : EndIf
                If SpringC\g > 1.0 : SpringC\g = 1.0 : EndIf : If SpringC\g < 0.0 : SpringC\g = 0.0 : EndIf
                If SpringC\b > 1.0 : SpringC\b = 1.0 : EndIf : If SpringC\b < 0.0 : SpringC\b = 0.0 : EndIf
                LineXY(p1\x, p1\y, p2\x, p2\y, RGB(SpringC\r * 255, SpringC\g * 255, SpringC\b * 255))
                Drawed + 1
              EndIf
            EndIf
            *pNode + SizeOf(Long)
            *pSpring + SizeOf(Long)
            *pFirst + 1
          Next
        EndIf
        
        r = Pow(Nodes()\m * 50, 0.25)
        Circle(p1\x, p1\y, r, $FFFFFF) ;Weiße Kreise für Nodes
      EndIf
    Next
    
    DrawText(0, 0, "Energy: " + RSet(StrD(Energy, 4), 15, "0"), $FFFFFF)
    DrawText(0, 16, "Gravity: " + StrD(Gravity, 2) + "  (+/-)", $FFFFFF)
    DrawText(0, 32, "Friction: " + StrD(Friction, 4) + "  (PgUp/PgDn)", $FFFFFF)
    DrawText(0, 48, "Speed: " + StrD(speed, 2) + " x", $FFFFFF)
    DrawText(0, 64, "Duration: " + Str(duration) + " ms/Frame", $FFFFFF)
    If ActTest = 3 : DrawText(0, 80, "Field: " + Str(NodeDims) + "x" + Str(NodeDims) + "  (Strg +/-)", $FFFFFF) : EndIf
    If MouseFocus = 0
      DrawText(0, 96, "Modus: Fixpunktmodus", $FF0000)
    Else
      DrawText(0, 96, "Modus: Kameramodus", $FF0000)
    EndIf
    DrawText(0, 112, "Drawed Lines: " + Str(Drawed) + "  (L)", $FFFFFF)
    DrawText(0, 128, "Springconstant: " + StrD(SpringD, 0) + "  (F/V)", $FFFFFF)
    DrawText(0, 144, "Nodemass: " + StrD(NodeM, 1) + "  (G/B)", $FFFFFF)
    DrawText(0, 160, "deltatime: " + StrD(deltatime, 6) + "  (H/N)", $FFFFFF)
    DrawText(0, 176, "Springlength: " + StrD(MaxL * 100, 0) + "% (J/M)", $FFFFFF)
  StopDrawing()
EndProcedure

Global *MouseNode.Node

Procedure Test1()
  Protected a.l, Nodes.l, *Spring.Spring
  ActTest = 1
  ClearNodes()
  ClearSprings()
 
  Nodes = 40
  Gravity = 0
  Protected Dim *Nodes.Node(Nodes - 1)
 
  For a = 0 To Nodes - 1
    *Nodes(a) = NewNode(10 * a / Nodes - 5, 0, 0, 0.01)
  Next
  For a = 0 To Nodes - 2
    *Spring = LinkNodes(*Nodes(a), *Nodes(a + 1), 5, 0.8)
    SetSpring(*Spring, 0, 0, 200)
  Next
 
  For a = 1 To 0
    LinkNodes(*Nodes(Random(Nodes - 1)), *Nodes(Random(Nodes - 1)), 1)
  Next
 
  *Nodes(Nodes - 1)\Fixed = #True
 
  *MouseNode = *Nodes(0)
EndProcedure

Procedure Test2()
  Define a.l, *Spring.Spring, Nodes = 20
  ActTest = 2
  ClearNodes()
  ClearSprings()
 
  Nodes = 20
  Protected Dim *Nodes.Node(Nodes - 1)
 
  *Nodes(0) = NewNode(0, 0, 0, 1, #True)
 
  For a = 1 To Nodes - 1
    *Nodes(a) = NewNode(Random(100) / 10 - 5, Random(100) / 10 - 5, Random(100) / 10 - 5, 1)
    If a > 2
      *Spring = LinkNodes(*Nodes(a), *Nodes(a - 1), 1)
      SetSpring(*Spring, 0, 0, 5)
    EndIf
    *Spring = LinkNodes(*Nodes(0), *Nodes(a), 1)
    SetSpring(*Spring, 0, 0, 5)
  Next
  *Spring = LinkNodes(*Nodes(1), *Nodes(Nodes - 1), 1)
  SetSpring(*Spring, 0, 0, 5)
 
  *MouseNode = *Nodes(0)
EndProcedure

Macro p(x, y, yD)
  ((x) * yD + y)
EndMacro
Procedure Test3()
  Protected x.l, y.l, p.V3D, *Spring.Spring, d.d, LengthMult.d, SpringPercent.d, m.d, CountX.l, CountY.l, arrp.l
  ActTest = 3
  ClearNodes()
  ClearSprings()
 
  CountX = NodeDims
  CountY = NodeDims
  d = SpringD             ;Federkonstante: Je höher, desto stärkerer Zug bei Dehnung
  LengthMult = MaxL       ;maximale Länge in 100%: 500 = 50000%
  SpringPercent = 1.0     ;Startlänge in 100%: Werte kleiner als 1 bewirken einen sofortigen Zug
  m = NodeM               ;Gewicht der Nodes: wirkt sich auf die Schwerkraft aus
;   Gravity = 9.81
;   Friction = 0.98
 
  Protected Dim *Nodes.Node(CountX * CountY - 1)
 
  p\y = 0
 
  For x = 0 To CountX - 1
    p\x = x * 10 / CountX - 5
    For y = 0 To CountY - 1
      p\z = y * 10 / CountY
      ;If 0
      If x = 0 Or y = 0 Or y = CountY - 1 Or x = CountX - 1
      ;If (x = 0 Xor x = CountX - 1) And (y = 0 Or y = CountY - 1)
        *Nodes(p(x, y, CountY)) = NewNode(p\x, p\y, p\z, m, #True)
      Else
        *Nodes(p(x, y, CountY)) = NewNode(p\x, p\y, p\z, m)
      EndIf
      If x > 0
        *Spring = LinkNodes(*Nodes(p(x, y, CountY)), *Nodes(p(x - 1, y, CountY)), d, SpringPercent)
        SetSpring(*Spring, 0, 0, LengthMult)
      EndIf
      If y > 0
        *Spring = LinkNodes(*Nodes(p(x, y, CountY)), *Nodes(p(x, y - 1, CountY)), d, SpringPercent)
        SetSpring(*Spring, 0, 0, LengthMult)
      EndIf
      If x > 0 And y > 0
        *Spring = LinkNodes(*Nodes(p(x, y, CountY)), *Nodes(p(x - 1, y - 1, CountY)), d, SpringPercent)
        SetSpring(*Spring, 0, 0, LengthMult)
        *Spring = LinkNodes(*Nodes(p(x - 1, y, CountY)), *Nodes(p(x, y - 1, CountY)), d, SpringPercent)
        SetSpring(*Spring, 0, 0, LengthMult)
      EndIf
    Next
  Next
 
  ;*Nodes(1 + Random(CountX - 3), 1 + Random(CountY - 3))\m * 10
 
  *Nodes(p(CountX / 2, 0, CountY))\Fixed = #True
  *Nodes(p(CountX / 2, CountY - 1, CountY))\Fixed = #True
  *Nodes(p(0, CountY / 2, CountY))\Fixed = #True
  *Nodes(p(CountX - 1, CountY / 2, CountY))\Fixed = #True
 
  *MouseNode = *Nodes(p(CountX / 2, CountY / 2, CountY))
  ;*MouseNode\m = 50
EndProcedure

InitSprite()
InitMouse()
InitKeyboard()

OpenWindow(0, 0, 0, width, height, "Feder-Simulation", #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
OpenWindowedScreen(WindowID(0), 0, 0, width, height, 0, 0, 0)

;Kamera
Define pos.V3D, angle.V3D, p2d.V3D, p2d.V3D
Define p_add.d, a_add.d, FOV.d
p_add.d = 0.5
a_add.d = 1.5
FOV.d = 45

*Camera = Camera_New()
Camera_Size(*Camera, 0, width - 1, 0, height - 1)

If *MouseNode : MouseLocate(*MouseNode\p\x, *MouseNode\p\y) : EndIf

Define MouseFocusChange.l
MouseFocus = 0
MouseFocusChange = #False

deltatime = 0.0025
Test3()
Repeat
  ClearScreen(0)
 
  ExamineMouse()
  ExamineKeyboard()
 
  pos\x = 0 : pos\y = 0 : pos\z = 0
  angle\x = 0 : angle\y = 0 : angle\z = 0
 
  If MouseButton(2)
    If MouseFocusChange = #False
      If MouseFocus = 1 : MouseFocus = 0 : Else : MouseFocus + 1 : EndIf
      MouseFocusChange = #True
    EndIf
  Else
    MouseFocusChange = #False
  EndIf
 
  Select MouseFocus
    Case 0
      If *MouseNode
        If Not MouseButton(1)
          *MouseNode\p\x + MouseDeltaX() * 0.01
          *MouseNode\p\y + MouseDeltaY() * 0.01
          *MouseNode\Fixed = #True
        Else
          *MouseNode\Fixed = #False
        EndIf
      EndIf
   
    Case 1
      angle\x + MouseDeltaY() * 0.5
      angle\y + MouseDeltaX() * 0.5
  EndSelect
  
  If KeyboardPushed(#PB_Key_PageUp)   : Friction + 0.0005 : EndIf
  If KeyboardPushed(#PB_Key_PageDown) : Friction - 0.0005 : EndIf
  If KeyboardPushed(#PB_Key_F1)       : Test1(): EndIf
  If KeyboardPushed(#PB_Key_F2)       : Test2(): EndIf
  If KeyboardPushed(#PB_Key_F3)       : Test3() : EndIf
  
  If KeyboardPushed(#PB_Key_LeftControl) Or KeyboardPushed(#PB_Key_RightControl)
    If KeyboardReleased(#PB_Key_Add)      : NodeDims = Min(255, NodeDims + 2) : Test3() : EndIf
    If KeyboardReleased(#PB_Key_Subtract) : NodeDims = Max(3, NodeDims - 2) : Test3() : : EndIf
    If KeyboardPushed(#PB_Key_Up)    : pos\y - p_add : EndIf
    If KeyboardPushed(#PB_Key_Down)  : pos\y + p_add : EndIf
    If KeyboardPushed(#PB_Key_Left)  : angle\y - a_add : EndIf
    If KeyboardPushed(#PB_Key_Right) : angle\y + a_add : EndIf
  Else
    If KeyboardPushed(#PB_Key_Add)      : Gravity + 0.01 : EndIf
    If KeyboardPushed(#PB_Key_Subtract) : Gravity - 0.01 : EndIf
    If KeyboardPushed(#PB_Key_Up)       : pos\z + p_add : EndIf
    If KeyboardPushed(#PB_Key_Down)     : pos\z - p_add : EndIf
    If KeyboardPushed(#PB_Key_Left)     : pos\x - p_add : EndIf
    If KeyboardPushed(#PB_Key_Right)    : pos\x + p_add : EndIf
  EndIf
  
  If KeyboardPushed(#PB_Key_A)     : angle\x + a_add : EndIf
  If KeyboardPushed(#PB_Key_Z)     : angle\x - a_add : EndIf
  If KeyboardPushed(#PB_Key_S)     : angle\z + a_add : EndIf
  If KeyboardPushed(#PB_Key_X)     : angle\z - a_add : EndIf
  If KeyboardPushed(#PB_Key_D)     : FOV - 0.25 : EndIf
  If KeyboardPushed(#PB_Key_C)     : FOV + 0.25 : EndIf
  If KeyboardPushed(#PB_Key_F)     : SpringD = Min(1000, SpringD + 1) : SetAllSpringsD(SpringD) : EndIf
  If KeyboardPushed(#PB_Key_V)     : SpringD = Max(1, SpringD - 1) : SetAllSpringsD(SpringD) : EndIf
  If KeyboardPushed(#PB_Key_G)     : NodeM = Min(20, NodeM + 0.1) : SetAllNodesM(NodeM) : EndIf
  If KeyboardPushed(#PB_Key_B)     : NodeM = Max(0.1, NodeM - 0.1) : SetAllNodesM(NodeM) : EndIf
  If KeyboardReleased(#PB_Key_H)   : deltatime = Min(0.02, deltatime * 2) : EndIf
  If KeyboardReleased(#PB_Key_N)   : deltatime = Max(0.000625, deltatime / 2) : EndIf
  If KeyboardPushed(#PB_Key_J)     : MaxL = Min(1000, MaxL + 2) : SetAllSpringsL(MaxL) : EndIf
  If KeyboardPushed(#PB_Key_M)     : MaxL = Max(2, MaxL - 2) : SetAllSpringsL(MaxL) : EndIf
  If KeyboardReleased(#PB_Key_L)   : DrawLines ! 1 : EndIf
  
  Camera_FOV(*Camera, FOV)
  Camera_Move(*Camera, pos, angle)
  
  CalcFriction(deltatime)
  Energy = 0
  ForEach Nodes()
    CalcForce(@Nodes(), deltatime)
  Next
  CalcMove(deltatime)
  DrawNodes(deltatime)
  
  FlipBuffers()
Until WindowEvent() = #PB_Event_CloseWindow Or KeyboardPushed(#PB_Key_Escape)