การแปลงเส้นโค้ง 2D เป็นจุดสำหรับการจัดเก็บข้อมูล


12

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

วิธีนี้ง่ายมาก: มันขยับสามจุดในขั้นตอนที่เท่ากันและวัดมุมระหว่างเส้นที่จุดเหล่านี้ก่อตัวขึ้น หากมุมมีขนาดใหญ่กว่าค่าความคลาดเคลื่อนก็จะสร้างเส้นโค้งลูกบาศก์ใหม่ไปยังจุดนั้น จากนั้นมันจะเลื่อนเส้นไปข้างหน้าและวัดมุมอีกครั้ง ...

สำหรับผู้ที่รู้ว่า Android Path Class - โปรดทราบว่าdstPathเป็นคลาสที่กำหนดเองซึ่งบันทึกคะแนนลงใน Array เพื่อให้ฉันสามารถบันทึกคะแนนในภายหลังในขณะที่srcPathเป็นผลมาจากการรวมกันของภูมิภาคดังนั้นจึงไม่มีประเด็นสำคัญสำหรับฉัน เพื่อบันทึก.

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

แก้ไข: ฉันได้โพสต์รหัสทั้งหมดสำหรับผู้ที่ใช้ Android java เพื่อให้พวกเขาสามารถลองและทดลองได้อย่างง่ายดาย

ป้อนคำอธิบายรูปภาพที่นี่

public class CurveSavePointsActivity extends Activity{

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(new CurveView(this));
    }

    class CurveView extends View{

        Path srcPath, dstPath;
        Paint srcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        Paint dstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        public CurveView(Context context) {
            super(context);

            srcPaint.setColor(Color.BLACK);
            srcPaint.setStyle(Style.STROKE);
            srcPaint.setStrokeWidth(2);
            srcPaint.setTextSize(20);

            dstPaint.setColor(Color.BLUE);
            dstPaint.setStyle(Style.STROKE);
            dstPaint.setStrokeWidth(2);
            dstPaint.setTextSize(20);

            srcPath = new Path();
            dstPath = new Path();

        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);

            //make a circle path
            srcPath.addCircle(w/4, h/2, w/6 - 30, Direction.CW);

            //make a rectangle path
            Path rectPath = new Path();
            rectPath.addRect(new RectF(w/4, h/2 - w/16, w*0.5f, h/2 + w/16), Direction.CW);


            //create a path union of circle and rectangle paths
            RectF bounds = new RectF();
            srcPath.computeBounds(bounds, true);
            Region destReg = new Region();
            Region clip = new Region();
            clip.set(new Rect(0,0, w, h));
            destReg.setPath(srcPath, clip);
            Region srcReg = new Region();
            srcReg.setPath(rectPath, clip); 
            Region resultReg = new Region();
            resultReg.op(destReg, srcReg, Region.Op.UNION);
            if(!resultReg.isEmpty()){
                srcPath.reset();
                srcPath.addPath(resultReg.getBoundaryPath());
            }

            //extract a new path from the region boundary path
            extractOutlinePath();

            //shift the resulting path bottom left, so they can be compared
            Matrix matrix = new Matrix();
            matrix.postTranslate(10, 30);
            dstPath.transform(matrix);

        }

         @Override 
            public void onDraw(Canvas canvas) { 
                super.onDraw(canvas);    
                canvas.drawColor(Color.WHITE);
                canvas.drawPath(srcPath, srcPaint);
                canvas.drawPath(dstPath, dstPaint);

                canvas.drawText("Source path", 40, 50, srcPaint);
                canvas.drawText("Destination path", 40, 100, dstPaint);
         }


         public void extractOutlinePath() {

             PathMeasure pm = new PathMeasure(srcPath, false); //get access to curve points

             float p0[] = {0f, 0f}; //current position of the new polygon
             float p1[] = {0f, 0f}; //beginning of the first line
             float p2[] = {0f, 0f}; //end of the first & the beginning of the second line
             float p3[] = {0f, 0f}; //end of the second line

             float pxStep = 5; //sampling step for extracting points
             float pxPlace  = 0; //current place on the curve for taking x,y coordinates
             float angleT = 5; //angle of tolerance

             double a1 = 0; //angle of the first line
             double a2 = 0; //angle of the second line

             pm.getPosTan(0, p0, null); //get the beginning x,y of the original curve into p0
             dstPath.moveTo(p0[0], p0[1]); //start new path from the beginning of the curve
             p1 = p0.clone(); //set start of the first line

             pm.getPosTan(pxStep, p2, null); //set end of the first line & the beginning of the second

             pxPlace = pxStep * 2;
             pm.getPosTan(pxPlace, p3, null); //set end of the second line


             while(pxPlace < pm.getLength()){
             a1 = 180 - Math.toDegrees(Math.atan2(p1[1] - p2[1], p1[0] - p2[0])); //angle of the first line
             a2 = 180 - Math.toDegrees(Math.atan2(p2[1] - p3[1], p2[0] - p3[0])); //angle of the second line

             //check the angle between the lines
             if (Math.abs(a1-a2) > angleT){

               //draw a straight line to the first point if the current p0 is not already there
               if(p0[0] != p1[0] && p0[1] != p1[1]) dstPath.quadTo((p0[0] + p1[0])/2, (p0[1] + p1[1])/2, p1[0], p1[1]);

               dstPath.quadTo(p2[0] , p2[1], p3[0], p3[1]); //create a curve to the third point through the second

               //shift the three points by two steps forward
               p0 = p3.clone();
               p1 = p3.clone();
               pxPlace += pxStep;
               pm.getPosTan(pxPlace, p2, null); 
               pxPlace += pxStep;
               pm.getPosTan(pxPlace, p3, null);
               if (pxPlace > pm.getLength()) break;
             }else{
               //shift three points by one step towards the end of the curve
               p1 = p2.clone(); 
               p2 = p3.clone();
               pxPlace += pxStep;
               pm.getPosTan(pxPlace, p3, null); 
             }
             }
             dstPath.close();
         }
    }

}

นี่คือการเปรียบเทียบระหว่างต้นฉบับและสิ่งที่อัลกอริทึมของฉันสร้าง:

การเปรียบเทียบระหว่างเส้นทาง  เห็นได้ชัดว่ามุมที่ราบรื่นของอนุพันธ์


ทำไมไม่ใช้ b-splines
GriffinHeart

4
ถ้าคุณรู้ว่าสิ่งนั้นเป็นวงกลมและสี่เหลี่ยมทำไมไม่เก็บวงกลมและสี่เหลี่ยมผืนผ้าล่ะ? และในรูปแบบทั่วไป - อะไรก็ตามที่คุณสร้างขึ้นมาอาจเป็นรูปแบบที่เหมาะสมในการเก็บไว้หากคุณกำลังมองหารูปแบบการบีบอัดที่ดูเหมือนคำถามอื่น (หรืออย่างน้อยเราต้องการข้อมูลเพิ่มเติมเกี่ยวกับแหล่งข้อมูลเพิ่มเติม จะเป็นประโยชน์)
Jeff Gates

มันอาจจะเป็นรูปร่างที่คาดเดาไม่ได้อย่างที่ฉันพูดในประโยคแรก - วงกลมและรูปสี่เหลี่ยมผืนผ้าที่นี่เป็นเพียงตัวอย่างทดสอบ
Lumis

@ Lumis คุณควรดูเป็น b-splines จริง ๆ มันคืออะไร เหตุผลใดที่จะลองใช้โซลูชันของคุณเอง?
GriffinHeart

1
คลาสเส้นทางที่ดีจะสร้างเส้นโค้งเหล่านั้นด้วยเส้นโค้งดังนั้นคุณจึงใช้มัน ฉันมีข้อเสนอแนะอื่นมุ่งเน้นคณิตศาสตร์น้อยลง: แทนที่จะบันทึกคะแนนบันทึกผู้ใช้ (รูปแบบคำสั่ง) และเล่นซ้ำเพื่อสร้าง "ภาพ" เดียวกัน
GriffinHeart

คำตอบ:


6

ฉันคิดว่าคุณมีปัญหาสองประการ:

จุดควบคุมที่ไม่สมมาตร

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

เส้นโค้งกำลังสอง

ดังที่กล่าวไว้ในคำตอบอื่น ๆ เช่นกันเส้นโค้งกำลังสองไม่ดีสำหรับกรณีนี้ เส้นโค้งที่อยู่ติดกันคุณสร้างควรจะร่วมกันจุดควบคุมและการสัมผัส เมื่อข้อมูลอินพุตของคุณเป็นเพียงจุดCatmull-Rom Splineเป็นตัวเลือกที่ดีสำหรับจุดประสงค์นั้น มันเป็นโค้งเฮอร์ไมต์ลูกบาศก์ที่ซึ่งแทนเจนต์ของจุดควบคุมคำนวณจากจุดก่อนหน้าและจุดต่อไป

Path API ใน Android รองรับBézier curves ซึ่งแตกต่างจาก Hermite curves เล็กน้อยเกี่ยวกับพารามิเตอร์ โชคดีที่ Hermite curves สามารถแปลงเป็นBézier curves นี่คือตัวอย่างรหัสแรกที่ฉันพบเมื่อ Googling คำตอบ Stackoverflowนี้ก็ดูเหมือนจะให้สูตร

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

แก้ไข: หลังจากคิดเส้นโค้งสมการกำลังสองเพิ่มเติมสามารถนำมาใช้หลังจากทั้งหมด แทนที่จะวาดเส้นโค้งกำลังสองจาก p0 ถึง p2 โดยใช้ p1 เป็นจุดควบคุมให้วาดจาก p0 ถึง p1 โดยใช้จุดใหม่ p0_1 เป็นจุดควบคุม ดูภาพด้านล่าง จุดควบคุมใหม่

ถ้า p0_1 อยู่ในจุดตัดของแทนเจนต์ใน p0 และ p1 ผลลัพธ์ควรราบรื่น ยิ่งไปกว่านั้นเนื่องจากPathMeasure.getPosTan()ผลตอบแทนยังแทนเจนต์เป็นพารามิเตอร์ที่สามคุณสามารถใช้แทนเจนต์ที่แท้จริงได้แทนการประมาณจากจุดที่อยู่ติดกัน ด้วยวิธีนี้คุณต้องมีการเปลี่ยนแปลงน้อยลงสำหรับโซลูชันที่มีอยู่ของคุณ

ตามคำตอบนี้คุณสามารถคำนวณจุดตัดด้วยสูตรต่อไปนี้:

getPosTan(pxPlace0, p0, t0); // Also get the tangent
getPosTan(pxPlace1, p1, t1);
t1 = -t1; // Reverse direction of second tangent
vec2 d = p1 - p0;
float det = t1.x * t0.y - t1.y * t0.x;
float u = (d.y * t1.x - d.x * t1.y) / det;
float v = (d.y * t0.x - d.x * t0.y) / det; // Not needed ... yet
p0_1 = p0 + u * t0;

อย่างไรก็ตามวิธีนี้ใช้งานได้เฉพาะเมื่อทั้ง u และ v ไม่เป็นลบ ดูภาพที่สอง: รังสีไม่ได้ตัดกัน

ที่นี่รังสีจะไม่ตัดกันแม้ว่าจะมีเส้นเนื่องจากคุณเป็นลบ ในกรณีนี้มันเป็นไปไม่ได้ที่จะวาดเส้นโค้งกำลังสองซึ่งจะเชื่อมต่อกับเส้นโค้งก่อนหน้าอย่างราบรื่น ที่นี่คุณต้องการโค้งเบซิเยร์ คุณสามารถคำนวณจุดควบคุมได้ด้วยวิธีที่กำหนดไว้ก่อนหน้าในคำตอบนี้หรือรับมาโดยตรงจากแทนเจนต์ การฉาย p0 ไปยังรังสีแทนเจนต์ p0 + u * t0 และคีมจับในทางกลับกันสำหรับรังสีอื่นให้ทั้งจุดควบคุม c0 และ c1 นอกจากนี้คุณยังสามารถปรับเส้นโค้งโดยใช้จุดใดก็ได้ระหว่าง p0 และ c0 แทน c0 ตราบใดที่มันอยู่บนรังสีแทนเจนต์

แก้ไข 2: หากตำแหน่งการวาดของคุณอยู่ใน p1 คุณสามารถคำนวณจุดควบคุม bezier เป็น p2 ด้วยรหัสหลอกต่อไปนี้:

vec2 p0, p1, p2, p3; // These are calculated with PathMeasure
vec2 cp1 = p1 + (p2 - p0) / 6;
vec2 cp2 = p2 - (p3 - p1) / 6;

ด้วยสิ่งเหล่านี้คุณสามารถผนวกเส้นทางจาก p1 ถึง p2:

path.cubicTo(cp1.x, cp1.y, cp2.x, cp2.y, p2.x, p2.y);

แทนที่การดำเนินการของเวกเตอร์ด้วยการดำเนินการต่อส่วนประกอบในอาร์เรย์ลอย [ 2 ] เพื่อให้ตรงกับรหัสของคุณ คุณเริ่มต้นด้วยการเริ่มต้นp1 = start;และ p2 และ p3 เป็นจุดต่อไป p0 ไม่ได้กำหนดไว้ในตอนแรก สำหรับส่วนแรกที่คุณยังไม่มี p0 คุณสามารถใช้เส้นโค้งกำลังสองจาก p1 ถึง p2 กับ cp2 เป็นจุดควบคุม เช่นเดียวกับจุดสิ้นสุดของเส้นทางที่คุณไม่มี p3 คุณสามารถวาดเส้นโค้งกำลังสองจาก p1 ถึง p2 ด้วย cp1 เป็นจุดควบคุม หรือคุณสามารถกำหนดค่าเริ่มต้น p0 = p1 สำหรับกลุ่มแรกและ p3 = p2 สำหรับกลุ่มสุดท้าย หลังจากทุกกลุ่มคุณเลื่อนค่าp0 = p1; p1 = p2; and p2 = p3;เมื่อเคลื่อนที่ไปข้างหน้า

เมื่อคุณบันทึกเส้นทางคุณเพียงบันทึกคะแนนทั้งหมด p0 ... pN ไม่จำเป็นต้องบันทึกจุดควบคุม cp1 และ cp2 เนื่องจากสามารถคำนวณได้ตามต้องการ

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


นั่นฟังดูมีแนวโน้ม อย่างไรก็ตามมันไม่ได้เป็น p0 แต่ใช้ p1, p2, p3 ที่ใช้, p0 เป็นเพียงการจัดเก็บคะแนนแน่นอนใหม่เมื่อพวกเขาถูกคำนวณและเพื่อประโยชน์ของเส้นตรงเพื่อที่พวกเขาจะไม่ได้ตัวอย่างแต่ละขั้นตอน คุณสามารถช่วยฉันคำนวณ x, y สำหรับจุดควบคุมใหม่ได้ไหม
Lumis

ฉันจะทำอย่างนั้นต่อมา แต่ในขณะเดียวกันตรวจสอบstackoverflow.com/questions/2931573/... ด้วย u และ v คุณจะได้จุดตัดกัน
msell

ขอบคุณสำหรับความช่วยเหลือฉันต้องการลองสิ่งนี้ แต่จำเป็นต้องเขียนใน Java สำหรับ Android ไม่มี vector2 และ t1 และ p1 เป็นต้นเป็นอาร์เรย์แบบลอยดังนั้นฉันไม่สามารถดำเนินการโดยตรงกับพวกเขาเช่น t1 = -t1 หรือ u * t0 ฉันถือว่า t1 = -t1 หมายถึง t1.x = -t1x; t1.y = -t1.y ฯลฯ ใช่ไหม
Lumis

ใช่นั่นเป็นเพียงรหัสหลอกเพื่อให้กะทัดรัดและอ่านง่ายขึ้น
msell

โครงเรื่องหนา เนื่องจากจุดตัดภูมิภาคของสองเส้นทางใน Android ส่งคืนเส้นทางซึ่งไม่ได้มีการต่อต้านนามแฝงแทนเจนต์จึงอยู่ที่นั้น วิธีแก้ปัญหาที่เหมาะสมคือการขับโค้งเรียบ ๆ ผ่านจุดที่กำหนดก่อนจากนั้นจึงสุ่มตัวอย่าง รหัสของคุณทำงานได้อย่างสมบูรณ์บนเส้นทางที่ต่อต้านนามแฝงซึ่งสร้างจุดควบคุมที่เหมาะสม
Lumis

13

W3C จะทำอะไร?

อินเทอร์เน็ตมีปัญหานี้ World Wide Web Consortiumสังเกตเห็น แต่ก็มีวิธีการแก้ปัญหามาตรฐานแนะนำตั้งแต่ปี 1999: Scalable Vector Graphics (SVG) เป็นรูปแบบไฟล์ที่ยึดตามXMLซึ่งออกแบบมาโดยเฉพาะสำหรับการจัดเก็บรูปร่าง 2D

" สิ่งที่ปรับขนาดได้? "

กราฟิกแบบเวกเตอร์ที่ปรับขนาดได้ !

  • ปรับขนาดได้ : มันหมายถึงการปรับขนาดได้อย่างราบรื่นทุกขนาด
  • เวกเตอร์ : ก็ขึ้นอยู่กับความคิดทางคณิตศาสตร์ของเวกเตอร์
  • กราฟิก มันหมายถึงการสร้างภาพ

นี่คือข้อกำหนดทางเทคนิคสำหรับ SVG เวอร์ชั่น 1.1
(อย่ากลัวชื่อ; มันน่าอ่านจริง ๆ )

พวกเขาได้เขียนลงอย่างชัดเจนถึงวิธีการจัดเก็บรูปร่างพื้นฐานเช่นวงกลมหรือสี่เหลี่ยม xตัวอย่างเช่นรูปสี่เหลี่ยมที่มีคุณสมบัติเหล่านี้: y, width, height, rx, ry, ( rxและryสามารถใช้สำหรับมุมโค้งมน)

นี่คือตัวอย่างสี่เหลี่ยมผืนผ้าของพวกเขาใน SVG: (อืม, สองอันจริงๆ - หนึ่งสำหรับโครงร่างผ้าใบ)

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="12cm" height="4cm" viewBox="0 0 1200 400"
     xmlns="http://www.w3.org/2000/svg" version="1.1">
  <desc>Example rect01 - rectangle with sharp corners</desc>

  <!-- Show outline of canvas using 'rect' element -->
  <rect x="1" y="1" width="1198" height="398"
        fill="none" stroke="blue" stroke-width="2"/>

  <rect x="400" y="100" width="400" height="200"
        fill="yellow" stroke="navy" stroke-width="10"  />
</svg>

นี่คือสิ่งที่มันหมายถึง:

สี่เหลี่ยมสีเหลืองที่มีเค้าร่างสีน้ำเงิน

ตามที่ระบุไว้คุณมีอิสระที่จะละทิ้งคุณสมบัติบางอย่างหากคุณไม่ต้องการ (ตัวอย่างเช่นrxและryคุณลักษณะที่ไม่ได้ใช้ที่นี่) ใช่มี cruft ที่ด้านบนเกี่ยวกับDOCTYPEที่คุณไม่ต้องการเพียงแค่เกมของคุณ พวกมันก็เป็นทางเลือกด้วย

เส้นทาง

เส้นทาง SVG คือ "เส้นทาง" ในแง่ที่ว่าหากคุณวางดินสอลงบนกระดาษให้เลื่อนไปรอบ ๆ และในที่สุดก็เพิ่มอีกครั้งคุณจะมีเส้นทาง พวกเขาไม่จำเป็นต้องปิดแต่อาจจะเป็น

แต่ละเส้นทางมีdแอตทริบิวต์ (ผมชอบที่จะคิดว่ามันย่อมาจาก "วาด") ที่มีข้อมูลเส้นทางลำดับของคำสั่งสำหรับพื้นเพียงวางปากกากระดาษและย้ายไปรอบ ๆ

พวกเขาให้ตัวอย่างของรูปสามเหลี่ยม:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="4cm" height="4cm" viewBox="0 0 400 400"
     xmlns="http://www.w3.org/2000/svg" version="1.1">
  <title>Example triangle01- simple example of a 'path'</title>
  <desc>A path that draws a triangle</desc>
  <rect x="1" y="1" width="398" height="398"
        fill="none" stroke="blue" />
  <path d="M 100 100 L 300 100 L 200 300 z"
        fill="red" stroke="blue" stroke-width="3" />
</svg>

สามเหลี่ยมสีแดง

ดูdแอตทริบิวต์ในpath?

d="M 100 100 L 300 100 L 200 300 z"

Mเป็นคำสั่งสำหรับการย้ายไป (ตามพิกัด) ที่Ls สำหรับแถว (พิกัด) และzเป็นคำสั่งที่จะปิดเส้นทาง (เช่นวาดเส้นกลับไปยังสถานที่แรกนั่นไม่จำเป็นต้องพิกัด)

เส้นตรงน่าเบื่อ? ใช้คำสั่งลูกบาศก์หรือกำลังสองเบซิเยร์!

ลูกบาศก์เบซิเยร์บางส่วน

ทฤษฎีที่อยู่เบื้องหลังเส้นโค้งเบซิเยร์นั้นครอบคลุมที่อื่น (เช่นใน Wikipedia ) แต่นี่เป็นบทสรุปของผู้บริหาร: เบซิเยร์มีจุดเริ่มต้นและจุดสิ้นสุดโดยอาจมีจุดควบคุมหลายจุดที่มีอิทธิพลต่อการโค้งระหว่าง

ติดตามBézierกำลังสอง

ข้อมูลจำเพาะยังให้คำแนะนำสำหรับการแปลงรูปร่างพื้นฐานส่วนใหญ่เป็นเส้นทางในกรณีที่คุณต้องการ

ทำไมและเมื่อใช้ SVG

ตัดสินใจอย่างรอบคอบถ้าคุณต้องการที่จะลงเส้นทางนี้ (ปุนตั้งใจ) เพราะมันค่อนข้างซับซ้อนที่จะแสดงรูปร่าง 2D โดยพลการในข้อความ! คุณสามารถทำให้ชีวิตของคุณง่ายขึ้นหากคุณ จำกัด ตัวเองเป็นเพียงเส้นทางที่ทำจากเส้นตรง

แต่ถ้าคุณตัดสินใจว่าคุณต้องการรูปทรงโดยพลการ SVG เป็นวิธีที่จะไป: มีการสนับสนุนเครื่องมือที่ยอดเยี่ยม: คุณสามารถค้นหาไลบรารีจำนวนมากสำหรับการแยกวิเคราะห์ XML ในระดับต่ำและเครื่องมือแก้ไข SVGในระดับสูง

ไม่ว่ามาตรฐาน SVG จะเป็นตัวอย่างที่ดี


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

@Lumis ชื่อและเนื้อหาจะแนะนำเป็นอย่างอื่น พิจารณาการเรียบเรียงคำถามใหม่ (หรือว่าตอนนี้คนนี้จะจัดตั้งขึ้นค่อนข้างขอให้อีกคนหนึ่ง.)
Anko

4

รหัสของคุณมีความคิดเห็นที่ทำให้เข้าใจผิด:

dstPath.quadTo(p2[0] , p2[1], p3[0], p3[1]); //create a curve to the third point through the second

เส้นโค้งเบซิเยร์กำลังสองไม่ได้ไปผ่านจุดที่สอง หากคุณต้องการผ่านจุดที่สองคุณจำเป็นต้องมีเส้นโค้งที่แตกต่างกันเช่นเส้นโค้งเฮอร์ไมต์ คุณอาจแปลง hermite curves เป็น beziers เพื่อให้คุณสามารถใช้คลาส Path

ข้อเสนอแนะอื่นคือแทนที่จะสุ่มตัวอย่างคะแนนใช้ค่าเฉลี่ยของคะแนนที่คุณข้าม

ข้อเสนอแนะอื่นคือแทนที่จะใช้มุมเป็นเกณฑ์ใช้ความแตกต่างระหว่างเส้นโค้งที่เกิดขึ้นจริงและเส้นโค้งโดยประมาณ มุมไม่ใช่ปัญหาจริง ปัญหาที่แท้จริงคือเมื่อชุดของจุดไม่พอดีกับเส้นโค้งเบซิเยร์

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


คุณถูกต้องจุดที่สองเท่านั้น "ดึง" โค้งไปทางนั้น ลูกบาศก์ไปยังต้องใช้สองจุดควบคุมแทนหนึ่งเป็น quadTo ปัญหาคือว่าจะได้รับคะแนนการควบคุมที่ถูกต้องแน่นอน โปรดทราบว่าฉันไม่ต้องการเสียมุมที่แหลมเนื่องจาก Path Path อาจเป็นการรวมกันของรูปร่างใด ๆ เป็นแนวตรงหรือกลม - โดยทั่วไปฉันกำลังสร้างเครื่องมือเลือกรูปภาพที่ฉันสามารถบันทึกเส้นทางที่เลือกได้
Lumis

4

เพื่อให้ได้จุดตัดสองทางที่นุ่มนวลขึ้นคุณสามารถขยายพวกมันก่อนถึงทางแยกและไต่ลงหลังจากนั้น

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

นี่รหัสของฉัน:

    Path mypath=new Path(<desiredpath to fill with a pattern>);
    String sPatternType=cpath.getsPattern();

    Path pathtempforbounds=new Path(cpath.getPath());
    RectF rectF = new RectF();
     if (sPatternType.equals("1")){
         turnPath(pathtempforbounds, -45);
     }
     pathtempforbounds.computeBounds(rectF, true);

     float ftop=rectF.top;
     float fbottom=rectF.bottom;
     float fleft=rectF.left;
     float fright=rectF.right;
     float xlength=fright-fleft;

     Path pathpattern=new Path();

     float ypos=ftop;
     float xpos=fleft;

     float fStreifenbreite=4f;

     while(ypos<fbottom){
         pathpattern.moveTo(xpos,ypos);
         xpos=xpos+xlength;
         pathpattern.lineTo(xpos, ypos);
         ypos=ypos+fStreifenbreite;
         pathpattern.lineTo(xpos, ypos);
         xpos=xpos-xlength;
         pathpattern.lineTo(xpos, ypos);
         ypos=ypos-fStreifenbreite;
         pathpattern.lineTo(xpos, ypos);
         pathpattern.close();
         ypos=ypos+2*fStreifenbreite;

     }

     // Original vergrössern

     scalepath(pathpattern,10);
     scalepath(mypath,10);

     if (sPatternType.equals("1")){
         Matrix mdrehen=new Matrix();
         RectF bounds=new RectF();
         pathpattern.computeBounds(bounds, true);
         mdrehen.postRotate(45, (bounds.right + bounds.left)/2,(bounds.bottom + bounds.top)/2);
         pathpattern.transform(mdrehen);
     }

     RectF rectF2 = new RectF();
     mypath.computeBounds(rectF2, true);

     Region clip = new Region();
     clip.set((int)(rectF2.left-100f),(int)(rectF2.top -100f), (int)(rectF2.right+100f),(int)( rectF2.bottom+100f));
     Region region1 = new Region();
     region1.setPath(pathpattern, clip);

     Region region2 = new Region();
     region2.setPath(mypath, clip);

     region1.op(region2, Region.Op.INTERSECT);


     Path pnew=region1.getBoundaryPath();


     scalepath(pnew, 0.1f);
     cpath.setPathpattern(pnew);




public void turnPath(Path p,int idegree){
     Matrix mdrehen=new Matrix();
     RectF bounds=new RectF();
     p.computeBounds(bounds, true);
     mdrehen.postRotate(idegree, (bounds.right + bounds.left)/2,(bounds.bottom + bounds.top)/2);
     p.transform(mdrehen);
}

public void scalepath(Path p,float fscale){
     Matrix mverkleinern=new Matrix();
     mverkleinern.preScale(fscale,fscale);
     p.transform(mverkleinern);
}

ป้อนคำอธิบายรูปภาพที่นี่

ดูเรียบเนียนเมื่อซูมด้วย canvas.scale (): ป้อนคำอธิบายรูปภาพที่นี่


ขอขอบคุณผู้ที่ใช้จ่าย 10 ชื่อเสียงให้ฉันเพื่อเพิ่มรูปภาพ :-)
user1344545

1
น่าอัศจรรย์เคล็ดลับง่ายๆนี้แก้ปัญหาที่สอง: ประการแรกมันทำให้เส้นทางของการแยกหรือสหภาพราบรื่นและประการที่สองรหัสของฉันในคำถามเมื่อสุ่มตัวอย่างเส้นทางการขยายขนาดเดียวกันนี้ให้ผลลัพธ์ที่ราบรื่นอย่างสมบูรณ์แบบ ช่างเป็นทางออกที่ไม่คาดคิดและเรียบง่ายขอบคุณ!
Lumis

@user Editing ฟรี สำหรับผู้ใช้ <2k-rep จริงๆแล้วมันคือ +2
Anko

@ Lumis ฉันสับสนเล็กน้อย - ฉันคิดว่าคุณถามวิธีเก็บเส้นทาง?
Anko

1
น่าเสียดายที่หลังจากการทดสอบเพิ่มเติมฉันพบว่าเนื่องจากภูมิภาคใช้พิกเซลซึ่งเส้นทางจะครอบครองเมื่อวาดแอปหมดหน่วยความจำได้อย่างง่ายดายหากขนาดของเส้นทางมีขนาดใหญ่และทำซ้ำหลายครั้ง ดังนั้นโซลูชันนี้มี จำกัด & เสี่ยง แต่ดีที่ควรทราบ
Lumis

3

ดูการแก้ไขรูปหลายเหลี่ยม ( http://en.wikipedia.org/wiki/Polynomial_interpolation )

โดยทั่วไปคุณจะใช้โหนด n equispaced (การแก้ไขที่ดีที่สุดไม่เท่ากัน แต่สำหรับกรณีของคุณมันควรจะดีพอและง่ายต่อการใช้งาน)

คุณลงท้ายด้วยรูปหลายเหลี่ยมของคำสั่ง n ซึ่งลดข้อผิดพลาดระหว่างเส้นโค้งของคุณถ้า (<- ใหญ่ถ้า) เส้นของคุณราบรื่นพอ

ในกรณีของคุณคุณกำลังทำการแก้ไขเชิงเส้น (คำสั่งที่ 1)

อีกกรณี (ตามที่GriffinHeartแนะนำ) คือการใช้ Splines ( http://en.wikipedia.org/wiki/Spline_interpolation )

ไม่ว่ากรณีใดจะให้พหุนามพอดีกับเส้นโค้งของคุณ


2

หากจุดประสงค์ของการแปลงนั้นใช้สำหรับการจัดเก็บข้อมูลเท่านั้นและเมื่อคุณแสดงมันกลับมาบนหน้าจอคุณต้องทำให้มันราบรื่นการจัดเก็บความเที่ยงตรงสูงสุดที่คุณจะได้รับในขณะที่ยังคงลดการจัดเก็บข้อมูลทั้งหมด เพื่อจัดเก็บคุณลักษณะของวงกลม (หรือส่วนโค้งแทน) และวาดใหม่ตามต้องการ

ที่มา รัศมี. เริ่ม / หยุดมุมเพื่อวาดส่วนโค้ง

หากคุณต้องการแปลงวงกลม / ส่วนโค้งเป็นคะแนนสำหรับการเรนเดอร์คุณก็สามารถทำได้เมื่อทำการโหลดจากที่เก็บข้อมูลในขณะที่จัดเก็บแอตทริบิวต์เท่านั้น


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

2

มีเหตุผลสำหรับการโค้งเมื่อเทียบกับเส้นตรงหรือไม่? เส้นตรงนั้นใช้งานได้ง่ายกว่าและสามารถแสดงผลได้อย่างมีประสิทธิภาพในฮาร์ดแวร์

อีกวิธีที่ควรพิจารณาคือเก็บสองสามบิตต่อพิกเซลโดยระบุว่าอยู่ข้างในข้างนอกหรือบนร่างของรูปร่าง สิ่งนี้ควรบีบอัดได้ดีและอาจมีประสิทธิภาพมากกว่าเส้นสำหรับตัวเลือกที่ซับซ้อน

คุณอาจพบว่าบทความเหล่านี้น่าสนใจ / มีประโยชน์:


1

ดูการแก้ไขเส้นโค้ง - มีหลายประเภทที่คุณสามารถนำไปใช้ซึ่งจะช่วยทำให้เส้นโค้งของคุณราบรื่น ยิ่งคุณได้คะแนนในวงกลมนั้นมากเท่าไหร่ก็ยิ่งดีเท่านั้น พื้นที่จัดเก็บค่อนข้างถูก - ดังนั้นหากการแยกโหนดปิดแบบ 360 นั้นราคาถูกพอ (แม้ที่ตำแหน่ง 8 ไบต์สำหรับตำแหน่ง;

คุณสามารถวางตัวอย่างการแก้ไขที่นี่ด้วยจุดสี่จุดเท่านั้น และผลลัพธ์ก็ค่อนข้างดี (ที่ฉันชอบคือ Bezier สำหรับกรณีนี้แม้ว่าคนอื่นอาจจะพูดไม่ชัดเกี่ยวกับวิธีแก้ปัญหาที่มีประสิทธิภาพอื่น ๆ )

คุณสามารถเล่นได้ที่นี่เช่นกัน

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