Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

LukasBanana

Alter Hase

  • »LukasBanana« ist der Autor dieses Themas

Beiträge: 1 097

Beruf: Shader Tools Programmer

  • Private Nachricht senden

1

15.04.2014, 10:19

C++ Templates für Kollisions Funktionen

Hi,
ich denke gerade darüber nach, ob es sinnvoll ist, alle Kollisions-Funktionen als Templates zu schreiben, oder sich stattdessen doch auf einen konkreten Datentyp (z.B. float) zu beschränken.
Der Grund dafür ist, dass Templates den Code extrem aufblähen - aber ist das heutzutage noch tragisch?
Immer hin sind diese Templates nicht gerade klein und ich habe eine ganze Menge davon (Intersection Test mit Line/Ray mit AABB/Sphere/Capsule etc., Distanz Berechnungen zwischen Point/Line gegen AABB/Sphere/Triangle etc.).
Gibt es vlt. noch andere Gründe, warum man das nicht mit Templates machen sollte?
Wie seht ihr das?

2

15.04.2014, 11:38

Wozu? Für double ändern sich normalerweise auch alle Toleranzen und Epsilons in deinem Quelltext; die lassen sich nur sehr schwer template-isieren. Und wenn du deine Physik von float auf int umstellen willst? Zum einen ändern sich dann höchstwahrscheinlich die Berechnungsvorschriften, zum anderen ergibt zwar float * float wieder float, aber für int * int möchtest du in den meisten Fällen unsigned int oder unsigned long. (Ja; du kannst dir ein Festkommazahlen-Template schreiben das die optimalen Typen auswählt, aber dann nehmen deine Vektoren nicht mehr einen Skalartypen als Parameter, sondern drei, weil je nach Rechenweg jede Komponente unterschiedlich hohe Genauigkeit hat. Spätestens dann hast du 4× so viele Zeilen wie vorher ohne jeden Nährwert und verstehst nur noch Bahnhof, was in welcher Funktion passiert.)

Behalt deine float-Funktionen und überlad sie für die paar Stellen, an denen du einen anderen Datentypen brauchst. Premature reuse is the root of all evil.

LukasBanana

Alter Hase

  • »LukasBanana« ist der Autor dieses Themas

Beiträge: 1 097

Beruf: Shader Tools Programmer

  • Private Nachricht senden

3

15.04.2014, 12:53

Warum sollte man denn "int" als Datentyp für Kollisions-Funktionen nehmen?
Ich dachte jetzt eigentlich nur an "float" und "double".

Und ja, die "epsilons" (also z.B. 0.0001) ändern sich dann auch, aber das lässt sich wie ich finde mit spezialisierten Templates sehr einfach template-isieren:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
static const float epsilon = 0.00001;
static const double epsilon64 = 0.00000001;

template <typename T> T Epsilon()
{
    return T(epsilon); // By default 32-bit epsilon
}

template <> double Epsilon<double>()
{
    return epsilon64; // Specialized template
}

Aber generell stimme ich dir zu. Ich würde mich wahrscheinlich auf Floats festnageln.

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

4

15.04.2014, 13:25

Zitat von »LukasBanana«

Der Grund dafür ist, dass Templates den Code extrem aufblähen - aber ist das heutzutage noch tragisch?

Welcher Code wird aufgebläht? Der Quellcode? Wohl kaum. Wenn auch nur für die Hälfte der Funktionen einfache und doppelte Genauigkeit notwendig ist, wird die Templatevariante kompakter ausfallen. Der Maschinencode? Auch eher weniger. Der Compiler instanziert genau die Funktionen des Template für genau die benötigten Typen. Der Linker wird unbenutzte Funktionen einfach entfernen. (Zumindest wenn er richtig konfiguriert ist...)

Das einzige was sich aufblähen kann sind in extremen Fällen die Objektdateien und Compilezeiten. Mit "extern template" sollte sich auch das einigermaßen vermeiden lassen.

Zitat von »Krishty«

Für double ändern sich normalerweise auch alle Toleranzen und Epsilons in deinem Quelltext; die lassen sich nur sehr schwer template-isieren.

Verstehe ich nicht ganz. Erstens glaube ich nicht, dass man sehr viele Epsilons brauchen sollte und zweitens gibts es ja "std::numeric_limits" mit dem die wichtigsten Werte generisch herauszufinden sind. Des Weiteren sollte man solche Werte ohnehin nicht einfach als magische Zahlen in den Code schreiben, sondern dafür Konstanten verwenden. Diese Konstanten könnte man auch ganz einfach in eine spezialisierte Templateklasse packen um je nach Datentyp andere Werte zu verwenden. Ich sehe darin keinen Grund auf Templates zu verzichten.

Zitat von »Krishty«

zum anderen ergibt zwar float * float wieder float, aber für int * int möchtest du in den meisten Fällen unsigned int oder unsigned long.

Welche Berechnungsvorschriften sollen sich ändern? In Ausnahmefällen kann man spezialisieren. Nebenbei möchte ich drauf hinweisen, dass wohl zumindest auf dem Microsoftcompiler "sizeof(unsigned long) == sizeof(unsigned int)" gilt. Wo ich schonmal dabei bin, möchte ich auch gleich die festen Datentypen "std::int32_t" und Andere empfehlen, sonst kann es jede Menge Probleme geben, die noch dazu vollkommen überflüssig sind. Normalerweise sollte es unerwünscht sein, dass mal so und mal anders gerechnet wird, je nach Platform und Lust und Laune der Compilerhersteller. Wenn das erwünscht wäre, mit Datentypen wie "std::ptrdiff_t" und Andere, wäre das bei der Templatevariante möglich.

Zitat von »Krishty«

nicht mehr einen Skalartypen als Parameter, sondern drei, weil je nach Rechenweg jede Komponente unterschiedlich hohe Genauigkeit hat.

Das wäre in der Tat ziemlicher Quatsch. Am Besten nimmt man einfach "std::int64_t" für alles. Damit kann man eigentlich so gut wie jeden Wert abdecken und in der Zeit der 64Bit CPUs ist auch der Rechenaufwand kein großes Problem mehr. Da unterschiedliche Typen zu verwenden, würde nur alles viel komplizierter machen als es ist.
Und gerade in dem Gedankengang möglicherweise auch Festkommatypen verwenden zu wollen, sehe ich einen großen Vorteil für Templates. Kommt man nämlich irgendwann auf die Idee, dass "32.32" nicht eine optimalen Wertebereich bietet und "48.16" besser passen würde, darf man alle Funktionen neuschreiben. Die Anzahl an redundanten Code der Menge Überladungen würde gerade zu explodieren, während bei nur "double" und "float" die Varianten gerade noch so zu überschauen sein sollten.

Zitat von »Krishty«

Premature reuse is the root of all evil.

Das ist genauso eine Wirtshausparole wie die bekannte "Premature Optimzation".
Man muss im Einzellfall entscheiden. Ist es sehr wahrscheinlich, das je nach Anwendungsfall des selben Codes verschiedene Typen eingesetzt werden sollen, sind Templates sinnvoll, um die Menge doppelt und dreifachen Codes durch eine generische Lösung zu reduzieren. Ist es dagegen absehbar, dass "double" allen ausreichen sollte und der damit verbundene möglicherweise leicht höhere Rechenaufwand verkraftbar ist, dann schreibt man es eben nicht generisch mit Templates. Das kann aber nur LukasBanana selbst entscheiden. Ich persönlich würde es für mich wahrscheinlich als Template schreiben, weil ich je nach Anwendung einen anderen Wertebereich/Genauigkeit benötige.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Spiele Programmierer« (15.04.2014, 13:31)


BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

5

15.04.2014, 13:30

Das ist genauso eine Wirtshausparole wie die bekannte "Premature Optimzation".
Man muss im Einzellfall entscheiden.
Nein, eben nicht. Das Wort "premature" schließt die Entscheidung des Einzelfalls schon ein.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

6

15.04.2014, 14:09

Warum sollte man denn "int" als Datentyp für Kollisions-Funktionen nehmen?
Ich dachte jetzt eigentlich nur an "float" und "double".
int bietet konstante Genauigkeit und erlaubt in gewissem Rahmen stabilere Berechnung (a + b entspricht bei int dem genauen Ergebnis, wird mit float aber unter Umständen gerundet; int-Funktionen greifen wie Zahnräder ineinander während float-Funktionen sich immer überlappen oder Lücken bilden). Diese Vorteile sind bei Positionen in großen Welten sehr deutlich. Bei Geschwindigkeiten hingegen sind Gleitkommazahlen erste Wahl weil sie höhere Dynamik erlauben; bei 3D-Grafik auch (weil das, was weit weg vom Betrachter ist, auch nicht so genau sein muss wie das nah dran).

Zitat von »Krishty«

Für double ändern sich normalerweise auch alle Toleranzen und Epsilons in deinem Quelltext; die lassen sich nur sehr schwer template-isieren.

Verstehe ich nicht ganz. Erstens glaube ich nicht, dass man sehr viele Epsilons brauchen sollte und zweitens gibts es ja "std::numeric_limits" mit dem die wichtigsten Werte generisch herauszufinden sind. Des Weiteren sollte man solche Werte ohnehin nicht einfach als magische Zahlen in den Code schreiben, sondern dafür Konstanten verwenden. Diese Konstanten könnte man auch ganz einfach in eine spezialisierte Templateklasse packen um je nach Datentyp andere Werte zu verwenden. Ich sehe darin keinen Grund auf Templates zu verzichten.
Naja … such mal das CAD-Programm deiner Wahl raus und interpretier den Datenabschnitt als double; dann wird dir wahrscheinlich schlecht ;) Die Toleranz hängt in erster Linie vom Rechenweg ab und kommt nicht einfach aus std::numeric_limits. Auch über ULPs lässt sich die Genauigkeit kaum eingrenzen (das ist sogar noch qualvoller). So lange „größte Zahl, deren dreifaches Quadrat nicht unendlich wird“ (oberes Limit für Normalisierung eines 3D-Vektors) nur in der 3D-Vektor-Normalisierung gebraucht wird, macht man keine globale Template-Konstante draus.

Zitat von »Krishty«

zum anderen ergibt zwar float * float wieder float, aber für int * int möchtest du in den meisten Fällen unsigned int oder unsigned long.

Welche Berechnungsvorschriften sollen sich ändern?
Bei Schnittpunktberechnungen mit Strecken berechnet man z.B. oft den Schnitt einer Linie und prüft dann, ob die Projektion auf die Strecke zwischen 0 und 1 liegt. Das möchte man bei Festkommazahlen nicht weil die Division destruktiv ist. Der Rechenweg lässt sich stattdessen z.B. ändern, so dass mit 0 < n² < Länge² verglichen wird. Sowas kann ein Template nicht.

In Ausnahmefällen kann man spezialisieren.
… oder bei Überladung bleiben, die sehr viel einfacheren Regeln folgt.

Zitat von »Krishty«

nicht mehr einen Skalartypen als Parameter, sondern drei, weil je nach Rechenweg jede Komponente unterschiedlich hohe Genauigkeit hat.

Das wäre in der Tat ziemlicher Quatsch. Am Besten nimmt man einfach "std::int64_t" für alles. Damit kann man eigentlich so gut wie jeden Wert abdecken und in der Zeit der 64Bit CPUs ist auch der Rechenaufwand kein großes Problem mehr. Da unterschiedliche Typen zu verwenden, würde nur alles viel komplizierter machen als es ist.
Falls du mit Festkommazahlen arbeitest, willst du aber wissen, wo das Komma ist. Das kann man im Template-Parameter festhalten, und kann sich eben auch für alle Komponenten unterscheiden. Damit sind es für deinen Compiler auch verschiedene Typen.

Das kann aber nur LukasBanana selbst entscheiden.
Er soll entscheiden, ja. Wenn er eine double-Variante oder eine Festkommavariante benötigt, soll er die schreiben und testen. Und wenn sie funktioniert und dann *immernoch* den gleichen Quelltext hat wie die float-Variante, *dann* soll er sich entscheiden.

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

7

15.04.2014, 14:34

Zitat von »BlueCobold«

Nein, eben nicht. Das Wort "premature" schließt die Entscheidung des Einzelfalls schon ein.

Eigentlich schon, ja. Leider wird zb. gerade das Schlagwort "Premature Optimization" von zu vielen Leuten dogmatisch verwendet, ohne die Entscheidung im Einzellfall.

Zitat von »Krishty«

nur in der 3D-Vektor-Normalisierung gebraucht wird, macht man keine globale Template-Konstante draus.

Sollte man meiner Meinung nach, der Übersicht wegen, aber machen. Insbesondere man viele verschiedene Typen unterstützen muss und bei der generischen Lösung sowieso.

Zitat von »Krishty«

Das kann man im Template-Parameter festhalten, und kann sich eben auch für alle Komponenten unterscheiden.

Es macht kaum Sinn in einem Vektor verschiedene Zahlen mit verschiedenen mit verschiedenen Kommas zu speichern. Ein Vektor besteht aus mehreren Koordinaten des selben Types. Nicht X als "float", Y als Festkomma "32.32" und Z als Ganzzahl. Ein Vektor hat für alle Komponenten den selben Wertebereich.

Zitat von »Krishty«

*dann* soll er sich entscheiden.

Dann darf er erstmal den gesamten Code und insbesondere die Abhänigkeiten umschreiben.
Soetwas sollte man sich in der Regel vorher überlegen, sonst hat man nachher mehr Arbeit als notwendig gewesen wäre.

8

15.04.2014, 15:05

Zitat von »Krishty«

Das kann man im Template-Parameter festhalten, und kann sich eben auch für alle Komponenten unterscheiden.

Es macht kaum Sinn in einem Vektor verschiedene Zahlen mit verschiedenen mit verschiedenen Kommas zu speichern. Ein Vektor besteht aus mehreren Koordinaten des selben Types. Nicht X als "float", Y als Festkomma "32.32" und Z als Ganzzahl. Ein Vektor hat für alle Komponenten den selben Wertebereich.
Das kann durchaus sinnvoll sein um hier und da noch ein Bisschen Genauigkeit rauszuholen – ich weiß bei meinem Flugsimulator, dass man sich zwar 4000 km nach X oder Z bewegen kann, aber nur 100 km nach Y. Aber mir wird gerade klar, dass ich durch die Bank „Festkomma“ mit „statisch vorberechnetes gleitendes Komma“ durcheinanderschmeiße. Bei Festkommazahlen hat man tatsächlich nur einen Typ für alles; dafür aber kaum mehr Vorteile gegenüber Gleitkommazahlen.

Zitat von »Krishty«

*dann* soll er sich entscheiden.

Dann darf er erstmal den gesamten Code und insbesondere die Abhänigkeiten umschreiben.
Immer hin sind diese Templates nicht gerade klein und ich habe eine ganze Menge davon (Intersection Test mit Line/Ray mit AABB/Sphere/Capsule etc., Distanz Berechnungen zwischen Point/Line gegen AABB/Sphere/Triangle etc.).
Das hatte ich so interpretiert dass der Quelltext bereits geschrieben ist. Jetzt wo du es erwähnst bin ich mir aber auch nicht mehr sicher.
Soetwas sollte man sich in der Regel vorher überlegen, sonst hat man nachher mehr Arbeit als notwendig gewesen wäre.
Ja, aber niemand hat eine Kristallkugel. Meiner Meinung nach sollte man den einfachsten möglichen Weg zuerst implementieren und erweitern, sobald man an die Grenzen kommt. Das bedeutet zwar häufiges Umschreiben, aber dafür bleibt die Funktionsweise einfach. Quelltext, der für Großes ausgelegt ist, ist normalerweise viel schwerer fehlerfrei zu kriegen.

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

9

15.04.2014, 15:26

Zitat von »Krishty«

Das kann durchaus sinnvoll sein um hier und da noch ein Bisschen Genauigkeit rauszuholen

Bei 32 Bit könnte es Sinn machen, allerdings verkompliziert es auch die Rechnungen drastisch, weshalb ich das kritisch sehe. Mit 64Bit Werten wie ich sie heute verwenden würde, kaum noch. Bei einer Genauigkeit von einem Nanometer, würde der Wertebereich für bis zu 18 446 744km Abstand reichen. Das reicht locker, als das von der Erde aus, auch noch der Mond mit in den Wertebereich passt.

Ich gebe dir prinzipiell recht, das Erweitern nach Bedarf sinnvoll ist. Wenn man nur vorher schon weiß was man will und eine andere Implementierung dazu führen würde, dass man alle Abhänigkeiten in möglicherweise darauf aufbauende Anwendungen, ändern muss, dann ist das etwas unpraktisch. Ich vermute, LukasBanana hat hier nachgefragt, weil er eine gute Lösung sucht, die er nicht nächste Woche wieder umschreiben muss, weil er damit nicht mehr zufrieden ist.

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

10

16.04.2014, 06:33

Mit 64Bit Werten wie ich sie heute verwenden würde, kaum noch. Bei einer Genauigkeit von einem Nanometer, würde der Wertebereich für bis zu 18 446 744km Abstand reichen. Das reicht locker, als das von der Erde aus, auch noch der Mond mit in den Wertebereich passt.
Ich glaube da unterliegst Du einem Irrtum. Wenn Du Abstände zwischen Mond und Erde korrekt modellierst, wirst Du feststellen, dass sich Nanometer eben nicht mehr bestimmen lassen. Sogar mit Zentimetern wird es dann schon problematisch. Double kann zwar große Wertebereiche abdecken, aber die Genauigkeit ist relativ zum absoluten Wert des Doubles. Große Zahlen führen dazu, dass Nachkommastellen oder sogar die unteren Stellen vor dem Komma nicht mehr dargestellt werden können. Bei Double sind das in etwa 15 Stellen. Da wird bei den 18.446.744km nix mehr die Nanometer aufzulösen, die stolze 20 Stellen bräuchten.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »BlueCobold« (16.04.2014, 07:21)


Werbeanzeige