Autor Beitrag
delfiphan
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: Fr 26.08.05 11:51 
Elastische Stösse zwischen Kugeln
Im folgenden wird die Berechnung von nicht-zentralen, elastischen Stössen bei Kugeln in 2D anhand eines Codegerüstes vorgestellt. Die Wandkollision ist ebenfalls implementiert. Die Kugeln können beliebig gross sein und beliebige Massen besitzen. Die Berechnung ist physikalisch korrekt; nicht berücksichtigt werden Reibung, Deformationsverluste und die Kugelrotation.

Source
Der Code dient zur Illustration und ist nicht sonderlich optimiert. Die Kernberechnung findet in Timer1Timer statt. Details zur Berechnung siehe Kommentare und Erklärung unten.
ausblenden volle Höhe 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:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
unit Unit1;

interface

uses
  SysUtils, Forms, StdCtrls, ExtCtrls, Classes;

const
 dt = 20// in ms
 SubIterations = 10;
 KugelCount = 10;

type
  TKugel = class;

  TForm1 = class(TForm)
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    Kugeln: array[0..KugelCount-1of TKugel;
  end;

  Borders = (boLeft, boTop, boBottom, boRight);
  BordersSet = set of Borders;
  TKugel = class(TShape)
  private
    FX, FY: Extended;
    FR: Extended;
    FM: Extended;
    FVx, FVy: Extended;
    procedure setR(const Value: Extended);
  public
    property X: Extended read FX write FX; // Position [Pixel]
    property Y: Extended read FY write FY;
    property R: Extended read FR write setR; // Radius [Pixel]
    property M: Extended read FM write FM; // Masse
    property Vx: Extended read FVx write FVx; // Geschwindigkeit [Pixel/s]
    property Vy: Extended read FVy write FVy;
    procedure Move; // Nächster Zeitschritt
    procedure Apply;
    function CollidesWith(const Kugel2: TKugel): Boolean;
    function WallCollisions: BordersSet;
    constructor Create(AOwner: TComponent); override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

 { TKugel }



function TKugel.CollidesWith(const Kugel2: TKugel): Boolean;
begin
  Result := sqr(FX-Kugel2.FX)+sqr(FY-Kugel2.FY) < sqr(FR+Kugel2.FR);
end;

constructor TKugel.Create(AOwner: TComponent);
begin
  inherited;
  Shape := stCircle;
end;

procedure TKugel.Move;
begin
  X := X + (dt/1000)/SubIterations*FVx; // ms => s
  Y := Y + (dt/1000)/SubIterations*FVy;
end;

function TKugel.WallCollisions: BordersSet;
const
  Border = 10;
begin
  Result := [];
  if X-Border < R then // links über Rand
   Result := Result + [boLeft];
  if Y-Border < R then // oben über Rand

   Result := Result + [boTop];
  if X+Border >= Parent.ClientWidth-R then // rechts über Rand
   Result := Result + [boRight];
  if Y+Border >= Parent.ClientHeight-R then // unten über Rand
   Result := Result + [boBottom];
end;

procedure TKugel.setR(const Value: Extended);
begin
  FR := Value;
  Width := Round(FR*2); // Grösse des Shapes mitändern
  Height := Round(FR*2);
end;

procedure TKugel.Apply;
begin
  Left := round(X-FR); // Position des Shapes anpassen
  Top := round(Y-FR);
end;

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
const
 Border = 10;
var
 I: Integer;
begin
  // Timer initialisieren
  with Timer1 do
  begin
   Interval := dt;
   OnTimer := Timer1Timer;
  end;

  // Kugeln zufällig in den Raum platzieren
  for I := 0 to Length(Kugeln)-1 do
  begin
    Kugeln[I] := TKugel.Create(Form1);
    with Kugeln[I] do
    begin
     Parent := self;
     R := 20+Random(8);
     X := Random(self.ClientWidth-2*Round(R+Border))+Round(R+Border);
     Y := Random(self.ClientHeight-2*Round(R+Border))+Round(R+Border);
     Vx := Random(400);
     Vy := Random(400);
     M := 4/3*R*R*R*pi;
     Brush.Color := Random(256*256*256); // farbige Bälle
    end;
  end;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
 DX, DY, M11, M21, M12, M22, L, Vp1, Vp2, Vs1, Vs2, MTot, Vp1_, Vp2_: Extended;
 I, J, K: Integer;
 B: BordersSet;
begin
  for K := 0 to SubIterations-1 do
  begin
   // Position nachführen (gemäss gegebenen Geschwindigkeiten)
   for I := 0 to Length(Kugeln)-1 do
    Kugeln[I].Move; 

   // Kollisionserkennung zwischen Kugeln
   for I := 0 to Length(Kugeln)-1 do
    for J := I+1 to Length(Kugeln)-1 do // über alle geordneten Paare
     // Falls Kollision zwischen Kugel I und Kugel J:
     if Kugeln[I].CollidesWith(Kugeln[J]) then
     begin
      DX := Kugeln[j].X-Kugeln[i].X; // Delta x
      DY := Kugeln[j].Y-Kugeln[i].Y; // Delta y
      L := sqrt(sqr(DX)+sqr(DY)); // Abstand

      // Matrix mit neuen Koordinatenachsen (parallel/senkrecht zum Stoss)
      M11 := DX/L; M12 := -DY/L;
      M21 := DY/L; M22 := DX/L;

      // Koordinatentransformation
      Vp1 := Kugeln[i].Vx*M11+Kugeln[i].Vy*-M12;
      Vs1 := Kugeln[i].Vx*-M21+Kugeln[i].Vy*M22;
      Vp2 := Kugeln[j].Vx*M11+Kugeln[j].Vy*-M12;
      Vs2 := Kugeln[j].Vx*-M21+Kugeln[j].Vy*M22;

      if Vp1-Vp2<0 then
       Continue; // Bälle gehen bereits auseinander

      // Zentraler Stoss
      MTot := Kugeln[i].M+Kugeln[j].M;
      Vp1_ := (Kugeln[i].M-Kugeln[j].M)/MTot*Vp1+2*Kugeln[j].M/MTot*Vp2;
      Vp2_ := (Kugeln[j].M-Kugeln[i].M)/MTot*Vp2+2*Kugeln[i].M/MTot*Vp1;

      // Rücktransformation
      Kugeln[i].Vx := Vp1_*M11+Vs1*M12;
      Kugeln[i].Vy := Vp1_*M21+Vs1*M22;
      Kugeln[j].Vx := Vp2_*M11+Vs2*M12;
      Kugeln[j].Vy := Vp2_*M21+Vs2*M22;
     end;

   // Wandkollisionen
   for I := 0 to Length(Kugeln)-1 do
    with Kugeln[I] do
    begin
     B := WallCollisions;
     if ((boLeft in B) and (Vx < 0)) or ((boRight in B) and (Vx > 0)) then 
      Vx := -Vx;
     if ((boTop in B) and (Vy < 0)) or ((boBottom in B) and (Vy > 0)) then
      Vy := -Vy;
    end;
  end// for K

  for I := 0 to Length(Kugeln)-1 do
   Kugeln[I].Apply; // Angezeigte Position updaten
end;

end.


Vollständigkeitshalber noch die Form-Daten (in Form Alt+F12):
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
object Form1: TForm1
  Left = 192
  Top = 114
  Width = 800
  Height = 600
  Caption = 'Form1'
  Color = clBtnFace
  OnCreate = FormCreate
  object Timer1: TTimer
    OnTimer = Timer1Timer
  end
end


Physikalischer Hintergrund
Die Berechnung des nicht-zentralen Stosses geht indirekt: Bei einer Kollision wird ein neues, gedrehtes Koordinatensystem definiert: Eine Koordinatenachse senkrecht und die andere parallel zum Stosspunkt (ausgehend vom Kugelmittelpunkt). Die senkrechten Geschwindigkeiten der Kugeln bleiben erhalten und die parallelen verhalten sich wie beim zentralen Stoss. Somit ist Energieerhaltung und Impulserhaltung garantiert. Die Umrechnung ins gedrehte Koordinatensystem und zurück geht einfach mittels Matrizenrechnung.

 Kugel 1
  _____
 /     \
|  -->  |_____          * Stosspunkt
 \_____*      \
       |  <--  |
        \_____/
        Kugel 2
[meta]Billard Billiard Kugel Kugeln nicht zentraler elastischer dezentraler Stoss Stoß Kollision[/meta]
Moderiert von user profile iconjasocul: Beitrag geprüft am 06.04.2006


Zuletzt bearbeitet von delfiphan am Mo 19.09.05 11:48, insgesamt 3-mal bearbeitet