Hilfsvariablen als Membervariablen anlegen?

Design Patterns, Erklärungen zu Algorithmen, Optimierung, Softwarearchitektur
Forumsregeln
Wenn das Problem mit einer Programmiersprache direkt zusammenhängt, bitte HIER posten.
Antworten
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Hilfsvariablen als Membervariablen anlegen?

Beitrag von starcow »

Hallo zfx'lör :-)
Ich bin gerade dabei meine Kollisionsabfrage auf Vektorgeometrie umzustellen (2D).
Dabei bin ich auf ein paar Dinge gestossen, bei denen ich nie so recht weiss, wie das jetzt am besten umzusetzen ist.

Grundsätzlich stosse ich immer wieder auf die Situation, in welcher ich in einer Memberfunktion Hilfsvariablen definieren muss.
Da stellt sich mir dann immer die Frage, ob man diese Hilfsvariable gleich als Membervariable anglegen soll.
Ich stelle mir vor, ein Vorteil könnte dabei sein (ich weiss nicht ob das zutrifft), dass so eine Hilfsvariable vom Compiler nicht immer wieder gelöscht und neu angelegt werden müsste.
Z. B. verwendet meine Kreaturen-Klasse für die Berechnung des Tempos (Move Methode) zwei Hilfsvariablen vom Typ vector2d.
Auch wenn diese Vektor-Klasse bis auf zwei Variablen des Datentyps "double" (x und y) nur Methoden besitzt, weiss ich nicht, ob das ins Gewicht fallen kann, wenn diese Klassen-Instanzen bei jedem Schlaufendruchlauf neu angelegt werden müssen.
Auf der anderen Seite könnte ein Nachteil sein, dass sich die Klasse durch solche Hilfsvariablen unnötig aufbläht und man zudem aufpassen muss, dass diese immer wieder sauber "resettet" werden und man nicht versehentlich mit veralteten Werten arbeitet.
Gibt es da vielleicht eine Regel oder Richtlinie?

Zudem stehe ich gerade vor dem Problem, wie ich per Geradengleichung den Schnittpunkt (respektive den Skalierungsfaktor) zweier Vektoren berechnen kann.
Mathematisch ja keine grosse Geschichte, doch wenn ich das Gleichungssystem auflöse, gibt es zwei Situationen, in welchen eine Division durch 0 auftreten kann.
Lässt sich dies vielleicht irgendwie elegant lösen? Mich würde euer Ansatz sehr interessieren.

Gruss starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
NytroX
Establishment
Beiträge: 358
Registriert: 03.10.2003, 12:47

Re: Hilfsvariablen als Membervariablen anlegen?

Beitrag von NytroX »

Hi,

der erste Punkt ist leicht zu beantworten.

Lokale Variablen ("Hilfvariablen") in Funktionen und/oder Methoden werden auf dem Stack verwaltet und müssen quasi nicht angelegt und gelöscht werden. Der Pointer auf das obere Ende vom Stack wird lediglich etwas mehr verschoben (also im Prinzip macht das Programm bei 2 lokalen Variablen anstatt "pointer+1" dann "pointer+3").
Das verschieben des Stack-Pointers ist also immer genau eine Berechnung, es wird nur mit einem anderen Wert addiert; Speicherplatzverwaltung für lokale Variablen kostet also erstmal nichts extra.

Anders sieht das natürlich aus, wenn du sie immer wieder neu berechnen musst und die Berechnung sehr kompliziert ist; oder wenn dein vector2d einen komplizierten Konstruktor hat (glaube ich aber beides nicht).
Wie du schon selbst bemerkt hast, würden Member-Variablen nur deine Klasse/Struktur zumüllen und sind schwer zu verstehen und zu verwalten.
Und es kann dann sogar noch eher passieren (muss aber nicht), dass dich das was kostet, weil deine Klasse größer ist und mehr Daten hin- und her geschaufelt werden; das kann oft sogar teurer sein, als die Variablen neu zu berechnen.

Also nimm einfach lokale Variablen... es sein denn dein Profiler sagt nachweislich was anderes :-)


zum zweiten Punkt mit der Division durch 0:

Wenn das passiert, gibt es ja keinen Schnittpunkt. Das ist natürlich deinem Programm überlassen, was du dann machen willst.
Programmatisch hast du 2 einfache Möglichkeiten:
- Prüfung auf 0 vor der Division (if) und dann bei 0 entsprechend reagieren
- "DivideByZeroException" abfangen (try-catch) und dann entsprechend reagieren

Es gibt immer mehr die Tendenz, für den normalen Programmablauf keine Exceptions nach oben durchzureichen.
Soll heißen: dass es mal keinen Schnittpunkt gibt, ist eigentlich ganz normal und keine problematische Ausnahmesituation für dein Programm.

Du könntest demnach z.B. einen optionalen Schnittpunkt zurückgeben, der dann einfach leer ist, wenn es keinen Schnittpunkt gibt:

Code: Alles auswählen

//C++
#include <optional>
...
std::optional<vector2d> berechneSchnittpunkt(...)
{
	...
	if(divisor==0) return {}; 
	...
}

Code: Alles auswählen

//C#
#nullable enable
...
vector2d? berechneSchnittpunkt(...)
{
	try
	{
		... //berechnung
	}
	catch(DivideByZeroException)
	{
		return null;
	}
}
joeydee
Establishment
Beiträge: 1039
Registriert: 23.04.2003, 15:29
Kontaktdaten:

Re: Hilfsvariablen als Membervariablen anlegen?

Beitrag von joeydee »

starcow hat geschrieben: 28.07.2019, 17:01 irgendwie elegant
Ja, mit Vektorarithmetik. Ich lese heraus, dass du das in 2D lösen willst? Edit: steht ja da, ich bin blind :D
Gegeben sind 2 Strahlen mit Positionsvektoren p0 und p1 sowie Richtungsvektoren d0 und d1 (normalisiert)
Du brauchst von einem Strahl die Normale. In 2D ist das einfach n0=(d0.y, -d0.x).
Dann geht die Berechnung wie folgt:

Code: Alles auswählen

    double nn=d1.dot(n0);
    if(nn!=0)//nicht parallel
    	{
        vec pr=p0-p1;
        double t=pr.dot(n0)/nn;//das ist der Skalierungsfaktor für Ray 1 (p1,d1)
        vec hitPoint=p1+d1*t;//die übliche Berechnung
        }
In 3D ist das Problem übrigens gleichzusetzen mit "Ray hits Plane" - dann ist die Normale diejenige der Plane, und die Berechnung ist exakt dieselbe.
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Hilfsvariablen als Membervariablen anlegen?

Beitrag von starcow »

Super! Vielen Dank für eure Hilfe! :-)

@joeydee

Code: Alles auswählen

    double nn=d1.dot(n0);
    if(nn!=0)//nicht parallel
    	{
        vec pr=p0-p1;
        double t=pr.dot(n0)/nn;//das ist der Skalierungsfaktor für Ray 1 (p1,d1)
        vec hitPoint=p1+d1*t;//die übliche Berechnung
        }
Ok, das ist ja interessant!
Ich hab jetzt grad versucht das nachzuvollziehen, bin aber nicht auf einen grünen Zweig gekommen.
Mir war nur die Geradengleichung bekannt, um den Schnittpunkt (resp. die Skalierungsfaktoren der Vektoren) zu berechnen

Nämlich so:

Code: Alles auswählen

p0.x + s1 * d0.x = p1.x + s2 * d1.x
p0.y + s1 * d0.y = p1.y + s2 * d1.y
In diesem Gleichungssystem habe ich dann zwei Unbekannte (s1, s2) und zwei Aussagen.
Wenn ich das ganze nun umstelle und dann die eine Gleichung in die andere einfügen, erhalte ich eine Gleichung, aufgelöst (wahlweise) nach s1 oder s2.
Das Problem ist allerdings, dass diese Gleichung dann an zwei Stellen eine Division enthält und dort eine potentielle Division durch 0 möglich werden kann.
Insofern sieht natürlich dein Ansatz schon sehr elegant aus.
Gibt es dafür irgendwie noch eine Erklärung oder Herleitung, weshalb das so geht? Ich finde immer nur den Ansatz über die mir bekannte Geradengleichung.
Woher hast du das her, wenn ich fragen darf? :-)

LG, starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
joeydee
Establishment
Beiträge: 1039
Registriert: 23.04.2003, 15:29
Kontaktdaten:

Re: Hilfsvariablen als Membervariablen anlegen?

Beitrag von joeydee »

Herleitung:

Den Ray mit der Normalen nenne ich mal "Basis-Ray", in der Zeichnung der untere, damit es beim Erklären keine Verwechslung gibt. Welchen man nimmt ist egal.
Dieser liegt auf einer gedachten Linie, genannt "Basis", auf die sich alles bezieht. Darauf liegt Punkt p0, von dort startet die Normale n0.
Ich füge unten noch das Zwischenergebnis h ein, zur besseren Erklärung.

Edit: Zeichnung erstellt:
ray-plane-hit.jpg
nn=d1.dot(n0)
Das wird genau dann 0 wenn auch mindestens eine deiner zwei Divisionen 0 wird.
Das Punktprodukt "projiziert" jeweils einen Vektor auf einen anderen. Wenn sie senkrecht zueinander sind, ist das also 0. Und senkrecht zur Normalen bedeutet parallel zum Basis-Ray.
Aber der Wert kann gleichzeitig noch für den Strahlensatz später gebraucht werden, 2 Fliegen mit 1 Klappe.

h=(p0-p1).dot(n0)
(p0-p1) ist der Vektor von einem zum anderen Startpunkt der beiden Rays.
Dieser wird ebenfalls auf die Normale projiiziert und ergibt damit den senkrechten Abstand des einen Ray-Startpunkts von der Basis, also dessen "Höhe" über/unter der Basisline. In 3D wird so auch der Abstand eines Punktes zu einer Plane berechnet.

Man kann die beiden ermittelten Werte nun in zwei gedachte Dreiecke einsetzen:
- Beide starten bei p1.
- Das erste: Schenkel 1 ist d1 und hat die Länge 1, Schenkel 2 geht senkrecht in Richtung Basis und hat die oben ermitelte Länge nn.
- Das zweite: Schenkel 1 entlang d1 mit der gesuchten Länge t (trifft di Basis im gesuchten Punkt), Schenkel 2 senkrecht zur Basis und hat die Länge h.
Die Schenkel liegen also aufeinander, enden parallel, und nur t ist unbekannt -> Strahlensatz. Der zur Basis senkrechte Schenkel ist in seiner Länge für beide bekannt (nn bzw. h). Die Länge des anderen Schenkels ist im einen Fall 1 und im anderen das gesuchte t.
Das erste Dreieck geteilt durch nn mal h ergibt das zweite Dreieck.
D.h. t=h/nn*1

Unter "ray plane hit" oder "ray plane intersection" findest du etliche 3D-Erklärungsansätze, auch bebildert.
Die 2D-Ray-Ray-Lösung ist lediglich die Plane von der Seite als Ray betrachtet.
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Hilfsvariablen als Membervariablen anlegen?

Beitrag von starcow »

Wow! Das ist ja hervorragend! Vielen lieben Dank joeydee für die Liebesmüh! :-D
Ich werde morgen Zeit haben, dass so umzusetzen. Bin schon extrem gespannt :-D Und die Erklärung kommt natürlich bei mir ins Archiv! :-)

Lieber Gruss
starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Hilfsvariablen als Membervariablen anlegen?

Beitrag von starcow »

Ich muss sagen, ganz schön elegant gelöst! :-)
Eine Sache verstehe ich allerdings noch nicht.

Code: Alles auswählen

h=(p0-p1).dot(n0)
Du projizierst hier den Vektor der Positionsdifferenz (p0 - p1) auf die Normale n0.
Dafür aber müsste doch der Positionsdifferenz-Vektor ebenfalls (wie n0) ein Einheitsvektor, mit der Länge 1 sein...?
Ich dachte eigentlich, dass beim "Projizieren" beide Vektoren die Länge 1 besitzen müssten, damit auch was brauchbares rauskommt.
Allerdings funktioniert es mit dieser Methode, das steht fest ;-)

Gruss starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
joeydee
Establishment
Beiträge: 1039
Registriert: 23.04.2003, 15:29
Kontaktdaten:

Re: Hilfsvariablen als Membervariablen anlegen?

Beitrag von joeydee »

Dein Gedanke ist zu 50% richtig ;)

Das Ergebnis des Skalarprodukts ist definiert als Länge(a) mal Länge(b) mal cos(alpha), in Kurzschreibweise: a dot b = |a|*|b|*cos(alpha)
D.h. wenn beide normalisiert sind, bekommst du direkt den Cos-Wert des eingeschlossenen Winkels 1*1*cos(alpha). Wo du diesen Wert direkt benötigst, ist also tatsächlich die Normalisierung beider wichtig.

Ok, den cos könnten wir ja eigentlich gut gebrauchen, und dann mit dem Satz für rechtwinklige Dreiecke cos(alpha)=a/c die Strecke am rechten Winkel berechnen, mit a=h und c=|pr|, also h=|pr|*cos(alpha)
Aber halt, die Länge von pr wäre ja schon reinmultipliziert, wenn wir genau diesen vorher nicht normalisieren, da gilt: n0 dot pr = 1*|pr|*cos(alpha) ... ;)

Immer wenn nur ein Vektor beim Skalarprodukt normalisiert ist, kann man das so interpretieren, dass der nicht-normalisierte auf die Gerade des normalisierten projiziert wird, denn die Projektion ist ja von seiner Länge abhängig.
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Hilfsvariablen als Membervariablen anlegen?

Beitrag von starcow »

Leider komm ich erst jetzt dazu dir zu antworten. Ich hatte auf der Arbeit ein Projekt abzuschliessen, was mir über mein normales Pensum hinaus nochmals viel von meiner Freizeit abverlangt hat. Sorry joeydee und nochmals vielen Dank für deine Erläuterungen!
Ich glaube mein Verständnis über Vektoren hat sich durch deine demonstrierten Überlegungen nochmals ganz grundsätzlich verbessert! :-)
Ich hab dadurch auch realisiert, dass wenn ich blos den Skalierungsfaktor des ersten Vektors wissen will (also bis er sich mit dem Vektor Zwei schneidet), auch die zweite Skalierung (die Skalierung der Normalen von d0) auf die Länge Eins weglassen kann. Das ganze kürzt sich am Ende wieder raus.
Demnach ist der Skalierungsfaktor s von d1:

Code: Alles auswählen

pr.dot(n) / d1.dot(n)
n ist dabei die normale von d0, die nicht die Länge Eins zu haben braucht.

Dass das so einfach und schnell geht ist wirklich sehr cool! :-)

Aktuell bin ich gerade dabei die Kollision zwischen einem Kreis und einer Linie zu realisieren.
Aber es scheint mir sinnvoller dies in einem separaten Thread zu diskutieren (wenn ich morgen hoffentlich dazu komme).
Vielleicht fällt dir (euch) dazu nochmals etwas raffiniertes ein. Bis auf eine "Kleinigkeit" ist es mir mit ähnlichen Überlegungen fast gelungen... :-)

Lieber Gruss
starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Antworten