2D Physik für Spiele

Übersicht BlitzBasic FAQ und Tutorials

Gehe zu Seite Zurück  1, 2

Neue Antwort erstellen

coolo

BeitragMi, Jan 07, 2009 21:44
Antworten mit Zitat
Benutzer-Profile anzeigen
Sehr schön, freue mich auf mehr! Werde mich dann darin einarbeiten.
http://programming-with-design.at/ <-- Der Preis ist heiß!
That's no bug, that's my project!
"Eigenzitate sind nur was für Deppen" -Eigenzitat
 

FWeinb

ehemals "ich"

BeitragMi, Jan 07, 2009 22:14
Antworten mit Zitat
Benutzer-Profile anzeigen
Sehr schönes Tutorial, ich habe mich auch mal etwas Informiert und dieses Tutorial bzw. diese Beschreibung gefunden sie beschreibt die Implementation von SAT, dort sind auch Beispiele in Flash zu finden.
Dort wird das ganze zwar nicht sogut erklärt, wie hier, doch finde ich die Beispiele dort sehr anschaulich.
Klick

Danke für dieses Super Tutorial.

Ich bin mir aber nicht sicher ob das ganze auch Schnell genug für eine Echtzeitberechnung ist, da das ganze doch schon ziemlich Komplex wird.

mfg

ich
"Wenn die Menschen nur über das sprächen, was sie begreifen, dann würde es sehr still auf der Welt sein." Albert Einstein (1879-1955)
"If you live each day as if it was your last, someday you'll most certainly be right." Steve Jobs

Noobody

BeitragMi, Jan 07, 2009 22:25
Antworten mit Zitat
Benutzer-Profile anzeigen
Oh, es ist Echtzeitfähig, wie man sich hier überzeugen kann Wink *Werbung mach*
Man is the best computer we can put aboard a spacecraft ... and the only one that can be mass produced with unskilled labor. -- Wernher von Braun
 

FWeinb

ehemals "ich"

BeitragMi, Jan 07, 2009 22:55
Antworten mit Zitat
Benutzer-Profile anzeigen
Da hast du das ganze doch auch nach, lass mich lügen, C Portier, ich meinte die hier im Thread angesprochen vorgehensweise, es mit Blitz2D berechnen zu lassen. Da C oder die Sprache die du Verwendet hast um die Dll zu erstellen doch sicherlicht schneller ist als BB.

mfg

ich
"Wenn die Menschen nur über das sprächen, was sie begreifen, dann würde es sehr still auf der Welt sein." Albert Einstein (1879-1955)
"If you live each day as if it was your last, someday you'll most certainly be right." Steve Jobs

Noobody

BeitragMi, Jan 07, 2009 23:04
Antworten mit Zitat
Benutzer-Profile anzeigen
Ich habe eine Version, die pures Blitz3D ist (da ich den Code nur portiert habe auf C und nicht darin entwickelt habe), welche ich ebenfalls veröffentlicht habe.
Das ganze ist hier zu finden und es ist sehr wohl Echtzeittauglich Razz
Das reine SAT ist relativ schnell, den Grossteil der Berechnungszeit in der Demo fressen die Matrizenberechnungen, die leider benötigt werden.
Auch im Beispielcode im Tutorial sieht man ja, dass das SAT bei 40 Objekten und jeder Menge Vertices und Edges trotzdem kaum Berechnungszeit frisst (läuft ja alles noch flüssig) - nur mal ausführen und sich überzeugen lassen Wink

EDIT: Ich hab mal nachgemessen; bei 390 Vertices und 730 Edges frisst die Funktion 1-2 Ms bei mir - und das, obwohl noch jedes Objekt gegen jedes Objekt getestet wird.
Eine winzige Optimierung mit Rectsoverlap reduziert den Aufwand auf 0 Ms.
Man is the best computer we can put aboard a spacecraft ... and the only one that can be mass produced with unskilled labor. -- Wernher von Braun

Noobody

BeitragFr, Jul 17, 2009 21:21
Antworten mit Zitat
Benutzer-Profile anzeigen
Nach gut einem halben Jahr (meine Güte, schon so lange her? Ich bin wohl ein ziemlich fauler Sack, was das Tutorial hier angeht Razz ) kommt nun endlich der dritte und vermutlich letzte Teil dieses Tutorials.

Wir erinnern uns: Im ersten Teil des Tutorials haben wir ein System geschrieben, um Partikeln und Feder mithilfe der Verletintegration einigermassen realistisch berechnen zu lassen. Im zweiten Teil haben wir einen Kollisionscheck mithilfe des SAT geschrieben.
Um richtige Kollisionen zu ermöglichen, müssen wir also 'nur' noch diese beiden Systeme irgendwie kombinieren - aber dafür benötigen wir zum Glück nicht komplizierte Matrizenrechnungen, wie anfangs von mir vermutet, sondern können das über ein wenig simple Vektorgeometrie lösen.

Zuallererst müssen wir das SAT erweitern. Bisher haben wir das volle Potenzial des Algorithmus nämlich noch nicht ganz ausgenutzt; er kann nämlich noch mehr als nur feststellen, ob zwei Objekte kollidieren oder nicht.
Führen wir uns nochmals kurz Kapitel zwei vor Augen: Dort haben wir die beiden Objekte jeweils auf eine Achse projiziert und geschaut, ob sich die Projektionen überlappen. Falls nicht, so kollidieren die beiden Objekte nicht.
Falls die Projektionen aber überlappen, so soll sich das Programm nun merken, wie gross diese Überlappung ist. Stellen wir am Ende tatsächlich eine Kollision fest, interessiert uns die Achse, auf der nach der Projektion die geringste Überlappung stattgefunden hat, da sie uns zusätzliche Informationen über die Kollision liefert.

Wenn man nämlich zwei Physikobjekte kollidieren lässt, muss man sie ja irgendwie wieder auseinanderbewegen, so dass sie nicht mehr kollidieren. Die Richtung, in der man die Objekte bewegt, ist dabei entscheidend - wir können sie nicht einfach in irgendeine Richtung bewegen, ansonsten bekommen wir Probleme mit der Verletintegration.
Damit alles richtig läuft, müssen wir sie um den kürzesten Vektor bewegen, der möglich ist.

Die Zeichnung verdeutlicht dies:
user posted image
Der grüne Vektor ist jeweils der kürzeste Vektor, um den man das rote Objekt verschieben muss, damit es nicht mehr mit dem Grünen kollidiert.

Diesen Vektor müssen wir zuerst noch ermitteln, aber das wird überraschend einfach. Er steht nämlich parallel zu der Achse, auf der die geringste Überlappung stattgefunden hat. Die Länge des Vektors ist gleichzeitig das Mass der Überlappung.

Wenn wir das bisherige in Code umsetzen, erhalten wir folgendes: BlitzBasic: [AUSKLAPPEN]
Function ProcessCollision( E1.TEntity, E2.TEntity )
Local MinDistance# = 10000000 ;Die kleinste Überlappung auf einen möglichst hohen Wert initialisieren

For i = 0 To E1\EdgeCount + E2\EdgeCount - 1 ;Die folgenden Berechnungen sind grösstenteils identisch mit denen aus dem zweiten Kapitel
If i < E1\EdgeCount Then
Edge.TConstraint = E1\Edge[ i ]
Else
Edge.TConstraint = E2\Edge[ i - E1\EdgeCount ]
EndIf

AxisX# = -( Edge\P2\Y# - Edge\P1\Y# )
AxisY# = Edge\P2\X# - Edge\P1\X#
TFormNormal AxisX#, AxisY#, 0, 0, 0
AxisX# = TFormedX()
AxisY# = TFormedY()

ProjectToAxis( AxisX#, AxisY#, E1 )
Min# = ProjectedMin#
Max# = ProjectedMax#
ProjectToAxis( AxisX#, AxisY#, E2 )

Distance# = IntervalDistance( Min#, Max#, ProjectedMin#, ProjectedMax# )
If Distance# > 0 Then
Return False ;Bis hier haben wir den gleichen Code wie aus dem vorherigen Teil des Tutorials
ElseIf Abs( Distance# ) < MinDistance# Then ;Falls die Überlappung auf dieser Achse kleiner ist als der bisher gemerkte Wert, dann merken wir uns diese Achse
MinDistance# = Abs( Distance# ) ;Überlappung auf dieser Achse zwischenspeichern
CollAxisX# = AxisX# ;Achse zwischenspeichern
CollAxisY# = AxisY#
EndIf
Next

CollisionVX# = CollAxisX#*MinDistance# ;Kollisionsvektor berechnen - er hat die Länge MinDistance (kleinste Überlappung)
CollisionVY# = CollAxisY#*MinDistance# ;und zeigt in Richtung der Achse mit der kleinsten Überlappung (CollAxisX/CollAxisY)
End Function


Wir könnten nun einfach beide Objekte um die Hälfte dieses Vektors verschieben und wir hätten die Kollision aufgelöst; die Objekte stecken nicht mehr ineinander. Allerdings sieht das dann nicht besonders super aus - die Objekte gleiten dann nämlich nur voneinander ab und fangen sich nicht an zu drehen.

Bevor wir uns mit der Lösung zu diesem Problem befassen, rufen wir uns nochmals ins Gedächtnis, dass die Verletintegration auf einzelnen Punkten basiert - nur dadurch erhalten wir Rotation.
Wenn wir das ganze Objekt verschieben, stellt sich natürlich keine Drehung ein; wir müssen also einzelne Punkte der beiden Objekte verschieben.

Zum Glück müssen wir nicht jeden Punkt beider Objekte einzeln berechnen, sondern müssen unser Interesse nur drei speziellen Punkten zuwenden, in der Zeichnung schwarz markiert:
user posted image
Zwei dieser Punkte gehören zum einen Objekt und bilden die penetrierte Seite, während der andere Punkt zum zweiten Objekt gehört und als penetrierter Punkt bezeichnet wird.
Um relativ realistische aussehende Ergebnisse zu erreichen, müssen wir nur diese drei Punkte entsprechend dem Kollisionsvektor verschieben und wir sind fertig. Bevor wir aber die Punkte verschieben können, müssen wir die drei Punkte ja ermitteln - das ist leider nicht ganz einfach.

Zuerst einmal benötigen wir die Erkenntnis, dass die Achse, auf der die kleinste Überlappung stattfindet, zu dem Objekt gehört, dass die penetrierte Seite beinhaltet. Nachdem wir also den Kollisionsvektor ermittelt haben, sorgen wir dafür, dass das Objekt, zu dem die penetrierte Seite gehört, in der Variable E2 liegt und das andere Objekt mit dem penetrierenden Punkt in E1

Nachdem das gesichert ist, beschäftigen wir uns vorerst mal mit dem einfacheren Fall, nämlich den penetrierenden Punkt zu bestimmen.
Der penetrierende Punkt ist der Punkt, der am nächsten bei dem Objekt E2 liegt. Das ist durchaus logisch, da er der Punkt ist, der im zweiten Objekt drinsteckt - sehr viel näher kann man gar nicht kommen Razz
Wir müssen also irgendwie den Abstand von einem Punkt zu einem Objekt bestimmen - aber wie ist das zu bewerkstelligen? Den Mittelpunkt vom Objekt zu berechnen und dann mit dem Pythagoras zu arbeiten liefert falsche Ergebnisse, also müssen wir uns einer Formel bedienen, die auf den schönen Namen "Hessesche Normalform" hört.
Die Hessesche Normalform (kurz HNF) berechnet uns den Abstand eines Punktes von einer Geraden. Diese Gerade steht senkrecht zu dem Kollisionsvektor:
user posted image
Der Kollisionsvektor ist grün gekennzeichnet, die Gerade als dicker schwarzer Strich.

Mit der HNF berechnen wir nun die Abstände der Punkte von Objekt 1 zu der Gerade - wir messen quasi die Länge der dünnen schwarzen Striche auf der Abbildung. Der Punkt, dessen Abstand (oder schwarzer Strich) am kürzesten ist, ist der gesuchte penetrierende Punkt.

Setzen wir das bisherige mal in Code um BlitzBasic: [AUSKLAPPEN]
;Die Hessesche Normalform hat die Gleichung X*NX + Y*NY + C
;NX und NY bezeichnen den Normalenvektor - in diesem Falle CollAxisX# bzw. CollAxisY#
;X und Y sind die Koordinaten des Punktes, dessen Abstand man berechnen will

;C ist eine Konstante, die wir am Anfang kurz berechnen; ihre Bedeutung zu erklären, würde den Rahmen hier sprengen.

Local C# = -E2\CenterX#*CollAxisX# - E2\CenterY#*CollAxisY# ;C berechnen

SmallestY# = 10000 ;Kleinsten Abstand auf einen möglichst hohen Wert initialisieren
For i = 0 To E1\VertexCount - 1 ;Alle Punkte von E1 durchgehen
Vertex.TParticle = E1\Vertex[ i ]

D# = Vertex\X#*CollAxisX# + Vertex\Y#*CollAxisY# + C# ;Abstand berechnen

If D# < SmallestY# Then ;Ist der Abstand dieses Punktes kleiner als der bisherige kleinste Abstand? Dann den momentanen Abstand als neuen kleinsten Abstand setzen und den Punkt zwischenspeichern
SmallestY# = D#
LowestVertex.TParticle = Vertex
EndIf
Next

Damit haben wir den penetrierenden Punkt schon ermittelt. Einfach, oder? Wink

Als nächstes folgt der ein wenig kompliziertere Fall. Wir müssen jetzt nämlich noch die penetrierte Seite bzw. die beiden Endpunkte dieser Seite ermitteln.
Die penetrierte Seite hat zwei besondere Eigenschaften, die wir beachten müssen. Zum einen liegt die Seite parallel zu der Geraden, die wir zur Abstandsbestimmung verwenden. Das bedeutet, dass die Abstände der beiden Endpunkte der Seite gleich sind - da die Seite parallel zur Geraden liegt, müssen ja auch ihre beiden Endpunkte gleich weit davon entfernt sein.

Zum anderen muss der penetrierende Punkt auf der penetrierten Seite liegen, wenn wir die penetrierte Seite um den Kollisionsvektor verschieben. Dies können wir einfach berechnen, indem wir den Abstand der Endpunkte der Seite von der Geraden (nachdem wir die Endpunkte um den Kollisionsvektor verschoben haben) mit dem Abstand des penetrierten Punktes vergleichen - falls es sich um die penetrierte Seite handelt, muss der Abstand gleich sein.

Der entsprechende Code gestaltet sich nicht besonders kompliziert BlitzBasic: [AUSKLAPPEN]
Treshold# = 0.001 ;Da Floats ungenau sind, können wir zwei Floatwerte nicht à la If A# = B# Then ... vergleichen, sondern müssen überprüfen, ob ihre Differenz unter einem Grenzwert liegt - also If A#-B# < Treshold# Then ...

For i = 0 To E2\EdgeCount - 1 ;Alle Seiten von E2 durchgehen
V1.TParticle = E2\Edge[ i ]\P1 ;Endpunkte zwischenspeichern
V2.TParticle = E2\Edge[ i ]\P2

V1Y# = ( V1\X# + CollisionVX# )*CollAxisX# + ( V1\Y# + CollisionVY# )*CollAxisY# + C# ;Distanz der (um den Kollisionsvektor verschobenen) Endpunkte von der Geraden berechnen
V2Y# = ( V2\X# + CollisionVX# )*CollAxisX# + ( V2\Y# + CollisionVY# )*CollAxisY# + C#

If Abs( V1Y# - V2Y# ) < Treshold# Then ;Sind die Abstände der beiden Endpunkte ungefähr gleich gross?
If Abs( ( V1Y# + V2Y# )/2. - SmallestY# ) < Treshold# Then Exit ;Liegt der penetrierte Punkt auf der Seite? Wenn ja, dann nichts wie raus der Schleife.
EndIf
Next


Nun haben wir das komplizierteste hinter uns - wir haben die drei Punkte, die von Bedeutung sind, herausgefiltert und müssen sie nun nur noch entsprechend dem Kollisionsvektor bewegen.

Eine kleine Skizze zeigt, wie das ungefähr vonstatten geht
user posted image

Wie wir sehen, ist der penetrierende Punkt sehr einfach zu berechnen - einfach um die Hälfte des Kollisionsvektors verschieben und fertig. BlitzBasic: [AUSKLAPPEN]
LowestVertex\X# = LowestVertex\X# + CollisionVX#*0.5
LowestVertex\Y# = LowestVertex\Y# + CollisionVY#*0.5


Ein wenig schwieriger verhält es sich mit der penetrierten Seite; die beiden Endpunkte werden mehr oder weniger stark bewegt, je nach dem, wo auf der Seite der penetrierende Punkt liegt (siehe Skizze - die beiden Endpunkte werden nicht gleich viel nach unten bewegt).

Um das noch hinzukriegen, müssen wir eigentlich nur einen Wert berechnen, wie weit der penetrierende Punkt von einem der beiden Endpunkte entfernt ist. Bei Wert 0 ist er direkt beim einen Endpunkt, bei 0.5 in der Mitte und so weiter. Dieser Wert ist mit ein wenig Vektorgeometrie schnell berechnet BlitzBasic: [AUSKLAPPEN]
Ratio# = ( CollisionPX# - CollisionVX# - V1\X# )/( V2\X# - V1\X# )


Der eine Endpunkt (in der Gleichung als V2 bezeichnet) wird um den halben Kollisionsvektor multipliziert mit Ratio bewegt. Der andere Eckpunkt (V1) wiederum wird um den halben Kollisionsvektor mal 1-Ratio bewegt. Der Code gestaltet sich entsprechend simpel BlitzBasic: [AUSKLAPPEN]
V1\X# = V1\X# + CollisionVX#*( 1 - Ratio# )*0.5
V1\Y# = V1\Y# + CollisionVY#*( 1 - Ratio# )*0.5
V2\X# = V2\X# + CollisionVX#*Ratio#*0.5
V2\Y# = V2\Y# + CollisionVY#*Ratio#*0.5


Das wars - fertig ist die Kollisionsberechnung! Fertig? Naja, fast.
Ein kleines Problemchen mit dem Kollisionsvektor habe ich nämlich verschwiegen - je nach dem zeigt er in Richtung des Objekts E1 oder in Richtung von E2. Unser Programm rechnet aber mit der Vorraussetzung, dass der Kollisionsvektor stets nach E1 zeigt. Das können wir erreichen, indem wir folgendes unverständliche Stück Code hinzufügen BlitzBasic: [AUSKLAPPEN]
Sign = Sgn( E1\CenterX#*CollAxisX# + E1\CenterY#*CollAxisY# + C# )

If Sign = 1 Then
CollisionVX# = -CollisionVX#
CollisionVY# = -CollisionVY#
Else
CollAxisX# = -CollAxisX#
CollAxisY# = -CollAxisY#
EndIf

Um ganz ehrlich zu sein, verstehe ich nicht ganz, warum es den Else - Zweig benötigt. Den Code habe ich vor einem Dreivierteljahr geschrieben und wusste es schon damals nicht genau; ich hatte die Zeilen einfach testweise mal eingebaut und es hat funktioniert Razz


Jetzt ist die Kollision aber wirklich abgeschlossen. Definitiv ein kompliziertes Kapitel, aber nachdem das einmal fertiggestellt ist, hat man eine stabile Basis für künftige Erweiterungen - beispielsweise könnte man noch Reibung, Wasser oder elastische Federn einbauen, aber deren Implementation überlasse ich mal dem Leser Razz

Ich habe mir mal erlaubt, die bisherigen Codefetzen zusammenzufassen, damit man sich die nicht aus dem ganzen Thread zusammensuchen muss Code: [AUSKLAPPEN]
Const GWIDTH = 800
Const GHEIGHT = 600

Graphics GWIDTH, GHEIGHT, 0, 2
SetBuffer BackBuffer()

Const MAX_VERTICES   = 64
Const MAX_EDGES      = 64

Global ProjectedMin#, ProjectedMax#
Global ResultX#, ResultY#

Type TParticle
   Field X#
   Field Y#
   
   Field OldX#
   Field OldY#
   
   Field A_X#
   Field A_Y#
   
   Field Parent.TEntity
End Type

Type TConstraint
   Field ID
   Field P1.TParticle
   Field P2.TParticle
   
   Field Length#
   
   Field Parent.TEntity
End Type

Type TEntity
   Field VertexCount
   Field Vertex.TParticle[ MAX_VERTICES - 1 ]
   
   Field EdgeCount
   Field Edge.TConstraint[ MAX_EDGES - 1 ]
   
   Field CenterX#
   Field CenterY#
   
   Field MinX#
   Field MinY#
   Field MaxX#
   Field MaxY#
End Type

Const TIMESTEP# = 1
Global GRAVITY_X# = 0
Global GRAVITY_Y# = 1

Global Iterations = 10

Timer = CreateTimer( 60 )

While Not KeyHit( 1 )
   Cls
   
   Counter = MilliSecs()
   AccumulateForces()
   Verlet()
   SatisfyConstraints()
   LagString$ = "Physik: " + ( MilliSecs() - Counter ) + "ms "
   
   Counter = MilliSecs()
   Render()
   LagString$ = LagString$ + "Zeichnen: " + ( MilliSecs() - Counter ) + "ms"
   
   UserInput()
   
   Color 128, 128, 128
   Text 0, 0, LagString$
   
   Flip 0
   WaitTimer Timer
Wend

End


Function UserInput()
   If MouseHit( 1 ) Then ;Bei Linksklick ein Dreieck erstellen
      Entity.TEntity = New TEntity
      
      Particle.TParticle = CreateParticle( Entity, MouseX() - 25, MouseY() - 25 )
      CreateParticle( Entity, MouseX() - 25, MouseY() + 25 )
      CreateParticle( Entity, MouseX() + 25, MouseY() + 25 )
      
      CreateConstraint( Entity, Particle, After Particle )
      CreateConstraint( Entity, After Particle, After After Particle )
      CreateConstraint( Entity, After After Particle, Particle )
   EndIf
   
   If MouseDown( 2 ) Then CreateBox( MouseX(), MouseY(), 40, 40 ) ;Bei Rechtsklick einen Würfel
End Function

Function AccumulateForces() ;Kräfte zusammenrechnen - bei uns nur Gravitation
   For Particle.TParticle = Each TParticle
      Particle\A_X# = GRAVITY_X#
      Particle\A_Y# = GRAVITY_Y#
   Next
End Function

Function Verlet() ;Bewegung der Partikel, wie im ersten Kapitel beschrieben
   For Particle.TParticle = Each TParticle
      Temp_X# = Particle\X#
      Temp_Y# = Particle\Y#
      Particle\X# = 2*Particle\X# - Particle\OldX# + Particle\A_X#*TIMESTEP#*TIMESTEP#
      Particle\Y# = 2*Particle\Y# - Particle\OldY# + Particle\A_Y#*TIMESTEP#*TIMESTEP#
      Particle\OldX# = Temp_X#
      Particle\OldY# = Temp_Y#
   Next
End Function

Function SatisfyConstraints() ;Diese Funktion berechnet Federn und Kollision
   For i = 1 To Iterations
      For Entity.TEntity = Each TEntity ;Für jedes Objekt den Mittelpunkt berechnen und die Bounding Box bestimmen
         Entity\CenterX# = 0
         Entity\CenterY# = 0
         MaxX# = -1000
         MaxY# = -1000
         MinX# = 1000
         MinY# = 1000
         For t = 0 To Entity\VertexCount - 1
            Entity\CenterX# = Entity\CenterX# + Entity\Vertex[ t ]\X#/Entity\VertexCount
            Entity\CenterY# = Entity\CenterY# + Entity\Vertex[ t ]\Y#/Entity\VertexCount
            
            If Entity\Vertex[ t ]\X# > MaxX# Then MaxX# = Entity\Vertex[ t ]\X#
            If Entity\Vertex[ t ]\X# < MinX# Then MinX# = Entity\Vertex[ t ]\X#
            If Entity\Vertex[ t ]\Y# > MaxY# Then MaxY# = Entity\Vertex[ t ]\Y#
            If Entity\Vertex[ t ]\Y# < MinY# Then MinY# = Entity\Vertex[ t ]\Y#
         Next
         
         Entity\MaxX# = MaxX#
         Entity\MinX# = MinX#
         Entity\MaxY# = MaxY#
         Entity\MinY# = MinY#
      Next
      
      For E1.TEntity = Each TEntity ;Für jedes Objekt die Kollision berechnen lassen. Es wird vorher geprüft, ob sich die Bounding Boxes überlappen, um Rechenzeit zu sparen
         For E2.TEntity = Each TEntity
            If E1 <> E2 Then
               If RectsOverlap( E1\MinX#, E1\MinY#, E1\MaxX# - E1\MinX#, E1\MaxY# - E1\MinY#, E2\MinX#, E2\MinY#, E2\MaxX# - E2\MinX#, E2\MaxY# - E2\MinY# ) Then
                  ProcessCollision( E1, E2 )
               EndIf
            EndIf
         Next
      Next
      
      For Constraint.TConstraint = Each TConstraint ;Federn berechnen lassen wie im ersten Kapitel erklärt.
         For Particle.TParticle = Each TParticle
            Particle\X# = Min( Max( Particle\X#, 0 ), GWIDTH - 1 )
            Particle\Y# = Min( Max( Particle\Y#, 0 ), GHEIGHT - 1 )
         Next
         
         DeltaX# = Constraint\P2\X# - Constraint\P1\X#
         DeltaY# = Constraint\P2\Y# - Constraint\P1\Y#
         
         DeltaLength# = Sqr( DeltaX#*DeltaX# + DeltaY#*DeltaY# )
         Diff# = ( DeltaLength# - Constraint\Length# )/DeltaLength#
                  
         Constraint\P1\X# = Constraint\P1\X# + DeltaX#*Diff#*0.5
         Constraint\P1\Y# = Constraint\P1\Y# + DeltaY#*Diff#*0.5
         Constraint\P2\X# = Constraint\P2\X# - DeltaX#*Diff#*0.5
         Constraint\P2\Y# = Constraint\P2\Y# - DeltaY#*Diff#*0.5
      Next
   Next
End Function

Function Render()
   LockBuffer BackBuffer()
   
   For Entity.TEntity = Each TEntity
      If Entity\CenterX# >= 0 And Entity\CenterX# < GWIDTH Then
         If Entity\CenterY# >= 0 And Entity\CenterY# < GHEIGHT Then
            WritePixelFast Floor( Entity\CenterX# ), Floor( Entity\CenterY# ), $00FFFF00 ;Mittelpunkt einzeichnen
         EndIf
      EndIf
   Next
   
   Color 255, 0, 0
   For Constraint.TConstraint = Each TConstraint
      Line Constraint\P1\X#, Constraint\P1\Y#, Constraint\P2\X#, Constraint\P2\Y# ;Federn zeichnen
   Next
   
   For Particle.TParticle = Each TParticle
      If Particle\X# >= 0 And Particle\X# < GWIDTH Then
         If Particle\Y# >= 0 And Particle\Y# < GHEIGHT Then
            WritePixelFast Floor( Particle\X# ), Floor( Particle\Y# ), $00FFFFFF ;Punkte zeichnen
         EndIf
      EndIf
   Next
   
   UnlockBuffer BackBuffer()
End Function

Function Min#( A#, B# )
   If A# < B# Then Return A# Else Return B#
End Function

Function Max#( A#, B# )
   If A# > B# Then Return A# Else Return B#
End Function

Function CreateParticle.TParticle( Parent.TEntity, X#, Y#, Mass# = 1 )
   If Parent <> Null Then
      If Parent\VertexCount < MAX_VERTICES - 1 Then
         Particle.TParticle = New TParticle
            Particle\X# = X#
            Particle\Y# = Y#
            Particle\OldX# = X#
            Particle\OldY# = Y#
            Particle\Parent = Parent
         
         Parent\Vertex[ Parent\VertexCount ] = Particle ;Punkt zum Objekt hinzufügen
         Parent\VertexCount = Parent\VertexCount + 1
         
         Return Particle
      EndIf
   EndIf
End Function

Function CreateConstraint.TConstraint( Parent.TEntity, P1.TParticle, P2.TParticle )
   If Parent <> Null Then
      If Parent\EdgeCount < MAX_EDGES - 1 Then
         If P1 <> P2 Then
            Constraint.TConstraint = New TConstraint
               Constraint\P1 = P1
               Constraint\P2 = P2
               ConstraintLengthSq# = ( Constraint\P1\X# - Constraint\P2\X# )*( Constraint\P1\X# - Constraint\P2\X# ) + ( Constraint\P1\Y# - Constraint\P2\Y# )*( Constraint\P1\Y# - Constraint\P2\Y# )
               Constraint\Length# = Sqr( ConstraintLengthSq# )
               Constraint\Parent = Parent
            
            Parent\Edge[ Parent\EdgeCount ] = Constraint ;Seite zum Objekt hinzufügen
            Parent\EdgeCount = Parent\EdgeCount + 1
            
            Return Constraint
         EndIf
      EndIf
   EndIf
End Function

Function CreateBox( X#, Y#, Width#, Height# )
   Entity.TEntity = New TEntity
   
   P1.TParticle = CreateParticle( Entity, X# - Width#/2, Y# - Height#/2 )
   P2.TParticle = CreateParticle( Entity, X# + Width#/2, Y# - Height#/2 )
   P3.TParticle = CreateParticle( Entity, X# + Width#/2, Y# + Height#/2 )
   P4.TParticle = CreateParticle( Entity, X# - Width#/2, Y# + Height#/2 )
   
   CreateConstraint( Entity, P1, P2 )
   CreateConstraint( Entity, P2, P3 )
   CreateConstraint( Entity, P3, P4 )
   CreateConstraint( Entity, P4, P1 )
   CreateConstraint( Entity, P1, P3 )
   CreateConstraint( Entity, P2, P4 )
End Function

Function Dotproduct#( X1#, Y1#, X2#, Y2# )
   Return X1#*X2# + Y1#*Y2#
End Function

Function Normalize( X#, Y# )
   Length# = Sqr( X#*X# + Y#*Y# )
   ResultX# = X#/Length#
   ResultY# = Y#/Length#
End Function

Function ProjectToAxis( AxisX#, AxisY#, Entity.TEntity )
   Local DotP# = Dotproduct( AxisX#, AxisY#, Entity\Vertex[ 0 ]\X#, Entity\Vertex[ 0 ]\Y# )
   ProjectedMin# = DotP#
   ProjectedMax# = DotP#
   
   For i = 1 To Entity\VertexCount - 1
      DotP# = Dotproduct( AxisX#, AxisY#, Entity\Vertex[ i ]\X#, Entity\Vertex[ i ]\Y# )
      
      If DotP# < ProjectedMin# Then ProjectedMin# = DotP# ElseIf DotP# > ProjectedMax# Then ProjectedMax# = DotP#
   Next
End Function

Function IntervalDistance#( A1#, B1#, A2#, B2# )
   If A1# < A2# Then
      Return A2# - B1#
   Else
      Return A1# - B2#
   EndIf
End Function

Function ProcessCollision( E1.TEntity, E2.TEntity )
   MinDistance# = 10000000
   
   For i = 0 To E1\EdgeCount + E2\EdgeCount - 1
      If i < E1\EdgeCount Then
         Edge.TConstraint = E1\Edge[ i ]
      Else
         Edge.TConstraint = E2\Edge[ i - E1\EdgeCount ]
      EndIf
      
      AxisX# = -( Edge\P2\Y# - Edge\P1\Y# )
      AxisY# = Edge\P2\X# - Edge\P1\X#
      TFormNormal AxisX#, AxisY#, 0, 0, 0
      AxisX# = TFormedX()
      AxisY# = TFormedY()
      
      ProjectToAxis( AxisX#, AxisY#, E1 )
      Min# = ProjectedMin#
      Max# = ProjectedMax#
      ProjectToAxis( AxisX#, AxisY#, E2 )
      
      Distance# = IntervalDistance( Min#, Max#, ProjectedMin#, ProjectedMax# )
      If Distance# > 0 Then
         Return False
      ElseIf Abs( Distance# ) < MinDistance# Then
         MinDistance# = Abs( Distance# )
         CollAxisX# = AxisX#
         CollAxisY# = AxisY#
         
         DiffX# = E1\CenterX# - E2\CenterX#
         DiffY# = E1\CenterY# - E2\CenterY#
         
         CollEdge.TConstraint = Edge ;Kollisionsseite zwischenmerken
      EndIf
   Next
   
   CollisionVX# = CollAxisX#*MinDistance#
   CollisionVY# = CollAxisY#*MinDistance#
   
   If CollEdge\Parent <> E2 Then ;Falls E2 nicht das penetrierte Objekt ist, die beiden Objekte vertauschen, so dass ersteres zutrifft
      Temp.TEntity = E2
      E2 = E1
      E1 = Temp
   EndIf
   
   Local C# = -E2\CenterX#*CollAxisX# - E2\CenterY#*CollAxisY# ;Konstante berechnen
   
   Sign = Sgn( E1\CenterX#*CollAxisX# + E1\CenterY#*CollAxisY# + C# )
   
   If Sign = 1 Then ;Das hier ist eine wichtige Schutzabfrage, um sicherzustellen, dass der Kollisionsvektor immer Richtung E1 zeigt
      CollisionVX# = -CollisionVX#
      CollisionVY# = -CollisionVY#
   Else
      CollAxisX# = -CollAxisX#
      CollAxisY# = -CollAxisY#
   EndIf
   
   SmallestY# = 10000
   For i = 0 To E1\VertexCount - 1
      Vertex.TParticle = E1\Vertex[ i ]
      
      D# = Vertex\X#*CollAxisX# + Vertex\Y#*CollAxisY# + C#
      
      If D# < SmallestY# Then
         SmallestY# = D#
         LowestVertex.TParticle = Vertex
      EndIf
   Next
   
   Treshold# = 0.001
   For i = 0 To E2\EdgeCount - 1
      V1.TParticle = E2\Edge[ i ]\P1
      V2.TParticle = E2\Edge[ i ]\P2
      
      V1Y# = ( V1\X# + CollisionVX# )*CollAxisX# + ( V1\Y# + CollisionVY# )*CollAxisY# + C#
      V2Y# = ( V2\X# + CollisionVX# )*CollAxisX# + ( V2\Y# + CollisionVY# )*CollAxisY# + C#
      
      If Abs( V1Y# - V2Y# ) < Treshold# Then
         If Abs( ( V1Y# + V2Y# )/2. - SmallestY# ) < Treshold# Then Exit
      EndIf
   Next
   
   If Abs( V2\X# - V1\X# ) > Abs( V2\Y# - V1\Y# ) Then ;Das hier ist eine kleine Schutzabfrage, damit man nicht durch 0 teilt, wenn die Seite senkrecht steht
      Ratio# = ( LowestVertex\X# - CollisionVX# - V1\X# )/( V2\X# - V1\X# )
   Else
      Ratio# = ( LowestVertex\Y# - CollisionVY# - V1\Y# )/( V2\Y# - V1\Y# )
   EndIf
   
   V1\X# = V1\X# + CollisionVX#*( 1 - Ratio# )*0.5
   V1\Y# = V1\Y# + CollisionVY#*( 1 - Ratio# )*0.5
   V2\X# = V2\X# + CollisionVX#*Ratio#*0.5
   V2\Y# = V2\Y# + CollisionVY#*Ratio#*0.5
   LowestVertex\X# = LowestVertex\X# - CollisionVX#*0.5
   LowestVertex\Y# = LowestVertex\Y# - CollisionVY#*0.5
   
   Return True
End Function

Bedient wird mit der linken bzw. rechten Maustaste.
Kommentare habe ich in dem Code nur spärlich gestreut, da alle Codestücke schon im Tutorial selbst ausführlich erklärt und auch kommentiert sind.

Das wird voraussichtlich der letzte Teil des Tutorials sein, für Fragen stehe ich aber selbstverständlich noch zur Verfügung.
Man is the best computer we can put aboard a spacecraft ... and the only one that can be mass produced with unskilled labor. -- Wernher von Braun

ComNik

BeitragFr, Jul 17, 2009 22:50
Antworten mit Zitat
Benutzer-Profile anzeigen
Herzlichen Dank nochmal! Very Happy
Das Modul ist fast fertig. Ich denke ich schreib auch eine DLL.
WIP: Vorx.Engine
 

n-Halbleiter

BeitragSo, Jul 19, 2009 20:44
Antworten mit Zitat
Benutzer-Profile anzeigen
Also hast du doch noch Zeit gefunden. Vielen Dank dafür, dass du das geschrieben hast. Smile

Ich habe es ohne Probleme verstanden, nur mal so, dass du eine Rückmeldung bekommst. ^^
mfg, Calvin
Maschine: Intel Core2 Duo E6750, 4GB DDR2-Ram, ATI Radeon HD4850, Win 7 x64 und Ubuntu 12.04 64-Bit
Ploing!
Blog

"Die Seele einer jeden Ordnung ist ein großer Papierkorb." - Kurt Tucholsky (09.01.1890 - 21.12.1935)

Gehe zu Seite Zurück  1, 2

Neue Antwort erstellen


Übersicht BlitzBasic FAQ und Tutorials

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group