Dual Contouring - ค้นหาจุดคุณสมบัติโดยปรกติ


9

ฉันกำลังติดตามกวดวิชานี้เพื่อใช้ Dual Contouring 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);
    }

จากนั้นฉันตรวจสอบขอบทั้งหมดเพื่อดูการเปลี่ยนป้ายหากมีสิ่งหนึ่งที่ฉันค้นพบลูกบาศก์โดยรอบและรับจุดคุณลักษณะของลูกบาศก์เหล่านั้น

ตอนนี้ใช้ได้ถ้าฉันตั้งค่าคุณลักษณะชี้ไปที่ศูนย์ลูกบาศก์จากนั้นฉันได้รับ minecraft บล็อกที่มีลักษณะ แต่นั่นไม่ใช่สิ่งที่ฉันต้องการ

หากต้องการค้นหาจุดคุณลักษณะฉันต้องการทำตามที่โพสต์นี้: 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);
        }
 }

และฟังก์ชั่นเพื่อรับจุดคุณลักษณะที่ฉันสร้าง 3 ระนาบหนึ่งอันสำหรับแต่ละขอบและเฉลี่ยระยะห่างจากศูนย์กลาง:

 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) ดังนั้นฉันคิดว่ามันทำงานถูกต้อง ยังไม่ใช่จุดยอดที่ถูกต้อง ฉันคิดว่ามันเป็นเรื่องปกติของฉัน


ความสามัคคีมีการPlaneกำหนดโครงสร้างไว้แล้ว ( ดูที่นี่ ) ซึ่งมีวิธีการที่คุณกำหนดไว้แล้ว (ยกเว้นวิธีเวกเตอร์ที่สั้นที่สุดซึ่งคุณสามารถเพิ่มPlaneโครงสร้างโดยใช้วิธีการขยาย C #) คุณสามารถใช้GetDistanceToPointวิธีการของคุณแทนDistanceวิธีการ
EvilTak

ขอบคุณสำหรับความคิดเห็นของคุณฉันได้แทนที่การใช้งานของฉันด้วยการใช้ Unity และการใช้ฟังก์ชั่นนี้เป็น Vector3 shortestDistanceVector ส่วนตัว (Plane p, จุด Vector3) {return p.GetDistanceToPoint (จุด) * p.normal; } ฉันยังได้รับจุดยอดสุ่มเท่านั้น ฉันสงสัยว่าปกติของฉันจะปิดโดยสิ้นเชิง ฉันยังเพิ่มการแก้ไขโดยที่ฉันลองวิธีที่สองบางทีคุณสามารถดูได้และบอกฉันว่าฉันทำอะไรผิดที่นั่น
ElDuderino

2
Can I actually calculate the edge normal by averaging the normal of the edge vertices?- ฉันอาจเข้าใจผิด แต่ฉันคิดว่าฉันเคยเห็นคำแนะนำที่อื่นที่บอกว่าไม่ต้องสอดแทรกเพื่อให้ได้มาซึ่งบรรทัดฐาน - พวกเขาแค่ไม่ได้เข้ามาแทรกแซง คำนวณต่อหน้าปลอดภัยยิ่งขึ้น จริงๆคุณควรสร้างกรณีทดสอบขั้นต่ำเพื่อให้แน่ใจว่าการคำนวณบรรทัดฐานของคุณถูกต้อง จากนั้นไปต่อด้วยสิ่งนี้
วิศวกร

แต่ฉันจะได้รับใบหน้าหลังจากที่ฉันมีบรรทัดฐานฉันต้องมีบรรทัดฐานเพื่อสร้างเครื่องบินและรับจุดยอดสำหรับใบหน้าจากพวกเขา และตามที่กล่าวไว้กับโครงสร้างปัจจุบันของฉันฉันสามารถจัดทำดัชนีข้อมูลของฉันเฉพาะที่จุดยอดขอบ หรือคุณกำลังพูดถึงใบหน้าอะไร
ElDuderino

@ElDuderino Faces เช่นใบหน้า (หรือสามเหลี่ยม) ของตาข่าย แต่ฉันไม่รู้ว่าคุณจะได้รับสิ่งนั้นจากข้อมูลของคุณได้อย่างไร หากคุณสามารถสร้างสามเหลี่ยมแทนขอบการคำนวณตามปกติจะกลายเป็นเรื่องง่าย
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;

สิ่งนี้เกิดขึ้นเพราะโค้ดของคุณไม่ได้มาบรรจบกับจุดใดจุดหนึ่งและก่อนที่จะกระโดดออกจากกล่อง voxel ของคุณ ฉันไม่ทราบว่ารหัสจากใครบางคนสามารถอธิบาย contouring แบบคู่ได้หรือไม่? มีวัตถุประสงค์เพื่อใช้วิธีการฉายภาพที่จุดที่ฉายบนระนาบผ่าน:

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 จนถึงตอนนี้
ElDuderino

! น่ากลัว ฉันดีใจที่ฉันสามารถช่วยคุณ แจ้งให้เราทราบว่าเหมาะกับคุณหรือไม่
ทิม Rolff
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.