Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: C++: Törmäystarkistus 3D-avaruudessa: jana ja kolmio

Heikki [14.11.2005 17:55:47]

#

Itselleni tämä oli 3D-ohjelmoinnin aloittamisessa ensimmäinen todella iso ongelma. Miten tutkia, törmääkö jana (joka voi olla vaikkapa pelihahmon nykyisestä paikasta hieman eteenpäin sen liikkumissuuntaan) johonkin polygoniin. Sen verran tämän kanssa tappelin, että ajattelin pistää tänne jos joku vaikka pääsisi sitten tämän vaiheen ohitse helpommin.

Algoritmin toiminta yksinkertaistettuna, kommentoitu tarkemmin:
-Tutkitaan leikkaako säde tason, jossa kolmio on
-Jos leikkaa, tutkitaan onko leikkauspiste kolmiolla käyttäen 2D-projektiota

Muuten minusta melko tehokas, mutta tutkii jokaisen kolmion erikseen, joka ei minun n. kymmenestä kolmiosta koostuvassa testimaailmassani haittaa, mutta kun kolmioita on tuhansia, täytyy käyttää jonkinlaista optimointia.


Käyttää omaa vektoriluokkaani jota en nyt tähän viitsi laittaa, ellei joku sitä erikseen pyydä. Lisäksi funktiolla tulee olla saatavilla tiedot kolmioista ja niiden päätepisteistä. Lisäksi on oltava laskettuna kolmion normaalivektori ja kolmion tason yhtälön D (tason yhtälö muotoa Ax + By + Cz = -D).

Palauttaa 1 jos törmäys tapahtuu, muutoin 0. Parametreina lähtöpisteen paikkavektori ja suuntavektori (eli janan päätepiste on paikka+suunta).

int CMaailma::TormaysTarkistus(CVector lahto, CVector suunta) {

    // Tutkitaan törmäysta ensin tason ja janan välillä, jos tässä törmäys katsotaan, onko törmäyspiste kolmiossa
    // Tason yhtälö muotoa
    // Ax + By + Cz = -D
    // A on normaali.x() jne
    // D on kolmio[foo].d
    // Suora 3D-avaruudessa x = p + tpq,       p alkupiste, t reaaliluku ja pq suunta, x suoran piste
    // Taso Ax + By + Cz = -D       -> N dot x = -D
    //----->Piste tasossa, jos N dot x + D = 0
    // Eli leikkaus, jos (p+tpq) dot N + D = 0
    // Sievenee muotoon:
    // tN dot pq = -N dot p - D
    // Josta voidaan ratkaista t:
    //      N dot p + D
    //t = - ------------------
    //      N dot pq
    // Törmäys, jos t välillä [-1,1]


    // Jos N dot pq on 0, on suunta tason suuntainen ja tutkittava, onko alkupiste tasossa ja sitten onko se kolmiossa


    // JOKAINEN (hyi) kolmio erikseen  TODO: Optimointia...
    bool tormays;

    for (int i=0; i<kolmioita; i++) {

        // Ensin tutkitaan ja poistetaan selvät tapaukset

        tormays=true;
        float r=suunta.DotProduct(kolmio[i].normaali);      // r = N dot pq
        float t=0;

        if (r<0.0001 and r>-0.0001){  // Yhdensuuntainen, osuu jos alkupiste tasossa
            // Tutkitaan onko alkupiste tasossa
            float t=lahto.DotProduct(kolmio[i].normaali);
            if (!(t<kolmio[i].d+.9 and t>kolmio[i].d-.9)) {
                tormays=false;
            }
        }
        else {      // Lasketaan t
            float ptulo=lahto.DotProduct(kolmio[i].normaali);
            t = (ptulo-kolmio[i].d) / r;
        }


        if (t>1.03 or t<-1.03) {        // Hyväksytään pientä heittoa
            tormays=false;
        }
        if (tormays==true) {
            // Törmäys tasoon, tutkitaan osuuko kolmioon
            CVector piste=suunta*t; piste+=lahto;       // Törmäyspisteen paikkavektori
            CVector projektiopiste;                     // Törmäyspiste muodostettavassa 2D-projektiossa

            // Nyt tiedetään leikkauspiste, tutkitaan onko se kolmiolla
            // Ensin muodostetaan kolmiosta 2-ulotteinen projektio
            // Jätetään huomiotta se koordinaatti (x/y/z) jota vastaava kerroin tasoyhtälössä Ax+By+Cz on suurin (itseisarvo)
            // Tämän jälkeen tutkitaan montako reunaa pisteestä piirrettyääretön säde leikkaa, ja jos niitä on pariton määrä,
            // niin piste on kolmion (ja itse asiassa minkä tahansa monikulmion) sisällä.
            // Piirretään tämä säde positiivisen x-akselin suuntaan kun on ensin siirretty leikkauspiste origoon

            // Tutkitaan suurin kerroin tasoyhtälöstä
            float a=fabs(kolmio[i].normaali.x()), b=fabs(kolmio[i].normaali.y()), c=fabs(kolmio[i].normaali.z());


            CVector pisteet[3];     // Pisteet 2d-avaruudessa
            if (a>b and a>c) { // Ei huomioida X:ää
                for (int l=0; l<3; l++) {
                    pisteet[l].x(kolmio[i].piste[l].y);
                    pisteet[l].y(kolmio[i].piste[l].z);
                    pisteet[l].z(0);
                    projektiopiste.x(piste.y());
                    projektiopiste.y(piste.z());
                    projektiopiste.z(0);
                }
            }
            else if (b>a and b>c) {  // Ei huomioida Y:tä
                for (int l=0; l<3; l++) {
                    pisteet[l].x(kolmio[i].piste[l].x);
                    pisteet[l].y(kolmio[i].piste[l].z);
                    pisteet[l].z(0);
                    projektiopiste.x(piste.x());
                    projektiopiste.y(piste.z());
                    projektiopiste.z(0);
                }
            }
            else if (c>a and c>b) {      // Ei huomioida Z:aa
                for (int l=0; l<3; l++) {
                   pisteet[l].x(kolmio[i].piste[l].x);
                    pisteet[l].y(kolmio[i].piste[l].y);
                    pisteet[l].z(0);
                    projektiopiste.x(piste.x());
                    projektiopiste.y(piste.y());
                    projektiopiste.z(0);
                }
            }


            // Siirretään pisteitä siten, että törmäyspiste on origossa (eli vähennetään törmäyspisteen koordinaatit)
            pisteet[0]-=projektiopiste;
            pisteet[1]-=projektiopiste;
            pisteet[2]-=projektiopiste;


            // Nyt on selvillä pisteet, tutkitaan moniko niiden välisistä janoista leikkaa positiivisen x-akselin
            int leikkauksia=0;
            for (int l=0; l<3; l++) {
                int p=l+1; if (p>2) p=0;
                // Triviaalit hylkäykset
                if (pisteet[l].x()<=0 and pisteet[p].x()<=0)     //  Y-akselin väärällä puolella
                   continue;
                if ( (pisteet[l].y()>0 and pisteet[p].y()>0) or (pisteet[l].y()<0 and pisteet[p].y()<0))  // Pisteet + tai -
                   continue;

                // Selkeät tapaukset nyt hylätty

                // Selkeästi hyväksyttävät segmentit, eli y-akselin oikealla puolella molemmat ja päätepisteet eri puolilla
                // x-akselia
                if ( pisteet[l].x()>0 and pisteet[p].x()>0) {
                    if ( (pisteet[l].y()<0 and pisteet[p].y()>0) or (pisteet[l].y()>0 and pisteet[p].y()<0) )
                        leikkauksia++;
                }
                else {
                    // Tässä tapauksessa segmentin päät ovat molempien akselien eri puolilla eli leikkaus mahdollinen, mutta
                    // segmentti saattaa myös kulkea origon "väärältä" puolelta
                    // Olkoon p ja q segmentin päätepisteet
                    // Lisäksi olkoon r origo ja s äärettömän kaukana x-akselilla (käytetetään kuitenkin vaikka arvoa (99999,0)
                    // Segmentti pq leikkaa x-akselin, eli segmentin rs, jos käännös p->q->r on eri suuntaan
                    // kuin käännös p->q->s   ( siis käännös q:n jälkeen)
                    // Käännös voidaan laskea 2D-ristutlololla
                    // xy' - yx'     (skalaariluku)
                    // Joka on itse asiassa ristitulon z-koordinaatti


                    // Käytetään vektorista p nimeä vp, koska p on jo int-muuttuja

                    // Vektorit
                    CVector r(0,0,0); CVector s(99999,0,0); CVector vp=pisteet[l]; CVector q=pisteet[p];

                    // p->q->r
                    CVector v1=r-vp; CVector v2=q-vp;
                    float r1=v1.x()*v2.y() - v1.y()*v2.x();

                    // p->q->s
                    v1=s-vp; v2=q-vp;
                    float r2=v1.x()*v2.y() - v1.y()*v2.x();

                    if ((r1<0 and r2>0) or (r1>0 and r2<0)) // Erimerkkiset
                        leikkauksia++;


                }

            }


            if (leikkauksia % 2== 1 ) {  // Leikkaa parittoman määrän sivuja, eli törmäys tapahtui
                return 1;
            }

        } // End if törmäys
    } // End for kolmiot
    return 0;   // Ei törmäystä
}

Puhveli [23.11.2005 19:19:04]

#

Ihan kunnioitettavan näkönen päättelyketju. Rupesin vaan miettimään että mitenköhän jokaisen kolmion erikseen tarkistamisen nyt optimoisi. Mutta joo, oot sinä aika epeli.

Heikki [24.11.2005 15:24:19]

#

Täytyy tunnustaa ettei ole ihan täysin omasta päästä tuo algoritmi :)

Etenkin tuo tarkistus siitä, onko piste kolmion sisällä on algoritmina kopattu täältä. Mutta oli siinä kyllä itsekin vähän tekemistä ja laskeskelua.

dvd [28.11.2005 10:41:37]

#

Ihan jees,hyvä että tästä tuli vinkki, meinaan itsellänikin aikoinaan juuri törmäystarkistus 3d maailmassa tuotti ongelmia. Tuosta onkin sitten helppo muokata mm. pallo->kolmio törmäys yms.

dvd [28.11.2005 14:00:55]

#

hmm..tulipas mieleen optimoinnista että ihan käytännössähän ensin kannattaa tarkistaa törmäys esim objekteja ympäröivillä laatikoilla/palloilla, mikäli nämä leikkaavat niin sitten siirtyy tarkempaan tarkistukseen.

Megant [01.12.2005 12:59:35]

#

lainaus:

Käyttää omaa vektoriluokkaani jota en nyt tähän viitsi laittaa, ellei joku sitä erikseen pyydä.

No minä haluaisin nähdä sen :)

Le-Co-Las [31.08.2010 11:19:42]

#

Aivan mainio ja juuri oikeaan aikaan.
kiitos

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta