Подвійний контур - пошук точки функції, нормалізація вимкнена


9

Я дотримуюся цього підручника для впровадження подвійного контурування http://www.sandboxie.com/misc/isosurf/isosurfaces.html

Моє джерело даних - сітка 16x16x16; Я проходжу цю сітку знизу вгору, зліва направо, майже вдалину.

Для кожного індексу моєї сітки я створюю кубову структуру:

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

Через те, як я проходжу сітку, мені потрібно лише переглянути 4 вершини та 3 ребра. У цьому малюнку вершини 2, 5, 6, 7 відповідають моїм вершинам 0, 1, 2, 3, а ребра 5, 6, 10 відповідають моїм краям 0, 1, 2. Сітка куб

Край виглядає так:

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

Потім я перевіряю всі краї на предмет зміни знаків, якщо є один, я знаходжу навколишні куби і отримую крапку цих кубів.

Тепер, коли це працює, якщо я встановив точку функції в центрі куба, то я отримаю вигляд блокадної майнкрафт. Але це не те, що я хочу.

Щоб знайти пункт функції, я хотів це зробити, як у цій публікації: https://gamedev.stackexchange.com/a/83757/49583

В основному, ви починаєте вершину в центрі комірки. Потім ви оцінюєте всі вектори, взяті від вершини до кожної площини, і переміщуєте вершину вздовж цієї результуючої сили, і повторюєте цей крок фіксовану кількість разів. Я виявив, що переміщення його ~ 70% уздовж отриманого результату стабілізується в найменшій кількості ітерацій.

Отже, я отримав клас «Літак»:

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

і функція, щоб отримати точку функції, де я створюю три площини, по одній для кожного краю і середню відстань до центру:

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

Але це не працює, вершини всюди. Де помилка? Чи можу я насправді обчислити ребро нормальним шляхом усереднення норми крайових вершин? Я не можу отримати щільність у межі точки, оскільки у мене є лише ціла сітка як джерело даних ...

Редагувати: Я також виявив тут http://www.mathsisfun.com/algebra/systems-linear-equations-matrices.html, що я можу використовувати матриці для обчислення перетину 3 площин, принаймні так я це зрозумів, так Я створив цей метод

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

що з цими даними

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

обчислює перехрестя в (2.0, 2.0, 2.0), тому я припускаю, що воно працює правильно. Все-таки не правильні вершини. Я дійсно думаю, що це мої нормали.


У Unity вже є Planeвизначена структура ( див. Тут ), яка має визначені вами методи (крім найкоротшого векторного методу, який ви можете додати до Planeструктури за допомогою методів розширення C #). Ви можете використовувати GetDistanceToPointметод замість вашого Distance.
EvilTak

Дякуємо за Ваш коментар, я замінив свою реалізацію на реалізацію Unity та використовував цю функцію приватного Vector3 shorttestDistanceVector (Площина p, точка Vector3) {return p.GetDistanceToPoint (point) * p.normal; } Я також отримую лише випадкові вершини. Я підозрюю, що мої нормали повністю вимкнені. Я також додав правку, де я спробував другий метод, можливо, ви можете подивитися і сказати мені, що я там зробив неправильно.
ElDuderino

2
Can I actually calculate the edge normal by averaging the normal of the edge vertices?- Я можу помилятися, але я думаю, що я бачив поради деінде, де говорилося ніколи не інтерполювати, щоб отримати нормальні норми - вони просто не інтерполюються добре. Розрахуйте на обличчя, це безпечніше. Дійсно, вам слід спочатку створити мінімальний тестовий випадок, щоб переконатися, що ваш розрахунок норми правильний. Потім переходимо до цього.
Інженер

Але обличчя я отримую лише після того, як у мене з’являться нормали, мені потрібні нормали, щоб створити площини і отримати вершини для обличчя від них. І як сказано в моїй нинішній структурі, я можу індексувати свої дані лише в крайових вершинах. Або про які обличчя ви говорите?
ElDuderino

@ElDuderino Обличчя, як грані (або трикутники) сітки, але я не знаю, як ви можете це отримати зі своїх даних. Якщо ви можете генерувати трикутники замість ребер, то звичайний розрахунок стає дійсно простим.
EvilTak

Відповіді:


1

Перш за все, ваші нормальні норми повинні бути цілком нормальними, якщо вони обчислюються через різниці вперед / назад / центрально. Проблема полягає в тому, що ви перемістили центральну точку в сторону неправильного напрямку у своїй функції FeaturePoint, що призводить до того, що ви віддаляєтеся від мінімуму.

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;

Це трапляється через те, що ваш код не збігається з точкою і тому вискочить з вашої воксельної скриньки. Я не знаю, чи може код з Чи може хтось пояснити подвійний контур? повинен був використовувати проекційний підхід, коли точка проектується на площину через:

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

але це той самий метод. Якщо ви перезаписуєте проекцію у свій вихідний код, це призводить до:

    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;

який можна переписати на:

    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;

і тому виходить перший код. Спроектувавши точку на три неплощинні площини, вона повільно сходить до мінімуму, оскільки ви мінімізуєте відстань від кожної площини до точки, як показано на малюнку.

Червоні точки позначають точку ознаки, блакитні лінії - нормалі, а фіолетову - точку, намічену на площині. Вам також не потрібно використовувати коефіцієнт 0,7, оскільки він повинен швидше сходитися без нього. Якщо ви використовуєте цей метод, будьте обережні, алгоритм може не працювати, якщо у вас площини, що не перетинаються.


Гей, приємно отримати відповідь через 2 роки :) Я так і не знайшов рішення, тому я зупинив цей проект, але я перегляну його з цими знаннями і дам вам знати, як він пройшов. Отримайте +1 til тоді.
ElDuderino

Дивовижно! Я радий, що можу тобі допомогти. Дайте мені знати, чи працює це для вас.
Тім Ролф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.