有一项任务是使用HTML5 Canvas实现我自己的arc()方法。 它应该与原生弧(打字稿代码)具有相同的签名:
arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void;
我知道圆圈将通过lineTo()方法用很多短线绘制,所以我需要设置精度(行数。可能是本地const)。 有一些自己的例子只适用于某些圈子。但是该方法必须与native arc()方法一样通用。 任何帮助/链接将不胜感激。
当前代码(绘制完整的圆圈)。
private customCircleDraw(center: Point, radius: number, start: number = 0, end: number = 2) {
const p1 = new Point(center.x + radius * Math.cos(start * Math.PI), -center.y + radius * Math.sin(start * Math.PI));
const p2 = new Point(center.x + radius * Math.cos(end * Math.PI), -center.y + radius * Math.sin(end * Math.PI));
const points = 50;
let angle1;
let angle2;
for(let i = 0; i < points; ++i) {
angle1 = i * 2 * Math.PI / points;
angle2 = (i+1) * 2 * Math.PI / points;
let firstPoint = new Point(center.x + radius * Math.cos(angle1), center.y + radius * Math.sin(angle1));
let secondPoint = new Point(center.x + radius * Math.cos(angle2), center.y + radius * Math.sin(angle2));
// if (some advanced condition)
this.drawLine(firstPoint,secondPoint);
}
另外,请注意,CENTER指向Y轴。画布原点移动了。为了更好地了解情况,我已将其部署到临时link。该示例现在使用native arc(),我想替换它。
答案 0 :(得分:2)
以下是使用Javascript的示例,您应该可以修改以打印您的Typescript。
另一个选项,如反时钟,你应该能够处理。
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
function degtorad(degrees) {
return degrees * Math.PI / 180;
};
function customCircleDraw(center, radius, start, end) {
var step_size = (end - start) / 50;
var angle = start;
var first = true;
while (angle <= end) {
let px = (Math.sin(angle) * radius) + center.x,
py = (-Math.cos(angle) * radius) + center.y;
if (first) {
ctx.moveTo(px,py);
first = false;
} else {
ctx.lineTo(px,py);
}
angle = angle + step_size;
}
}
customCircleDraw({x:100, y:100}, 50, degtorad(0), degtorad(90));
customCircleDraw({x:100, y:100}, 50, degtorad(180), degtorad(180+45));
ctx.stroke();
<canvas id="myCanvas" width="200" height="200" style="border:1px solid red;">
</canvas>
答案 1 :(得分:2)
我假设实现应尽可能匹配现有函数。
要绘制圆圈,我们会围绕圆圈逐步划线。我们不想使用太小的步骤,因为这只是不需要的工作,我们也不想让步骤太大或圆圈看起来很锯齿。对于此示例,高质量的线长约为6个像素。
步数是圆周除以线步长度。周长为= (PI * 2 * radius)
,因此数字为steps = (PI * 2 * radius) / 6
。但我们可以作弊。使线长度为两个饼,使得数字步长等于整圆的半径。
现在有些标准行为。如果半径<1,则抛出弧。 0,如果radius为0,那么arc就像一个lineTo函数。可选方向以时钟方式(CW)== false和CCW(如果为真)绘制线条。
如果在渲染方向上从开始到结束的角度大于或等于整圆(PI * 2)
start
和end
这两个角度可以位于> -Infinity
到< Infinity
的任意位置。我们需要将角度标准化(如果没有绘制整圆)到0到PI * 2的范围
一旦我们得到了正确的起点和终点角度,我们就可以找到弧steps = (end - start) / PI * radius
的步数以及我们可以计算步角的步数step = (end - start) / steps
现在只是绘制线段的问题。 Arc不使用moveTo
,因此所有线段都标有ctx.lineTo
。
圆圈上的一个点是
x = Math.cos(angle) * radius + centerX
y = Math.sin(angle) * radius + centerY
步数将包含小数部分,因此最后一个线段将比其余线段短。在主迭代之后,我们添加最后一个线段以结束角度结束。
要完成该功能需要使用CanvasRenderingContext2D,因此我们将使用新的函数覆盖现有的arc函数。作为CanvasRenderingContext2D.prototype.arc = //the function
CanvasRenderingContext2D.prototype.arc = function (x, y, radius, start, end, direction) {
const PI = Math.PI; // use PI and PI * 2 a lot so make them constants for easy reading
const PI2 = PI * 2;
// check radius is in range
if (radius < 0) { throw new Error(`Failed to execute 'arc' on 'CanvasRenderingContext2D': The radius provided (${radius}) is negative.`) }
if (radius == 0) { ctx.lineTo(x,y) } // if zero radius just do a lineTo
else {
const angleDist = end - start; // get the angular distance from start to end;
let step, i;
let steps = radius; // number of 6.28 pixel steps is radius
// check for full CW or CCW circle depending on directio
if((direction !== true && angleDist >= PI2)){ // full circle
step = PI2 / steps;
} else if((direction === true && angleDist <= -PI2)){ // full circle
step = -PI2 / steps;
}else{
// normalise start and end angles to the range 0- 2 PI
start = ((start % PI2) + PI2) % PI2;
end = ((end % PI2) + PI2) % PI2;
if(end < start) { end += PI2 } // move end to be infront (CW) of start
if(direction === true){ end -= PI2 } // if CCW move end behind start
steps *= (end - start) / PI2; // get number of 2 pixel steps
step = (end - start) / steps; // convert steps to a step in radians
if(direction === true) { step = -step; } // correct sign of step if CCW
steps = Math.abs(steps); // ensure that the iteration is positive
}
// iterate circle
for (i = 0 ; i < steps; i += 1){
this.lineTo(
Math.cos(start + step * i) * radius + x,
Math.sin(start + step * i) * radius + y
);
}
this.lineTo( // do the last segment
Math.cos(start + step * steps) * radius + x,
Math.sin(start + step * steps) * radius + y
);
}
}
仅仅因为答案应该有一个可运行的例子。使用新的弧函数绘制随机圆。红色圆圈是CCW和蓝色CW。外部绿色圆圈是要比较的原始圆弧函数。
CanvasRenderingContext2D.prototype.arcOld = CanvasRenderingContext2D.prototype.arc;
CanvasRenderingContext2D.prototype.arc = function (x, y, radius, start, end, direction) {
const PI = Math.PI; // use PI and PI * 2 a lot so make them constants for easy reading
const PI2 = PI * 2;
// check radius is in range
if (radius < 0) { throw new Error(`Failed to execute 'arc' on 'CanvasRenderingContext2D': The radius provided (${radius}) is negative.`) }
if (radius == 0) { ctx.lineTo(x,y) } // if zero radius just do a lineTo
else {
const angleDist = end - start; // get the angular distance from start to end;
let step, i;
let steps = radius; // number of 6.28 pixel steps is radius
// check for full CW or CCW circle depending on directio
if((direction !== true && angleDist >= PI2)){ // full circle
step = PI2 / steps;
} else if((direction === true && angleDist <= -PI2)){ // full circle
step = -PI2 / steps;
}else{
// normalise start and end angles to the range 0- 2 PI
start = ((start % PI2) + PI2) % PI2;
end = ((end % PI2) + PI2) % PI2;
if(end < start) { end += PI2 } // move end to be infront (CW) of start
if(direction === true){ end -= PI2 } // if CCW move end behind start
steps *= (end - start) / PI2; // get number of 2 pixel steps
step = (end - start) / steps; // convert steps to a step in radians
if(direction === true) { step = -step; } // correct sign of step if CCW
steps = Math.abs(steps); // ensure that the iteration is positive
}
// iterate circle
for (i = 0 ; i < steps; i += 1){
this.lineTo(
Math.cos(start + step * i) * radius + x,
Math.sin(start + step * i) * radius + y
);
}
this.lineTo( // do the last segment
Math.cos(start + step * steps) * radius + x,
Math.sin(start + step * steps) * radius + y
);
}
}
const rand = (min, max = min + (min = 0)) => Math.random() * (max - min) + min;
// test code
const ctx = canvas.getContext("2d");
canvas.width = innerWidth - 20;
canvas.height = innerHeight - 20;
var count = 0;
(function randomCircle(){
count += 1;
if(count > 50){
ctx.clearRect(0,0,canvas.width,canvas.height);
count = 0;
}
var x = rand(canvas.width);
var y = rand(canvas.height);
var start = rand(-1000,1000);
var end = rand(-1000,1000);
var radius = rand(10,200);
var dir = rand(1) < 0.5;
ctx.strokeStyle = dir ? "red" : "blue";
ctx.beginPath()
ctx.arc(x,y,radius,start,end,dir)
ctx.stroke();
ctx.strokeStyle = "green";
ctx.beginPath()
ctx.arcOld(x,y,radius + 4,start,end,dir)
ctx.stroke();
setTimeout(randomCircle,250);
})();
&#13;
canvas { position : absolute; top : 0px; left : 0px; }
&#13;
Red circles CCW, blue CW.
<canvas id="canvas"></canvas>
&#13;
除了一件小事之外,几乎完美无缺。我使用了大约6像素长的线段大小。这不适用于小圈子&lt; ~8px半径。我把它留给你修理。
steps
的数量。如果steps
将行长度增加一半