Rzutowanie współrzędnych 3D na 2D

11-04-2009 przez Piotr Wierzgała Zostaw odpowiedź »

Jeżeli szukasz odpowiedzi na pytanie jak rzutować współrzędne 3D na 2D to trafiłeś w dobre miejsce. W tym wpisie rozwijam ten problem w oparciu o użycie metody calculateScreenCoords dostępnej we wszystkich obiektach dziedziczących po DisplayObject3D.

FLEX: 3.0, Papervision3D: 2.0.883

Zaczynamy od utworzenia klasy dziedziczącej po BasicView. Jeżeli ktoś nie ma doświadczenia z Papervision3D to polecam najpierw zapoznać się z wpisem: Pierwsze kroki z Papervision3D. Do klasy dodajemy trzy pola:

private var plane:Plane;
private var vertexPlane:Plane;
private var vertices2dSprite:Sprite;

Zmienna plane posłuży jako obiekt, którego współrzędne wierzchołków 3D będziemy rzutować na 2D. Zmienna vertexPlane jest niezbędna do rzutowania wspomnianych wierzchołków (ale o tym zaraz), natomiast vertices2dSprite to zwykły sprite, na którym będziemy wizualizować rzutowane wierzchołki.

Dodajmy teraz do naszej klasy metodę init3D(), w której wstawiamy na scenę wszystkie niezbędne w tym przykładzie obiekty 3D:

private function init3D():void {
    var wireframeMaterial: WireframeMaterial = new WireframeMaterial(0x000000);
    var colorMaterial:ColorMaterial = new ColorMaterial(0xffffff);
    var planeMaterial:CompositeMaterial = new CompositeMaterial;
           
    planeMaterial.addMaterial(colorMaterial);
    planeMaterial.addMaterial(wireframeMaterial);
    planeMaterial.doubleSided = true;
   
    plane = new Plane(planeMaterial,250,250);
    scene.addChild(plane);

    vertexPlane = new Plane(null,1,1);
    scene.addChild(vertexPlane);
}

Oraz metodę init2D dodającą do aplikacji sprite, na którym odrysujemy rzutowane wierzchołki:

private function init2D():void {
    vertices2dSprite = new Sprite;
    addChild(vertices2dSprite);
}

Przejdźmy teraz do sedna sprawy a mianowicie do obliczania współrzędnych 2D i ich odrysowywania.

Pierwszą kwestią zajmie się nasza metoda o nazwie getPoint2D.

public function getPoint2D(do3d:DisplayObject3D):Number2D {
    singleRender();
    do3d.calculateScreenCoords(camera);
    return new Number2D(do3d.screen.x + viewport.viewportWidth*0.5, do3d.screen.y + viewport.viewportHeight*0.5);
}

Metoda getPoint2D przyjmuje jako parametr dowolny obiekt typu DisplayObject3D i zwraca jego zrzutowane na 2D współrzędne. W tym celu na rzutowanym obiekcie wywołuje funkcję calculateScreenCoords, która jako parametr przyjmuje obiekt kamery względem, którego ma odbyć się rzutowanie. Wywołanie funkcji calculateScreenCoords powoduje ustawienie wartości dla pola screen rzutowanego obiektu. Pole screen zaś to nic innego jak struktura, która przechowuje pożądane współrzędne ekranowe (2D). Wiążą się z tym dwa szczegóły, na które trzeba zwrócić uwagę.

Pierwszy to taki, że żeby pole screen mogło zostać ustawione to obiekt dla którego wywoływana jest funkcja calculateScreenCoords musi być wyrenderowany (czyli też dodany do sceny przyp.). Dlatego też na samym początku omawianej metody wywołujemy funkcję singleRender klasy BasicView.

Druga kwestia to konieczność zwiększenia składowych pola screen odpowiednio o wartość połowy wysokości i szerokości viewportu. Dzieje się tak ponieważ współrzędne w polu screen liczone są od środka viewportu.

Teraz pozostało nam tylko odrysować to co umiemy już liczyć. Posłuży do tego nasza kolejna metoda:

public function calculate2DCoordinates():void {
    var vertex2D:Number2D;     
    vertices2dSprite.graphics.beginFill(0xffff00);
    vertices2dSprite.graphics.lineStyle(1,0x000000);

    plane.geometry.transformVertices(plane.transform);
    for (var i:Number=0; i<plane.geometry.vertices.length; i++) {
        vertexPlane.x = plane.geometry.vertices[i].x;
        vertexPlane.y = plane.geometry.vertices[i].y;
        vertexPlane.z = plane.geometry.vertices[i].z;
        vertex2D = getPoint2D(vertexPlane);
        vertices2dSprite.graphics.drawCircle(vertex2D.x,vertex2D.y,7);
    }
    plane.geometry.transformVertices(Matrix3D.inverse(plane.transform));

    vertices2dSprite.graphics.beginFill(0xff0000)
    vertex2D = getPoint2D(plane);
    vertices2dSprite.graphics.drawCircle(vertex2D.x,vertex2D.y,7);
    vertices2dSprite.graphics.endFill();
}

Warto tu zwrócić uwagę na to co się dzieje w miejscu wystąpienia pętli for. Ponieważ wierzchołki płaszczyzny nie dziedziczą po klasie DisplayObject3D dlatego nie ma możliwości ich bezpośredniego zrzutowania na 2D. Żeby to obejść korzystamy z dodatkowej, stworzonej w metodzie init3D płaszczyzny vertexPlane o wymiarach 1×1. W pętli for iterujemy po wszystkich wierzchołkach ustawiając położenie vertexPlane zgodnie z aktualnie przetwarzanym a następnie rzutujemy współrzędne vertexPlane na 2D.

Żeby uzyskać poprawne odwzorowanie położenia rzutowanych wierzchołków przed pętlą musimy zastosować na nich macierz przekształceń obiektu do którego należą. W przeciwnym razie nie uwzględnione zostaną zmiany współrzędnych wierzchołków wynikające ze skalowania, obracania i przesunięcia plane’a. Ponieważ macierz transformacji odnosi się do bazowych wartości współrzędnych wierzchołków dlatego po wyjściu z pętli przywracamy je do stanu sprzed przekształcenia. W tym celu stosujemy na nich odwrotność macierzy transformacji.

Oprócz wierzchołków odrysowujemy też to co zwraca metoda getPoint2D dla plane’a (czerwona kropka). Dzięki temu widzimy, że po obliczeniu wartości pola screen zawiera ono współrzędne środka rzutowanego obiektu.

Pobierz źródła

Reklama

Dodaj komentarz

Flexmaniaks on Facebook