Contorneado dual: búsqueda del punto de característica, normal desactivado

9

Estoy siguiendo este tutorial para implementar Dual Contouring http://www.sandboxie.com/misc/isosurf/isosurfaces.html

Mi fuente de datos es una cuadrícula de 16x16x16; Atravieso esta cuadrícula de abajo hacia arriba, de izquierda a derecha, de cerca a lejos.

Para cada índice de mi cuadrícula, creo una estructura de cubo:

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);
        }

Debido a cómo atravesar la cuadrícula, solo necesito mirar 4 vértices y 3 bordes. En esta imagen, los vértices 2, 5, 6, 7 corresponden a mis vértices 0, 1, 2, 3, y los bordes 5, 6, 10 corresponden a mis bordes 0, 1, 2. Cubo de rejilla

Un borde se ve así:

    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);
    }

Luego verifico todos los bordes para un cambio de signo, si hay uno, encuentro los cubos circundantes y obtengo el punto característico de esos cubos.

Ahora que funciona si configuro el punto de característica en el centro del cubo, entonces obtengo el aspecto de Minecraft en bloque. Pero eso no es lo que quiero.

Para encontrar el punto destacado, quería hacerlo como en esta publicación: https://gamedev.stackexchange.com/a/83757/49583

Básicamente, comienzas el vértice en el centro de la celda. Luego, promedia todos los vectores tomados del vértice a cada plano y mueve el vértice a lo largo de ese resultado, y repite este paso un número fijo de veces. Descubrí que moverlo ~ 70% a lo largo de la resultante se estabilizaría en la menor cantidad de iteraciones.

Entonces obtuve una clase de avión:

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);
        }
 }

y una función para obtener el punto de entidad, donde creo 3 planos, uno para cada borde y promedio la distancia al 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;
            }
        }

Pero no funciona, los vértices están por todas partes. ¿Dónde está el error? ¿Puedo calcular el borde normal promediando el normal de los vértices del borde? No puedo obtener la densidad en el punto medio del borde, ya que solo tengo una cuadrícula entera como fuente de datos ...

Editar: También encontré aquí http://www.mathsisfun.com/algebra/systems-linear-equations-matrices.html que puedo usar matrices para calcular la intersección de los 3 planos, al menos así lo entendí, así que Creé este método

 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;
        }

que con estos datos

        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);

calcula una intersección en (2.0, 2.0, 2.0), por lo que supongo que funciona correctamente. Aún así, no son los vértices correctos. Realmente creo que es mi normalidad.

ElDuderino
fuente
Unity ya tiene una Planeestructura definida ( consulte aquí ), que tiene los métodos que proporcionó ya definidos (excepto el método vectorial más corto, que puede agregar a la Planeestructura utilizando los métodos de extensión de C #). Puede usar el GetDistanceToPointmétodo en lugar de su Distancemétodo.
EvilTak
Gracias por su comentario, reemplacé mi implementación con la implementación de Unity y utilicé esta función privada Vector3 shortestDistanceVector (Plano p, Vector3 punto) {return p.GetDistanceToPoint (punto) * p.normal; } También obtengo solo vértices aleatorios. Sospecho que mis normales están totalmente apagados. También agregué una edición, donde probé un segundo método, tal vez puedas echarle un vistazo y decirme qué hice mal allí.
ElDuderino
2
Can I actually calculate the edge normal by averaging the normal of the edge vertices?- Puedo estar equivocado, pero creo que he visto consejos en otros lugares que dicen que nunca se debe interpolar para obtener la normalidad, simplemente no interpolan bien. Calcular por cara, es más seguro. Realmente, primero debe construir un caso de prueba mínimo para asegurarse de que su cálculo normal sea correcto. Entonces sigue con esto.
Ingeniero
Pero solo obtengo las caras después de tener las normales, necesito las normales para crear los planos y obtener los vértices de las caras. Y como dije con mi estructura actual, puedo indexar mis datos solo en los vértices del borde. ¿O de qué caras estás hablando?
ElDuderino
@ElDuderino Caras como caras (o triángulos) de una malla, pero no sé cómo puedes obtener eso de tus datos. Si puede generar triángulos en lugar de bordes, entonces el cálculo normal se vuelve realmente fácil.
EvilTak

Respuestas:

1

En primer lugar, sus normales deberían estar totalmente bien si se calculan a través de las diferencias hacia adelante / atrás / central. El problema es que movió su punto central hacia la dirección incorrecta en su función FeaturePoint, lo que resulta en ir más lejos del mínimo.

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;

Esto sucedió porque su código no converge contra un punto y, por lo tanto, salta de su caja de vóxel. No sé si el código de ¿Alguien puede explicar el doble contorno? estaba destinado a utilizar un enfoque de proyección donde el punto se proyecta en el plano a través de:

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

Pero es el mismo método. Si reescribe la proyección en su código original, esto resulta en:

    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;

que se puede reescribir a:

    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;

y por lo tanto resulta en el primer código. Al proyectar el punto en tres planos no planos, converge lentamente hacia un mínimo porque minimiza la distancia desde cada plano a su punto como se muestra en la imagen.

Los puntos rojos denotan el punto característico, las líneas azules las normales y el punto morado el punto proyectado en el plano. Tampoco necesita usar el factor 0.7 porque debería converger más rápido sin él. Si usa este método, tenga en cuenta que el algoritmo puede no funcionar si tiene planos que no se cruzan.

Tim Rolff
fuente
Hola, increíble obtener una respuesta después de 2 años :) Nunca encontré una solución, así que detuve este proyecto, pero lo revisaré con este conocimiento y les haré saber cómo fue. Tener un +1 hasta entonces.
ElDuderino
¡Increíble! Me alegro de poder ayudarte. Avísame si te funciona.
Tim Rolff