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

1

03.01.2005, 17:06

Problem mit Picking

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.

2

04.01.2005, 17:42

Ich habe einige neue Erkenntnisse gewonnen. Der Fehler hängt vermutlich mit der Invertierung der Viewmatrix zusammen, auch wenn ich das nicht beschwören kann. Da die Abweichung zwischen Markierung und tatsächlichem Klick desto größer wird, je weiter man sich vom Ursprung entfernt und Y außerdem noch invertiert ist, vermute ich den Fehler in diesen Zeilen:

C-/C++-Quelltext

1
2
3
    (*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);


Durch das Anfügen eines festen Faktors, in diesem Fall ungefähr 6 für X und -6 für Y, lässt sich eine starke Verbesserung erreichen: Die Markierung befindet sich fast exakt unter dem Mausklick, auch wenn die Abweichung natürlich bei großer Entfernung vom Ursprung größer wird.

Die Viewmatrix sieht folgendermaßen aus:

Quellcode

1
2
3
4
1.000000, 0.000000, 0.000000, 0.000000
0.000000, 1.000000, 0.000000, 0.000000
0.000000, 0.000000, 1.000000, 0.000000
0.000000, 0.000000, 0.000000, 1.000000


Die invertierte Viewmatrix ist mit der Viewmatrix identisch. Dies trifft zwar nur bei Matrizen zu, die nur aus Nullen und Einsen bestehen, ich bin mir aber trotzdem nicht sicher, ob die Invertierung korrekt ist.

NoName

Treue Seele

Beiträge: 118

Beruf: Student

  • Private Nachricht senden

3

04.01.2005, 18:16

Zitat von »"rFaust"«


Die Viewmatrix sieht folgendermaßen aus:

Quellcode

1
2
3
4
1.000000, 0.000000, 0.000000, 0.000000
0.000000, 1.000000, 0.000000, 0.000000
0.000000, 0.000000, 1.000000, 0.000000
0.000000, 0.000000, 0.000000, 1.000000


Die invertierte Viewmatrix ist mit der Viewmatrix identisch. Dies trifft zwar nur bei Matrizen zu, die nur aus Nullen und Einsen bestehen, ich bin mir aber trotzdem nicht sicher, ob die Invertierung korrekt ist.


Diese Matrix ist die Identität Id, die alles so lässt wie es war.
Und wegen Id ° Id = Id (nach den Axiomen übrigens), ist Id selbstinvers.

Im übrigen ist z. B. auch folgende Matrix selbstinvers:

-1, 0, 0, 0
0, 1, 0, 0
0, 0, 1, 0
0, 0, 0, 1

Dave

Alter Hase

Beiträge: 757

Wohnort: Berlin

  • Private Nachricht senden

4

04.01.2005, 19:27

Zitat von »"NoName"«

Diese Matrix ist die Identität Id, die alles so lässt wie es war.
Und wegen Id ° Id = Id (nach den Axiomen übrigens), ist Id selbstinvers.


stichwort neutrales element ;)

5

05.01.2005, 21:31

hier mal meine lösung für die berechnung der mausrichtung ;)

natürlich aus der direct3d-docu kopiert:

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
float3 d3dMain::getMouse3D()const{  
    float3          v, vPickRayDir;
    int2            ptCursor, ptc;
    D3DXMATRIXA16   matProj;    
    D3DXMATRIXA16   matView, m; 
    RECT            r;
    int             w,h,x,y;
        //hier entwas vorsichtig sein

    GetWindowRect(hwnd, &r);
/*
    w = r.right - r.left;
    h = r.bottom - r.top;
*/
    w = width;      //breite des fensters oder x-auflösung

    h = height;     //höhe des fesnters oder y-auflösung

    x = r.left;
    y = r.top;
    
    device->GetTransform( D3DTS_PROJECTION, &matProj );
    ptCursor = diMain::getMousePos();   
    
    // Get mouse position in screen coordinates [0... Width/Height]

    //if (x < r.left || x > r.right || y < r.top || y > r.bottom)return float3(0,0,0);

    
    v.x = ( ( ( 2.0f * (ptCursor.a) ) / (width) ) - 1 ) / matProj._11;
    v.y = -( ( ( 2.0f * (ptCursor.b) ) / (height) ) - 1 ) / matProj._22;
    v.z = 1.0f;

    // Get the inverse view matrix

    
    device->GetTransform( D3DTS_VIEW, &matView );
    D3DXMatrixInverse( &m, NULL, &matView );

    // Transform the screen space pick ray into 3D space

    vPickRayDir.x = v.x*m._11 + v.y*m._21 + v.z*m._31;
    vPickRayDir.y = v.x*m._12 + v.y*m._22 + v.z*m._32;
    vPickRayDir.z = v.x*m._13 + v.y*m._23 + v.z*m._33;

    return vPickRayDir;
}


und hier der code um den schnitpunkt einer geraden mit einer ebene
zu berechnen.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bool getIntersect(float3 B, float3 dir, float3 A, float3 N, float3 *erg){
    float   ne, za, r;
    if (!erg)return false;
    za = N.x*B.x + N.y*B.y + N.z*B.z + (A.x*dir.x + A.y*dir.y + A.z*dir.z);
    ne = N.x*dir.x + N.y*dir.y + N.z*dir.z;
    if (ne == 0.0f || (ne == 0.0f && za == 0.0f))return false;
    r = za / ne;
    erg->x = B.x - dir.x*r;
    erg->y = B.y - dir.y*r;
    erg->z = B.z - dir.z*r;
    return true;
}

//B = Aufhängepunkt der Geraden

//dir = Richtungsvektor der Geraden

//A = Aufhängepunkt der Ebene

//N = Normalenvektor der Ebene

//*erg = Ergebnis-buffer

//Die Ebene wird in Hesse-Normalenform übergeben


vielleicht hilfts.

gruß 23h

6

06.01.2005, 12:27

Danke für alle Antworten, wir konnte den Fehler dadurch endlich lokalisieren.
Eigentlich waren es mehrere Fehler, der entscheidende lag hier:

C-/C++-Quelltext

1
2
vS.x = ( ((nX*2.0f) / tbDirect3D::GetScreenSize().x)-1.0f) / mProjection.m11;
vS.y = ( ((nY*2.0f) / tbDirect3D::GetScreenSize().y)-1.0f) / mProjection.m22;

Durch das fehlende Minuszeichen vor der Klammer der zweiten Zeile war die Y-Achse natürlich invertiert. Weitere Probleme waren die ungünstige Testbedingung (nur ein Teilfenster eines Editors) sowie das falsche Übergeben von Mauskoordinaten an die Funktion, das aus der Fehlersuche der vorhergehenden Fehler entstand. Hinderlich war auch, nachdem wir versuchten mit einem Strahl ein Dreieck zu treffen, dass die entsprechende Kollisionsroutine der Engine einen Fehler enthielt, von dem wir nichts wussten (hier im Forum aber zu finden).

Allerdings stellen sich uns jetzt neue Probleme.
Im Vollbildmodus ist das Picking jetzt äußerst exakt, im Fenstermodus verfälschen leider Titelleiste und Fensterränder die Ergebnisse. Durch das modifizieren der obigen beiden Zahlen lässt sich z. B. so für eine 1024*768 Auflösung ein ganz gutes, wenn auch nicht perfektes Ergebnis erzielen.

C-/C++-Quelltext

1
2
vS.x = ( (((nX-3)*2.0f) / (tbDirect3D::GetScreenSize().x-6))-1.0f) / mProjection.m11;
vS.y = -( (((nY-22)*2.0f) / (tbDirect3D::GetScreenSize().y-28))-1.0f) / mProjection.m22;

Die subtrahierten Werte ergeben sich aus der geschätzten Höhe der Titelleiste bzw. der Breite des linken Fensterrandes in der ersten Klammer (-3/-22) und der Höhe der Titelleiste plus dem unteren Rand bzw. die Breite beider seitlichen Ränder für den Wert der zweiten Klammer (-6/-28).

Das ist natürlich keine wirklich elegante Methode, furchtbar exakt ist sie auch nicht. Außerdem sind diese Werte u. U. Auflösungs- und Einstellungsabhängig. Vielleicht hat jemand so etwas (Picking in einem Windowsfenster) schon einmal gemacht und kann uns hier weiterhelfen.

7

07.02.2005, 20:04

Hallo Faust,

Im Wesentlichem liegt du da richtig,

Ich lehne mich im folgenden an das "Pick" - Beispiel - Programm in der DirectX 9- SDK an,
dort wird der Bereich innerhalb der Fensters (sonst Client) als Oberfläche betrachtet, und der BackBuffer hat
als DIRECT3DSURFACE9 - Oberfläche dieselbe Ausdehnung. In dessen Beschreibung steht also stets die Breite, und
Höhe des inneren Bereich:

Ein Codeschnipsel das Du einfach anpassen solltest (IN: PDIRECT3DDEVICE9 pD3DDevice, HWND hWnd)

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
if (GetCapture())
{
// like in class declaration of "class CD3DApplication" 

// in "c:\DXSDK\SAMPLES\C++\COMMON\INCLUDE\d3dapp.cpp(97f)"

// and defintion "CD3DApplication::Initialize3DEnvironment()"

// in "c:\DXSDK\SAMPLES\C++\COMMON\SRC\d3dapp.cpp(820ff)" ;

// begin...

   // Store render target surface desc

    LPDIRECT3DSURFACE9 pBackBuffer = NULL;
    D3DSURFACE_DESC d3dsdBackBuffer;   // Surface desc of the backbuffer

    pD3DDevice->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer );
    pBackBuffer->GetDesc( &d3dsdBackBuffer );
    pBackBuffer->Release();
    int iWidth = d3dsdBackBuffer.Width;
    int iHeight = d3dsdBackBuffer.Height;
// ... end      

    
        tbMatrix mProj;
        pD3DDevice->GetTransform( D3DTS_PROJECTION, (D3DMATRIX*)(&mProj));

        POINT ptCursor;
        GetCursorPos( &ptCursor );
        ScreenToClient( hWnd, &ptCursor );

        tbVector3 v;
        v.x= ( ( 2.0f * ptCursor.x / iWidth ) - 1.0f ) / mProj.m11;
        v.y= -( ( 2.0f * ptCursor.y / iHeight ) - 1.0f ) / mProj.m22;
        v.z= 1.0f;
...
}

nofi

Werbeanzeige