Entwickler-Ecke

Sonstiges (Delphi) - Minigolf, Kugel an einer Kante


beastofchaos - Di 01.03.11 14:54
Titel: Minigolf, Kugel an einer Kante
Hallo Leute,
Ich bin Thomas ( 15 Jahre alt ). Hab schon einige Fragen hier gestellt und hab imemr tolle Antworten bekommen. Vll kann mir wieder so erfolgreich geholfen werden.
Also wie der Titel schon sagt, arbeite ich an einem Minigolfprogramm. Ich bin schon so gut wie fertig, doch das wichtigste ist ja, dass das Programm immer wieder bei der neuen Position der Kugel ( in einer Bewegung ) prüft, ob sie ein Hindernis trifft.
Bisher hab ich nur mit dem Mittelpunkt und der Geschwindigkeit gearbeitet - das heißt, dass die Kugel manchmal fast zur Hälfte "im Hindernis" drin war, da er immer nur eine Collision zwischen dem Vektor Mittelpunkt-Geschwindigkeit und dem Hindernis ( in Linien unterteilt als Vektoren mit Ortsvektoren p und Richtungsvektoren r ) geprüft hat.
Nun will ich diesen kleinen Fehler beheben und habe mich mal dran gemacht, dass eine Collision zwischen Kugel und Hindernis geprüft wird. In meinem "Timer" ( ich benutze IdleHandler -> zuverlässiger ) sah das bisher so aus:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
TVector2D = record
  x,y: extended;
  end;

TLine = record
  p, r: TVector2D;
  end;

.
.
.

procedure TMainForm.IdleHandler(Sender: TObject; var Done: Boolean);
begin
.
.
.
(* tmp := Kugelgeschwindigkeit*timestep *)
(* lines sind die Linien aller Hindernis in einem Array *)

for i := 1 to high(lines) do
    if LineLineCollision(lines[i], tmp, p) then
      pixel.r := Reflect(tmp, to_v2d(-lines[i].r.y, lines[i].r.x));

.
.
.
end;


Inzwischen habe ich die Linien außer den Randlinien in Typen namens TBarricade ( record = a,b,c,d: TLine; ) eingeteilt und das Programm kann schon prüfen, ob die Kugel sich schneidet mit dem Hindernis, aber wie die Reflektion funktionieren soll, ist immer noch fraglich.

Wenn er auf eine normale Linie trifft benutz ich die selbe Funktion wie schon im vorherigen Quelltext und reflektiere einfach den Geschwindigkeitsvektor ( function Reflect(A, B: TVector2D): TVector2D; ), aber, wenn aber auf eine Kante trifft, könnte es sein, dass er sich an den beiden anliegenden Linien spiegelt und somit einfach in die andere Richtung rollt( die beiden Linien liegen rechtwinklig zu einander ) oder spielt verrückt. Hab ihr shcon mal in der Schule der woanders über die Reflektion einer Kugel an einem Hindernis ( hier Viereck ) nachgedacht? Im Anhang ein kleines Bild fürs Verständnis.

MfG Thomas

PS: es mag nciht sooo viel mit Delphi zu tun haben, aber man braucht in vielen Spielen Vektorenrechnung und entsprechende Funktionen ( ich sag nur Ping Pong, Bubble Shooter etc. ), deswegen wäre so etwas schon gut ;)


Moderiert von user profile iconNarses: Topic aus Dateizugriff verschoben am Di 01.03.2011 um 16:36


Bergmann89 - Di 01.03.11 16:53

Hey,

(erst Bild angucken)
berechne den Schnittpunkt S der beiden Linien. Stelle den ortogonalen Einheitsvektor (a) auf die Gerade, an der gespiegelt werden soll auf. Berechne mit der Hilfe von a den Punkt x1 (x1 = p - a*radius). Dann stellst du mit p und a die Linie durch p und x1 auf und berechnest den Schnittpunkt (x2) mit der Geraden, an der gespiegelt werden soll. Dann stellst du den Vektor zwischen x1 und x2 auf. Wenn dieser Vektor negativ oder 0 ist und x2 auf der Geraden liegt, dann musst die Kugel am Hinderniss abprallen.

€: wenn x2 nicht auf der Geraden liegt, und Gerade(p, x1) kleiner ist als r, dann musst du die Kugel an der Ecke abprallen lassen, wenn Gerade(p, x3) kleinergleich r ist. Spiegelgerade ist die Ortogonale zur Geraden(p, x3)(ist im Bild bischen schief, aber die roten Striche haben nen rechten Winkel ;) ). siehe Bild2.

MfG Bergmann.


FinnO - Di 01.03.11 18:32

Wenn ich mir das so anschaue, könnte man alle Kollisionen direkt mit der zweiten Methode behandeln. Würde die Unterscheidung sparen und würde bei gleichschweren Kugeln auch Kugel-Kugel-Kollision beinhalten...


Bergmann89 - Di 01.03.11 18:39

Hey,

stimmt, das bedeutet dann aber auch ein bischen mehr Rechenaufwand für jede Linie. Weil ja das 2. immer noch mit berechnet werden muss. Ich würde es trotzdem trennen. Is ja nur eine kleine if-Abfrage^^

MfG Bergmann.


FinnO - Di 01.03.11 19:15

Ich stelle es mir aber eher kompliziert vor, herauszufinden, ob ich die ecke vorher berühre, oder auf die kante pralle. Im prinzip sollte beides aber erst ab SEHR vielen Kugeln ins Gewicht fallen.


Bergmann89 - Di 01.03.11 20:08

Hey,

warum ist das schwer? Hab doch oben beschrieben wie es geht :?!?:

Mfg Bergmann


beastofchaos - Di 01.03.11 23:14

So, ich hab mir das mal durch gelesen und das klingt totaql logisch. Genauso versteh ich auch die Bilder und kanns mir ( grafisch) vorstellen. Bloß 2 Fragen:

-Wie finde ich den Schnittpunkt von zwei Vektoren (hier ist der Schnittpunkt x2 gemeint). Hab das schonmal gemacht, aber mit einem Umweg, in dem ich die Vektoren teilweise in Funktionen umgewandelt habe und dann vieles umstellen musste.

-Und was meinst du mit negativen Vektoren. Soll ich ihn abfragen, ob die neue Position ( also den Vektor dazugerechnet ) hinter der inie leigt und wie soll ich das anstellen, wenn ich keine Ansicht habe ( naja, könnte die vorherige Kugelstelle merken und aus deren Sicht das anschauen ). Das alles immer in einem Computerprogramm mittels Formeln/Funktionen etc. zu schreiben, ist nochmal was ganz anderes. Bisher habe ich zur BallLinienCollision das Schema folgender Webseite versucht: http://wiki.delphigl.com/index.php/Tutorial_Separating_Axis_Theorem. Leider kann man an manchen Stellen nur schwer eine Formel für das ganze WirumWarum finden.


So, ich muss jetzt schlafen - habe morgen früh Schule. Also bis morgen Nachmittag oder so - freue mich auf neue Antworten

MfG beast alias Thomas


Bergmann89 - Mi 02.03.11 00:07

Hey,

zu Frage 1: du brauchst hier nicht den Schnittpunkt von 2 Vektoren, sondern den Schnittpunkt der 2 Geraden, die durch jeweils ein Punkt und ein Vektor definiert sind.
f(x) = m*x + n mit Vektor v und Punkt p
m = v.y / v.x
n = p.y - m*p.x
daraus folgt f(x) := (v.y/v.x)*x + p.y - m*p.x als Formel. Damit kannst du dann den Schnittpunkt berechnen.

zu Frage 2: Man kann die Länge eines Vektor berechnen mit Länge = Wurzel(v.x² + v.y²). Aber mir fällt grad auf, dass das nich geht, weil sich die Objekte ja frei auf der Zeichenfläsche drehen können. Aber du kannst ja mit Vektor a verlgeichen. Wenn der Vektor(x1, x2) (ich nenn ihn mal v) in die andere Richtung zeigt, dann ist er sozusagen negativ. Wenn du die beiden normalisierst, und dann -v = a machst, dann kannst du den Vergleich auch mathematisch im Programm lösen. (Aber das Runden nicht gergessen, oder mit IsNull(v-a) rechnen, da wird intern automatisch gerundet.

MfG Bergmann.


beastofchaos - Mi 02.03.11 15:26

Wie ich vektoren in Funktionen umwandel und von denen dann den Schnittpunkt berechne, weiß ich. Bloß ist mir das zuviel Gekritzel und umstellen, so dass man am Ende gar nicht mehr erkennt, wie man angefangen hat :)
Mein Bruder (13. Klasse Mathe-LK) meinte, man könnte genauso gut, die Scalaren ( z.B. r1 und r2), mit denen man die Vektoren mal nehmen muss, berechnen. Aber leider ist halt beides zu viel Umgestelle. Naja, ich hatte shcon bereits mittels meiner Methode ( Seperating-Axis-Theorem ) das Problem gelöst, ob der Kreis ein Hindernis schneidet. Wenn es trotzdem nach ein paar Versuchen nicht funktionieren sollte, werd ich zu deiner Methode zurückgreifen.


Meine Frage von anfangs an, war ja eigentlich, wie ich die Kugel reflektiere an einer Kante. Du hast mir bisher den Teil erklärt, wie ich prüfe, ob die Position der Kugel das Hindernis schneidet.
Ich muss noch überlegen, wie ich prüfe, ob die Kugel eine Kante trifft ( vll, wenn er 2 Linien gleichzeitig schneiden sollte ), aber wie berechne ich z.B. in deinem zweiten Bild den Austrittsvektor. Den spiegel ich doch nicht einfach an der roten Linie ( vll "Tangente" in dem Fall ).

Grüße Thomas


Bergmann89 - Mi 02.03.11 15:32

Hey,

doch genau das machst du. Einfach an der roten Linie im 2. Bild spiegeln ;) Aber du darft die Bewegung der Kugel nicht mit dem roten Strich von p zu x3 verwechseln. Die Richtung der Kugel ist die schmale schwarze Linie (steht auch unten dran). Und wenn du diese schwarze Linie an der roten spiegelst, dann hast du doch ein 1a Ergebniss. Die Prüfung ob eine Ecke getroffen wird hab ich doch oben im 2. Teil auch erklärt :? In dem Beitrag steht alles was man benötigt um die Kollision mit allem möglichen zu realisieren, egal ob Gerade, Ecke oder Kugel.

Mfg Bergmann.


beastofchaos - Mi 02.03.11 20:15

Vielen Dank. Hab nochmal dein Text vom Anfang gelesen und er schneidet doch eine Ecke, wenn bei zwei Linien ( nur mit rechten Winkel - benutz ich sowieso ) der Punkt x2 in beiden Fällen nicht auf der Linie liegt und die Gerade(p, x1) kleiner als r ist (wieder in beiden Fällen).

Habe dir mal meinen Quelltext kopiert, mit dem ich einen Schnittpunkt von 2 Vektoren berechnen will - es gibt halt nur ziemlich viele Sonderfälle, bei denen ich die Rechnung extra aufstellen muss:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
Function Schnittpunkt(A, B: TLine): TVector2D;
begin
if (v2d_length(A.r) = 0or (v2d_length(B.r) = 0then Exit;
  if (v2d_normalize(A.r).x = v2d_normalize(B.r).x) and (v2d_normalize(A.r).y = v2d_normalize(B.r).y) then Exit;

  if (A.r.x=0and (B.r.y=0then
    begin
    result.x := A.p.x;
    result.y := B.p.y;
    end
  else if (A.r.y=0and (B.r.x=0then
    begin
    result.x := B.p.x;
    result.y := A.p.y;
    end

  else if (A.r.x=0then
    begin
    result.x := A.p.x;
    result.y := B.r.y/B.r.x * A.p.x + B.p.y - B.r.y/B.r.x * B.p.x;
    end
  else if (A.r.y=0then
    begin
    result.x := (B.p.y-A.p.y-B.r.y/B.r.x*B.p.x) / ( - B.r.y/B.r.x );
    result.y := A.p.y;
    end

  else if (B.r.x=0then
    begin
    result.x := B.p.x;
    result.y := A.r.y/A.r.x * B.p.x + A.p.y - A.r.y/A.r.x * A.p.x;
    end
  else if (B.r.y=0then
    begin
    result.x := (A.p.y-B.p.y-A.r.y/A.r.x*A.p.x) / ( - A.r.y/A.r.x );
    result.y := B.p.y;
    end

  else
    begin
    result.x := (B.p.y-A.p.y-B.r.y/B.r.x*B.p.x+A.r.y/A.r.x*A.p.x) / (A.r.y/A.r.x - B.r.y/B.r.x);
    result.y := A.r.y/A.r.x * s.x + A.p.y - A.r.y/A.r.x * A.p.x;
    end;
end;




Naja...Hauptsache es funktioniert xD

Grüße Thomas


Bergmann89 - Mi 02.03.11 20:24

Hey,

bin grad noch unterwegs. Ich guck dann daheim mal (so um 12 rum). Kannst du mal noch die deklaration von TLine posten? Ich nehme an p is ein punkt un r der richtungsvektor, oder?

Mfg Bergmann


Kha - Mi 02.03.11 21:24

user profile iconBergmann89 hat folgendes geschrieben Zum zitierten Posting springen:
du brauchst hier nicht den Schnittpunkt von 2 Vektoren, sondern den Schnittpunkt der 2 Geraden, die durch jeweils ein Punkt und ein Vektor definiert sind.
f(x) = m*x + n mit Vektor v und Punkt p
m = v.y / v.x
n = p.y - m*p.x
daraus folgt f(x) := (v.y/v.x)*x + p.y - m*p.x als Formel. Damit kannst du dann den Schnittpunkt berechnen.
Stopp; das Tolle an Vektoren ist, dass man sich diese ganzen Spezialfälle sparen kann: http://sputsoft.com/blog/2010/03/line-line-intersection.html


Bergmann89 - Mi 02.03.11 23:43

Hey,

an ein lineares Gleichungssystem hab ich auch erst gedacht, als ich die Vektoren gesehen hab^^
Also hier ist jetzt die Methode zur Berechnug der Kollision:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
//berechnet den Schnittpunkt von 2 Geraden
//@A: 1. Gerade;
//@B: 2. Gerade;
//@Point: Schnittpunkt der Geraden;
//@result: TRUE wenn Berechnung möglich/erfolgreich, sonst FALSE;
function PointOfIntersection(A, B: TLine; var Point: TVector2f): Boolean;
var
  s, t, tmp: Single;
begin
  result := not((IsZero(A.V.x) and IsZero(A.V.y)) or
                (IsZero(B.V.x) and IsZero(B.V.y)));
  //GleichungsSystem aufstellen und Parameter isolieren
  //A = B
  //AP  + s * AV  = BP  + t * BV
  //-----------------------------------------
  //APx + s * AVx = BPx + t * BVx Gleichung 1
  //APy + s * AVy = BPy + t * BVy Gleichung 2
  //-----------------------------------------
  //APx*AVy + s*AVx*AVy = BPx*AVy + t*BVx*AVy
  //APy*AVx + s*AVx*AVy = BPy*AVx + t*BVy*AVx
  //-----------------------------------------
  //APx*AVy - APy*AVx = BPx*AVy - BPy*AVx + t*(BVx*AVy - BVy*AVx)
  //-----------------------------------------
  //t = (APx*AVy - BPx*AVy + BPy*AVx - APy*AVx)/(BVx*AVy - BVy*AVx)
  //-----------------------------------------
  //t = ((APx-BPx)*AVy + (BPy-APy)*AVx)/(BVx*AVy - BVy*AVx)
  if result then
    tmp := (B.V.x*A.V.y - B.V.y*A.V.x);
    if not IsZero(tmp) then
      t := ((A.P.x-B.P.x)*A.V.y + (B.P.y-A.P.y)*A.V.x)/tmp
    else
      result := false;

  //2. Parameter mit Gleichung 1 berechnen
  //APx + s * AVx = BPx + t * BVx
  //-----------------------------
  //s = (BPx + t * BVx - APx)/AVx
  if result then
    if not IsZero(A.V.x) then
      s := (B.P.x + t * B.V.x - A.P.x)/A.V.x
    else
      s := (B.P.y + t*B.V.Y - A.P.y)/A.V.y;

  //Probe mit Gleichung 1 und 2
  //APy + s * AVy = BPy + t * BVy
  if result then
    result :=
      IsZero((A.P.x + s * A.V.x) - (B.P.x + t * B.V.x), 0.001and
      IsZero((A.P.y + s * A.V.y) - (B.P.y + t * B.V.y), 0.001);
      
  if result then begin
    Point.x := A.P.x + s * A.V.x;
    Point.y := A.P.y + s * A.V.y;
  end;
end;

Wenn du das mit den Gleichungssystemen nich richtig verstehst, dann Frag dein Bruder nochma, das sollte er schon zig tausend mal durchgekaut haben. Du kannst aber auch gerne hier fragen ;)

MfG Bergmann.

p.s.: bei mir ist der Richtungsvektor der TLine das V für Vector. Fand ich besser als das R^^


beastofchaos - Do 03.03.11 16:39

Klar weiß ich wie das geht und hab heute in der Schule mal mir ein paar minütchen Zeit genommen und das so einfach wie möglich gemacht :)
Und ob V oder R ist eig Jacke wie Hose xD Hab die Typen TLine, TVector2D von jemandem anderen abgeschaut. p steht für Point, aber r... ( Richtungsvektor = Direction vector ??? ) - vll hat er ja mit "deutschen Variablen" gearbeitet.

Hier mal mein Quelltext mit dieser Vorgehensweise:


Vorweg: Ich hab geprüft, ob eine Linie eine Gerade prüft, weil ich bei deinem Collisionsverfahren zum Ermitteln von x2 mit der Geraden -a und der Linie des Hindernis arbeite -> erspart eine Prüfung bei der zweiten "Linien", ob der Faktor zwischen 0 und 1 liegt bzw. man muss diesen gar nicht erst berechnen ;)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
function LineStraightCollision(L, S: TLine): Boolean;
var f: extended;
begin
  result := False;

  if (v2d_length(L.r) = 0or (v2d_length(S.r) = 0then Exit;                    // "v2d_length(v: TVector2D): TVector2D" ermittelt die Länge eines Vektors

  if v2d_equal(v2d_normalize(L.r), v2d_normalize(S.r), 6then Exit;              // "v2d_equal(v1, v2: TVector2D): Boolean" prüft, ob die Vektoren parallel zueinander sind ( prüft x- und y-wert auf 6. Stelle )
                                                                                 // "v2d_normalize(v: TVector2D): TVector2D" ermittelt den Einheitsvektor
  if S.r.x = 0 then
    f := (S.p.x - L.p.x) / L.r.x

  else if S.r.y = 0 then
    f := (S.p.y - L.p.y) / L.r.y

  else if L.r.x = 0 then
    f := (S.r.x * (L.p.y - S.p.y) / S.r.y + S.p.x - L.p.x) / (L.r.x - (S.r.x * L.r.y) / S.r.y)
 
  else (*if (L.r.y = 0) oder Normalfall*)
    f := (S.r.y * (L.p.x - S.p.x) / S.r.x + S.p.y - L.p.y) / (L.r.y - (S.r.y * L.r.x) / S.r.x);

  if (f >= 0and (f <= 1then result := True;

end;


(*
- Für den Normalfall oder wenn L.r.y = 0:

I :   L.p.x + f * L.r.x = S.p.x + g * S.r.x
II:   L.p.y + f * L.r.y = S.p.y + g * S.r.y
I²:   g = (L.r.x * f + L.p.x - S.p.x) / S.r.x

I² in II: 
                         L.p.y + f * L.r.y  =  S.p.y + S.r.y * (L.r.x * f + L.p.x - S.p.x) / S.r.x    | - L.p.y; - S.r.y * L.r.x * f / S.r.x
       f * (L.r.y - S.r.y * L.r.x / S.r.x)  =  S.r.y * (L.p.x - S.p.x) / S.r.x + S.p.y - L.p.y        | / (L.r.y - S.r.y * L.r.x / S.r.x)
                                         f  =  (S.r.y * (L.p.x - S.p.x) / S.r.x + S.p.y - L.p.y) / (L.r.y - (S.r.y * L.r.x) / S.r.x)          // Und das ist das gleiche wie in Zeile 19


- Für L.r.x = 0 muss man einfach von Anfang an bei Gleichung II nach g umstellen, in Gleichung I einsetzen und dann f berechnen  -> dann kommt man auf die Rechnung in Zeile 16

- Für S.r.x = 0 fängt man, wie folgt, an:

I :   L.p.x + f * L.r.x = S.p.x + g * S.r.x           // Das "g * S.r.x" löst sich auf, da "S.r.x = 0"
      L.p.x + f * L.r.x = S.p.x 

 ->   f  =  (S.p.x - L.p.x) / L.r.x

- Für S.r.y = 0 fängt man wie gerade eben an (bloß statt Gleichung I nimmt man Gleichung II)

 ->   f  =  (S.p.y - L.p.y) / L.r.y

*)


Wenn man diese Sonderfälle nicht durchkaut, kommt es vor, dass das das Programm an manchen Stellen mal Null rechnet ( was vll zu Fehlern im Ergebnis führt ) oder er sogar durch Null rechnen will, was zum Error führt :/

Ich denke, meine Vorgehensweise ist gut so und ich verstehe sie zum Glück leichter als deine ;)



Grüße, Thomas

PS: Vll findet ihr einen Fehler in meiner Vorgehesenweise und sagt mir Bescheid - mache das jetzt zum 2. Mal heute (1 mal in der Schule auf nem Schmierblatt) und finde, dass die Rechnung 1A ist :))


Bergmann89 - Do 03.03.11 17:10

Hey,

ich habs jetzt nicht durchgerechnet, aber im Grunde ist dass das selbe wie meine Rechnung oben. Nur mit dem Unterschied, das du die Sonderfälle vorher abfängst. Ich fang nur die Division mit 0 ab und mach ne Probe (mit s und t). So haben wir das damals in der Schule gelernt. Und ich finde das auch einfacher, als voher die ganze If-Abfragen. Aber wenn du damit deiner Methode besser klar kommst, dann nimm lieber die :)
Nur zur Ergänzung: Wenn man bei meiner Rechnung prüfen will, ob sich die beiden Strecken (nicht mit Geraden verwechseln) schneiden/berühren, dann muss man nur prüfen ob 0 <= s <= 1 und (0 <= t <= 1).

MfG Bergmann.


beastofchaos - Do 03.03.11 17:50

Jep, ich weiß wie das funktioniert ;)

bei dir kann man das ein bisschen logischer gestalten, indem du statt "if result then" einfach nach der ersten Rechnung nur noch mit else-Blöcken weitermachst :))

Hab da mal ne Frage, du willst in deiner Funktion den Punkt auch noch ausgeben( Zeile 52/53 ). Hab das versucht, aber das geht leider nicht - eine funktion kann also wirklich nur einen Wert ausgeben. Um aber bei deinem Collisionsverfahren x2 zu berechnen, müsste man das iwie schaffen.
Hat da jem ne Idee, sonst muss ich dafür auch noch eine Funktion basteln, die im Grunde genommen das selbe ist, bloß am Ende den Vektor ausgibt.

Also:

Delphi-Quelltext
1:
2:
if LineStraightCollision(A, B) then
  x2 := LineStraightIntersection(A, B);


Grüße, Thomas


edit: Problem ist gelöst :))

Man muss, um die originale Variable zu verändern in der Funktion ein var davor schreiben, also: function LineStraightCollision(L, S: TLine; var i: TVector2D): Boolean;
Hast du übrigens auch so xD


Bergmann89 - Do 03.03.11 19:11

Hey,

so wie bei mir oben: function PointOfIntersection(A, B: TLine; var Point: TVector2f): Boolean; ? ;)
Bei dem if result then hab ich das else besusst nicht geschrieben, weil ich dann da viel einrücken hätte müssen, un da sah einfach doof aus un hat sich beschissen lesen lassen ;)

MfG Bergmann.


beastofchaos - Do 03.03.11 19:24

Hab doch noch eine Frage, hatte mal am Anfang eine function im Internet gefunden, die ich nicht verstehe - muss aber, wenn ich das Spiel vorstelle, alles erklären können.
Könnt ihr verstehen und mir erklären, wie diese funktion arbeitet, denn sie ist um einiges leichter ist, denke ich xD Aber um das zu verstehen, muss man es warsch. gechrieben hat, aber vll ist das ja doch einem bekannt ;)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
function LineLineCollision(A, B: TLine): boolean;
var
  D, Ds, s, Dt, t: extended;
begin

  D := A.r.y * B.r.x - A.r.x * B.r.y;
  if D=0 then                                     // Übrigens weiß ich schon: D = 0 wenn die beiden Linien parallel sind
    begin
    result := false;
    exit;
    end;

    Ds := (B.p.y - A.p.y) * B.r.x - (B.p.x - A.p.x) * B.r.y;
  s := Ds / D;
  if (s < 0or (s > 1then
  begin
    result := false;
    exit;
  end;

    Dt := (B.p.y - A.p.y) * A.r.x - (B.p.x - A.p.x) * A.r.y;
  t := Dt / D;
  if (t < 0or (t > 1then
  begin
    result := false;
    exit;
  end;
  result := true;
end;


FinnO - Do 03.03.11 19:28

ohne das genauer angesehen zu haben, sieht das nach Determinanten aus. Ist ein Verfahren zur Lösung von LGS. Dazu in der Formelsammlung mal Cramersche Regel angucken!


Kha - Do 03.03.11 20:00

user profile iconbeastofchaos hat folgendes geschrieben Zum zitierten Posting springen:
aber vll ist das ja doch einem bekannt ;)
Das wäre es dir auch gewesen, hättest du meinen Link angeschaut.


F34r0fTh3D4rk - Do 03.03.11 22:35

Du könntest dazu deine eigene Methode schreiben. Einfach auf Papier mal den allgemeinen Fall für den Schnitt zweier Geraden durchrechnen. Das kannst du dann ziemlich direkt implementieren.
Dann musst du es nur noch prüfen, ob der Schnitt in einem bestimmten Intervall auf deinen Geraden liegt, dann hast du die Kollision der Strecken.


Bergmann89 - Do 03.03.11 22:41

Das hab ich doch oben schon alles implementiert?! Ich hab das auch auf Papier umgestellt und berechnet. Ich hab sogar stichpunktartig die Rechnung mit aufgeschrieben. Ich versteh grad iwie nich wo es noch Probleme gibt :?!?:

MfG Bergmann


beastofchaos - Fr 04.03.11 20:36

Ich habe bisher mit einer zweiten Unit gearbeitet, in der alle Vektorfunktion definiert sind. Nun gibts da nco heni paar Fragen. z.B. hab ich die Funktion meistens mit dem Praefix "v2d_" verziert. Nun möchte ich eine classe erstellen, in der die Funktionen ohne Suffix genannt sind. Denn dann muss man im Programm v2d.add oder sonst was schreiben. Meine Unit müsste doch dann ungefähr so aussehen:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
.
.
.
type
  TVector2d = record

  .
  .
  .

  V2d = Class;
    function add(v1, v2: TVector2D): TVector2D;
    fucntion sub(v1, v2: TVector2D): TVector2D;
    .
    .
    .
    end;


Aber wenn ich mal die Forms im Standardprogramm als Vorbild nehme wird das da so deklariert:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
.
.
.
TForm1 = Class(TForm);
   .
   .
   .
   end;


Muss man diese Class (irgendein Typ) machen der kann ich das umgehen?


edit: sollte ich vll einfach "V2d = Class(TObject);" schreiben oder wäre das falsch?


Marc. - Fr 04.03.11 20:39

user profile iconbeastofchaos hat folgendes geschrieben Zum zitierten Posting springen:
Muss man diese Class (irgendein Typ) machen der kann ich das umgehen?
edit: sollte ich vll einfach "V2d = Class(TObject);" schreiben oder wäre das falsch?

Das wäre sogar äquivalent. :)
Die Klasse in den Klammern ist die übergordnete (engl. Super) Klasse, von der deine Klasse erbt. Dabei stammt jede Klasse immer von TObject ab. Insofern brauchst du die nicht explizit in den Klammern mit anzugeben. ;)

Schau dir auf jeden Fall mal den Abschnitt über OOP aus user profile iconChristians Tutorial [http://www.christian-stelzmann.de/artikel/crashkurs.htm#objektorientierteprogrammierung] an. :idea:


Bergmann89 - Fr 04.03.11 23:14

Hey,

dann würd ich aber die Methoden der Klasse als Klassenmethoden machen. Diese arbeiten dann ohne eine Referenz auf ein Objekt der Klasse. Eig ist es dann nix anderes als vorher, nur das du die Methoden über den Klassennamen aufrufst:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
type
  TTest = class
    class procedure DoIt;
  end;

class procedure TTest.DoIt;
begin
  ShowMessage('Done');
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  TTest.DoIt;
end;
So musst du kein Objekt der Klasse erstellen. Das hat den Vorteil, das du mehr Ordnung in deinen Methoden hast und deine Vektoren aber immer noch per Zuweisung übergeben kannst. Wenn du jetzt eine komplett neue Klasse für deine Vektoren erstellst, wo auch die VektorDaten von der Klasse verwaltet werden, dann kannst du die Werte nicht mehr per Zuweisung übergeben. Bsp:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
type
  TVector = class
    x, y: Single;
    procedure DoSomething;
    constructor Create(x, y);
  end;

procedure TForm1.FormCreate(Sender: TObject);
var
  v1, v2: TVector;
begin
  v1 := TVector.Create(35.37.6);
  v2 := v1;
  {...}
end;
v1 und v2 zeigen jetzt beide auf das selbe Objekt, es sind nich nur die selben Werte, sondern es wird der selbe Speicher benutzt. Und dann müsstest du deinen kompletten Methoden umstellen, da du da ja bestimmt relativ oft Zuweisungen drin hast, oder?

Es sei denn du hast eine etwas neuere Delphi-Version, bei der man die Operatoren überladen kann. Da kann man sich das dann wieder so zusammenbauen, das eine Zuweisung die Daten zwichen den Objekten kopiert, also nur die Werte übernimmt und nicht nur den Speicher. Aber ich denke mit KlassenMethoden ist das erreicht was du erreichen wolltest und das Programm muss nicht großartig verändert werden.

MfG Bergmann


beastofchaos - Sa 05.03.11 00:46

Das ist jetzt ein Missverständnis xD
Mit der Klasse V2d will ich nur auf Prozeduren und Funktionen greifen. Den Typ Vector2d hab ich schon vorher definiert

Also eigentlich sieht das so aus:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
Unit Vektoren;

uses Windows;

type

  TVector2D = record
    x, y: extended;
    end;
  
  TLine = record
    p, r: TVector2d;
    end;
  .
  .
  .
  V2d = class
    procedure Reflect(var A, B: TVector2D): TVector2D;
    function StraightStraightIntersection(A, B: TLine): TVector2D;
    function add(A, B: TVector2D): TVector2D;                                         // so geht das dann mit ca. 20-30 funktionen/prozeduren weiter
    .
    .
    .
    end;
.
.
.
procedure V2d.Reflect(var A, B: TVector2D): TVector2D;
.
.
.



Und V2d soll übrigens ja kein "Objekt", wie man es sich vll in der Realität vorstellt, sein, sondern der "Bereich" Vektor2d...
also ich werde es jetzt so wie hier machen, da das anscheinend eine gute Lösung ist :)


Bergmann89 - Sa 05.03.11 01:01

Hey,

na genauso meinte ich das auch. Nur das normale Methoden dann objektgebunden sind. Und KlassenMethoden eben nicht. Ich bin mir nicht sicher ob es da irgendwelche Probleme gibt, wenn da kein Objekt angelegt wird. Eigentlich arbeitest du ja nicht mit objektgebundenen Variablen, aber sicher ist sicher, dehalb das class vor die Methoden.
Vlt kann da aber auch jmd anderes nochmal Licht ins Dunkel bringen. Hab damit auch ncoh nich so viel Erfahrung.

MfG Bergmann.


beastofchaos - Sa 05.03.11 10:27

Jep, das kann sein. Ich machs so und hoffe, dass es keine Probleme gibt. Wenn doch, dann greif ich doch gerne auf deine Methode zurück :)

Ich dachte, jetzt hab ich eigentlich alles, aber es gibt wohl doch noch einen kleinen Denkfehler bei mir :/
Und zwar die Reflektion. Da Einfallswinkel = Ausfallswinkel und die Länge des einfallenden Vektors gleich der des austretenden Vektors ist, gilt doch folgendes:

(siehe Bild 1)


man kann auch andersrum arbeiten, also, anstatt den Vektor a mit -1 mal zu nehmen, die andere Normale nimmt und ganz am Ende den ausfallenden Vektor dann -1 mal nimmt. Heißt:

siehe (Bild 2)

in meiner function sieht das dann so aus:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
// bar ist das Hindernis
// bar_n ist der orthogonale Vektor N aus Bild 2
// v ist Vektor A aus den Bildern
// result ist Vektor b
function TForm1.Reflect(v, bar: TVector2D): TVector2D;
var
  bar_n: TVector2D;
  Alpha, len: extended;
begin
  len := v2d_length(v);
  v := v2d_normalize(v);
  bar_n := v2d_normalize(to_v2d(-bar.y, bar.x));

  Alpha := ArcCos(v2d_dotproduct(v, bar_n));                        // Das muss man machen, um dir Normale zu nehmen - nicht, dass man dann mit der falschen Normale rechnet
  if Alpha > 90 then bar_n := v2d_scale(bar_n, -1);

  result := v2d_scale(v2d_sub(v2d_scale(bar_n, 2), v), -len);

  // result :=  v2d_scale(v2d_sub(v2d_scale(bar_n, v2d_dotproduct(bar_n, v) * 2), v), -len);                // Das hab ich im Internet gefunden, versteh ich aber iwie nicht. 
                                                                                                            // Wieso muss ich da das Skalarprodukt nehmen?

end;




MfG Thomas


Kha - Sa 05.03.11 11:20

Vektoren machen (teure) Winkelberechnungen eigentlich immer überflüssig. Allgemein bildest du über (a . u) * u einen Vektor a auf einen Einheitsvektor u ab (rechne es nach ;) ). Hier wird also v auf n abgebildet und verdoppelt, sodass sich eine Raute ergibt, von deren Spitze nur noch v abgzogen werden muss: http://mathworld.wolfram.com/Reflection.html. Das Schöne an dieser Projektion: Es ist ganz egal, in welche Richtung n zeigt.
Vielleicht siehst du jetzt auch, was an deinen Bildern falsch ist: Du gehst davon aus, dass die Pfeilspitzen von a, b, und n auf einer Parallelen zum Objekt liegen, was i.A. natürlich nicht der Fall ist. Deswegen erst diese Projektion.

user profile iconbeastofchaos hat folgendes geschrieben Zum zitierten Posting springen:
Wenn doch, dann greif ich doch gerne auf deine Methode zurück :)
Oder du machst es gleich richtig, denn deine Klasse zu instanzieren macht wirklich nicht viel Sinn :nixweiss: .


beastofchaos - So 06.03.11 00:14

ALso der Unterschied ist doch im moment:
1.Beispiel(meine Idee):


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
.
.
.
type
  procedure to_v2d(v1, v2: TVector2D): TVector2D;

  V2d = class
    procedure add(v1, v2: TVector2D): TVector2D;
    procedure sub(v1, v2: TVector2D): TVector2D;
    .
    .
    .
  end;
.
.
.
procedure V2d.add(v1, v2: TVector2D): TVector2D;
begin
  result.x := v1.x + v2.x;
  result.y := v1.y + v2.y;

.
.
.

procedure TForm1.Create(Sender: TObject);
var a, b, c: TVector2D;
begin
  a := to_v2d(34);
  b := to_v2d(45);
  c := v2d.add(a, b);
end;



2. Beispiel(Eure Empfehlung):


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
.
.
.
type
  procedure to_v2d(v1, v2: TVector2D): TVector2D;

  V2d = class
    class procedure add(v1, v2: TVector2D): TVector2D;
    class procedure sub(v1, v2: TVector2D): TVector2D;
    .
    .
    .
  end;
.
.
.
class procedure V2d.add(v1, v2: TVector2D): TVector2D;
begin
  result.x := v1.x + v2.x;
  result.y := v1.y + v2.y;

.
.
.

procedure TForm1.Create(Sender: TObject);
var a, b, c: TVector2D;
begin
  a := to_v2d(34);
  b := to_v2d(45);
  c := v2d.add(a, b);     // diese Zeile bleibt aber gleich, oder?
end;



Und was hab ich jetzt im ersten Beispiel falschgemacht, was ihr im zweiten nicht gemacht habt? :)
Vll gibt mir dieses Tutorial Abhilfe, aber ich kam noch nicht dazu, es zu lesen... :/ vll morgen

Grüße, Thomas


Bergmann89 - So 06.03.11 00:25

Hey,

in dem Tutorial stand darüber nix (zumindest hab ich beim überfliegen nix gesehen).
Der Unterschied zu deiner und unserer Methode ist folgender: Deine Methoden arbeiten normalweiße nur, wenn man mithilfe der Klasse ein Objekt erstellt:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
type
  TTest = class
    procedure DoIt;
  end;

var
  Test: TTest;

Test := TTest.Create; //Objekt erstellen
Test.DoIt;  //Methoden benutzen
Test.Free;  //Objekt freigeben
Du musst das Objekt erstellen und wieder freigeben. Wenn du jetzt aber kein Objekt erstellst, dann kann das unvorhersehbare Folgen haben. Deshalb sollst du es ja gleich richtig machen. Wenn du eine KlassenMethode erstellst, dann ist das nichts anderes als eine normale Methode (so wie du es jetzt in der Unit hast) nur das man die Methoden zur besseren Übersicht in die Klassen ablegen kann. Ein Konstructor ist z.B. auch eine Klassenmethode und kann ohne ein Objekt aufgerufen werden:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
type
  TTest = class
    class procedure DoIt;
  end;

TTest.DoIt; //Methoden ohne Objekt nutzen
Du musst kein Objekt erstellen um die Methode aufzurufen. Und das ist genau das was du machen wolltest. Natürlich arbeitet dein Programm jetzt, aber das kann auch schnell mal nach hinten los gehen. Also lieber gleich ordentlich arbeiten. Und das kleine Wort class vor den Methoden ist nun wirklich keine Arbeit von mehreren Stunden ;)

MfG Bergmann


Kha - So 06.03.11 00:32

user profile iconBergmann89 hat folgendes geschrieben Zum zitierten Posting springen:
Wenn du eine KlassenMethode erstellst, dann ist das nichts anderes als eine normale Methode Prozedur/Funktion[...]


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
type
  TTest = class
    class procedure DoIt;
  end;

//var
//  Test: TTest;

TTest.DoIt;
So ;)


Bergmann89 - So 06.03.11 00:39

Ja natürlich^^ Habs angepasst. Scheiß copy&paste, bringt nur Schwierigkeiten :mrgreen:


beastofchaos - Di 08.03.11 23:56

Und was ist dann der Unterschied, ob ich davor ein "class" setzte oder nicht. Also nicht, ob es dann eine Klassenfunktion oder nicht ist, sondern was zweckmäßig ein Vorteil/Nachteil ist.


Bergmann89 - Mi 09.03.11 14:41

Hey,

das ham wir doch die ganze Zeit gesagt :? Du musst kein Objekt anlegen und kannst die Methoden gleich über die Klasse aufrufen. Und die Klassenmethoden sind nu mal für das vorgesehen, was du vor hast, also solltest du sie auch benutzen. Und wenn du deine Methode benutzt ohne ein Objekt anzulegen, dann ist das sowieso falsch:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
type
  Test = class
    procedure DoIt;
  end;

var 
  Test: TTest;

Test.DoIt; //falsch, das Objekt wurde nicht angelegt!!!

Und wenn du erst ein Objekt anlegen musst, dann musst du das auch überall benutzen können, wo du die Methoden benutzen willst. Also entweder wird das eine globale Variable (was sowieso schlecht ist), oder du musst jedesmal ein neues Objekt anlegen (was auch umständlich ist). Deshalb --> KLASSENMETHODEN!

MfG Bergmann


beastofchaos - Mi 09.03.11 17:51

Ah, ich glaube jetzt hats Klick bei mir gemacht :) Hab alles angeschaut und die Erleuchtung gefunden - also Unterschied ist, dass bei mir ein Typ deklariert wird und ich dann extra ein Objekt dafür noch in var angeben muss ( stimmt doch, oder? ) und dass bei euch die funktionen/prozeduren kein objekt brauchen sondern man einfach den Typen, ein punkt und dann die jeweilig funktion dann aufruft - also TTest.DoIt ( bei mir wäre es an der stelle Test.DoIt, bloß gäb es mehr drumherum gefasel xD ).

Danke danke xD



Ich weiß nicht, ob ich das noch mache, aber wüsstet ihr, nachdem ich das mit den Ecken- und Kugel-reflektionen verstanden habe, auch wie ich das bei dem gegenteil einer kugel mache, also, dass da eine halbe kugel als hindernis ist.
1. wie zeichne ich so ne halbe kugel in Delphi
2. müsste dann wieder die "äußere" tangente ( also die Orthogonale zum Vektor vom Mittelpunkt der Spielkugel zum Mittelpunkt der Hinderniskugel ) als reflektionsfläche zeichnen?

Ich weiß nicht, ob ich das relativ gut erklärend gefragt habe, aber hier ein bildchen für die situation im anhang



MfG Thomas