Hallo,
wir haben uns damit auseinandergesetzt, ein Picking für einen 3D-Editor zu realisieren. Wir haben uns dabei an Stefan Zerbsts Variante orientiert. Ansonsten liegt unserem Programm die TriBase-Engine zugrunde.
Das grundlegende Vorgehen ist folgendes: Als erstes werden die Mauskoordinaten bestimmt, die Funktion Maus::Weltstrahl(int nX, int nY,tbVector3 *vOrig, tbVector3 *vDir) erzeugt damit mit Hilfe der View- und der Projektionsmatrix einen Richtungsvektor, der von der Position des Betrachters (vOrig) über den Mauszeiger (2D-Koordinaten: nX und nY) in den View-Frustrum zeigt.
Als nächstes wird mit der Funktion tbLineHitsPlane auf eine Kollision dieses Strahls mit der vorher mit tbPlaneFromPointNormal erzeugten Ebene, die unser Gitternetz im Editor repräsentiert, geprüft und an den Zielkoordinaten eine Markierung gerendert.
Das Problem ist dabei, dass der Punkt, an dem die Kollision zwischen Ebene und Strahl stattfindet, offenbar falsch ist. Der LOG-Datei sind folgende Informationen zu entnehmen:
|
Quellcode
|
1
2
3
4
5
|
Orig: X: 0.000000, Y: 0.000000, Z: 0.000000
Dir: X: 0.301511, Y: -0.301511, Z: 0.904534
Dest: X: 301.511353, Y: -301.511322, Z: 904.534058
Kollision
Coll: X: 0.066667, Y: -0.066667, Z: 0.200000
|
Wobei Orig die Kameraposition, Dir die Richtung des Strahls, Dest ein Punkt auf dem Strahl hinter der Ebene und Coll der Punkt der Kollision ist. Die Werte entstammen einem Klick auf den Ursprung, also X=0, Y=0. Sowohl X als auch Y sind aber offenkundig falsch. Der Abstand der Kamera zur Ebene (0,2) ist aber korrekt.
Diese Funktion benutzen wir, um den Strahl aus den Mauskoordinaten, der View- und der Projektionsmatrix zu erzeugen:
|
C-/C++-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
|
inline float xxxD3DMathe_VBetrag(const D3DVECTOR &v)
{
return (float) sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
}
tbResult Maus::Weltstrahl(int nX, int nY,tbVector3 *vOrig, tbVector3 *vDir)
{
tbVector3 vS;
tbMatrix mInv,mProjection,mView;
tbDirect3D::GetDevice()->GetTransform(D3DTS_PROJECTION, (D3DMATRIX*)(&mProjection));
// Die Position des Mauszeigers wird auf "gültige" Werte umgerechnet
// Bei einer Auflösung von 800*600 also 0 -> -1.0f, 400 -> 0.0f, 800 -> 1.0f etc.
// Z wird auf 1.0f gesetzt, da der Mauszeiger über keine Tiefeninformation verfügt
vS.x = ( ((nX*2.0f) / tbDirect3D::GetScreenSize().x)-1.0f) / mProjection.m11;
vS.y = ( ((nY*2.0f) / tbDirect3D::GetScreenSize().y)-1.0f) / mProjection.m22;
vS.z = 1.0f;
// Die Viewmatrix wird invertiert, inv. Viewmatrix = Verschiebung der Kamera in Weltkoordinaten
tbDirect3D::GetDevice()->GetTransform(D3DTS_VIEW, (D3DMATRIX*)(&mView));
mInv = tbMatrixInvert(mView);
// Erzeugung des Strahls
(*vDir).x = (vS.x*mInv.m11)+(vS.y*mInv.m21)+(vS.z*mInv.m31);
(*vDir).y = (vS.x*mInv.m12)+(vS.y*mInv.m22)+(vS.z*mInv.m32);
(*vDir).z = (vS.x*mInv.m13)+(vS.y*mInv.m23)+(vS.z*mInv.m33);
// Kameraposition = inv. Viewmatrix -> vOrig = Position des Betrachters
(*vOrig).x = mInv.m41;
(*vOrig).y = mInv.m42;
(*vOrig).z = mInv.m43;
// Strahl normalisieren, um Richtung zu erhalten
float f = xxxD3DMathe_VBetrag((D3DVECTOR)*vDir);
(*vDir).x /= f;
(*vDir).y /= f;
(*vDir).z /= f;
tbWriteToLog("Orig: X: %f, Y: %f, Z: %f",(*vOrig).x,(*vOrig).y,(*vOrig).z);
tbWriteToLog("Dir: X: %f, Y: %f, Z: %f",(*vDir).x,(*vDir).y,(*vDir).z);
return TB_OK;
}
|
So wird die Funktion anschließend verwendet:
|
C-/C++-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
|
POINT mousePos;
GetCursorPos(&mousePos);
ScreenToClient( tbDirect3D::GetWindow(), &mousePos);
if(g_pbButtons[TB_MOUSE_BUTTON(0)]==1)
{
tbVector3 vOrig,vDir,vDest,vColl;
g_pFocusMain->g_pMaus->Weltstrahl(mousePos.x,mousePos.y,&vOrig,&vDir);
// fernen Punkt finden, zu dem ein Strahl von vOrig durch die Ebene geht
vDest.x = vOrig.x+(vDir.x*1000);
vDest.y = vOrig.y+(vDir.y*1000);
vDest.z = vOrig.z+(vDir.z*1000);
tbWriteToLog("Dest: X: %f, Y: %f, Z: %f",vDest.x,vDest.y,vDest.z);
// Ebene aus einem Punkt der Ebene und ihrer Normalen erzeugen
// zoom ist dabei der Abstand der Ebene von der unbewegbaren Kamera
tbPlane plane;
plane = tbPlaneFromPointNormal(tbVector3(0.0f,0.0f,zoom),tbVector3(0.0f,0.0f,1.0f));
// auf Kollision prüfen und Kollisionsvektor zum setzen der Transformationsmatrix nutzen
if (tbLineHitsPlane(vOrig,vDest,plane,&vColl))
tbWriteToLog("Kollision");
else
tbWriteToLog("keine Kollision");
tbWriteToLog("Coll: X: %f, Y: %f, Z: %f",vColl.x,vColl.y,vColl.z);
mMarkierung = tbMatrixTranslation(tbVector3(vColl.x+Get_ver_x(),vColl.y-Get_ver_y(),vColl.z));
}
|
Mit mMarkierung als Transformationsmatrix wird dann das Testobjekt gerendert. Dieses erscheint jedoch an der völlig falschen Stelle. Es liegt offenbar keine Regelmäßigkeit vor. Es ist durchaus möglich, dass das Testobjekt an der "richtigen" Stelle, als unter dem Mauszeiger gerendert wird, aber das geschieht nur an einem einzigen Punkt.
Natürlich könnte der Fehler auch im sonstigen Code, im Festlegen der Projektions- oder der Viewmatrix, beim Rendern des Testobjektes usw. liegen, aber dort konnten wir trotz ausführlicher Suche keinen Fehler finden. Wir vermuten einen Denkfehler in einer der beiden obigen Funktionen, können ihn aber nicht finden.
Wir hoffen, hier Hilfe zu finden und sind für jeden Hinweis dankbar.