#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <Misc.au3>
#include <Math.au3>
#include <GDIPlus.au3> ; Wird benoetigt, um Bilder fuer Texturen zu laden

; --- OPENGL KONSTANTEN ---
Global Const $PFD_DOUBLEBUFFER = 0x00000001
Global Const $PFD_DRAW_TO_WINDOW = 0x00000004
Global Const $PFD_SUPPORT_OPENGL = 0x00000020
Global Const $PFD_SUPPORT_COMPOSITION = 0x00008000 ; Wichtig fuer Windows 11 DWM!
Global Const $PFD_TYPE_RGBA = 0
Global Const $PFD_MAIN_PLANE = 0

Global Const $GL_COLOR_BUFFER_BIT = 0x00004000
Global Const $GL_QUADS = 0x0007
Global Const $GL_TRIANGLE_FAN = 0x0006
Global Const $GL_TEXTURE_2D = 0x0DE1
Global Const $GL_TEXTURE_MAG_FILTER = 0x2800
Global Const $GL_TEXTURE_MIN_FILTER = 0x2801
Global Const $GL_LINEAR = 0x2601
Global Const $GL_RGBA = 0x1908
Global Const $GL_UNSIGNED_BYTE = 0x1401
Global Const $GL_MODELVIEW = 0x1700
Global Const $GL_PROJECTION = 0x1701
Global Const $GL_TEXTURE = 0x1702
Global Const $GL_BLEND = 0x0BE2
Global Const $GL_SRC_ALPHA = 0x0302
Global Const $GL_ONE_MINUS_SRC_ALPHA = 0x0303
Global Const $GL_COMPILE = 0x1300
Global Const $GL_POLYGON_SMOOTH = 0x0B42
Global Const $GL_POLYGON_SMOOTH_HINT = 0x0C53
Global Const $GL_NICEST = 0x1102
Global Const $GL_MODELVIEW_MATRIX = 0x0BA6
Global Const $GL_DEPTH_TEST = 0x0B71
Global Const $GL_DEPTH_BUFFER_BIT = 0x00000100
Global Const $GL_NO_ERROR = 0x0

Global Const $GL_CULL_FACE = 0x0B44
Global Const $GL_TRIANGLES = 0x0004

Global Const $GL_VERSION = 0x1F02
Global Const $GL_VENDOR = 0x1F00
Global Const $GL_RENDERER = 0x1F01

; --- KONFIGURATION ---
Global $iWidth = 1000
Global $iHeight = 600
Global $fFriction = 0.990
Global $fBallRadius = 18
Global $iBorder = 40
Global $sFolder = @ScriptDir

; Globale Arrays fuer die Kugeln
Global $iMaxBalls = 16
Global $aBalls[$iMaxBalls][8]
Global $aTextures[$iMaxBalls] ; OpenGL Textur-IDs
Global $aBallMatrices[$iMaxBalls] ; Strukturen fuer 4x4 Rotations-Matrizen

; Display Listen (Optimierung: Speichert Zeichenbefehle auf der Grafikkarte)
Global $g_iListTable = 0
Global $g_iListSphere = 0
Global $g_iTexBlank = 0
Global $g_bMSAA = False ; Status fuer Multisampling Anti-Aliasing

; DLL Handles
Global $hOpenGL32 = DllOpen("opengl32.dll")
Global $hGDI32 = DllOpen("gdi32.dll")
Global $hUser32 = DllOpen("user32.dll")

_Main()

Func _Main()
    _GDIPlus_Startup()

    Local $hGui = GUICreate("AutoIt Billard Simulation (OpenGL)", $iWidth, $iHeight)

    ; --- OPENGL EINRICHTUNG ---
    Local $hDC = _WinAPI_GetDC($hGui)
    Local $hRC = _SetupOpenGL($hGui, $hDC)

    GUISetState(@SW_SHOW)

    ; OpenGL Status initialisieren
    _glEnable($GL_TEXTURE_2D) ; Texturierung aktivieren
    _glEnable($GL_BLEND) ; Transparenz aktivieren
    _glBlendFunc($GL_SRC_ALPHA, $GL_ONE_MINUS_SRC_ALPHA)
    _glEnable($GL_POLYGON_SMOOTH) ; Kantenglaettung fuer Polygone
    _glHint($GL_POLYGON_SMOOTH_HINT, $GL_NICEST) ; Beste Qualitaet anfordern
    _glEnable($GL_DEPTH_TEST) ; Tiefentest aktivieren (wichtig fuer 3D-Kugeln)
    _glClearColor(0.0, 0.25, 0.0, 1.0) ; Dunkelgruener Hintergrund (Clear Color)

    _CheckOpenGLVersion()

    ; Ansichtsbereich & Orthogonale Projektion (2D-Koordinatensystem fuer 3D-Welt)
    _glViewport(0, 0, $iWidth, $iHeight)
    _glMatrixMode($GL_PROJECTION)
    _glLoadIdentity()
    ; Wir setzen den Z-Bereich auf -100 bis 100, damit die Kugeln (Radius 18) nicht abgeschnitten werden.
    _glOrtho(0, $iWidth, $iHeight, 0, -100, 100) ; Ursprung oben-links
    _glMatrixMode($GL_MODELVIEW)

    ; Texturen laden
    _LoadBallTextures()
    $g_iTexBlank = _CreateBlankTexture()

    ; Rotations-Matrizen initialisieren
    _InitBallMatrices()

    ; Display Listen erstellen (Vorkompilierte Geometrie fuer Performance)
    _InitDisplayLists()

    ; Simulation starten (Kugeln aufbauen)
    _ResetTable()

    Local $hTimer = TimerInit()
    Local $hFpsTimer = TimerInit()
    Local $iFrames = 0
    Local $iFPS = 0
    Local $iIdleCounter = 0

    While 1
        Local $iMsg = GUIGetMsg()
        If $iMsg = $GUI_EVENT_CLOSE Then ExitLoop

        ; Framerate Begrenzung entfernt fuer maximale Fluessigkeit
        $hTimer = TimerInit()

        ; FPS Berechnung (Anzeige im Fenstertitel)
        $iFrames += 1
        If TimerDiff($hFpsTimer) >= 1000 Then
            $iFPS = $iFrames
            Local $sStatus = " [MSAA: " & ($g_bMSAA ? "An" : "Aus") & " | Smooth: An]"
            WinSetTitle($hGui, "", "AutoIt Billard Simulation (OpenGL)" & $sStatus & " - FPS: " & $iFPS)
            $iFrames = 0
            $hFpsTimer = TimerInit()
        EndIf

        ; --- 1. PHYSIK BERECHNUNG ---
        Local $bMovement = _UpdatePhysics()

        ; --- 1b. 3D ROTATION BERECHNUNG ---
        _UpdateRotations()

        ; --- 2. AUTO-RESET (Wenn alles stillsteht) ---
        If Not $bMovement Then
            $iIdleCounter += 1
            If $iIdleCounter > 120 Then
                _ResetTable()
                $iIdleCounter = 0
            EndIf
        Else
            $iIdleCounter = 0
        EndIf

        ; --- 3. ZEICHNEN (RENDERING) ---
        _Render()
        _SwapBuffers($hDC) ; Puffer tauschen (Double Buffering)
    WEnd

    ; Aufraeumen
    _wglMakeCurrent(0, 0)
    _wglDeleteContext($hRC)
    _WinAPI_ReleaseDC($hGui, $hDC)
    DllClose($hOpenGL32)
    DllClose($hGDI32)
    DllClose($hUser32)
    _GDIPlus_Shutdown()
EndFunc

Func _Render()
    ; Bildschirm und Tiefenpuffer loeschen
    _glClear(BitOR($GL_COLOR_BUFFER_BIT, $GL_DEPTH_BUFFER_BIT))
    _CheckGLError("_Render Clear")

    ; --- PROJEKTION NEU SETZEN (Sicherheitshalber fuer jeden Frame) ---
    ; Dies verhindert Probleme, falls die Matrix verloren geht oder der Kontext spinnt.
    _glViewport(0, 0, $iWidth, $iHeight)
    _glMatrixMode($GL_PROJECTION)
    _glLoadIdentity()
    _glOrtho(0, $iWidth, $iHeight, 0, -100, 100)
    _CheckGLError("_Render Projection")

    ; --- MODELVIEW ---
    _glMatrixMode($GL_MODELVIEW)
    _glLoadIdentity()

    ; --- TISCH ZEICHNEN (Display List) ---
    ; [WIN11 FIX] Display Lists nutzen, da Immediate Mode (glBegin/glEnd) auf Intel/Win11 flackert
    _glDisable($GL_DEPTH_TEST)
    ; [WIN11 FIX] Texturing NICHT deaktivieren (glDisable($GL_TEXTURE_2D) ist buggy).
    ; Stattdessen Texturing anlassen und weisse 1x1 Textur binden.
    _glEnable($GL_TEXTURE_2D)    
    _glBindTexture($GL_TEXTURE_2D, $g_iTexBlank) 
    _glDisable($GL_CULL_FACE)
    
    _glCallList($g_iListTable)
    _CheckGLError("_Render Table List")

    ; --- KUGELN ZEICHNEN ---
    _glEnable($GL_DEPTH_TEST)
    _glEnable($GL_TEXTURE_2D)
    _glColor3f(1.0, 1.0, 1.0) ; Farbe zuruecksetzen (Weiss = Originaltextur)

    For $i = 0 To $iMaxBalls - 1
        If Not $aBalls[$i][5] Then ContinueLoop

        Local $fX = $aBalls[$i][0]
        Local $fY = $aBalls[$i][1]
        Local $iTexID = $aTextures[$i]

        _glPushMatrix()
        ; Kugel an ihre 2D-Position verschieben
        _glTranslatef($fX, $fY, 0.0)

        If $iTexID <> 0 Then
            _glBindTexture($GL_TEXTURE_2D, $iTexID)

            ; 3D ROTATION ANWENDEN
            ; Wir laden die gespeicherte Rotationsmatrix der Kugel.
            ; Diese Matrix enthaelt die akkumulierte Rotation aller vergangenen Frames.
            Local $pMatrix = DllStructGetPtr($aBallMatrices[$i])
            _glMultMatrixf($pMatrix)

            ; Kugel zeichnen (3D Sphaere aus der Display List)
            _glCallList($g_iListSphere)
        Else
            ; Fallback fuer fehlende Textur (Weisse Kugel)
            ; [WIN11 FIX] Weisse Textur binden statt Texturing zu deaktivieren
            _glBindTexture($GL_TEXTURE_2D, $g_iTexBlank)
            _glColor3f(1.0, 1.0, 1.0)
            _glCallList($g_iListSphere)
        EndIf

        _glPopMatrix()
    Next
    _CheckGLError("_Render Balls")
    
    _glFinish() ; Synchronisation erzwingen
EndFunc

Func _DrawTableGeometry()
    ; 1. Holzrahmen
    _glColor3f(0.545, 0.27, 0.075) ; Sattelbraun
    _DrawRect(0, 0, $iWidth, $iHeight)

    ; 2. Spielflaeche
    _glColor3f(0.0, 0.39, 0.0) ; Dunkelgruen
    _DrawRect($iBorder, $iBorder, $iWidth - 2*$iBorder, $iHeight - 2*$iBorder)

    ; 3. Loecher
    _glColor3f(0.0, 0.0, 0.0) ; Schwarz
    Local $iHoleR = 22
    _DrawCircle($iBorder - $iHoleR, $iBorder - $iHoleR, $iHoleR) ; Oben-Links
    _DrawCircle($iWidth - $iBorder - $iHoleR, $iBorder - $iHoleR, $iHoleR) ; Oben-Rechts
    _DrawCircle($iBorder - $iHoleR, $iHeight - $iBorder - $iHoleR, $iHoleR) ; Unten-Links
    _DrawCircle($iWidth - $iBorder - $iHoleR, $iHeight - $iBorder - $iHoleR, $iHoleR) ; Unten-Rechts
    _DrawCircle($iWidth/2 - $iHoleR, $iBorder - $iHoleR, $iHoleR) ; Oben-Mitte
    _DrawCircle($iWidth/2 - $iHoleR, $iHeight - $iBorder - $iHoleR, $iHoleR) ; Unten-Mitte
EndFunc

Func _InitDisplayLists()
    ; --- LISTE 1: TISCH ---
    ; Erstellt die Geometrie fuer den Billardtisch
    $g_iListTable = _glGenLists(1)
    _glNewList($g_iListTable, $GL_COMPILE)
        _DrawTableGeometry()
    _glEndList()

    ; --- LISTE 2: KUGEL (3D Sphaere) ---
    ; Erstellt eine detaillierte 3D-Kugel mit Texturkoordinaten
    $g_iListSphere = _glGenLists(1)
    _glNewList($g_iListSphere, $GL_COMPILE)
        ; Zeichnen einer echten 3D Sphaere mittels Kugelkoordinaten.
        ; Die Textur wird mittels Equirectangular Mapping (Weltkarte) auf die Kugel gelegt.

        Local $iStacks = 48 ; Anzahl der horizontalen Scheiben (Breitengrade)
        Local $iSlices = 48 ; Anzahl der vertikalen Segmente (Laengengrade)
        Local $r = $fBallRadius

        For $i = 0 To $iStacks - 1
            Local $lat0 = 3.1415926 * (-0.5 + Number($i) / $iStacks)
            Local $z0 = Sin($lat0) * $r
            Local $zr0 = Cos($lat0) * $r

            Local $lat1 = 3.1415926 * (-0.5 + Number($i + 1) / $iStacks)
            Local $z1 = Sin($lat1) * $r
            Local $zr1 = Cos($lat1) * $r

            _glBegin(0x0008) ; GL_QUAD_STRIP (Streifen aus Vierecken)
            For $j = 0 To $iSlices
                Local $lng = 2 * 3.1415926 * Number($j) / $iSlices
                Local $x = Cos($lng)
                Local $y = Sin($lng)

                ; Texturkoordinaten und Vertexposition berechnen
                _glTexCoord2f(Number($j) / $iSlices, Number($i) / $iStacks)
                _glVertex3f($x * $zr0, $y * $zr0, $z0)

                _glTexCoord2f(Number($j) / $iSlices, Number($i + 1) / $iStacks)
                _glVertex3f($x * $zr1, $y * $zr1, $z1)
            Next
            _glEnd()
        Next
    _glEndList()
EndFunc

Func _DrawRect($x, $y, $w, $h)
    ; Wir nutzen GL_TRIANGLES statt GL_QUADS fuer maximale Kompatibilitaet
    _glBegin($GL_TRIANGLES)
    ; Dreieck 1
    _glVertex3f($x, $y, 0.0)
    _glVertex3f($x + $w, $y, 0.0)
    _glVertex3f($x, $y + $h, 0.0)
    ; Dreieck 2
    _glVertex3f($x + $w, $y, 0.0)
    _glVertex3f($x + $w, $y + $h, 0.0)
    _glVertex3f($x, $y + $h, 0.0)
    _glEnd()
EndFunc

Func _DrawCircle($x, $y, $r)
    Local $iSegments = 32
    _glBegin($GL_TRIANGLE_FAN)
    _glVertex2f($x + $r, $y + $r) ; Center
    For $i = 0 To $iSegments
        Local $fAngle = $i * 2.0 * 3.14159 / $iSegments
        _glVertex2f($x + $r + Cos($fAngle) * $r, $y + $r + Sin($fAngle) * $r)
    Next
    _glEnd()
EndFunc

Func _DrawCircleTextured($x, $y, $r)
    Local $iSegments = 32
    _glBegin($GL_TRIANGLE_FAN)
    _glTexCoord2f(0.5, 0.5)
    _glVertex2f($x + $r, $y + $r) ; Center

    For $i = 0 To $iSegments
        Local $fAngle = $i * 2.0 * 3.14159 / $iSegments
        Local $tx = 0.5 + 0.5 * Cos($fAngle)
        Local $ty = 0.5 + 0.5 * Sin($fAngle)
        _glTexCoord2f($tx, $ty)
        _glVertex2f($x + $r + Cos($fAngle) * $r, $y + $r + Sin($fAngle) * $r)
    Next
    _glEnd()
EndFunc

Func _LoadBallTextures()
    For $i = 0 To 15
        Local $sPath = $sFolder & "\" & $i & ".bmp"
        If FileExists($sPath) Then
            $aTextures[$i] = _LoadTexture($sPath)
        Else
            $aTextures[$i] = 0
        EndIf
    Next
EndFunc

Func _LoadTexture($sFile)
    Local $hImage = _GDIPlus_ImageLoadFromFile($sFile)
    Local $iW = _GDIPlus_ImageGetWidth($hImage)
    Local $iH = _GDIPlus_ImageGetHeight($hImage)

    ; Bits sperren, um an die Rohdaten zu kommen
    Local $tBitmapData = _GDIPlus_BitmapLockBits($hImage, 0, 0, $iW, $iH, $GDIP_ILMREAD, $GDIP_PXF32ARGB)
    Local $pScan0 = DllStructGetData($tBitmapData, "Scan0")

    Local $iTexID = _glGenTextures()
    _glBindTexture($GL_TEXTURE_2D, $iTexID)
    _glTexParameteri($GL_TEXTURE_2D, $GL_TEXTURE_MAG_FILTER, $GL_LINEAR)
    _glTexParameteri($GL_TEXTURE_2D, $GL_TEXTURE_MIN_FILTER, $GL_LINEAR)

    ; Textur hochladen
    ; Wir nutzen hier 0x80E1 (GL_BGRA_EXT), damit die Farben (Rot/Blau) korrekt sind.
    _glTexImage2D($GL_TEXTURE_2D, 0, 4, $iW, $iH, 0, 0x80E1, $GL_UNSIGNED_BYTE, $pScan0) ; 0x80E1 = GL_BGRA

    _GDIPlus_BitmapUnlockBits($hImage, $tBitmapData)
    _GDIPlus_ImageDispose($hImage)

    Return $iTexID
EndFunc

Func _CreateBlankTexture()
    Local $iTexID = _glGenTextures()
    _glBindTexture($GL_TEXTURE_2D, $iTexID)
    _glTexParameteri($GL_TEXTURE_2D, $GL_TEXTURE_MAG_FILTER, $GL_LINEAR)
    _glTexParameteri($GL_TEXTURE_2D, $GL_TEXTURE_MIN_FILTER, $GL_LINEAR)
    
    Local $tData = DllStructCreate("byte[4]")
    DllStructSetData($tData, 1, 255, 1) ; R
    DllStructSetData($tData, 1, 255, 2) ; G
    DllStructSetData($tData, 1, 255, 3) ; B
    DllStructSetData($tData, 1, 255, 4) ; A
    
    _glTexImage2D($GL_TEXTURE_2D, 0, 4, 1, 1, 0, $GL_RGBA, $GL_UNSIGNED_BYTE, DllStructGetPtr($tData))
    
    Return $iTexID
EndFunc

; --- OPENGL WRAPPERS ---

Func _SetupOpenGL($hWnd, $hDC)
    ConsoleWrite("> _SetupOpenGL: Start" & @CRLF)
    
    ; --- WINDOWS 11 OPTIMIERUNG ---
    ; Windows 11: PFD_SUPPORT_COMPOSITION ist Pflicht fuer DWM!
    ; Wir versuchen MSAA zu aktivieren, falls moeglich.
    $g_bMSAA = False
    Local $iPixelFormat = 0

    Local $tPFD = DllStructCreate("ushort Size;ushort Version;dword Flags;byte PixelType;byte ColorBits;byte RedBits;byte RedShift;byte GreenBits;byte GreenShift;byte BlueBits;byte BlueShift;byte AlphaBits;byte AlphaShift;byte AccumBits;byte AccumRedBits;byte AccumGreenBits;byte AccumBlueBits;byte AccumAlphaBits;byte DepthBits;byte StencilBits;byte AuxBuffers;byte LayerType;byte Reserved;dword LayerMask;dword VisibleMask;dword DamageMask")
    DllStructSetData($tPFD, "Size", DllStructGetSize($tPFD))
    DllStructSetData($tPFD, "Version", 1)
    ; [WIN11 FIX] $PFD_SUPPORT_COMPOSITION (0x00008000) ist zwingend notwendig fuer Fenster-Modus
    DllStructSetData($tPFD, "Flags", BitOR($PFD_DRAW_TO_WINDOW, $PFD_SUPPORT_OPENGL, $PFD_DOUBLEBUFFER, $PFD_SUPPORT_COMPOSITION))
    DllStructSetData($tPFD, "PixelType", $PFD_TYPE_RGBA)
    DllStructSetData($tPFD, "ColorBits", 32)
    DllStructSetData($tPFD, "DepthBits", 24)
    DllStructSetData($tPFD, "LayerType", $PFD_MAIN_PLANE)

    ; Standard-Format waehlen
    ; Zuerst versuchen wir, ein Multisampling-Format (MSAA) zu bekommen
    Local $iPixelFormatMSAA = _GetMultisamplePixelFormat()
    
    If $iPixelFormatMSAA > 0 Then
        $iPixelFormat = $iPixelFormatMSAA
        $g_bMSAA = True
        ConsoleWrite("> MSAA Enabled (Format Index: " & $iPixelFormat & ")" & @CRLF)
    Else
        ConsoleWrite("> MSAA Not Available, using standard format." & @CRLF)
        $iPixelFormat = DllCall($hGDI32, "int", "ChoosePixelFormat", "handle", $hDC, "struct*", $tPFD)[0]
    EndIf
    
    ConsoleWrite("> Selected PixelFormat: " & $iPixelFormat & @CRLF)

    If $iPixelFormat = 0 Then
        MsgBox(16, "Fehler", "Kein passendes PixelFormat gefunden!")
        Return 0
    EndIf

    Local $iSetRes = DllCall($hGDI32, "int", "SetPixelFormat", "handle", $hDC, "int", $iPixelFormat, "struct*", $tPFD)[0]
    ConsoleWrite("> SetPixelFormat Result: " & $iSetRes & @CRLF)

    Local $hRC = DllCall($hOpenGL32, "handle", "wglCreateContext", "handle", $hDC)[0]
    ConsoleWrite("> wglCreateContext Result: " & $hRC & @CRLF)
    
    If $hRC = 0 Then
        MsgBox(16, "Fehler", "OpenGL Kontext konnte nicht erstellt werden!")
        Return 0
    EndIf

    Local $iMakeCurrentRes = DllCall($hOpenGL32, "int", "wglMakeCurrent", "handle", $hDC, "handle", $hRC)[0]
    ConsoleWrite("> wglMakeCurrent Result: " & $iMakeCurrentRes & @CRLF)
    
    If $g_bMSAA Then
        _glEnable(0x809D) ; GL_MULTISAMPLE_ARB
    EndIf

    _CheckGLError("_SetupOpenGL End")

    Return $hRC
EndFunc

Func _GetMultisamplePixelFormat()
    ; Erstellt ein Dummy-Fenster & Kontext, um Zugriff auf erweiterte OpenGL-Funktionen zu erhalten.
    Local $hDummyGui = GUICreate("Dummy", 10, 10, -1000, -1000)
    Local $hDummyDC = _WinAPI_GetDC($hDummyGui)

    Local $tPFD = DllStructCreate("ushort Size;ushort Version;dword Flags;byte PixelType;byte ColorBits;byte RedBits;byte RedShift;byte GreenBits;byte GreenShift;byte BlueBits;byte BlueShift;byte AlphaBits;byte AlphaShift;byte AccumBits;byte AccumRedBits;byte AccumGreenBits;byte AccumBlueBits;byte AccumAlphaBits;byte DepthBits;byte StencilBits;byte AuxBuffers;byte LayerType;byte Reserved;dword LayerMask;dword VisibleMask;dword DamageMask")
    DllStructSetData($tPFD, "Size", DllStructGetSize($tPFD))
    DllStructSetData($tPFD, "Version", 1)
    ; [WIN11 FIX] Auch das Dummy-Fenster braucht PFD_SUPPORT_COMPOSITION
    DllStructSetData($tPFD, "Flags", BitOR($PFD_DRAW_TO_WINDOW, $PFD_SUPPORT_OPENGL, $PFD_DOUBLEBUFFER, $PFD_SUPPORT_COMPOSITION))
    DllStructSetData($tPFD, "PixelType", $PFD_TYPE_RGBA)
    DllStructSetData($tPFD, "ColorBits", 32)
    DllStructSetData($tPFD, "DepthBits", 24)
    DllStructSetData($tPFD, "LayerType", $PFD_MAIN_PLANE)

    Local $iPF = DllCall($hGDI32, "int", "ChoosePixelFormat", "handle", $hDummyDC, "struct*", $tPFD)[0]
    DllCall($hGDI32, "int", "SetPixelFormat", "handle", $hDummyDC, "int", $iPF, "struct*", $tPFD)
    Local $hDummyRC = DllCall($hOpenGL32, "handle", "wglCreateContext", "handle", $hDummyDC)[0]
    _wglMakeCurrent($hDummyDC, $hDummyRC)

    ; Pruefen, ob wglChoosePixelFormatARB verfuegbar ist
    Local $pChoosePixelFormatARB = _wglGetProcAddress("wglChoosePixelFormatARB")
    Local $iRetFormat = 0

    If $pChoosePixelFormatARB Then
        ; Attribute fuer 4x MSAA definieren
        Local $tIntAttribs = DllStructCreate("int[22]")
        Local $i = 1
        
        ; WGL_DRAW_TO_WINDOW_ARB = GL_TRUE
        DllStructSetData($tIntAttribs, 1, 0x2001, $i) 
        DllStructSetData($tIntAttribs, 1, 1, $i+1)
        $i+=2
        
        ; WGL_SUPPORT_OPENGL_ARB = GL_TRUE
        DllStructSetData($tIntAttribs, 1, 0x2010, $i)
        DllStructSetData($tIntAttribs, 1, 1, $i+1)
        $i+=2
        
        ; WGL_DOUBLE_BUFFER_ARB = GL_TRUE
        DllStructSetData($tIntAttribs, 1, 0x2011, $i)
        DllStructSetData($tIntAttribs, 1, 1, $i+1)
        $i+=2
        
        ; WGL_PIXEL_TYPE_ARB = WGL_TYPE_RGBA_ARB
        DllStructSetData($tIntAttribs, 1, 0x2013, $i)
        DllStructSetData($tIntAttribs, 1, 0x202B, $i+1)
        $i+=2
        
        ; WGL_COLOR_BITS_ARB = 32
        DllStructSetData($tIntAttribs, 1, 0x2014, $i)
        DllStructSetData($tIntAttribs, 1, 32, $i+1)
        $i+=2
        
        ; WGL_DEPTH_BITS_ARB = 24
        DllStructSetData($tIntAttribs, 1, 0x2022, $i)
        DllStructSetData($tIntAttribs, 1, 24, $i+1)
        $i+=2
        
        ; WGL_SAMPLE_BUFFERS_ARB = 1 (MSAA an)
        DllStructSetData($tIntAttribs, 1, 0x2041, $i)
        DllStructSetData($tIntAttribs, 1, 1, $i+1)
        $i+=2
        
        ; WGL_SAMPLES_ARB = 4 (4x Samples)
        DllStructSetData($tIntAttribs, 1, 0x2042, $i)
        DllStructSetData($tIntAttribs, 1, 4, $i+1)
        $i+=2
        
        ; Ende (0, 0)
        DllStructSetData($tIntAttribs, 1, 0, $i)
        DllStructSetData($tIntAttribs, 1, 0, $i+1)

        Local $tFloatAttribs = DllStructCreate("float[2]")
        DllStructSetData($tFloatAttribs, 1, 0, 1)
        DllStructSetData($tFloatAttribs, 1, 0, 2)

        Local $tPixelFormats = DllStructCreate("int[1]")
        Local $tNumFormats = DllStructCreate("uint")

        ; wglChoosePixelFormatARB aufrufen
        Local $aRet = DllCallAddress("int", $pChoosePixelFormatARB, "handle", $hDummyDC, "ptr", DllStructGetPtr($tIntAttribs), "ptr", DllStructGetPtr($tFloatAttribs), "uint", 1, "ptr", DllStructGetPtr($tPixelFormats), "ptr", DllStructGetPtr($tNumFormats))

        If $aRet[0] And DllStructGetData($tNumFormats, 1) > 0 Then
            $iRetFormat = DllStructGetData($tPixelFormats, 1)
        EndIf
    EndIf

    ; Dummy aufraeumen
    _wglMakeCurrent(0, 0)
    _wglDeleteContext($hDummyRC)
    _WinAPI_ReleaseDC($hDummyGui, $hDummyDC)
    GUIDelete($hDummyGui)

    Return $iRetFormat
EndFunc

Func _wglGetProcAddress($proc)
    Local $aRet = DllCall($hOpenGL32, "ptr", "wglGetProcAddress", "str", $proc)
    Return $aRet[0]
EndFunc

Func _wglMakeCurrent($hDC, $hRC)
    DllCall($hOpenGL32, "int", "wglMakeCurrent", "handle", $hDC, "handle", $hRC)
EndFunc

Func _wglDeleteContext($hRC)
    DllCall($hOpenGL32, "int", "wglDeleteContext", "handle", $hRC)
EndFunc

Func _SwapBuffers($hDC)
    DllCall($hGDI32, "int", "SwapBuffers", "handle", $hDC)
EndFunc

Func _glClearColor($r, $g, $b, $a)
    DllCall($hOpenGL32, "none", "glClearColor", "float", $r, "float", $g, "float", $b, "float", $a)
EndFunc

Func _glClear($mask)
    DllCall($hOpenGL32, "none", "glClear", "uint", $mask)
EndFunc

Func _glViewport($x, $y, $w, $h)
    DllCall($hOpenGL32, "none", "glViewport", "int", $x, "int", $y, "int", $w, "int", $h)
EndFunc

Func _glMatrixMode($mode)
    DllCall($hOpenGL32, "none", "glMatrixMode", "uint", $mode)
EndFunc

Func _glLoadIdentity()
    DllCall($hOpenGL32, "none", "glLoadIdentity")
EndFunc

Func _glOrtho($l, $r, $b, $t, $n, $f)
    DllCall($hOpenGL32, "none", "glOrtho", "double", $l, "double", $r, "double", $b, "double", $t, "double", $n, "double", $f)
EndFunc

Func _glEnable($cap)
    DllCall($hOpenGL32, "none", "glEnable", "uint", $cap)
EndFunc

Func _glDisable($cap)
    DllCall($hOpenGL32, "none", "glDisable", "uint", $cap)
EndFunc

Func _glBlendFunc($s, $d)
    DllCall($hOpenGL32, "none", "glBlendFunc", "uint", $s, "uint", $d)
EndFunc

Func _glColor3f($r, $g, $b)
    DllCall($hOpenGL32, "none", "glColor3f", "float", $r, "float", $g, "float", $b)
EndFunc

Func _glBegin($mode)
    DllCall($hOpenGL32, "none", "glBegin", "uint", $mode)
EndFunc

Func _glEnd()
    DllCall($hOpenGL32, "none", "glEnd")
EndFunc

Func _glVertex2f($x, $y)
    DllCall($hOpenGL32, "none", "glVertex2f", "float", $x, "float", $y)
EndFunc

Func _glTexCoord2f($s, $t)
    DllCall($hOpenGL32, "none", "glTexCoord2f", "float", $s, "float", $t)
EndFunc

Func _glGenTextures()
    Local $tTextures = DllStructCreate("uint")
    DllCall($hOpenGL32, "none", "glGenTextures", "int", 1, "struct*", $tTextures)
    Return DllStructGetData($tTextures, 1)
EndFunc

Func _glBindTexture($target, $texture)
    DllCall($hOpenGL32, "none", "glBindTexture", "uint", $target, "uint", $texture)
EndFunc

Func _glTexParameteri($target, $pname, $param)
    DllCall($hOpenGL32, "none", "glTexParameteri", "uint", $target, "uint", $pname, "int", $param)
EndFunc

Func _glTexImage2D($target, $level, $internalformat, $width, $height, $border, $format, $type, $pixels)
    DllCall($hOpenGL32, "none", "glTexImage2D", "uint", $target, "int", $level, "int", $internalformat, "int", $width, "int", $height, "int", $border, "uint", $format, "uint", $type, "ptr", $pixels)
EndFunc

Func _glTranslatef($x, $y, $z)
    DllCall($hOpenGL32, "none", "glTranslatef", "float", $x, "float", $y, "float", $z)
EndFunc

Func _glScalef($x, $y, $z)
    DllCall($hOpenGL32, "none", "glScalef", "float", $x, "float", $y, "float", $z)
EndFunc

Func _glMultMatrixf($m)
    DllCall($hOpenGL32, "none", "glMultMatrixf", "ptr", $m)
EndFunc

Func _glVertex3f($x, $y, $z)
    DllCall($hOpenGL32, "none", "glVertex3f", "float", $x, "float", $y, "float", $z)
EndFunc

Func _glGetFloatv($pname, $params)
    DllCall($hOpenGL32, "none", "glGetFloatv", "uint", $pname, "ptr", $params)
EndFunc

Func _glLoadMatrixf($m)
    DllCall($hOpenGL32, "none", "glLoadMatrixf", "ptr", $m)
EndFunc

Func _glRotatef($angle, $x, $y, $z)
    DllCall($hOpenGL32, "none", "glRotatef", "float", $angle, "float", $x, "float", $y, "float", $z)
EndFunc

Func _glGenLists($range)
    Local $aRet = DllCall($hOpenGL32, "uint", "glGenLists", "int", $range)
    Return $aRet[0]
EndFunc

Func _glNewList($list, $mode)
    DllCall($hOpenGL32, "none", "glNewList", "uint", $list, "uint", $mode)
EndFunc

Func _glEndList()
    DllCall($hOpenGL32, "none", "glEndList")
EndFunc

Func _glCallList($list)
    DllCall($hOpenGL32, "none", "glCallList", "uint", $list)
EndFunc

Func _glPushMatrix()
    DllCall($hOpenGL32, "none", "glPushMatrix")
EndFunc

Func _glPopMatrix()
    DllCall($hOpenGL32, "none", "glPopMatrix")
EndFunc

Func _glHint($target, $mode)
    DllCall($hOpenGL32, "none", "glHint", "uint", $target, "uint", $mode)
EndFunc

; --- PHYSIK  ---
Func _ResetTable()
    $aBalls[0][0] = $iWidth * 0.25
    $aBalls[0][1] = $iHeight / 2
    Local $fChaos = Random(-0.5, 0.5)
    $aBalls[0][2] = 35
    $aBalls[0][3] = $fChaos
    $aBalls[0][5] = True
    $aBalls[0][6] = 0
    $aBalls[0][7] = 0

    Local $fStartX = $iWidth * 0.75
    Local $fStartY = $iHeight / 2
    Local $fD = $fBallRadius * 2 + 1

    Local $iBallIdx = 1
    For $col = 0 To 4
        For $row = 0 To $col
            If $iBallIdx >= $iMaxBalls Then ExitLoop
            Local $fX = $fStartX + ($col * ($fD * 0.866))
            Local $fY = ($fStartY - ($col * $fD / 2)) + ($row * $fD)
            $aBalls[$iBallIdx][0] = $fX
            $aBalls[$iBallIdx][1] = $fY
            $aBalls[$iBallIdx][2] = 0
            $aBalls[$iBallIdx][3] = 0
            $aBalls[$iBallIdx][5] = True
            $aBalls[$iBallIdx][6] = 0
            $aBalls[$iBallIdx][7] = 0
            $iBallIdx += 1
        Next
    Next
EndFunc

Func _InitBallMatrices()
    For $i = 0 To $iMaxBalls - 1
        $aBallMatrices[$i] = DllStructCreate("float[16]")
        ; Identitaetsmatrix setzen (Keine Rotation)
        Local $pMatrix = DllStructGetPtr($aBallMatrices[$i])
        _glLoadIdentity()
        _glGetFloatv($GL_MODELVIEW_MATRIX, $pMatrix)
    Next
EndFunc

Func _UpdateRotations()
    For $i = 0 To $iMaxBalls - 1
        If Not $aBalls[$i][5] Then ContinueLoop

        Local $vx = $aBalls[$i][2]
        Local $vy = $aBalls[$i][3]
        Local $fSpeed = Sqrt($vx*$vx + $vy*$vy)

        If $fSpeed > 0.01 Then
            ; Rotationsachse berechnen:
            ; Die Achse steht senkrecht zur Bewegungsrichtung.
            ; Bei Bewegung (vx, vy) ist die Senkrechte (-vy, vx).
            Local $rx = -$vy
            Local $ry = $vx

            ; Rotationswinkel berechnen:
            ; Winkel = Zurueckgelegter Weg / Radius (in Radiant)
            ; Weg pro Frame entspricht der Geschwindigkeit ($fSpeed).
            ; Umrechnung in Grad: * (180 / PI)
            Local $fAngle = ($fSpeed / $fBallRadius) * (180.0 / 3.1415926)

            ; Matrix Update (Die Magie der 3D-Rotation)
            _glMatrixMode($GL_MODELVIEW)
            _glLoadIdentity()

            ; 1. Neue Rotation anwenden (um die Welt-Achsen)
            ; Wir rotieren zuerst um die berechnete Achse.
            _glRotatef($fAngle, $rx, $ry, 0.0)

            ; 2. Alte Rotation anwenden
            ; Dann multiplizieren wir die bestehende Rotation der Kugel dazu.
            ; Dies akkumuliert die Drehungen korrekt, sodass die Kugel "weiterrollt"
            ; anstatt sich nur lokal zu drehen.
            Local $pMatrix = DllStructGetPtr($aBallMatrices[$i])
            _glMultMatrixf($pMatrix)

            ; 3. Ergebnis speichern
            ; Die kombinierte Matrix wird fuer den naechsten Frame gespeichert.
            _glGetFloatv($GL_MODELVIEW_MATRIX, $pMatrix)
        EndIf
    Next
EndFunc

Func _UpdatePhysics()
    Local $bAnyMovement = False
    Local $fSpeedThreshold = 0.08

    For $i = 0 To $iMaxBalls - 1
        If Not $aBalls[$i][5] Then ContinueLoop
        Local $vx = $aBalls[$i][2]
        Local $vy = $aBalls[$i][3]
        $aBalls[$i][0] += $vx
        $aBalls[$i][1] += $vy
        $vx *= $fFriction
        $vy *= $fFriction
        Local $fSpeed = Sqrt($vx*$vx + $vy*$vy)
        If $fSpeed > $fSpeedThreshold Then
            $bAnyMovement = True
            $aBalls[$i][6] += $vx
            $aBalls[$i][7] += $vy
        Else
            $vx = 0
            $vy = 0
        EndIf
        $aBalls[$i][2] = $vx
        $aBalls[$i][3] = $vy

        If $aBalls[$i][0] - $fBallRadius < $iBorder Then
            $aBalls[$i][0] = $iBorder + $fBallRadius
            $aBalls[$i][2] *= -1
        ElseIf $aBalls[$i][0] + $fBallRadius > $iWidth - $iBorder Then
            $aBalls[$i][0] = $iWidth - $iBorder - $fBallRadius
            $aBalls[$i][2] *= -1
        EndIf
        If $aBalls[$i][1] - $fBallRadius < $iBorder Then
            $aBalls[$i][1] = $iBorder + $fBallRadius
            $aBalls[$i][3] *= -1
        ElseIf $aBalls[$i][1] + $fBallRadius > $iHeight - $iBorder Then
            $aBalls[$i][1] = $iHeight - $iBorder - $fBallRadius
            $aBalls[$i][3] *= -1
        EndIf

        For $j = $i + 1 To $iMaxBalls - 1
             If $aBalls[$j][5] Then _CheckCollision($i, $j)
        Next
    Next
    Return $bAnyMovement
EndFunc

Func _CheckCollision($i1, $i2)
    Local $dx = $aBalls[$i2][0] - $aBalls[$i1][0]
    Local $dy = $aBalls[$i2][1] - $aBalls[$i1][1]
    Local $distSq = $dx*$dx + $dy*$dy
    Local $minDist = $fBallRadius * 2
    If $distSq < ($minDist * $minDist) Then
        Local $dist = Sqrt($distSq)
        If $dist = 0 Then $dist = 0.01
        Local $overlap = ($minDist - $dist) / 2
        Local $nx = $dx / $dist
        Local $ny = $dy / $dist
        $aBalls[$i1][0] -= $nx * $overlap
        $aBalls[$i1][1] -= $ny * $overlap
        $aBalls[$i2][0] += $nx * $overlap
        $aBalls[$i2][1] += $ny * $overlap
        Local $tx = -$ny
        Local $ty = $nx
        Local $dpTan1 = $aBalls[$i1][2] * $tx + $aBalls[$i1][3] * $ty
        Local $dpTan2 = $aBalls[$i2][2] * $tx + $aBalls[$i2][3] * $ty
        Local $dpNorm1 = $aBalls[$i1][2] * $nx + $aBalls[$i1][3] * $ny
        Local $dpNorm2 = $aBalls[$i2][2] * $nx + $aBalls[$i2][3] * $ny
        Local $v1n = $dpNorm2
        Local $v2n = $dpNorm1
        $aBalls[$i1][2] = $tx * $dpTan1 + $nx * $v1n
        $aBalls[$i1][3] = $ty * $dpTan1 + $ny * $v1n
        $aBalls[$i2][2] = $tx * $dpTan2 + $nx * $v2n
        $aBalls[$i2][3] = $ty * $dpTan2 + $ny * $v2n
    EndIf
EndFunc

Func _CheckGLError($sLocation)
    Local $iErr = _glGetError()
    If $iErr <> $GL_NO_ERROR Then
        ConsoleWrite("! OpenGL Error at " & $sLocation & ": " & $iErr & " (0x" & Hex($iErr, 4) & ")" & @CRLF)
        Return True
    EndIf
    Return False
EndFunc

Func _glGetError()
    Local $aRet = DllCall($hOpenGL32, "uint", "glGetError")
    Return $aRet[0]
EndFunc

Func _CheckOpenGLVersion()
    Local $sVersion = _glGetString($GL_VERSION)
    Local $sVendor = _glGetString($GL_VENDOR)
    Local $sRenderer = _glGetString($GL_RENDERER)
    
    ConsoleWrite("--- OpenGL Info ---" & @CRLF)
    ConsoleWrite("Version:  " & $sVersion & @CRLF)
    ConsoleWrite("Vendor:   " & $sVendor & @CRLF)
    ConsoleWrite("Renderer: " & $sRenderer & @CRLF)
    
    If StringInStr($sVersion, "Core") Then
        MsgBox(16, "Error", "You are in a CORE Profile context! Immediate mode (glBegin) will not work.")
    EndIf
EndFunc

Func _glGetString($iName)
    Local $aRet = DllCall($hOpenGL32, "str", "glGetString", "uint", $iName)
    If @error Then Return "Error"
    Return $aRet[0]
EndFunc

Func _glFinish()
    DllCall($hOpenGL32, "none", "glFinish")
EndFunc
