Contorno doppio - Trovare il punto di funzione, normali


9

Sto seguendo questo tutorial per implementare il Dual Contouring http://www.sandboxie.com/misc/isosurf/isosurfaces.html

La mia fonte di dati è una griglia 16x16x16; Attraverso questa griglia dal basso verso l'alto, da sinistra a destra, vicino al lontano.

Per ogni indice della mia griglia, creo una struttura cubica:

public Cube(int x, int y, int z, Func<int, int, int, IsoData> d, float isoLevel) {
            this.pos = new Vector3(x,y,z);
            //only create vertices need for edges
            Vector3[] v = new Vector3[4];
            v[0] = new Vector3 (x + 1, y + 1, z);
            v[1] = new Vector3 (x + 1, y, z + 1);
            v[2] = new Vector3 (x + 1, y + 1, z + 1);
            v[3] = new Vector3 (x, y + 1, z + 1);
            //create edges from vertices
            this.edges = new Edge[3];
            edges[0] = new Edge (v[1], v[2], d, isoLevel);
            edges[1] = new Edge (v[2], v[3], d, isoLevel);
            edges[2] = new Edge (v[0], v[2], d, isoLevel);
        }

A causa di come attraverso la griglia, ho solo bisogno di guardare 4 vertici e 3 bordi. In questa immagine, i vertici 2, 5, 6, 7 corrispondono ai miei vertici 0, 1, 2, 3, e i bordi 5, 6, 10 corrispondono ai miei bordi 0, 1, 2. Cubo a griglia

Un bordo è simile al seguente:

    public Edge(Vector3 p0, Vector3 p1, Func<int, int, int, IsoData> d, float isoLevel) {
        //get density values for edge vertices, save in vector , d = density function, data.z = isolevel 
        this.data = new Vector3(d ((int)p0.x, (int)p0.y, (int)p0.z).Value, d ((int)p1.x, (int)p1.y, (int)p1.z).Value, isoLevel);
        //get intersection point
        this.mid = LerpByDensity(p0,p1,data);
        //calculate normals by gradient of surface
        Vector3 n0 = new Vector3(d((int)(p0.x+1),   (int)p0.y,      (int)p0.z       ).Value - data.x,
                                 d((int)p0.x,       (int)(p0.y+1),  (int)p0.z       ).Value - data.x,
                                 d((int)p0.x,       (int)p0.y,      (int)(p0.z+1)   ).Value - data.x);

        Vector3 n1 = new Vector3(d((int)(p1.x+1),   (int)p1.y,      (int)p1.z       ).Value - data.y,
                                 d((int)p1.x,       (int)(p1.y+1),  (int)p1.z       ).Value - data.y,
                                 d((int)p1.x,       (int)p1.y,      (int)(p1.z+1)   ).Value - data.y);
        //calculate normal by averaging normal of edge vertices
        this.normal = LerpByDensity(n0,n1,data);
    }

Quindi controllo tutti i bordi per un cambio di segno, se ce n'è uno trovo i cubi circostanti e ottengo il punto caratteristica di quei cubi.

Ora funziona se imposto il punto di funzione sul centro del cubo, quindi ottengo l'aspetto di Minecraft a blocchi. Ma non è quello che voglio.

Per trovare il punto di funzionalità, volevo farlo come in questo post: https://gamedev.stackexchange.com/a/83757/49583

Fondamentalmente, si avvia il vertice al centro della cella. Quindi fai la media di tutti i vettori presi dal vertice su ciascun piano e muovi il vertice lungo quel risultante, e ripeti questo passaggio un numero fisso di volte. Ho scoperto che spostandolo di circa il 70% lungo la risultante si sarebbe stabilizzato nella minima quantità di iterazioni.

Quindi ho preso una classe di aereo:

private class Plane {

        public Vector3 normal;
        public float distance;

        public Plane(Vector3 point, Vector3 normal) {
            this.normal = Vector3.Normalize(normal);
            this.distance = -Vector3.Dot(normal,point);
        }

        public float Distance(Vector3 point) {
            return Vector3.Dot(this.normal, point) + this.distance;
        }

        public Vector3 ShortestDistanceVector(Vector3 point) {
            return this.normal * Distance(point);
        }
 }

e una funzione per ottenere il punto caratteristica, in cui creo 3 piani, uno per ogni bordo e in media la distanza dal centro:

 public Vector3 FeaturePoint {
            get {
                Vector3 c = Center;
 //                 return c; //minecraft style

                Plane p0 = new Plane(edges[0].mid,edges[0].normal);
                Plane p1 = new Plane(edges[1].mid,edges[1].normal);
                Plane p2 = new Plane(edges[2].mid,edges[2].normal);

                int iterations = 5;
                for(int i = 0; i < iterations; i++) {
                    Vector3 v0 = p0.ShortestDistanceVector(c);
                    Vector3 v1 = p1.ShortestDistanceVector(c);
                    Vector3 v2 = p2.ShortestDistanceVector(c);
                    Vector3 avg = (v0+v1+v2)/3;
                    c += avg * 0.7f;
                }

                return c;
            }
        }

Ma non funziona, i vertici sono dappertutto. Dov'è l'errore? Posso effettivamente calcolare il margine normale calcolando la media dei vertici dei bordi? Non riesco a ottenere la densità nel punto medio del bordo, poiché ho solo una griglia intera come origine dati ...

Modifica: ho anche trovato qui http://www.mathsisfun.com/algebra/systems-linear-equations-matrices.html che posso usare le matrici per calcolare l'intersezione dei 3 piani, almeno così ho capito, quindi Ho creato questo metodo

 public static Vector3 GetIntersection(Plane p0, Plane p1, Plane p2) {              
            Vector3 b = new Vector3(-p0.distance, -p1.distance, -p2.distance);

            Matrix4x4 A = new Matrix4x4 ();
            A.SetRow (0, new Vector4 (p0.normal.x, p0.normal.y, p0.normal.z, 0));
            A.SetRow (1, new Vector4 (p1.normal.x, p1.normal.y, p1.normal.z, 0));
            A.SetRow (2, new Vector4 (p2.normal.x, p2.normal.y, p2.normal.z, 0));
            A.SetRow (3, new Vector4 (0, 0, 0, 1));

            Matrix4x4 Ainv = Matrix4x4.Inverse(A);

            Vector3 result = Ainv * b;
            return result;
        }

quale con questi dati

        Plane p0 = new Plane (new Vector3 (2, 0, 0), new Vector3 (1, 0, 0));
        Plane p1 = new Plane (new Vector3 (0, 2, 0), new Vector3 (0, 1, 0));
        Plane p2 = new Plane (new Vector3 (0, 0, 2), new Vector3 (0, 0, 1));

        Vector3 cq = Plane.GetIntersection (p0, p1, p2);

calcola un'intersezione in (2.0, 2.0, 2.0), quindi suppongo che funzioni correttamente. Tuttavia, non i vertici corretti. Penso davvero che siano le mie normali.


Unity ha già una Planestruttura definita ( vedi qui ), che ha già definito i metodi che hai dato (tranne il metodo vettoriale più breve, che puoi aggiungere alla Planestruttura usando i metodi di estensione C #). È possibile utilizzare il GetDistanceToPointmetodo anziché il Distancemetodo.
EvilTak

Grazie per il tuo commento, ho sostituito la mia implementazione con l'implementazione di Unity e usando questa funzione privata Vector3 shortestDistanceVector (piano p, punto Vector3) {return p.GetDistanceToPoint (point) * p.normal; } Ottengo anche solo vertici casuali. Sospetto che le mie normali siano totalmente fuori. Ho anche aggiunto una modifica, in cui ho provato un secondo metodo, forse puoi dare un'occhiata e dirmi cosa ho fatto di sbagliato lì.
ElDuderino,

2
Can I actually calculate the edge normal by averaging the normal of the edge vertices?- Potrei sbagliarmi, ma penso di aver visto altri consigli dire che non interpolare mai per ottenere le normali - semplicemente non si interpolano bene. Calcola per faccia, è più sicuro. Davvero, dovresti prima costruire un test case minimo per assicurarti che il tuo calcolo normale sia corretto. Quindi vai avanti con questo.
Ingegnere

Ma ottengo le facce solo dopo che ho le normali, ho bisogno delle normali per creare i piani e ottenere i vertici per le facce da loro. E come detto con la mia struttura attuale, posso indicizzare i miei dati solo ai vertici dei bordi. O di quali volti stai parlando?
ElDuderino,

@ElDuderino Volti come facce (o triangoli) di una mesh, ma non so come puoi ottenerlo dai tuoi dati. Se riesci a generare triangoli anziché bordi, il calcolo normale diventa davvero facile.
EvilTak

Risposte:


1

Prima di tutto le tue normali dovrebbero andare benissimo se sono calcolate attraverso differenze avanti / indietro / centrale. Il problema è che hai spostato il punto centrale nella direzione sbagliata nella funzione FeaturePoint, il che si traduce in un allontanamento dal minimo.

Vector3 c = Center;
Plane p0 = new Plane(edges[0].mid,edges[0].normal);
Plane p1 = new Plane(edges[1].mid,edges[1].normal);
Plane p2 = new Plane(edges[2].mid,edges[2].normal);

int iterations = 5;
for(int i = 0; i < iterations; i++) {
    Vector3 v0 = p0.GetDistanceToPoint(c) * edges[0].normal;
    Vector3 v1 = p1.GetDistanceToPoint(c) * edges[1].normal;
    Vector3 v2 = p2.GetDistanceToPoint(c) * edges[2].normal;
    Vector3 avg = (v0+v1+v2)/3;
    c -= avg * 0.7f; // Error was here!
}
return c;

Questo è successo perché il tuo codice non converge contro un punto e quindi salta fuori dalla tua casella voxel. Non so se il codice di Qualcuno può spiegare il doppio contouring? è stato progettato per utilizzare un approccio di proiezione in cui il punto viene proiettato sul piano attraverso:

distance = Vector3.Dot(point - origin, normal);
projectedPoint = point - distance * normal;

ma è lo stesso metodo. Se riscrivi la proiezione nel tuo codice originale, questo si traduce in:

    Vector3 v0 = c - p0.GetDistanceToPoint(c) * edges[0].normal;
    Vector3 v1 = c - p1.GetDistanceToPoint(c) * edges[1].normal;
    Vector3 v2 = c - p2.GetDistanceToPoint(c) * edges[2].normal;
    c = (v0+v1+v2)/3;

che può essere riscritto in:

    Vector3 v0 = p0.GetDistanceToPoint(c) * edges[0].normal;
    Vector3 v1 = p1.GetDistanceToPoint(c) * edges[1].normal;
    Vector3 v2 = p2.GetDistanceToPoint(c) * edges[2].normal;
    c = c - (v0+v1+v2)/3;

e quindi risulta nel primo codice. Proiettando il punto su tre piani non planari, converge lentamente verso il minimo perché minimizzi la distanza da ciascun piano al punto come mostrato nella figura.

I punti rossi indicano il punto caratteristica, le linee blu le normali e il punto viola il punto proiettato sul piano. Inoltre, non è necessario utilizzare il fattore 0.7 perché dovrebbe convergere più velocemente senza di esso. Se si utilizza questo metodo, fare attenzione, che l'algoritmo potrebbe non funzionare se si hanno piani non intersecanti.


Ehi, fantastico avere una risposta dopo 2 anni :) Non ho mai trovato una soluzione, quindi ho interrotto questo progetto, ma lo rivisiterò con questa conoscenza e ti farò sapere come è andata. Avere un +1 fino ad allora.
ElDuderino,

Eccezionale! Sono felice di poterti aiutare. Fammi sapere se funziona per te.
Tim Rolff,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.