Face-Click-Erkennung (wie in Minecraft)

7

Ich arbeite an einer Box-basierten Spiel-Engine wie Minecraft und habe mich gefragt, wie ich das "angeklickte Gesicht" beim Platzieren von Blöcken erkennen kann.

Ich habe diese Engine in C ++ (DirectX / D3D), C # (XNA) und Flash (away3d & papervision3d) erstellt.

Der Engine-Code ist bei jedem ziemlich gleich, aber da einer C # und der andere C ++ ist, suche ich nur nach dem Pseudocode, der Theorie oder dem Rat, wie man platzierte Blöcke auf dem angeklickten Gesicht "schnappt".

Wie soll ich seine Ausrichtung erhalten und dann einen Block auf diesem Gesicht in dieser Position mit der entsprechenden Ausrichtung platzieren?

AKTUALISIEREN

Im Moment habe ich mich für eine einfache, aber effektive Methode entschieden:

Zuerst habe ich mich an den grundlegenden 3D-2D-Projektionsalgorithmus erinnert. Geben Sie hier die Bildbeschreibung ein

Wenn ich jetzt einen Klickpunkt auf dem Bildschirm habe, kann ich einfach eine Linie von der Kamera (Auge) durch den Bildschirm bis zu einer bestimmten Entfernung verfolgen.

Mit dieser Linie "im Auge" und einer modifizierten (3D) Version von Bresenhams (2D) Linienalgorithmus konnte ich nun sehen, ob meine imaginäre Linie auf einen Block in meinem 3D-Gitter "zeigt".

Danke für all deine Vorschläge.

UPDATE 2

Geben Sie hier die Bildbeschreibung ein

Ich hoffe es erklärt meine Lösung. (Rot ist der ursprüngliche Algorithmus, Grün ist meine modifizierte Version)

auch: mit "gerundet" meine ich, dass der ursprüngliche Algorithmus nur den xy-Faktor-Wert rundet, um das nächste Pixel zu erhalten. Ich hoffe ihr wisst wovon ich rede.

As
quelle
Schauen Sie sich die unproject-Methoden in directX (xna) und OpenGL an. Dies übersetzt 2D-Bildschirmkoordinaten (bei denen Sie mit der Maus geklickt haben) in 3D-Weltkoordinaten, abhängig von den verwendeten Matrizen. Dies kann dann verwendet werden, um Raycast zu senden und das richtige Gesicht zu finden. Das einzige, was ich nicht weiß, ist, wie man das nächstgelegene Gesicht effizient bestimmt und wie man mehrere Gesichter findet, die sich praktisch an derselben Stelle befinden (wie wenn man 2 Kästchen nahe beieinander platziert). Dies könnte zu einem seltsamen Verhalten führen
Thomas
Eine Sache, mit der Sie vorsichtig sein sollten, wenn Sie das Bresenham-Algorthimn selbst verwenden, ist, dass es einige Zellen überspringt (siehe en.wikipedia.org/wiki/File:Bresenham.svg ). Der Playtechs-Ansatz sollte dies jedoch nicht tun. Wenn Sie beispielsweise diagonal Blockreihen haben (z. B. viele Dächer in Minecraft), können Sie diese mit Bresenhams "durchschauen".
Will
Bietet diese Lösung auch Mittel zur Unterstützung einer gebrochenen Startposition innerhalb eines Blocks? Ich fand, dass ich es schaffen musste, damit sich die Dinge richtig anfühlten.
Will
Ja, Sie haben Recht, Bresenhams Algorithmus überspringt einige Zellen, deshalb habe ich es wie gesagt geändert. Sehen Sie sich mein Update2 an, um zu verstehen, wie ich es gemacht habe. Was meinst du mit "Bruchstartposition"?
Ass

Antworten:

4

Wenn Ihr Strahl durch ein Gitter verfolgt wird, können Sie einfach die Zellen durchlaufen, durch die der Strahl der Reihe nach gehen würde. Ich fand das extrem schnell im Vergleich zum Umgang mit einer Reihe von AABBs in einer großen Welt.

Für jede Zelle können Sie sehen, ob sie als fest angesehen wird. Wenn dies der Fall ist, haben Sie angesichts der vorherigen Zellkoordinaten auch das Gesicht. Sie können auch eine maximale Entfernung festlegen und die Ein- und Ausstiegspunkte für jede Zelle berechnen.

Sie können dies mit einigen komplexeren Kollisionstests für Nicht-Würfel-Zellen kombinieren (z. B. wissen Sie, dass der Strahl mit einem Zaun durch diese Zelle geht, sodass Sie detaillierte Tests durchführen können, um festzustellen, ob Sie den Zaun getroffen oder eine Lücke passiert haben.

Hier gibt es einen Blog-Beitrag, der einige Details und eine Implementierung in 2D enthält. Ich habe es geschafft, meine eigene 3D-Version ohne wirkliche Probleme zu erstellen.

http://playtechs.blogspot.co.uk/2007/03/raytracing-on-grid.html

BEARBEITEN:

Beachten Sie, dass es wichtig sein kann zu überlegen, wo der Strahl innerhalb einer Zelle beginnt, z. B. das Bild unten mit einem Strahl, der am roten Punkt beginnt und in Richtung des grünen Punkts geht. Die Start- und Endzellen sind gleich, aber ich habe die Position in der Zelle geändert, wodurch dann einige der Zellen zwischen den ausgewählten Zellen geändert wurden. Die blauen Punkte sind die Schnittpunkte. Ich habe diese mit "Start + Zeit * Überschrift" berechnet und die Iteration beendet, sobald ich Zeit = 1 überschritten habe (in diesem Fall ist die Richtung kein Einheitsvektor. Wenn es sich um einen Einheitsvektor handelt, dann natürlich um die Zeit = Entfernung, damit Sie auf diese Weise bis zu einer gewissen Entfernung iterieren können).

Geben Sie hier die Bildbeschreibung ein

Wille
quelle
Danke für diese Antwort. Sie haben gerade meine persönliche Lösungsmethode genau beschrieben, an die ich nach stundenlanger Recherche gedacht habe. Ich denke, in einer Cube-basierten Game-Engine ist diese Methode definitiv die leistungsstärkste. Ich werde die "richtige" Antwort markieren, nachdem ich alle angegebenen Methoden getestet habe.
Ass
Könnten Sie uns ein wenig Code geben, zum Beispiel "Finden des Look-Vektors" und Durchlaufen der "Look-Linie" und Abrufen aller "Blockpositionen", die die "Look-Linie" passiert, bis sie auf a trifft fester Block oder der "maximale Abstand"? Dies dient nur dazu, die richtige Antwort zu markieren, da Jason Coombes mit seiner Lösung einen ähnlichen Versuch unternimmt.
Ass
Ich habe gerade die Mitte des Bildschirms gehandhabt, indem ich (0,0,1) genommen und es um die Neigung und das Gieren der Kamera gedreht habe. Bei Mausklicks an einer beliebigen Stelle auf dem Bildschirm ist dies aufgrund des Sichtfelds falsch. Sie können den Vektor nicht einfach mit der Mausposition übersetzen. Die meisten APIs scheinen so etwas wie Viewport.Unproject zu haben (mousex, mousey, z, viewProjMatrix). Wenn es eine Weltmatrix will, funktioniert Identität meiner Meinung nach gut. Sie können z auf 0 setzen, um die Position der nahen Ebene zu erhalten, und z auf 1, um die Position der fernen Ebene zu erhalten. Ihre Richtung / Ihr Strahl ist nur der Vektor von nah nach fern.
Will
Danke für dein Update. Denken Sie daran, dass mein Player (die Kamera) meine Blöcke nicht berechnet. Die Startposition ist also immer genau. In meiner Version von Bresenhams Algorithmus gibt es nur eine "Rundung", während die Linie entsprechend der Anzahl der Blöcke in der längsten Richtung (x, y oder z) der Sichtlinie in gleichmäßige Stücke geteilt wird.
Ass
Sie sagen also, Ihre Kamera "schnappt" immer an Gitterlinien? Während für die Zwecke des Algorithmus das Raster wie ein Pixelraster ist, sind meine Zellen in der Praxis 16 x 16, und in 3D ist es noch weniger sinnvoll, sie als Pixel zu betrachten, da aus nächster Nähe eine Zelle 600 x 600 auf dem Bildschirm angezeigt wird .
Will
1

Die Idee ist, einen Strahl zu werfen und zu sehen, auf welches Gesicht er trifft. Dies ist so ziemlich ein Ray-to-AABB-Kreuzungstest, bei dem Sie den anfänglichen Kontaktpunkt finden.

Eine gute Ressource (die ich mir beim Schreiben anschaue) ist das Buch Real-Time Collision Detection von Christer Ericson. Die Idee ist, 3 Platten zu verwenden und den Strahl gegen sie zu testen. Eine Platte wäre ein Bereich zwischen zwei Ebenen (gegenüberliegende Seiten des AABB). Wenn der Strahl alle drei Platten schneidet, schneidet er den AABB und ein Kontaktpunkt und eine Zeit t können zurückgegeben werden.

Unter Verwendung dieses Kontaktpunkts kann leicht bestimmt werden, welches Gesicht den meisten Gesichtern dieser Punkt zugewandt ist, insbesondere wenn der AABB implizit gespeichert ist. Wenn der AABB implizit definiert ist, kann das Gesicht direkt berechnet werden, da Sie wissen, dass der Kontaktpunkt auf diesem Gesicht liegt.

Wenn Ihre Box eine Ausrichtung hat, kann das Problem in Strahl gegen AABB umgewandelt werden, indem der Strahl in die Basis der ausgerichteten Box umgewandelt wird.

Diese Berechnung kann durch die Verwendung einer breiten Phasenbaumstruktur unter Verwendung von Begrenzungsrahmen erheblich beschleunigt werden. AABB-Bäume oder dynamische AABB-Bäume eignen sich hervorragend für diese Art von Ray Cast-Abfragen.

Da Sie mit der Maus klicken, wird dies auch als "Kommissionieren" bezeichnet.

Ich hoffe das hilft! Ich habe Raycasting noch nicht selbst implementiert, daher kann ich den Pseudocode für die Implementierung nicht selbst schreiben, habe Sie aber zumindest auf eine großartige Ressource verwiesen.

RandyGaul
quelle
Danke für diese Antwort. Ich denke, die Methode, die Will Newbery beschrieben hat, ist die einfachste Lösung für diese Art von Projekt.
Ass
1

Diese Frage ähnelt der Frage, wie ich einen Auswahlblock in TechCraft verwaltet habe:

public void RenderSelectionBlock()
{
    //Get exact centre vector
    Vector3 position = player.currentSelection.Value.position.asVector3() + new Vector3(0.5f, 0.5f, 0.5f);

    Matrix matrix_a, matrix_b;
    Matrix identity = Matrix.Identity;                       // setup the matrix prior to translation and scaling  
    Matrix.CreateTranslation(ref position, out matrix_a);    // translate the position a half block in each direction
    Matrix.CreateScale((float)0.51f, out matrix_b);          // scales the selection box slightly larger than the targetted block

    identity = Matrix.Multiply(matrix_b, matrix_a);          // the final position of the block

    // set up the World, View and Projection
    _selectionBlockEffect.World = identity;
    _selectionBlockEffect.View = camera.View;
    _selectionBlockEffect.Projection = camera.Projection;
}

Dies gibt uns die WVP-Matrix zum Rendern des Netzes im ausgewählten Block. Wir müssen jedoch noch bestimmen, welcher Block neben diesem Auswahlblock platziert werden soll.

Dies geschieht unter Verwendung eines Vektors3 an dieser Position:

private float setPlayerSelectedBlock(bool waterSelectable)
    {

// 0,5f = min zu prüfender Abstand // 8f = maximal zu prüfender Abstand. Außerdem wird es keinen Auswahlblock geben

        for (float x = 0.5f; x < 8f; x += 0.1f)
        {
            Vector3 targetPoint = camera.Position + (lookVector * x);

            Block block = player.world.BlockAt(targetPoint);

// Sollte nicht versuchen, Wasser oder andere Flüssigkeiten auszuwählen oder neben diese zu stellen

            if (block.Type != BlockType.None && (waterSelectable || block.Type != BlockType.Water))
            {
                player.currentSelection = new PositionedBlock(new Vector3i(targetPoint), block);
                return x;
            }
            else
            {
                player.currentSelection = null;
                player.currentSelectedAdjacent = null;
            }
        }
        return 0;
    }

// Jetzt können wir den Strahl richtig zum benachbarten Block bringen:

    private void setPlayerAdjacentSelectedBlock(float xStart)
    {
        for (float x = xStart; x > 0.7f; x -= 0.1f)
        {
            Vector3 targetPoint = camera.Position + (lookVector * x);
            Block block = player.world.BlockAt(targetPoint);

            if (player.world.BlockAt(targetPoint).Type == BlockType.None)
            {
                player.currentSelectedAdjacent = new PositionedBlock(new Vector3i(targetPoint), block);
                break;
            }
        }
    }
Jason Coombes
quelle
Vielen Dank für diese Lösung. Es ist wie die Lösung, die Will Bewbery gegeben hat. Wie bekommt man den LookVector?
Ass
Der gesamte Code, den ich eingefügt habe, ist unter techcraft.codeplex.com verfügbar. Schauen Sie sich speziell die Player-Handler-Klassen an. Verfolgen Sie den Code für den LookVector. Es ist besser als ich, ihn hier zu erklären.
Jason Coombes
Das sieht für mich so aus, als ob der Code gerade an einer Position beginnt und dann kleine (0,1) Schritte in Richtung der Richtung macht. Nach jedem Schritt wird angezeigt, was sich an der aktuellen Position befindet. Daher wird es wahrscheinlich mehrmals denselben Block besuchen, während es durchläuft, während meine Lösung tatsächlich direkt von Zelle zu Zelle springt, indem berechnet wird, welche Gitterlinie als nächstes gekreuzt wird.
Will
0

Es gibt einen einfachen, präzisen Algorithmus zum Besuchen der Würfel, die ein Strahl in einer gitterbasierten Welt wie Minecraft durchläuft. In dieser Antwort finden Sie den vollständigen Quellcode.

Wille
quelle
Danke für den Rat. Ich werde das überprüfen. aber wie ich schon erwähnt habe, denke ich, dass ich mit dieser einfachen und schnellen Methode gehen werde, die in meinem Update 1 & 2 zu sehen ist.
Ace
@Ace Ich habe mir gerade Will Newberys Code-Link angesehen und es ist der gleiche Algorithmus, auf den ich verlinke, obwohl der Quellcode, auf den ich verlinke, für 3D ist. Der Schlüssel ist seine genaue. Ich selbst habe versucht, Breshenhams einen Fuzz-Faktor hinzuzufügen, und das funktioniert im Grunde nicht. Übernehmen Sie einfach den richtigen Algorithmus als verknüpft und fahren Sie fort.
Will
4
Hinterlassen Sie anstelle einer Nur-Link-Antwort einen Kommentar oder markieren Sie ihn als Duplikat.
Sean Middleditch
Meine Lösung funktioniert einwandfrei. Ich denke auch nicht, dass dies ein Duplikat ist, da meine Lösung in ähnlichen Fragen nicht erwähnt wird. Ich denke, mein Code arbeitet viel schneller, weil es nur ein paar Codezeilen sind. Ich werde die Geschwindigkeit und Genauigkeit von beiden testen und Sie wissen lassen.
Ass
Der Algorithmus, mit dem @WillNewbery und ich verknüpfen, ist optimal. Geschwindigkeit! = Codezeilen.
Will