[bullet] Ray-Test

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Jonathan
Establishment
Beiträge: 2660
Registriert: 04.08.2004, 20:06
Kontaktdaten:

[bullet] Ray-Test

Beitrag von Jonathan »

Ich habe begonnen Bullet-Physics in mein Spiel einzubauen. Meine Objekte werden zunächst einmal nur durch BoundingBoxes dargestellt, dank des Debugdrawers bin ich mir auch sicher, dass deren Größe und Transformation stimmen.
Jetzt möchte ich aber einen RayTest auf die Szene machen um Objekte auszuwählen, nur liefert er nie ein Ergebnis. Ich hatte früher schon einen Selektionstest mit Bounding Spheres und benutze die selben Komponenten, der Strahl müsste also stimmen. Ich habe auch einmal die Werte explizit so gesetzt, dass sie das Objekt treffen müssen, aber es wird immer noch kein Ergebnis geliefert. Ich muss auch sagen mir sind eine ganze Menge Dinge unklar, aber ich finde leider auch keine Demo oder Tutorials zu dem Thema. Und die Doku ist auch nicht wirklich ausführlich.
Also, Ausgangspunkt ist btCollisionWorld::rayTest. Los geht es mit den Parametern rayFromWorld und rayToWorld. Das wird wohl der Strahl sein, aber sind das 2 Punkte auf dem Strahl oder Start und Richtung? Irgendwie steht das nirgends. Naja, ich habe beides ausprobiert, ohne Erfolg. Dann das Callback Objekt. Dafür habe ich mir eine kleine Hilfsfunktion geschrieben, damit ich Lambdas benutzen kann, das ganze sieht dann so aus:

Code: Alles auswählen


#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
template<typename tFunc> class RayResultFunctorCallbackClass : public btCollisionWorld::RayResultCallback
{
public:
	RayResultFunctorCallbackClass(tFunc Func): m_Func(Func) {}
	virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult &rayResult, bool normalInWorldSpace) override
	{
		m_Func(rayResult);
		return 1;
	}
private:
	tFunc m_Func;
};

template<typename tFunc> RayResultFunctorCallbackClass<tFunc> RayResultFunctorCallback(tFunc Func)
{
	return RayResultFunctorCallbackClass<tFunc>(Func);
}


list<EntityRef> SceneManager::GetObjects(Vector3f Start, Vector3f Direction)
{
	list<EntityRef> ReturnList;

	m_CollisionWorld->rayTest(asBtVec(Start), asBtVec(Direction), RayResultFunctorCallback(
		[&ReturnList](btCollisionWorld::LocalRayResult &rayResult)
		{
			auto cod=reinterpret_cast<CollisionObjectData*>(rayResult.m_collisionObject->getUserPointer());
			if(cod && cod->entity)
				ReturnList.push_back(cod->entity);
		}
		));


	return ReturnList;
}
Mit dem Lambda will ich dann also alle gefundenen Objekte in eine Liste packen, die ich dann abarbeiten kann. Aber soweit kommt es gar nicht, das Lambda wird nie aufgerufen. Ich habe gesehen, dass es statt dem RayResultCallback noch 2 andere Klassen gibt, scheinbar eine für nur den ersten Treffen und eine für alle Treffer. Allerdings wollen die im Konstruktor wieder Parameter für den Strahl haben, was für mich keinen Sinn ergibt, denn so würde der Strahl ja 2 mal an die RayTest Funktion übergeben (als direkter Parameter und in dem Callback).
Bevor ich jetzt weiter im dunkel rumstocher und mir Funktionen und Klassen anschaue, die quasi nicht dokumentiert sind, frage ich mich, ob hier nicht schonmal jemand das selbe tun wollte und mir sagen kann, wie es geht.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Andre
Establishment
Beiträge: 186
Registriert: 21.12.2011, 20:33

Re: [bullet] Ray-Test

Beitrag von Andre »

Los geht es mit den Parametern rayFromWorld und rayToWorld. Das wird wohl der Strahl sein, aber sind das 2 Punkte auf dem Strahl oder Start und Richtung?
Weder noch. Es ist einfach Start und Endpunkt des Strahles. Er wird bei rayFromWorld losgeschossen und geht bis rayToWorld.
Dann das Callback Objekt. Dafür habe ich mir eine kleine Hilfsfunktion geschrieben, damit ich Lambdas benutzen kann, das ganze sieht dann so aus:
Ich bin mir nicht sicher was genau du damit bezwecken möchtest. Normalerweise benutzt man diese Struktur in etwa so:
[Edit: Leider meinte mir ZFX alle Whitespaces im Code-Tag zu löschen, ich hoffe du kommst trotzdem klar]

Code: Alles auswählen

struct FilteredRayResultCallback : public btCollisionWorld::RayResultCallback
	{
		FilteredRayResultCallback(){}

                // Diese Funktion wird bei einem Treffer aufgerufen. Wird 0 zurückgegeben wird der Treffer nicht gezählt. Bin mir aber nicht sicher wenn man immer
                // nur 1 zurückgibt. Bei einem Hit, den man haben möchte gibt  man rayResult.m_hitFraction zurück, wie ich es unten auch mache.
                // Bei mir ist diese Funktion z.B. dafür gut den TraceOwner rauszufiltern, also z.B. den Spieler, der einen Ray aus sich raus schießt. Da das ganze im
                // Script passiert muss das also recht einfach sein.
		virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace)
		{
			btRigidBody* rb = btRigidBody::upcast(rayResult.m_collisionObject);
			if(rb)
			{
				if(rb->getMotionState())
				{
					WMotionState* wm = (WMotionState *)rb->getMotionState();

					if(wm->GetLinkedContentMark() == TraceOwner)
					{
						return 0; // Wir haben uns selbst getroffen, diesen Hit brauchen wir nicht
					}
				}
			}

                        // Hit kommt nicht vom TraceOwner, wir können ihn weiterverarbeiten
			return addSingleResult_close(rayResult, normalInWorldSpace);
		}

		btVector3	m_rayFromWorld;//used to calculate hitPointWorld from hitFraction
		btVector3	m_rayToWorld;

		btVector3	m_hitNormalWorld;
		btVector3	m_hitPointWorld;
		
	        // Das hier habe ich mir mal aus der ClosestRayResultCallback-Struktur geborgt. Hier werden die Resultate ausgerechnet und der Hit wird weiter
                // nach unten gegeben
		virtual btScalar addSingleResult_close(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace)
		{
			//caller already does the filter on the m_closestHitFraction
			btAssert(rayResult.m_hitFraction <= m_closestHitFraction);
			
			m_closestHitFraction = rayResult.m_hitFraction;
			m_collisionObject = rayResult.m_collisionObject;
			if (normalInWorldSpace)
			{
				m_hitNormalWorld = rayResult.m_hitNormalLocal;
			} else
			{
				///need to transform normal into worldspace
				m_hitNormalWorld = m_collisionObject->getWorldTransform().getBasis()*rayResult.m_hitNormalLocal;
			}
			m_hitPointWorld.setInterpolate3(m_rayFromWorld,m_rayToWorld,rayResult.m_hitFraction);
			return rayResult.m_hitFraction;
		}

		WContentMark* TraceOwner;
	};
Wenn du nun also dir alle getroffenen Objekte in eine Liste packen möchtest, so könntest du einfach eine Liste in der Struktur als Variable erstellen und deine Treffer dort in addSingleResult direkt eintragen. Der Aufruf von dem Ganzen sieht so aus:

Code: Alles auswählen

FilteredRayResultCallback Result;
	Result.m_rayToWorld = btVector3(To.x, To.y, To.z);
	Result.m_rayFromWorld = btVector3(Start->x, Start->y, Start->z);
	Result.TraceOwner = TraceOwner;

	{
		const boost::mutex::scoped_lock sl(ThreadWork.SimulationMutex);
		DynamicsWorld->rayTest( btVector3(Start->x, Start->y, Start->z),
								btVector3(To.x, To.y, To.z),
								Result);
	}
	if(!Result.hasHit())
	{
		return -1;
	}
Ich habe gesehen, dass es statt dem RayResultCallback noch 2 andere Klassen gibt, scheinbar eine für nur den ersten Treffen und eine für alle Treffer. Allerdings wollen die im Konstruktor wieder Parameter für den Strahl haben, was für mich keinen Sinn ergibt, denn so würde der Strahl ja 2 mal an die RayTest Funktion übergeben (als direkter Parameter und in dem Callback).
Auf einer meiner langen Suchen im Bullet-Forum bin ich darauf mal gestoßen: http://bulletphysics.org/Bullet/phpBB3/ ... 124#p11124

Ich hoffe ich konnte dir wenigstens etwas helfen :)
Benutzeravatar
Jonathan
Establishment
Beiträge: 2660
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: [bullet] Ray-Test

Beitrag von Jonathan »

Was ich machen möchte ist folgendes: Ich habe ein paar Entities, die haben jeweils ein CollisionObject. Dessen UserPointer zeigt auf eine Struktur, die einen Zeiger zurück auf das Entity enthält (falls es es bei dem CollisionObject um ein Entity handelt). Ich möchte jetzt ein oder mehrere Objekte selektieren, indem ich sie anklicke.
Die Idee war jetzt eigentlich, dass die Callbackfunktion für jedes getroffene CollisionObject aufgerufen wird und ich so dessen Entity in die Trefferliste schreiben kann. Später würde ich dann vielleicht nur das erste getroffene nehmen oder so.
Achja: Dein Code wird schöner formatiert wenn du [ code=cpp ] als Tag benutzt.
Die 2 Unterklassen von RayResultCallback scheinen dann ja wirklich nur zur Bequemlichkeit zu sein. Die Frage ist dann aber doch, wenn ich den Strahl an 2 stellen definieren kann, welcher wird benutzt? Oder welcher überschreibt den anderen? Oder, was ich gerade sehe, speichert ihr in eurem Strahl die Position einzig und alleine, um interpolieren zu können? Weil das ganze irgendwie "schlecht" entworfen ist und ihr die Daten nicht dort habt, wo ihr sie braucht und sie deshalb redundant sein müssen? Hört sich ja eher nach einem Hack an, aber naja.

Jedenfalls weiß ich jetzt, dass ich "rayFromWorld" als "point FROM where the RAY starts, in WORLD coordinates" interpretieren muss. Was für ein sau dämlicher Name...
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Andre
Establishment
Beiträge: 186
Registriert: 21.12.2011, 20:33

Re: [bullet] Ray-Test

Beitrag von Andre »

Der Code, der da Interpoliert ist wie im Code beschrieben komplett aus einer Bulletfunktion übernommen. Was die sich dabei gedacht haben weiß ich aber auch nicht.
Es scheint wohl wirklich so gewollt zu sein, dass man dort an beiden stellen die selben Werte benutzt.

Wie gesagt, du brauchst da eigentlich keine Callbackfunktion. Wieso machst du nicht einfach eine Liste als Memvervariable in der Struktur, die du in AddSingleResult füllst?
Benutzeravatar
Jonathan
Establishment
Beiträge: 2660
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: [bullet] Ray-Test

Beitrag von Jonathan »

Also, der Strahlentest läuft jetzt, wie er soll. Allerdings bin ich von bullet schon enttäuscht, dass es zum einen so merkwürdig designt ist und dann zum anderen noch so schlecht dokumentiert ist.Vielleicht machen einige der Entscheidungen ja sogar Sinn, aber dann soll man wenigstens dabei schreiben, warum das so ist. Schon schade, aber nungut.

Was meinst du mit Callbackfunktion? Der Wrapper ist ja nur dafür da, damit ich Lambdas benutzen kann und nicht jedesmal eine neue (lokale) Klasse anlegen muss. Letztendlich kommt es natürlich ein wenig darauf an, wie ich das ganze wrappen will, damit werde ich wohl einfach bei der weiteren Integration ein wenig experimentieren. Aber ansich bin ich so schon recht zufrieden.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Andre
Establishment
Beiträge: 186
Registriert: 21.12.2011, 20:33

Re: [bullet] Ray-Test

Beitrag von Andre »

Ich freue mich teilweise schon über Comments in den Headern damit ich wenigstens einigermaßen aus den Nutzen mancher Klassen rausbekommen kann. ;)
Man muss bei Bullet eben wirklich versuchen so gut wie alles aus den Samples zu verstehen. Und wenn dann mal etwas nicht klappt gibt es meistens Suchorgien in deren Foren.

Was deine Callbackfunktion angeht: Vielleicht passt es so einfach besser in das Design deiner Engine. Bei mir ist alles so gehalten, dass es leicht vom Script aus benutzbar ist, deswegen komme ich ganz gut damit weg die Klassen lokal zu deklarieren.
Benutzeravatar
Jonathan
Establishment
Beiträge: 2660
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: [bullet] Ray-Test

Beitrag von Jonathan »

Die Situation schreit ja eigentlich nach Docu-Patches. Ich werde mich mal dransetzen, sobald ich mehr Erfahrung mit Bullet habe.

Nochmal zu den Callbacks: Ich weiß eigentlich nicht genau, was du meinst, es sind doch immer Callbacks. Ob als Methode einer eigens angelegten Klasse oder eben als Lambda macht doch keinen Unterschied. Nur dass ich durch den Template-Wrapper ein bisschen weniger tippen muss, wenn meine Callbackklasse keine Attribute oder zusätzliche Methoden braucht.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Antworten