ความสัมพันธ์ของเส้นโค้งเบซิเยร์กับวงรี?

ฉันต้องการวาดรูปวงรีบนผืนผ้าใบ html5 และฉันพบวิธีที่ดีสำหรับมันใน stackoverflow.แต่ฉันมีคำถามอื่น

function drawEllipse(ctx, x, y, w, h) {
  var kappa = 0.5522848;
      ox = (w / 2) * kappa, // control point offset horizontal
      oy = (h / 2) * kappa, // control point offset vertical
      xe = x + w,           // x-end
      ye = y + h,           // y-end
      xm = x + w / 2,       // x-middle
      ym = y + h / 2;       // y-middle

  ctx.beginPath();
  ctx.moveTo(x, ym);
  ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  ctx.closePath();
  ctx.stroke();
}

วิธีการในลิงค์ด้านบนใช้ bezierCurveTo เพื่อวาดวงรี แต่มีการวาด bezierCurveTo 4 ครั้ง แต่ฉันคิดว่าเพียง 2 bezierCurveTo สามารถวาดวงรีได้เช่นนี้:

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

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

ขอบคุณทุกๆคน


person LIXer    schedule 05.01.2013    source แหล่งที่มา


คำตอบ (5)


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

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

ปัญหาหนึ่งที่เราอาจจะพบคือเมื่อเราเริ่มจากด้านบนและทำ bezierCurve ที่ด้านล่างของวงรีโดยมีมุมของสี่เหลี่ยม (ที่มีความกว้างและความสูงเท่ากัน) เป็นจุดควบคุม ความกว้างของวงรีคือ จะเล็กกว่าสี่เหลี่ยม .75 เท่าของขนาดที่เราต้องการ ดังนั้นเราจึงสามารถปรับขนาดจุดควบคุมได้ตามนั้น

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

var cpx = (width / .75) / 2;

รวมภาพเข้าด้วยกัน ที่คุณสามารถเล่นกับความกว้างและความสูงและดูภาพวาดที่วาดไว้ วงรี

วงรีสีแดงเป็นวิธีที่เราต้องการให้วาด โดยวงรีด้านในจะวาดอย่างไรหากเราไม่เปลี่ยนตำแหน่งจุดควบคุม เส้นเหล่านี้แสดงอัลกอริทึมของ De Casteljau ที่แสดงในวิดีโอ

นี่คือภาพหน้าจอของการแสดงภาพ ป้อนคำอธิบายรูปภาพที่นี่

person DerekR    schedule 05.01.2013

คุณต้องใช้เส้นโค้งเบซิเยร์ลูกบาศก์สองลูกบาศก์เพื่อวาดวงรี ต่อไปนี้เป็นโค้ดของ DerekR เวอร์ชันเรียบง่ายที่ใช้อาร์กิวเมนต์ฟังก์ชันดั้งเดิมที่คุณระบุ โดยสมมติว่าคุณต้องการให้ x และ y เป็นจุดศูนย์กลางของวงรี:

jsFiddle

function drawEllipse(ctx, x, y, w, h) {
    var width_over_2 = w / 2;
    var width_two_thirds = w * 2 / 3;
    var height_over_2 = h / 2;

    ctx.beginPath();
    ctx.moveTo(x, y - height_over_2);
    ctx.bezierCurveTo(x + width_two_thirds, y - height_over_2, x + width_two_thirds, y + height_over_2, x, y + height_over_2);
    ctx.bezierCurveTo(x - width_two_thirds, y + height_over_2, x - width_two_thirds, y - height_over_2, x, y -height_over_2);
    ctx.closePath();
    ctx.stroke();
}
person BKH    schedule 14.12.2013

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

function drawEllipseByBezierCurves(ctx, x, y, radiusX, radiusY, rotationAngle) {
var width_two_thirds = radiusX * 4 / 3;

var dx1 = Math.sin(rotationAngle) * radiusY;
var dy1 = Math.cos(rotationAngle) * radiusY;
var dx2 = Math.cos(rotationAngle) * width_two_thirds;
var dy2 = Math.sin(rotationAngle) * width_two_thirds;

var topCenterX = x - dx1;
var topCenterY = y + dy1;
var topRightX = topCenterX + dx2;
var topRightY = topCenterY + dy2;
var topLeftX = topCenterX - dx2;
var topLeftY = topCenterY - dy2;

var bottomCenterX = x + dx1;
var bottomCenterY = y - dy1;
var bottomRightX = bottomCenterX + dx2;
var bottomRightY = bottomCenterY + dy2;
var bottomLeftX = bottomCenterX - dx2;
var bottomLeftY = bottomCenterY - dy2;

ctx.beginPath();
ctx.moveTo(bottomCenterX, bottomCenterY);
ctx.bezierCurveTo(bottomRightX, bottomRightY, topRightX, topRightY, topCenterX, topCenterY);
ctx.bezierCurveTo(topLeftX, topLeftY, bottomLeftX, bottomLeftY, bottomCenterX, bottomCenterY);
ctx.closePath();
ctx.stroke();

}

person PokatilovArt    schedule 26.02.2014

คุณจะพบว่าสิ่งนี้อธิบายตามหลักคณิตศาสตร์มากขึ้นเล็กน้อยใน http://pomax.github.io/bezierinfo/#circles_cubic แต่สิ่งสำคัญคือการใช้เส้นโค้งลูกบาศก์เบซิเยร์เป็นเวลานานกว่าหนึ่งในสี่ของการหมุนมักจะไม่ใช่ความคิดที่ดี โชคดีที่การใช้เส้นโค้งทั้งสี่ทำให้การค้นหาจุดควบคุมที่ต้องการค่อนข้างง่าย เริ่มต้นด้วยวงกลม ซึ่งในกรณีนี้ แต่ละวงกลมในสี่จะเป็น (1,0)--(1,0.55228)--(0.55228,1)--(0,1) โดยมีพิกัดมาตราส่วนสำหรับวงรีของคุณ วาดสี่ครั้งโดยสลับเครื่องหมาย +/- เพื่อให้เกิดเป็นวงกลมเต็มวง ปรับขนาดขนาดเพื่อให้ได้วงรี แล้วเสร็จ

หากคุณใช้เส้นโค้งสองเส้น พิกัดจะกลายเป็น (1,0)--(1,4/3)--(-1,4/3)--(-1,0) โดยปรับขนาดเป็นวงรีของคุณ มันอาจยังดูดีเพียงพอในใบสมัครของคุณ ขึ้นอยู่กับว่ารูปวาดของคุณจะใหญ่แค่ไหน

person Mike 'Pomax' Kamermans    schedule 19.04.2013
comment
ทรัพยากรที่น่าทึ่ง! และแน่นอนว่าคุณไม่สามารถใช้เส้นโค้ง 2 Bezier เพื่อวาดวงรีได้ ทำคณิตศาสตร์และอนุพันธ์อันดับสองสมการไม่ได้ผลดี - person Alex; 28.02.2014

สามารถพิสูจน์ได้ทางคณิตศาสตร์ว่า วงกลมนั้นไม่สามารถสร้างด้วยเส้นโค้งเบซิเยร์ได้ไม่ว่าองศาใดก็ตาม คุณสามารถสร้าง "เกือบเป็นวงกลม" ได้โดยการประมาณมัน

สมมติว่าคุณต้องการวาดวงกลมหนึ่งในสี่รอบ [0,0] พิกัดลูกบาศก์เบซิเยร์คือ:

[0   , 1   ]
[0.55, 1   ]
[1   , 0.55]
[1   , 0   ]

เป็นการประมาณที่ดีมาก แปลงเป็นเส้นตรงเพื่อให้ได้วงรี

person Ivan Kuckir    schedule 07.11.2013