Autor Beitrag
tommie-lie
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 4373

Ubuntu 7.10 "Gutsy Gibbon"

BeitragVerfasst: Fr 02.05.03 18:34 
Hier nun der zweite Teil. Die Lektüre des ersten Teils ist anzuraten, denn die ganzen Grundlagen der Hierarchie und warum man bei Drehen eines Objektes nicht alle anderen mitdreht, wurden dort vermittelt.

Alle, die lieber tippen, haben hier nun endlich Gelegenheit dazu, denn hier wird der erste wirklich notwendige Code geschrieben.
In GLScene wird jede Bewegung mit einem Cadencer erledigt. Das ist die Komponente, mit dem Metronom als Icon (auch wenn's nicht nach einem Metronom aussieht, dieses gelbbraune Schlüsselloch...). Er ist wirklich ein Zeitgeber, denn er hat ein Szenenweites OnProgress-Event, das nach jedem Rendern ausgelöst wird, in dem man also viele Sachen für das kommende Frame verändern kann. Zum Beispiel die Direction von Objekten. Zusätzlich hat jedes einzelne Objekt wiederum ein eigenes OnProgress-Event, um die Bearbeitungen etwas hierarchischer aufbauen zu können.
Zunächst die Properties des Cadencers:
  • Enabled: Entscheidet ob der Cadencer auch aktiviert ist, denn nur dann wird das Progress-Event ausgeführt (und nur dann werde Bewegungen, ob automatisch (Effekte/Behaviours) oder manuell (durch Progress) ausgeführt).
  • MaxDeltaTime: Beschränkt die Zeit (in ms), die ein Frame zur Berechnung haben darf. Dauert ein Event länger als MaxDeltaTime, wird deltaTime (Zeit zwischen zwei Frames) auf MaxDeltaTime gesetzt und die zusätzlich benötigte Zeit fällt einfach unter den Tisch. Die Berechnungen werden aber fortgesetzt. Das könnte z.B. bei Simulationen sinnvoll sein, wo hohe deltaTime-Werte zu falschen Resultaten führen könnten.
  • Mode: Legt fest, wie der Cadencer aufgerufen wird. ASAP steht für ?as soon as possible? und bedeutet, daß die höchstmögliche Framerate erzeugt wird (sobald das Bild gezeichnet wurde, wird Progress erneut aufgerufen), während bei Manual eine externe Quelle benutzt werden muss. Dazu kann zum Beispiel ein Timer benutzt werden, der GLCadencer1.Progress aufruft, wodurch eine konstante Framerate abhängig vom Intervall des Timers entsteht.
  • Scene: legt einfach nur die Szene fest, in der der Cadencer aktiv ist. Da wir nur eine Szene haben, können wir ihn einfach auf TGLScene1 stellen.
  • SleepLength: entspricht der Funktion Sleep von Delphi und wird vor jedem Progress ausgeführt. Dabei wird der Thread des Cadencers für diese Zeit auf Eis gelegt. In dieser Zeit bekommen andere Threads die Chance, selbst Code auszuführen, da der Cadencer eine hohe Priorität hat.
  • TimeMultiplier: Faktor, mit dem deltaTime bei jedem Progress-Event multipliziert wird. Dadurch kann man zum Beispiel Zeitrafferfunktionen global implementieren, anstatt jeden Geschwindigkeitsvektor zu verlängern. Allerdings kommen nicht alle Objekte (zum Beispiel einige SpecialFX) nicht mit negativen Werten klar, obwohl normale Objekte sie durchaus unterstützen.
  • TimeReference: Sie bestimmt, woher der Cadencer seine Zeitreferenzen holt und dadurch auch wie genau er ist.
    • RTC: Benutzt die RTC auf dem Board. Die ist zwar über sehr lange Zeit genau, hat aber eine zu geringe Auflösung im Millisekundenbereich und kann dadurch die Framerate auf einigen Systemen stark begrenzen.
    • PerformanceCounter: Benutzt den Windows PerformanceCounter, der eine äußerst hohe Auflösung hat (bei mir mehr als eine millionstel Sekunde, auszumachen mit QueryPerformanceFrequency). Auf den meisten Systemen ermöglicht diese Einstellung die flüssigsten Animationen.
    • External: Hierbei wird das CurrentTime-Property des Cadencers benutzt, das man in diesem Fall manuell setzen muss.


So, endlich ein wenig Praxis:
Wir erzeugen wie im ersten Teil beschrieben unsere Szene, einen Würfel, richten die Kamera mit Lichtquelle wie gewünscht aus und doppelklicken auf den Cadencer. Das erzeugt sofort den Eventhandler für OnProgress und öffnet den Codeeditor. Dort tippen wir jetzt mal folgendes ein:
ausblenden Delphi-Quelltext
1:
GLCube1.Roll(1)					

Dazu muss man wissen, daß jedes Szenenobjekt die Funktionen Roll, Pitch und Turn hat, die ihren -Angle-Property-Äquivalenten entsprechen. Man übergibt ihnen Winkel in ° und das Objekt wird entsprechend um das aktuelle Koordnatensystem gedreht.
In jedem Progress der ersten Szene wird nun der Würfel um 1° gerollt. Aber dafür, daß es nur 1° ist, erscheint es ziemlich schnell und die Bewegung ist unregelmäßig (was zwar bei der Geschwindigkeit kaum noch auffällt, aber dennoch der Fall ist). Der Grund liegt darin, daß der Cadencer auf ASAP steht, also wirklich so oft wie möglich rendert. Wir verändern also den Winkel des Würfels in jedem Frame um ein Grad, bei hohen Framerates können das schonmal mehrere Umdrehungen pro Sekunde sein. Daher müssen wir die Drehung framerateunabhängig, oder in anderen Worten zeitabhängig machen. Es gibt Leute, die benutzen dazu einen Timer. Wir brauchen das nicht mehr, denn das Progress-Event ist wie folgt deklariert:
ausblenden Delphi-Quelltext
1:
TGLProgressEvent = procedure (Sender : TObject; const deltaTime, newTime : Double) of object;					

Wir haben also eine Variable deltaTime und newTime. deltaTime ist die Zeit seit dem letzten Frame, also die Differenz (Wissenschaftlern als Delta bekannt ;-) ) und newTime ist die neue Gesamtzeit, also seit dem Start des Cadencers. Daher können wir deltaTime prima als Faktor benutzen um Bewegungen zeitgesteuert ablaufen zu lassen:
Im Progress-Event schreiben wir also diesmal:
ausblenden Delphi-Quelltext
1:
GLCube1.Roll(deltaTime * 180);					

Jetzt dreht sich der Würfel in zwei Sekunden genau einmal um sich selbst, und zwar extrem gleichmäßig.

Aber wir können das Progress-Event auch noch für andere Effekte benutzen.
ausblenden 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:
procedure TForm1.GLCadencer1Progress(Sender: TObject; const deltaTime,
  newTime: Double);
begin
  GLCube1.Pitch(deltaTime * 180);
  GLCube1.Roll(deltaTime * 180);

  with GLCube1.Material.FrontProperties.Diffuse do // change front's diffuse collor
    case GLCube1.Tag of
      0begin
           if Red >= 1 then GLCube1.Tag := 1;  // red-portion of the RGBA-color
           Red := Red + deltaTime * 0.2;
         end;

      1begin
           if Green >= 1 then GLCube1.Tag := 2;  // green-portion of the RGBA-color
           Green := Green + deltaTime * 0.2;
         end;

      2begin
           if Red <= 1 then GLCube1.Tag := 3;
           Red := Red + deltaTime * 0.2;
         end;

      3begin
           if Green <= 1 then GLCube1.Tag := 0;
           Green := Green + deltaTime * 0.2;
         end;
      end;
end;

Am besten sieht das Beispiel natürlich aus, wenn man vorher alle Front-Farben des Würfels auf reines Schwarz gesetzt hat, und nur das Diffuse auf reines Blau, aber auch mit dem standardgrauen Würfel dürfte es sein Ziel nicht verfehlt haben.
Man kann also wesentlich mehr machen als nur Bewegung. Zum Beispiel auch KI, Umgebungssimulation (zum Beispiel Natur in Spielen, physikalische Größen bei Simulationen usw.), Tonausgabe oder andere Abfragen und Berechnungen.

Verkomplizieren wir das Thema ein wenig, indem wir unsere Hierarchie mit einbauen.
Dazu löschen wir alle Objekte bis auf die Kamera, für die wir die Position auf (0,2,-5) seten, und die Lichtquelle (die aber ein Child (in Position (0,0,0)) der Kamera bleibt).
Dann erzeugen wir drei Cubes. Den ersten färben wir gelb, lassen ihn in der Mitte und machen ihn zum Target der Kamera. Den zweiten färben wir blau, machen ihn als Child des ersten, positionieren ihn auf (3,0,0). Und den letzten schließlich machen wir als Child des blauen, lassen ihn grau und positionieren ihn auf (0.7,0.7,0).
Dann kommt folgendes ins Progress-Event:
ausblenden Delphi-Quelltext
1:
2:
3:
GLCube1.Turn(deltaTime * 90);
GLCube2.Pitch(-deltaTime * 120);
GLCube2.Turn(deltaTime * 120);

Und schon haben wir mal eben unser Sonnensystem mit 3 fehlenden Planeten und 6 fehlenden Gasriesen, einschließlich deren Monden, aber immerhin unser aller Heimatplanet, dessen Satellit (wenn auch dessen Laufbahn nicht der Wahrheit entspricht) und die gute alte Sonne.

Zur Erklärung:
Wir drehen den ersten Würfel, die gelbe Sonne. Da die beiden anderen deren Children sind, drehen die sich mit, und zwar um die Sonne (nachzuvollziehen indem man die beiden Zeilen die den zweiten Cube betreffen auskommentiert). Dann drehen wir schließlich noch die Erde, und da diesmal nur der Mond an das Koordinatensystem gebunden ist, dreht der sich mit um die Erde.

So, bisher haben wir nur gedreht und die Farbe geändert.
Um richtig die Position zu verändern, modifizieren wir das letzte Beispiel etwas, indem wir alle Cubes in den Root bringen, also kein Cube mehr Children hat.
Dann verändern wir das Progress-Event folgendermaßen:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
procedure TForm1.GLCadencer1Progress(Sender: TObject; const deltaTime,
  newTime: Double);
begin
  GLCube1.Turn(deltaTime * 90);

  GLCube2.Turn(deltaTime * 50);
  GLCube2.Position.X := 3.5 * sin(DegToRad(newTime * 60));
  GLCube2.Position.Z := 3.5 * cos(DegToRad(newTime * 60));

  GLCube3.Pitch(deltaTime * 50);
  GLCube3.Position.X := GLCube2.Position.X + 0.8 * cos(DegToRad(newTime * 60));
  GLCube3.Position.Y := GLCube2.Position.Y;
  GLCube3.Position.Z := GLCube2.Position.Z + 0.8 * sin(DegToRad(newTime * 60));
end;

Ich sehe schon die ersten ihre Mathebücher rauskramen um zu schauen, was Sinus und Kosinus war, aus diesem Grund und weil ich kein Mathe-Lehrer bin, werde ich darauf jetzt nicht weiter eingehen.
Wenn man erstmal rausgekriegt hat, was Sin und Cos ist, versteht man schnell, was hier gemacht wird:
  1. Die Sonne wird gedreht.
  2. Dann wird die Erde um die sonne gedreht, und zwar mit Hilfe zweier um 90° verschobenen, periodischen Funktionen, deren X-Wert wir aus der Gesamtlaufzeit der Animation holen, der also stetig ansteigt.
  3. Zuletzt wird das gleiche mit dem Mond gemacht, allerdings wird die Position der Erde als Ausgangspunkt genommen, also von dort entsprechend abgezogen oder hinzuaddiert.


That's it.
Einfaches Prinzip, oder? Nach jedem Frame ändern wir Einstellungen der Objekte zeitabhängig, um für das nächste Frame andere Ausgangspunkte zu haben, wodurch eine gleichmäßige Animation entsteht.
Aber wir haben die ganze Zeit über Frames und Framerateunabhängigkeit geredet. Aber wieviele von denen haben wir eigentlich in einer Sekunde?
Fügen wir einfach dem Progress-Event folgende Zeile hinzu (den alten Code nicht entfernen):
ausblenden Delphi-Quelltext
1:
Form1.Caption := FloatToStr(GLSceneViewer1.Buffer.FramesPerSecond) + ' FPS';					

Der Buffer (also das, worauf wir letztendlich zeichnen, wenn wir es auch nicht selbst machen sondern machen lassen), gibt netterweise die FramesPerSecond als Float an, die wir uns anzeigen lassen können. Beobachtet man die Framerate bei verschiedenen Methoden der Animationskontrolle, kann man schön feststellen, wie optimal der Code ist. Man kann also während der Entwicklung eines Programmes ständig überprüfen, ob das zuletzt implementierte Feature zwar cool aussieht, aber die Framerate nicht in den Keller sinken lässt, oder ob die GraKa dermaßen darunter leidet, daß man es lieber sein lässt.
Wer sich die Framerate über längere Zeit hat anzeigen lassen, wird feststellen, daß sie sich zu Beginn sehr schnell ändert und sich mit der Zeit ?einpendelt?. Der Grund darin liegt, daß die Framerate des Buffers ein Durchschnitt ist, und zwar seit dem Anfang der Animation. Je länger diese also dauert, desto wenige fällt das letzte Frame ins Gewicht und desto langsamer verändert sich die Framerate. Um das zu unterbinden gibt es die Funktion ResetPerformanceMonitor, die den Framecounter wieder zurücksetzt. Da es FramesPerSecond heißt, bietet es sich an, diesen Aufruf einmal pro Sekunde durchzuführen. Da dadurch auch die Anzeige der Framerate beeinträchtigt wird, sollte man diese ebenfalls nur noch einmal pro Sekunde aktualisieren lassen, und zwar bevor man ResetPerformanceCounter aufruft. Also einen Timer auf die Form gesetzt, auf eine Sekunde eingestellt, folgende zwei Zeilen ins Timer-Event:
ausblenden Delphi-Quelltext
1:
2:
  Form1.Caption := FloatToStr(GLSCeneViewer1.FramesPerSecond);
GLSceneViewer1.ResetPerformanceMonitor;

noch schnell die eine Zeile aus dem Progress-Event vom Cadencer entfernt und F9 gedrückt.
Jetzt sieht man in der Caption der Form, wieviele Frames in der letzten Sekunde gerendert wurden.
Das war auch schon wieder alles, was man zur Framerate und deren Anzeige wissen muss.


Jetzt lasse ich euch mal wieder zappeln und arbeite an weiteren Animationsmöglichkeiten, zum Beispiel dem Scrollen in der Szene und den Behaviours.
Ihr könnt derweil andere Möglichkeiten unserer bisherigen, recht primitiven Animation ausprobieren, zum Beispiel ein Auto (was man ja aus GLCubes und GLCylinders zusammenbasteln kann), das ständig im Kreis fährt (oder auch eine Strecke abfährt, allerdings müsste man das irgendwie begrenzen und irgendwann das Auto zurückfahren lassen).

_________________
Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert
tommie-lie Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 4373

Ubuntu 7.10 "Gutsy Gibbon"

BeitragVerfasst: Sa 03.05.03 23:31 
Ich stellte mir gerade die Frage, was einfacher ist, Scrolling oder Behaviours, und komme zu dem Schluß, daß es wohl Scrolling sein wird... (und ich da außerdem gleich den DummyCube erklären kann)

Also, entfernen wir mal alle Objekte aus unserer Szene, außer die Camera und die Lichtquelle, den Cadencer auf der Form können wir bahelten, den brauchen wir später noch. Dann fügen wir einen GLDummyCube hinzu (direkt aus dem Root der Objektliste). Dann ziehen wir die Kamera (mit ihrem Lichtquellen-Child) in den DummyCube.
Aber bevor wir weitermachen, kurz, was ein DummyCube ist und was er bringt. Ein DummyCube ist, wie sein Name vermuten lässt, würfelförmig, hat also ähnliche Properties. Im Gegensatz zum normalen Cube hat er aber keine Materials und wird nicht aus Flächen zusammengesetzt und ist obendrein standardmäßig unsichtbar zur Laufzeit (wird nicht gerendert). Nun wird man fragen, was ein Würfel soll, der nicht gerendert werden soll. Die Antwort ist: er soll einen Parent darstellen. Indem man viele Objekte als Child eines DummyCubes macht, ann man bequem die Objekte zusammenfassen, ohne unbedingt ein sichtbares Objekt benutzen zu müssen. Durch bewegen des DummyCubes bewegen sich alle Childs mit und man muss nicht jedes Objekt einzeln bewegen (langsam wird klar, warum wir den für's Scrollen brauchen ;-) ). Er führt aber auch neue, DummyCube-spezifische Properties ein:
  • Amalgamate = besonderer rendering Modus. Er baut aus allen Childs des DummyCubes eine Displayliste, die statisch bleibt. D.h., wenn man die relative Position eines Childs ändern will, muss man nachher GLDummyCube1.StructureChange aufrufen, damit die Displayliste neu erstellt wird. Die Benutzung einer Displaylist kann die Performance in einigen Situationen erhöhen, weil die Objekte nicht neu berechnet werden müssen. Allerdings bringt sie nichts, wenn sich die Position der Objekte der ihr Lighting öfters änder, deswegen bringt dieses Property nur etwas, wenn die Objekte nicht transparent sind und sich ihre Position über längere Zeit nicht ändert (also eigentlich recht selten :-) ).
  • EdgeColor = legt die Farbe der Kanten fest, die man zur Designzeit (und evtl zur Laufzeit) sieht
  • VisibleAtRuntime = hiermit wird festgelegt, ob man den Würfel zur Laufzeit sieht. Aber selbst dann sieht man nur die Kanten, nicht die Flächen, und das Ding ist transparent. Kann ab und zu sinnvoll sein, auch wenn mir z.Zt. kein Beispiel einfällt

Wir haben also die Kamera als Child des DummyCubes und die Lichtquelle als Child der Kamera. Jetzt noch das Target der Kamera auf den DummyCube gestellt und fertig.
Beginnen wir mit der Rotation. Dazu aktivieren wir das VisibleAtRuntime-Property des DummyCubes (oder erzeugen ein oder mehrere Objekte außerhalb des DummyCubes, aber im Sichtbereich der Kamera, Ziel ist es, Anhaltspunkte zu haben) und erzeugen ein paar neue Eventhandler und Variabeln:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
// globaler var-Abschnitt:
var
  Form1: TForm1;
  MousePos: TPoint;

// OnMouseDown von GLSceneViewer1:
procedure TForm1.GLSceneViewer1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  MousePos.X := X;
  MousePos.Y := Y;
end;

// OnMouseMove von GLSceneViewer1:
procedure TForm1.GLSceneViewer1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
  if ssLeft in Shift then
    GLCamera1.MoveAroundTarget((MousePos.Y - Y) / 2, (MousePos.X - X) / 2);

  MousePos.X := X;
  MousePos.Y := Y;
end;

Ich weiß, das mit der globalen Variable ist nicht die eleganteste Methode, aber darüber will ich hier keine Vorträge hören, es ist einfach und schnell gemacht.
Zum Ablauf:
Wir speichern die Position, wo die Maus geklickt wurde. Dann, sobald sie bewegt wird (und die linke Maustaste gedrückt wurde), Drehen wir die Kamera um das Target. Hier sieht man einen Vorteil der Benutzung von DummyCubes für freie Kameraausrichtung an Stelle der Direction- und Up-Properties, nämlich daß man eifnach die Kamera um etwas herum bewegen kann und sich nicht mit dem ganzen (in Delphi sowieso nicht optimalen) Mathe-Kram rumärgern muss. MoveAroundTarget übernimmt als Parameter zwei Singles, und zwar nicht zwei alleinstehende Personen, sondern pitchDelta und turnDelta, also die Differenzen für die Rotation um X- und Y-Achse.
Damit wir bei der nächsten Bewegung wieder nur die Differenz (Delta, wir erinnern uns), speichern wir noch schnell die Position und fertig. Nach dem Kompilieren und Aufrufen können wir uns um unseren DummyCube drehen (sofern wir ihn auf VisibleAtRuntime gestellt haben, und/oder sehen andere Objekte in der Umgebung unseren Bewegungen folgen (oder auch nicht folgen, je nachdem von welchem Standpunkt aus). Einfach, oder?
Aber ich habe Scrolling versprochen, und werde Scrolling halten.
Also neues Event:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
// OnKeyDown von TForm1
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TshiftState);
  case Key of
    VK_Left: GLDummyCube1.Position.X := GLDummyCube1.Position.X - 0.1;
    VK_Right: GLDummyCube1.Position.X := GLDummyCube1.Position.X + 0.1;
    VK_Up: GLDummyCube1.Position.Z := GLDummyCube1.Position.Z + 0.1;
    VK_Down: GLDummyCube1.Position.Z := GLDummyCube1.Position.Z - 0.1;
  end;
end;

Jetzt kann man ziemlich primitiv mit den Pfeiltasten scrollen. Um hier was zu sehen braucht man unbedingt andere Objekte außerhalb des DummyCubes, sonst weiß man nicht, ob man sich wirklich bewegt.
Aber wenn man sich dreht, und dann versucht zu scrollen, klappt das nicht wirklich wie gewünscht (Quizfrage: Wer kann mir sagen, warum nicht?).
Normalerweise würde ich jetzt erst das tastatur- und framerateunabhängige erklären, denn OnKeyDown wird ja entsprechend der in Windows eingestellten Tastaturwiederholungsrate (zumindest in Win98, bei den NT-Systemen weiß ich nicht genau, aber ich denke mal dort ist es genauso) ausgelöst, also nicht sehr systemunabhängig.
Stattdessen mache ich beides in einem und erkläre gleich mit, wie man das Problem mit dem Drehen lösen kann (Vorsicht: Vektorarithmetik wird dringend vorausgesetzt!).
Dazu binden wir erstmal die Unit ?Geometry? von GLScene ein. Das ist sowas wie Math, nur schneller und mit vektorarithmetischen Funktionen (Sachen wie Sinus, Cosinus, usw. kann man auch mit Geometry berechnen lassen, und zwar wesentlich schneller als in Math, daher sollte man generell ganz auf Math verzichten und Geometry nutzen, auch in Nicht-GLScene-Programmen, die viel rechnen müssen). Dann deklarieren wir folgende neue Vriablen:
ausblenden Delphi-Quelltext
1:
2:
3:
ScrollDir: set of (sdUp, sdDown, sdLeft, sdRight) = []; // wohin scrollen wir eigentlich?
ScrollVector: TVector; // der dazugehörige (schon fertig gedrehte) Vektor
Speed: Single = 1// Scrollgeschwindigkeit

Dann verändern wir natürlich das KeyDown-Event und fügen ein KeyUp-Event hinzu:
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:
// OnKeyDown von Form1
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
//  if not ((sdLeft in ScrollDir) or (sdRight in ScrollDir)) then
    if Key = vk_Left then
      ScrollDir := ScrollDir + [sdLeft]
    else if Key = vk_Right then
      ScrollDir := ScrollDir + [sdRight];

    if Key = vk_Up then
      ScrollDir := ScrollDir + [sdUp]
    else if Key = vk_Down then
      ScrollDir := ScrollDir + [sdDown];


  SetVector(ScrollVector, 000);
  with GLCamera1 do
  begin
    if sdLeft in ScrollDir then
      AddVector(ScrollVector, ScreenDeltaToVectorXZ(-Speed, 01))
    else if sdRight in ScrollDir then
      AddVector(ScrollVector, ScreenDeltaToVectorXZ(Speed, 01));

    if sdUp in ScrollDir then
      AddVector(ScrollVector, ScreenDeltaToVectorXZ(0, Speed, 1))
    else if sdDown in ScrollDir then
      AddVector(ScrollVector, ScreenDeltaToVectorXZ(0, -Speed, 1));
  end;
end;

// OnKeyUp von Form1
procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if (Key = vk_Left) and (sdLeft in ScrollDir) then
    ScrollDir := ScrollDir - [sdLeft]

  else if (Key = vk_Right) and (sdRight in ScrollDir) then
    ScrollDir := ScrollDir - [sdRight]

  else if (Key = vk_Up) and (sdUp in ScrollDir) then
    ScrollDir := ScrollDir - [sdUp]

  else if (Key = vk_Down) and (sdDown in ScrollDir) then
      ScrollDir := ScrollDir - [sdDown];


  SetVector(ScrollVector, 000);
  with GLCamera1 do
  begin
    if sdLeft in ScrollDir then
      AddVector(ScrollVector, ScreenDeltaToVectorXZ(-Speed, 01))
    else if sdRight in ScrollDir then
      AddVector(ScrollVector, ScreenDeltaToVectorXZ(Speed, 01));

    if sdUp in ScrollDir then
      AddVector(ScrollVector, ScreenDeltaToVectorXZ(0, Speed, 1))
    else if sdDown in ScrollDir then
      AddVector(ScrollVector, ScreenDeltaToVectorXZ(0, -Speed, 1));
  end;
end;

Die letzten Abschnitte der beiden Prozeduren kommen einem natürlich extrem spanisch vor. Aber das erkläre ich gleich, nachdem wir das Progress-Event des Cadencers geändert haben:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
procedure TForm1.GLCadencer1Progress(Sender: TObject; const deltaTime,
  newTime: Double);
begin
  if not VectorEquals(ScrollVector, NullHmgVector) then
    GLDummyCube1.Position.AddScaledVector(deltaTime, ScrollVector);
end;

Kompiliert man jetzt, erhält man ein Wunderwerk meiner Programmierkun... ähh, wie dem auch sei, man kann mit den Pfeiltasten scrollen, wenn mehrere gedrückt werden, scrollt man entsprechend schräg (also nach vorne-rechts, bspw.), und wenn man sich dreht, dreht sich die Scrollrichtung gleich mit. Mit der Variable Speed kann man die Scrollgeschwindigkeit ändern, aber man muss ganze Zahlen angeben (oder das Prinzip ändern, was aber nichts mit GLScene zu tun hat, sondern eher eine programmiertechnische Aufgabe ist, für deren Lösung ihr das Wissen jedoch schon habt (Lösungsvorschläge an mich, der beste gewinnt ;-) )). Wer allerdings hoch/runter und links/rechts gleichzeitig drückt, wir ebenfalls noch einen Schönheitsfehler entdecken. Auch das ist eine rein programmiertechnische Aufgabe, für die lediglich ein paar weitere if-Abfragen nötig sind (Lösungsvorschläge auch an mich).

Diese Methode ist jetzt vollkommen systemunabhängig. Das könnt ihr einfach ausprobieren, indem ihr in Windows unter Systemsteuerung -> Tastatur (Win9x, bei WinNT-Systemen entsprechend ändern, falls notwendig) die Verzögerung oder die Widerholrate ändert. Auch von der Framerate ist es unabhängig, das könnt ihr ausprobieren, indem ihr diese ein wenig senkt (z.B. durch die bekannten Properties des Cadencers oder einem externen Taktgeber für diesen).

Nun aber zur Erklärung des letzten Blockes der beiden Key-Events. Einige Funktionen daras stammen aus der Geometry.pas.
Also, Erklärung:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
// setzt den Scrollvektor auf (0,0,0), er besitzt also keine Richtung und Länge
SetVector(ScrollVector, 000);
  with GLCamera1 do
  begin
    if sdLeft in ScrollDir then
// AddVector addiert zwei Vektoren (ScrollVektor und Result von ScreenDeltaToVectorXZ), siehe gutes Mathebuch ;-)
// ScreenDeltaToVectorXZ gehört zur Camera (daher auch das mit dem with/do) und gibt einen Vektor für die Bildschirmkoordinaten (Einheit also Pixel) zurück, und zwar projiziert auf die XZ-Ebene (es gibt auch XY und YZ, und einmal ohne Buchstaben, bei der man einen Normalenvektor für die Ebene angeben muss). Die Parameter sind X, Y und Ratio (Multiplikator für die Länge des Vektors)
      AddVector(ScrollVector, ScreenDeltaToVectorXZ(-Speed, 01))
    else if sdRight in ScrollDir then
      AddVector(ScrollVector, ScreenDeltaToVectorXZ(Speed, 01));

    if sdUp in ScrollDir then
      AddVector(ScrollVector, ScreenDeltaToVectorXZ(0, Speed, 1))
    else if sdDown in ScrollDir then
      AddVector(ScrollVector, ScreenDeltaToVectorXZ(0, -Speed, 1));
  end;

Es wird also zuerst ein fiktiver (Speed) Delta vom Bildschirm genommen (der ist ja entsprechend gedreht (bzw das Bild wird fertig gedreht angezeigt), daher über den Screen, sonst müsste man ständig den Vektor mitdrehen, was langsamer wäre). Das ganze dann wür alle Richtungen, fertig.

Zwei Codezeilen dürften jetzt noch unbekannt sein, und zwar folgende:
ausblenden Delphi-Quelltext
1:
2:
if not VectorEquals(ScrollVector, NullHmgVector) then
  GLDummyCube1.Position.AddScaledVector(deltaTime, ScrollVector);

im Progress-Event. Das ist schon ein Teil einer Optimierung (ohne Zweifel kann man durch weitere if-Abfragen den Code weiter optimieren, was aber hier zu kompliziert geworden wäre). VectorEquals gibt True zurück, wenn beide Vektoren gleich sind. NullHmgVector ist eine Konstante in Geometry.pas, und zwar ein einheitlicher Nullvektor, also ein Vektor mit Länge 0 und Richtung 0 (undefinierte Richtung). Die zweite Zeile weist dann der Position des DummyCubes (mit dem wir ja die Kamera mitbewegen) einen Vektor zu


Erstmal wieder kleine Verschnaufpause, dann geht's weiter mit den Behaviours (auch wenn mich die Physikstudenten hier (;-)) hier in der Luft zerreißen werden, wenn ich von Trägheit und Massenbeschleunigung anfange).
Die Lösungsvorschläge der kleinen Aufgaben können übrigens wirklich an mich geschickt werden, ich schaue dann, ob's noch besser geht. Auch mit anderen Problemen zum Thema könnt ihr zu mir kommen.

_________________
Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert
Christian S.
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: So 04.05.03 11:51 
Du kannst Dir ruhig etwas mehr Zeit lassen. Ich komme mit dem Lesen schon nicht mehr nach. Ach ja, und auf die Trägheit bin ich echt gespannt ... :lol:

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
tommie-lie Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 4373

Ubuntu 7.10 "Gutsy Gibbon"

BeitragVerfasst: Fr 30.05.03 19:58 
Nun geht es endlich weiter mit den Bewegungen, und zwar mit den Behaviours.
Zuerst erzeugen wir wieder eine leere Szene, nur mit Lichtquelle, Camera, einem DummyCube und einem normalen Cube.
Wie wir die Camera usw ausrichten wissen wir ja bereits, ich empfehle eine große Entfernung zum DummyCube (ungefähr 20 Einheiten). Aber der Cube hat ein Property „Behaviours“, welches wir hier auseinandernehmen werden.
Klicken wir also mal auf die Ellipse hinter dem Property. In dem Fenster gibt es ein Plus- und ein Minus-Symbol, und, richtig geraten, fügt Behaviours hinzu oder entfernt sie. Da Behaviours ähnlich Strukturiert sind wie die Effekte, sieht man in der Liste vom Plus-Icon auch noch die Effekte, die aber ausgegraut sind. Die Einträge, die zu Verfügung stehen sind Inertia, Sound Emitter, Collision und Movement Controls. Sound Emitter lasse ich hier mal weg, weil es ein etwas umfangreicheres Gebiet mit 3D-Sound ist, wovon man ohne netsprechende Lautsprecher und Grafikkarte sowieso nichts mitbekommt.
Fangen wir mit dem einfachsten, dem Movement Controls an.
Ein Movement Control lässt ein Objekt einen zuvor festgelegten Pfad entlangfahren, ohne das man selbst etwas dafür machen muss. Also eine echte, kontrollierte Bewegung. Das ist zum Beispiel praktisch, wenn man einen Zug eine Strecke abfahren lassen will, die ja immer gleich ist und deswegen nicht dynamisch berechnet werden muss.
Zuerst fügen wir die Units GLBehaviours und GLMovement unserer Unit hinzu, da dies ausnahmsweise nicht automatisch gemacht wird. Das Behaviour selbst hinzufügen dürfte kein großes Problem darstellen. Wählt man es dann in der Liste aus, erscheinen im Object Inspector die entsprechenden Properties. Die Ellipse hinter dem Paths-Property öffnet ein kleines Fenster, in dem man Pfade hinzufügen kann. Ein Objekt kann mehrere Pfade enthalten, man kann also wenn man Zugstrecken mit Weichen hat, sogar zwischen Pfaden wechseln ;-). Ein solcher Pfadeintrag hat einige Properties, die weitestgehend selbst erklärend sind. Der SplineMode legt fest, wie der Pfad geglättet werden soll, allerdings nur für die Visualisierung (ShowPath := True), die Bewegung an sich ist immer kubisch geglättet. Ich habe aber in den GLScene-Newsgroups mal gepostet, warum das nicht auch bei derBewegung so ist (in der Tat wird im Sourcecode gar nicht nach dem SplineMode gefragt, es wird immer ein TCubicSpline erzeugt). Der Pfadeintrag selbst wiederum hat ein Nodes-Property. Diesem muss man mindestens 2 Knoten hinzufügen. Jeder Knoten hat eine Position, Speed und Scale und Angle für jede drei Richtungen. Speed gibt an, welche Geschwindigkeit das Objekt nach diesem Knoten haben soll, das gilt allerdings für die Strecke zwischen diesem Knoten und dem nächsten, der Übergang ist also nicht besonders sanft. Die drei Scales und Angles verändern das Objekt selbst. Diese werden auf die Strecke hin interpoliert, werden also wirklich gleichmäßig angepasst. Alle 6 sollten von den normalen Objekten bekannt sein und strecken, bzw drehen das Objekt.
So, also ein paar Knoten nach Belieben hinzufügen, aber aufpassen: jeder Knoten muss einen Speed > 0 haben!
Wieder zurück zu den Behaviours selbst (also die zwei Fenster für Knoten und Pfade wieder schließen). Dort gibt es für das Path Control Behaviour ein Property ActivePathIndex, welches den zur Zeit aktiven Pfad angibt. Da wie immer bei 0 angefangen wird zu zählen, müssen wir es für unseren ersten Path auf 0 stellen. Daraufhin wird das ActivePath-Property gültig und erlaubt direkten Zugriff auf den Path. Das Property AutoStartNextPath ist nur dazu da, den nächsten Pfad automatisch zu laden, wenn der alte abgefahren wurde, erhöht also jedesmal den ActivePathIndex. Bei einem Pfad macht das wenig Sinn ;-)
So, nachdem wir das Programm kompilieren und starten, passiert aber noch nichts. Das liegt daran, daß wir den Pfad erst starten müssen, aber zusätzlich fehlt noch ein Cadencer, denn wir wissen ja bereits, daß ein Cadencer gelcihförmige Bewegung steuert, auch die der Behaviours und Effekte. Also einen Cadencer auf die Form und richtig eingestellt, und einen Eventhandler für das Form.OnClick-Event erstellen. Darein kommt dann folgender Code:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
procedure TForm1.FormCreate(Sender: TObject);
var
  Movement: TGLMovement;
begin
  { da TGLBehaivours eine Liste ist, haben wir keinen direkten Zugriff
    auf das Movement-Behaviour, also müssen wir es uns zuerst holen }

  { da wir wissen, daß eins da ist, können wir GetMovement benutzen, ansonsten
    immer GetOrCreateMovement! }

  Movement := GetMovement(GLCube1);
  { und ab geht die Reise }
  Movement.StartPathTravel;
end;

Wenn jetzt keiner der Knoten einen Speed von 0 hat, sieht man den Cube den Pfad entlangfahren, ist Looped-Property vom Path auf True, sogar unendlich oft.
Mit StopPathTravel hält man dann den Pfad wieder an. Movement.ActivePath zeigt den aktiven Pfad an, wenn man ihm dynamisch einen anderen zuweist, hat man die Möglichkeit die Pfade zu ändern.
Ein Beispiel, wie man den Pfad dynamisch erzeugt und Knoten hinzufügt findet man im GLScene-Verzeichnis bei den Demos im Unterverzeichnis /demos/behaviours/pathcontrol.




Und da kommen wir auch schon zu den Inertias. Dazu muss man nur die GLBehaviours-Unit einbinden, den Rest der Szene könne wir aber vom Aufbau her behalten (die alten Event-Handler natürlich rausschmeißen).
So, nun das Simple Inertia Behaviour unserem Cube hinzufügen.
Schauen wir uns zunächst einmal das RotationDamping-Property an. Es besteht aus Constant, Linear und Quadratic, die alle verschiedene Arten der Beschleunigung sind.
  • Constant: die Dämpfung, also der Geschwindigkeitsverlust, bleibt konstant
  • Linear: Die Dämpfung sinkt linear, nach der Formel Dämpfung = linear * Geschwindigkeit
  • Quadratic: Die Dämpfung sinkt quadratisch nach der Formel Dämpfung = quadratic * Geschwindigkeit². Ist das Objekt einmal in Bewegung wird es also nie stillstehen, weil selbst ein kleiner Wert, weil die Dämpfung sich asymptotisch dem Nullpunkt annähert, ihn aber nie erreicht.

Durch verknüpfen der Werte (also mehr als einen der drei Werte benutzen), kann man so das Verhalten eines Objektes feiner verändern. Aber wie der Name schon sagt handelt es sich hier um ein RotationDamping, also um Dämpfung der Rotationsgeschwindigkeit.
Bei den Behaviours wird eine Rotationsgeschwindigkeit durch eine Kraft erzeugt, bzw einen Drehmoment.
Setzen wir also beliebige Werte für die drei Damping-Komponenten und schreiben in das OnClick-Event des SceneViewers folgendes:
ausblenden Delphi-Quelltext
1:
GetOrCreateInertia(GLCube1).ApplyTorque(0.12002000);					

Damit holen wir uns das Inertia von unserem Würfel und weisem ihm einen Drehmoment zu. Der erste Parameter (deltaTime) ist dabei die Zeit, wie alnge die Kraft auf das Objekt wirken soll, die restlichen drei Parameter geben die Richtung an und stehen für Pitch, Roll und Turn.
Jetzt wird dem Würfel jedesmal wenn wir auf den SceneViewer klicken ein Drehmoment zugewiesen, und zwar additiv, klicken wir also nochmal bevor der Würfel zum Stillstand gekommen ist, addieren sich die beiden resultierenden Drehgeschwindigkeiten. Jetzt kann man mit den drei Damping-Werten ein wenig rumspielen, dann wird auch klarer, was welcher Wert macht.
Ein weiteres Inertia-Property ist das Mass-Property, das die Masse des Objektes angibt, die man in den Formeln wiederfindet. Die Masse eines Objekts beeinflusst hier nur die Kraft, die an ihm wirkt, nicht die Dämpfung. Da sich ein schwereres Objekt auch schwerer beschleunigen lässt, braucht man, wenn man für Mass einen höheren Wert angibt, auch entweder eine größere Kraft (einen gräßeren Drehmoment), oder muss diese länge wirken lassen (größere deltaTime).
Ein wenig interessanter wird es mit dem TranslationDamping, denn es wird auch ein wenig mathematischer/realistischer. Hier ist die Kraft kein auf das Objekt projizierter Winkel für die drei Richtungen, sondern ein echter Vektor. Die Bedeutung der drei Damping-Komponenten ist aber der gleiche wie für das RotationDamping. Schreiben wir also unseren Event-Handler folgendermaßen um:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
procedure TForm1.GLSceneViewer1Click(Sender: TObject);
var
  force: TVector;
begin
  SetVector(force, 0030);
  GetOrCreateInertia(GLCube1).ApplyForce(0.1, force);
end;

Wir erzeugen also erst einen Vektor (force) und weisen ihn dann für 0.1 Sekunden unserem Würfel zu. Die drei letzten Parameter von SetVector stehen für die drei Richtungen, der Würfel müsste sich also nun in z-Richtung in den positiven Bereich bewegen. Nach dem Kompilieren und Klicken tut er das auch.
Eine andere Funktion vom TGLInertia ist ApplyTranslationAcceleration. Sie bewirkt auf den ersten Blick das gleiche wie ApplyForce, tut sie aber nicht. ApplyForce wendet an dem Objekt eine Kraft an, die abhängig von dessen Masse in Beschleunigung umgewandelt wird. ApplyTranslationAcceleration hingegen beschleunigt das Objekt, unabhängig von der Masse, aber durch das Damping abgebremst wird es immer noch.
Die restlichen Properties von TGLInertia sind schnell erklärt:
[list][*]DampingEnabled: Legt fest, ob das Damping berücksichtigt wird oder nicht
[*]RollSpeed, PitchSpeed, TurnSpeed: Initiale Rotation des Objektes, nachdem es erzeugt wurde. Die Drehung wird wie ApplyTorque mit dem Damping verrechnet und stoppt je nach Einstellung früher, später oder gar nicht...
[*]TranslationSpeed: Die initiale Geschwindigkeit des Objektes, ebenfalls nach dem erzeugen. Auch hier wird das Damping das Objekt ggbfs. irgendwann zum Stillstand bringen.

Das gesamte Dämpfungsmodel von GLScene ist allerdings nicht ganz der Realität entsprechend. Bei GLScene wird aus der Masse des Ojektes und dem angegebenen Vektor ein Vektor erzeugt, der der Geschwindigkeit abgezogen wird und das Objekt so verlangsamt. In der Realität entspricht die Dämpfung in GLScene aber der Reibung, die dadurch entsteht, daß die Oberfläche des Objektes an der Oberfläche der Umgebung (Luft, Wasser, Diamanten falls verfügbar *g*) reibt und somit der Geschwindigkeit einen Vektor entgegensetzt, der die Geschwindigkeit zwangsläufig verringert (oder sogar in eine andere Richtung lenkt). Das alles ist in GLScene nicht der Fall, also ist es nicht wirklich Reibung, mit den richtigen Einstellungen kann man aber durchaus realistische Reibung mit GLScene simulieren. Eric Grange freut sich übrigens über eine realistischere Implementation, wenn die Physikdoktoranden hier also genug Zeit und Muße haben und ein neues Reibungsmodel implementieren, daß nicht so sehr auf die Performance drückt, einfach an ihn schicken.



So, das war auch schon der Anfängerkurs in Sachen Bewegung.
Fragen und technische Hinweise (da ich den gesamten Physikstoff noch nicht in der Schule hatte, kann da nicht alles richtig sein, das glaub' ich einfach nicht...) per PN oder eMail an mich.


Themenwünsche für den dritten Teil bitte ebenfalls an mich! (sonst wird es vielleicht keinen geben...)

_________________
Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert
worm
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 135


D6 Prof
BeitragVerfasst: Fr 04.07.03 22:09 
kleine Tipps für Mehrherausfindenwoller:
Mit IsKeyDown (Funktion aus keyboards.pas von GLScene, nur ein Wrapper für GetAsyncKeyState) lässt sich überprüfen, ob eine Taste gerade gedrückt ist. Das ist manchmal ganz praktisch im OnProgress-Event des Cadencers, auf jeden Fall ist man nicht immer darauf angewiesen, OnKeyDown und OnKeyUp abzufangen.

Und bei der Inertia gibt es auch noch die lustige Funktion SurfaceBounce. Ihr wird als Parameter der Normalenvektor der Ebene übergeben, an der das Objekt abprallen soll, und ein weiterer Parameter namens Restitution. Normalenvektor bedeutet: Ein Vektor, der genau senkrecht auf der Ebene steht. Ein Vektor, der nach oben (oder unten) zeigt, beschreibt also die Ebene des Erdbodens. Restitution gibt an, wie hart oder weich der Aufprall ist; also wieviel Geschwindigkeit dabei verloren geht. 1 bedeutet kein Geschwindigkeitsverlust, 0 bedeutet kein Abprall.
Zum Ausprobieren könnt ihr ein GLSphere auf die Scene packen, die Y-Koordinate auf z.B. 5 stellen, und im OnProgress-Event des GLSphere die Kugel mit GetOrCreateInertia(TGLSceneObject(Sender)).ApplyTranslationAcceleration(-deltaTime*9.81, YVector) runterfallen lassen. Per
ausblenden Delphi-Quelltext
1:
2:
if TGLSceneObject(Sender).Position.Y <= 0 then
  GetOrCreateInertia(TGLSceneObject(Sender)).SurfaceBounce(YVector, 0.6);
lasst ihr sie dann vom Boden abprallen. Experimentiert ein bisschen mit den Werten und habt Spaß. So wie die Codezeilen jetzt geschrieben sind, könnt ihr auch noch beliebige weitere Objekte (Kugeln, Zylinder, Würfel) zur Scene hinzufügen, auch auf verschiedenen Höhen, denen die gleiche Prozedur für OnProgress zuweisen und dann beobachten, wie sie alle gleichzeitig runterfallen und aufprallen :D.

PS @tommie-lie: Falls Du schon an einer Fortsetzung schreibst und ich dir jetzt irgendwas weggenommen habe, kannst Du's aus meinem Beitrag entfernen :)

_________________
In the beginning, the universe was created. This has made a lot of people very angry, and is generally considered to have been a bad move.


Zuletzt bearbeitet von worm am Sa 05.07.03 00:11, insgesamt 1-mal bearbeitet
tommie-lie Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 4373

Ubuntu 7.10 "Gutsy Gibbon"

BeitragVerfasst: Fr 04.07.03 22:38 
Danke worm, SurfaceBounce habe ich wohl übersehen...

Einen dritten Teil wird es hier wohl nicht geben, mangels Interesse...
aber du eknnst ja meine Pläne *g*

_________________
Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert
mimi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3458

Ubuntu, Win XP
Lazarus
BeitragVerfasst: So 06.07.03 11:55 
ich beschäfidge mich gearde mit glSCene und will ein brekOut clone schreiben:)bei mir findet er GetOrCreateInertia nicht.

_________________
MFG
Michael Springwald, "kann kein englisch...."
tommie-lie Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 4373

Ubuntu 7.10 "Gutsy Gibbon"

BeitragVerfasst: So 06.07.03 13:04 
hast du auch die Unit GLBehaviours eingebunden?

_________________
Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert
mimi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3458

Ubuntu, Win XP
Lazarus
BeitragVerfasst: So 06.07.03 13:40 
ja... aber es gibt leider complier fehler :(
könntes du mir das mit den vertoren noch etwas genauer beschreiben ?
Edit:
Zitat:

[Fehler] Unit1.pas(97): Inkompatible Typen: 'TVector4f' und 'TVector3f'
[Fehler] Unit1.pas(98): Operator oder Semikolon fehlt
[Hinweis] Unit1.pas(47): Das private-Symbol 'lastRenderTime' wurde deklariert, aber nie verwendet
[Hinweis] Unit1.pas(50): Das private-Symbol 'ResetGame' wurde deklariert, aber nie verwendet
[Fataler Fehler] Project2.dpr(5): Verwendete Unit 'Unit1.pas' kann nicht compiliert werden

_________________
MFG
Michael Springwald, "kann kein englisch...."
mimi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3458

Ubuntu, Win XP
Lazarus
BeitragVerfasst: So 06.07.03 15:20 
könntes du mir bitte ein beispiel posten wie man das mit der colisions abfrage bei einem BreakOut clone am besten machen könnte ?

_________________
MFG
Michael Springwald, "kann kein englisch...."
tommie-lie Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 4373

Ubuntu 7.10 "Gutsy Gibbon"

BeitragVerfasst: So 06.07.03 16:58 
mimi hat folgendes geschrieben:
ja... aber es gibt leider complier fehler :(
Zitat:

[Fehler] Unit1.pas(97): Inkompatible Typen: 'TVector4f' und 'TVector3f'
[Fehler] Unit1.pas(98): Operator oder Semikolon fehlt
[Hinweis] Unit1.pas(47): Das private-Symbol 'lastRenderTime' wurde deklariert, aber nie verwendet
[Hinweis] Unit1.pas(50): Das private-Symbol 'ResetGame' wurde deklariert, aber nie verwendet
[Fataler Fehler] Project2.dpr(5): Verwendete Unit 'Unit1.pas' kann nicht compiliert werden

Das snd aber keine Fehler die von GLBehaviours.pas kommen, und auch keine, die was mit GetOrCreateInertia zu tun haben, damit ist also alles in Ordnung... :roll:

Zitat:
könntes du mir das mit den vertoren noch etwas genauer
beschreiben ?

Das ist ein etwas komplexeres Thema, welches man z.B. in der Oberstufe (worm, welche Klasse genau? 12?) lernt. Im Prinzip kannst du dir einen Vektor als Pfeil vorstellen. Der Pfeil hat eine Richtung, die mit drei Koordinaten dargestellt wird, X, Y und Z. Dazu hat der Pfeil noch eine Länge, den so genannten Betrag, der sich aus den Koordinaten berechnen lässt. Daher wird die Länge meist nicht angegeben.
Du scheinst also in Zeile 97 irgendeine Variable falsch deklariert zu haben. Da ich weder weiß, was du dort aufrufst, noch welche Parameter du übergibst und wie diese deklariert sind, kann ich dir da nicht weiterhelfen ;-)

Zitat:
könntes du mir bitte ein beispiel posten wie man das mit der colisions abfrage bei einem BreakOut clone am besten machen könnte ?

Nein :mrgreen:
Das ist mir dann doch ein bisschen zu viel Aufwand. Aber im Prinzip so wie das Pong-Beispiel aus den GLScene.Demos, da wird auch mit Vektoren gearbeitet, die gedreht und gespiegelt und was weiß ich werden, wenn der Ball irgendwas berührt.

_________________
Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert
mimi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3458

Ubuntu, Win XP
Lazarus
BeitragVerfasst: So 06.07.03 17:04 
in zeile 97 steht der besagte befehl drin wenn ich den rauß mache gehts wieder.
naja. werde ich mal schauen ob noch was machen kann, sonst muss ich es aufgeben :(

_________________
MFG
Michael Springwald, "kann kein englisch...."
mimi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3458

Ubuntu, Win XP
Lazarus
BeitragVerfasst: So 06.07.03 21:02 
ich habe das jetzt geschaft. aber leider ist da noch ein kleiner fehler drin:
Zitat:

-S s
- -
- -
- -
- S -
- . -

s ist der anstos punkt vom ball so ungeäfr sieht es aus.
den code den ich geschrieben habe ist folgender:
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:
procedure TForm1.GLCadencer1Progress(Sender: TObject; const deltaTime,
  newTime: Double);
begin
//  Label3.Caption:=;

  if xdir = True then
    Ball.Position.x:=(Ball.Position.x+Speed*deltaTime)-w/10
  else
    Ball.Position.x:=(Ball.Position.x-Speed*deltaTime)-w/10;


  if YDir = True then
    Ball.Position.y:=Ball.Position.y+Speed*deltaTime
  else
    Ball.Position.y:=Ball.Position.y-Speed*deltaTime;

  CollisionManager1.CheckCollisions;

//  GetOrCreateInertia(TGLSceneObject(Sender)).ApplyTranslationAcceleration(-deltaTime*9.81, YVector);
end;

procedure TForm1.CollisionManager1Collision(Sender: TObject; object1,
  object2: TGLBaseSceneObject);
begin
  Label1.Caption:=object1.Name;
  Label2.Caption:=object2.Name;

  if object2.Name <> 'Pad' then begin
    if Object1.Visible = True then begin
      XDIR:=Not XDIR;
      YDir:=Not YDir;
      if Speed >= 30 then l:=True;
      if Speed <= 10 then l:=False;
      
      if L = False then
        Speed:=Speed+0.5
      else
        Speed:=Speed-0.2;

      Label3.Caption:=FloatToStr(Speed);
    end;

    if Pos('Wand',object1.Name) = 0 then object1.Visible:=False;
  end
  else begin
    if Pos('Wand',object1.Name) = 0 then begin
      YDir:=Oben ;
      w:=w*20
    end;
  end;
end;


wie kann ich das so machen das wenn er auf das panel bzw. auf irngeas stößt sich der winkel vom ball ändert ?

_________________
MFG
Michael Springwald, "kann kein englisch...."
tommie-lie Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 4373

Ubuntu 7.10 "Gutsy Gibbon"

BeitragVerfasst: So 06.07.03 23:26 
Es gibt einmal die von worm angesprochene Methode mit Bounce, da kannst du den Normalenvektor der Fläche nehmen, also z.B. die des Schlägers, oder du machst es wie in der Pong-Demo gezeigt manuell mit dem Vektor. Dort steht im OnProgress-Event die Zeile
ausblenden Delphi-Quelltext
1:
ballVector[1]:=-ballVector[1];					

Daimt wird die Y-Richtung genau umgedreht.
So kannst du bei jeder Kollision überprüfen, in welche Richtung der Ball nun abprallen müsste, den Richtungsvektor entsprechend drehen (auch dafür gibt es eine Funktion aus der Geometry.pas, die kenn' ich aber im Augenblick nicht auswendig...) und am Ende kannst du dann mit VectorCombine den Richtungsvektor auf den Positionvektor des Balles (Ball.Position.AsVector) hinzuaddieren, fertig. Schau dir einfach die Demos an, es sind glaub' ich noch andere dabei, die sich mit Vektoren bzw Bewegung beschäftigen.

_________________
Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert
mimi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3458

Ubuntu, Win XP
Lazarus
BeitragVerfasst: Mo 07.07.03 09:31 
die demos habe ich mir schon alle angeschaut aber nicht alle verstanden.
könntes du mir einbeispiel machen das was du angesprochen hattes?

_________________
MFG
Michael Springwald, "kann kein englisch...."
tommie-lie Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 4373

Ubuntu 7.10 "Gutsy Gibbon"

BeitragVerfasst: Mo 07.07.03 15:42 
Also gut, folgender Code:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
procedure TForm1.Button1Click(Sender: TObject);
begin
  GLCadencer1.Enabled := True;
  SetVector(BallDirection, 0, -1.50);
end;

procedure TForm1.GLCadencer1Progress(Sender: TObject; const deltaTime,
  newTime: Double);
var
  ZAxis: TVector3f;
begin
  GLSphere1.Position.AsVector := VectorCombine(GLSphere1.Position.AsVector, BallDirection, 1, deltaTime);
  if GLSphere1.Position.Y < 0.5 then
  begin
    BallDirection[1] := -BallDirection[1];
    SetVector(ZAxis, 001);
    RotateVector(BallDirection, ZAxis, DegToRad(45));
  end;
end;

Der Cadencer ist vorher disabled, drückt man den Button wird er auf enabled geschaltet.
BallDirection ist ein Vector vom Typ TVector4f, der global sien sollte, oder zumindest vom OnProgress und OnClick "gesehen" werden kann.
Mit SetVector wird er beim Klick initialisiert, als "Pfeil" nach unten (-1,5 nach Y).
In OnProgress wird solange die Position mit dem BallDirection-Vector verrechnet, bis der Ball (die Sphere) unter Y=0,5 ist. Dann wird zunächst die Richtung umgedreht (TVector4f ist ein Array, wir haben nur Y angegeben (im Button-Click-Event) und brauchen daher nur das zweite Element (begonnen wird bei 0) umzudrehen). Anschließend wird ein fester Vector in Richtung Z-Achse ("durch den Bildschirm", also von vorne nach hinten) erzeugt und BallDirection um diesen Vektor gedreht, mit dem Winkel 45°. Beim nächsten Durchlauf von OnProgress wird dieser gedrehte Vektor dann benutzt.

Wichtig:
- Die Unit Geometry muss eingebunden sein
- Die Sphere sollte beim Start eine Position über Y=0.5 haben, also beispielsweise Y = 3.
- Damit man die Ablenkung sieht, sollte man ein zweites Referenzobjekt irgendwohin positionieren, und die Kamera natürlich auch entsprechend, weil eine runde Kugel sonst immer gleich aussieht und man die Abweichung in der Richtung nur schwer erkennen kann. Ein Würfel auf (0;0;0) und die Kamera und Lichtquelle auf (0;2;-7) dürften ganz brauchbar sein.

Anmerkung: Der Code ist keinesfalls optimal, ZAxis sollte z.B. nicht jedesmal initialisiert werden, wenn die Kugel mit etwas kollidiert (zugegeben, passiert hier nur einmal, weil sie kein zweites Mal umgelenkt wird), da das bei Breakout relativ häufig vorkommt. Wenn du also Ecken, an denen die Kugel vollkommen schräg abprallt, nicht brücksichtigst, solltest du schon im Create-Event die benötigten Vektoren erzeugen, das spart Rechenzeit.

_________________
Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert
worm
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 135


D6 Prof
BeitragVerfasst: Mo 07.07.03 23:39 
Ich weiß nicht, ob das vorherige Initialisieren der Vektoren so viel nützt. Selbst bei Breakout liegen so viele Frames zwischen den Kollisionen, dass das nicht auffallen würde, und außerdem glaube ich, dass das auch Nachteile hat (Rechenzeit), wenn man globale statt lokaler Variablen benutzt.

_________________
In the beginning, the universe was created. This has made a lot of people very angry, and is generally considered to have been a bad move.
mimi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3458

Ubuntu, Win XP
Lazarus
BeitragVerfasst: Di 08.07.03 13:16 
könnte mir mal einer ein beispiel schreiben wie das genau geht ?
danke:)

_________________
MFG
Michael Springwald, "kann kein englisch...."
Lhid
ontopic starontopic starontopic starhalf ontopic starofftopic starofftopic starofftopic starofftopic star
Beiträge: 831



BeitragVerfasst: Di 23.09.03 19:33 
Meinung und mich grundlegend geändert-> alte beiträge gelöscht


Zuletzt bearbeitet von Lhid am Sa 26.09.09 14:22, insgesamt 1-mal bearbeitet
tommie-lie Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 4373

Ubuntu 7.10 "Gutsy Gibbon"

BeitragVerfasst: Di 23.09.03 19:57 
Lhid hat folgendes geschrieben:
Könntest du das mit dem collisionsmanager mal beschreiben ich hab beide tuts durchgemacht und jetzt steh ich in einem game vor einem Prog, ich bekomm irgendwie die Collisonsabfrage nicht hin.Ich hab einen sphere in einem Cube und die dürfen nicht zusammenstoßen

Meine Antwort dazu steht schon hier:
tommie-lie hat folgendes geschrieben:
Danke worm, SurfaceBounce habe ich wohl übersehen...

Einen dritten Teil wird es hier wohl nicht geben, mangels Interesse...
aber du eknnst ja meine Pläne *g*


Nun ja, an "meinen Plänen" wird zur Zeit mangels Zeit (doofe Formulierung irgendwie...) nicht weitergearbeitet. Ich hoffe, daß ich in den (hessischen) Herbstferien dazu kommen werde, darauf werden dann weitere Tutorials an anderer Stelle folgen, der Kollisionsmanager steht an erste Stelle auf der Liste, weil der hier anscheinend die meisten Probleme macht.
Aber im Prinzip findest du in den Demos von GLScene schon eine Menge Material zum Thema. Es gibt eine Demo, die zeigt, wann zwei Kugeln miteinander kollidieren. Das ganze auf Würfel-Kugel zu transformieren dürfte wirklich kein Problem sein ;-)

_________________
Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert