Partikelexplosion in 3D ?

  • Moin,

    Es gibt ja schon einige Partikelexplosionen hier im Forum.
    Die habe ich mir auch alle schon brav angesehen und habe feststellen müssen, dass alle mangelhaft sind^^
    Fast alle nutzen ein Rechteck als Partikelgrenze (Kommt von der 2Dimensionalen Berechnung ohne Sinus oder Vektorbeträge)

    Ich möchte eine Partikelexplosion in (pseudo) 3D haben. (Es ginge auch echtes 3D. Da weiß ich aber nicht wie das richtig funktioniert. Dazu muss man lineare Gleichungssysteme lösen oder so^^)
    -> Partikel haben x, y und z Koord und Geschwindigkeit
    -> usw usw.

    Das Problem ist: Ich bekomme es nicht hin, dass es so aussieht als würden die Partikel tatsächlich "nach hinten" fliegen...
    Voluminös sieht die Explosion schon aus. Man sieht aber immer nur eine "Halbkugel" als Begrenzung.

    Die Anzahl Partikel habe ich mal auf 200 gestellt, damit man schön alles sieht.

    In Zeile 127/128 kann man sich die nach vorne bewegenden Partikel oder die, die sich nach hinten bewegen anzeigen lassen.
    Dann erkennt man, dass die, die nach hinten gehen auch kleiner werden, die nach vorne größer usw.
    Ich bekomm die Perspektive aber nicht gebacken^^

    PS: Man kann eine lustige optische Täuschung sehen, wenn man so ca. 10 Explosionen anschaut. Anschließend klickt man während einer Explosion auf die Titelleiste um das Bild einzufrieren. Jetzt sieht es so aus als würde sich das bild zusammenziehen^^

    Spoiler anzeigen
    [autoit][/autoit] [autoit][/autoit] [autoit]

    #include <GDIPlus.au3>
    #include <Misc.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Opt('GUICloseOnESC', 0)
    Opt('GUIOnEventMode', 1)
    Opt('MustDeclareVars', 1)
    Opt('MouseCoordMode', 2)

    [/autoit] [autoit][/autoit] [autoit]

    Global Const $iB = 400, $iH = 400
    Global $hGFX, $hBMP, $hBUF, $hGUI, $Partikel[1][10], $hBrush[256], $L_Klick, $L_Tmp, $mPos[2]

    [/autoit] [autoit][/autoit] [autoit]

    OnAutoItExitRegister('_Release')

    [/autoit] [autoit][/autoit] [autoit]

    _Main()

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _Main()

    [/autoit] [autoit][/autoit] [autoit]

    $hGUI = GUICreate('Explosion', $iB, $iH)
    WinSetTrans($hGUI, '', 255)
    GUISetBkColor(0xFF0000, $hGUI)
    GUISetOnEvent(-3, '_Exit', $hGUI)

    [/autoit] [autoit][/autoit] [autoit]

    _GDIPlus_Startup()

    [/autoit] [autoit][/autoit] [autoit]

    $hGFX = _GDIPlus_GraphicsCreateFromHWND($hGUI)
    $hBMP = _GDIPlus_BitmapCreateFromGraphics($iB, $iH, $hGFX)
    $hBUF = _GDIPlus_ImageGetGraphicsContext($hBMP)

    [/autoit] [autoit][/autoit] [autoit]

    $hBrush = __GDIPlus_BrushCreateSolid('FF6800')

    [/autoit] [autoit][/autoit] [autoit]

    _GDIPlus_GraphicsSetSmoothingMode($hBUF, 4)

    [/autoit] [autoit][/autoit] [autoit]

    GUIRegisterMsg(0xF, 'WM_PAINT')

    [/autoit] [autoit][/autoit] [autoit]

    GUISetState(@SW_SHOW, $hGUI)

    [/autoit] [autoit][/autoit] [autoit]

    _AddExplosion($iB / 2, $iH / 2)

    [/autoit] [autoit][/autoit] [autoit]

    While Sleep(20)
    $mPos = MouseGetPos()
    $L_Tmp = _IsPressed('01')
    If $L_Klick And Not $L_Tmp Then _AddExplosion($mPos[0], $mPos[1])
    $L_Klick = $L_Tmp
    _GDIPlus_GraphicsClear($hBUF, 0x40000000)
    _DrawPartikel()
    WM_PAINT()
    WEnd

    [/autoit] [autoit][/autoit] [autoit]

    EndFunc ;==>_Main

    [/autoit] [autoit][/autoit] [autoit]

    Func _AddExplosion($x, $y, $D = 4, $n = 200)

    [/autoit] [autoit][/autoit] [autoit]

    ; Partikel
    ; 0 - x
    ; 1 - y
    ; 2 - z
    ; 3 - Vx
    ; 4 - Vy
    ; 5 - Vz
    ; 6 - Counter

    [/autoit] [autoit][/autoit] [autoit]

    Local $u = UBound($Partikel, 1)
    ReDim $Partikel[$u + $n + 1][UBound($Partikel, 2)]

    [/autoit] [autoit][/autoit] [autoit]

    For $i = $u To $u + $n Step 1
    $Partikel[$i][0] = $x
    $Partikel[$i][1] = $y
    $Partikel[$i][2] = 0 ; z = 0 - In der Ebene
    $Partikel[$i][3] = Random(0, 1)
    $Partikel[$i][4] = Random(0, (1 - $Partikel[$i][3]) ^ 0.5)
    $Partikel[$i][5] = (1 - $Partikel[$i][3] ^ 2 - $Partikel[$i][4] ^ 2) ^ 0.5
    $Partikel[$i][6] = 0
    $Partikel[$i][7] = $D ; Durchmesser
    $Partikel[$i][8] = $x
    $Partikel[$i][9] = $y

    [/autoit] [autoit][/autoit] [autoit]

    $Partikel[$i][3] = _PN($Partikel[$i][3])
    $Partikel[$i][4] = _PN($Partikel[$i][4])
    $Partikel[$i][5] = _PN($Partikel[$i][5])

    [/autoit] [autoit][/autoit] [autoit]

    If Random(0, 1, 1) Then _Swap($Partikel[$i][3], $Partikel[$i][4])
    If Random(0, 1, 1) Then _Swap($Partikel[$i][5], $Partikel[$i][4])
    If Random(0, 1, 1) Then _Swap($Partikel[$i][3], $Partikel[$i][5])

    [/autoit] [autoit][/autoit] [autoit]

    Next

    [/autoit] [autoit][/autoit] [autoit]

    EndFunc ;==>_AddExplosion

    [/autoit] [autoit][/autoit] [autoit]

    Func _Swap(ByRef $a, ByRef $b)
    Local $c = $a
    $a = $b
    $b = $c
    EndFunc ;==>_Swap

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _PN($a)
    If Random(0, 1, 1) Then Return $a
    Return -$a
    EndFunc ;==>_PN

    [/autoit] [autoit][/autoit] [autoit]

    Func _DrawPartikel()

    [/autoit] [autoit][/autoit] [autoit]

    Local $u = UBound($Partikel), $D, $K[$u], $T, $x, $y

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 1 To $u - 1 Step 1

    [/autoit] [autoit][/autoit] [autoit]

    $D = (9 + $Partikel[$i][2] / 30)
    If $D < 0 Then ContinueLoop

    [/autoit] [autoit][/autoit] [autoit]

    $T = Int(256 - ($Partikel[$i][6] / $Partikel[$i][7] * 25.5)) * 0.9 - 20 / $D

    [/autoit] [autoit][/autoit] [autoit]

    If $T < -5 Then
    ReDim $Partikel[$i][UBound($Partikel, 2)]
    ExitLoop
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    If $T <= 0 Then
    $Partikel[$i][2] += $Partikel[$i][5] * $Partikel[$i][7]
    $Partikel[$i][6] += 1
    ContinueLoop
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    $x = $Partikel[$i][3] * $Partikel[$i][6] * $Partikel[$i][7] + $Partikel[$i][8] - $D / 2
    $y = $Partikel[$i][4] * $Partikel[$i][6] * $Partikel[$i][7] + $Partikel[$i][9] - $D / 2

    [/autoit] [autoit][/autoit] [autoit]

    If $Partikel[$i][2] < 0 Then _GDIPlus_GraphicsFillEllipse($hBUF, $x, $y, $D, $D, $hBrush[$T - 1]) ; Hintere Halbkugel - Partikel werden kleiner
    ;~ If $Partikel[$i][2] > 0 Then _GDIPlus_GraphicsFillEllipse($hBUF, $x, $y, $D, $D, $hBrush[$T - 1]) ; Vordere Halbkugel - Partikel werden größer

    [/autoit] [autoit][/autoit] [autoit]

    $Partikel[$i][2] += $Partikel[$i][5] * $Partikel[$i][7]
    $Partikel[$i][6] += 1

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Next

    [/autoit] [autoit][/autoit] [autoit]

    EndFunc ;==>_DrawPartikel

    [/autoit] [autoit][/autoit] [autoit]

    Func __GDIPlus_BrushCreateSolid($c)

    [/autoit] [autoit][/autoit] [autoit]

    Local $a[256]

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To 255 Step 1
    $a[$i] = _GDIPlus_BrushCreateSolid('0x' & Hex($i, 2) & $c)
    Next

    [/autoit] [autoit][/autoit] [autoit]

    Return $a

    [/autoit] [autoit][/autoit] [autoit]

    EndFunc ;==>__GDIPlus_BrushCreateSolid

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func WM_PAINT()
    _GDIPlus_GraphicsDrawImage($hGFX, $hBMP, 0, 0)
    EndFunc ;==>WM_PAINT

    [/autoit] [autoit][/autoit] [autoit]

    Func _Exit()
    Exit
    EndFunc ;==>_Exit

    [/autoit] [autoit][/autoit] [autoit]

    Func _Release()

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To 255 Step 1
    _GDIPlus_BrushDispose($hBrush[$i])
    Next

    [/autoit] [autoit][/autoit] [autoit]

    _GDIPlus_GraphicsDispose($hBUF)
    _GDIPlus_BitmapDispose($hBMP)
    _GDIPlus_GraphicsDispose($hGFX)

    [/autoit] [autoit][/autoit] [autoit]

    _GDIPlus_Shutdown()

    [/autoit] [autoit][/autoit] [autoit]

    EndFunc ;==>_Release

    [/autoit]

    lg
    M

  • Moin!
    Also das sieht schonmal sehr gut aus! Wenn ich mich recht erinnere hat Eukalyptus mal einen 3D-Text in GDI+ realisiert - dieser war auf einer 3D-Kugel drauf oder so; vielleicht hilft dir ja dieses Script dabei die 'perspektivischen' Probleme lösen zu können...

    Grüsse!

  • Hab jetzt nochmal etwas daran rumgebastelt.

    Mit dem Strahlensatz bekomm ich nun auch die Skalierung ordentlich. Es sieht aber iwie immernoch nicht überzeugend aus^^

    Jetzt ist es eher so eine "Krimskramsexplosion". (Daher auch das .zip Archiv in dem die Bilder liegen).

    Sieht nicht schlecht aus. Vermutlich ist es aber eh nicht nutzbar, weil man zum gezielten Löschen aller ungenutzen Partikel (solche die zu klein/groß sind, oder das Bild verlassen haben) jedes Mal das Array neu einrichten muss mit ArraySort und Redim, oder mit ArrayDelete. Dieser Vorgang dauert aber leider unzumutbar lange (da das Array ja entpsrechend groß wird bei vielen Partikeln).

    also mehr zum Spaß das ganze hier...

    lg
    M

  • Wow, sieht sehr gut aus. Allerdings fällt mir da was auf: Sowohl in deinem ersten, als auch in deinem zweiten Script wird etwa jede zweite Explosion vorschnell beendet. Mnchmal bricht sie schon kurz nach dem Start ab, manchmal erst kurz vor dem Ende.

  • Sieht cool aus. Ich hab mittlerweile meine alte Partikelexplosion (geistert hier vielleicht auch noch irgendwo durchs Forum) ein wenig ausgebessert (es ist jetzt keine rechteckige Begrenzung mehr) und effizienter gestaltet. Jetzt ist das ganze als Screensaver im Einsatz. Aber auf die Idee einer 3D Explosion mit GDI+ bin ich noch nicht gekommen. Ich bin gespannt was daraus noch wird ;).

    Zitat

    Dieser Vorgang dauert aber leider unzumutbar lange (da das Array ja entpsrechend groß wird bei vielen Partikeln).


    Wie wäre es denn, wenn du die entsprechenden Elemente einfach übrspringen lässt? Vielleicht über ein zusätzliches Element in der 2ten Dimension für alle Partikel, das den "Lebensstatus" eines Partikels angibt. Eine kleine If-Abfrage die ggf. einen Schleifendurchlauf überspringt sollte nur einen Bruchtei einer Millisekunde in Anspruch nehmen.. RAM sparst du dadurch zwar nicht, aber zumindest kannst du Paritkel "löschen" ^^.

  • Das vorschnelle beenden kommt daher, weil ich den leichten Weg gewählt habe um "aufzuräumen", bis ich auf eine bessere Idee komme.

    Der "leichte aber schlechte Weg" ist einfach: Nach 100 Frames die ein Partikel zurückgelegt hat werden ALLE Partikel per ReDim entfernt.
    Das klappt super bei einer einzigen Explosion.
    Sobald man aber eine weitere Zeitversetzt startet wird mittendrin ein Partikel der 1sten die 100 erreichen und alles löschen.

    Ich habe auch schon eine Idee, wie man das ganze etwas besser machen kann und evtl auch Partikel einzeln löschen kann.
    Dann würde es aber eine Feste Partikelmaximalanzahl geben. (macht bei AutoIt eh sinn glaub ich^^). Zu Testzwecken ist dann bei z.B. 1000 Partikeln Schluss. In einem Spiel/Anderen Programm müsste dieser Wert bei ca. 100 liegen um die Performance nicht zu sehr zu drücken.

    Die Idee ist: Ein Festes Array was Platz für z.B. 100 Partikel bietet.
    Wird ein Partikel nicht mehr gebraucht wird sein Vx Wert auf 0 gesetzt und dementsprechend in der schleife übersprungen. (mache ich jetzt auch schon aus Performancegründen, nur etwas anderst)
    Bei einer neuen Explosion werden die 50 Partikel auf die nächsten 50 Freien Plätze gesetzt. Das ist minimaler mehraufwand und es muss nichts mehr gelöscht werden.

    Wenn man das jetzt noch in ASM macht ist nicht bei 100 sondern bei 10.000 schluss :P

    Der Richtige 3D Effekt kann so glaube ich nicht entstehen, da nur 2D Grafiken genutzt werden.
    So sieht ein Partikel von allen Seiten gleich aus.
    Das 2te Problem ist die Bewegungsunschärfe. (die wird mit einem Halbtransparenten Clear gemacht) Bei den Partikeln die nach Vorne fliegen stimmt somit alles, die die nach hinten fliegen müssten aber an der anderen Seite des "schweifs" sein. Daher sieht es nicht sehr realistisch aus...

    Besser als nix aber allemal :)

    Hab aber jetzt nicht so viel Zeit. Mathe/Chemie - Abi geht vor :P

    lg
    M