javascript - 使螺旋线通过任意点的最简单方法?
问题描述
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.beginPath();
let last = 1
let start = 1
let i = 0
let origin = [250, 250]
for (let i2 = 0; i2 < 20; i2++) {
ctx.ellipse(...origin, start, start, Math.PI / 2 * i, 0, Math.PI / 2);
i++
i %= 4
if (i == 1) origin[1] -= last
else if (i == 2) origin[0] += last
else if (i == 3) origin[1] += last
else if (i == 0) origin[0] -= last;
[last, start] = [start, start + last]
}
ctx.stroke();
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 7
ctx.strokeStyle = "red";
ctx.lineTo(400, 400)
ctx.stroke()
<canvas width="500" height="500" style="border:1px solid #000000;"></canvas>
使螺旋线穿过画布中任意点的最简单方法是什么?例如400x 400y
. 我认为根据一些计算调整初始值start
和值可能会起作用。last
第一个代码片段和第二个代码片段之间的唯一区别是初始值last
和start
变量。也欢迎其他重写整个事情的解决方案。
const canvas = document.querySelector( 'canvas' );
const ctx = canvas.getContext( '2d' );
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.beginPath();
let last = 0.643
let start = 0.643
let i = 0
let origin = [250,250]
for (let i2=0; i2<20; i2++) {
ctx.ellipse(...origin, start, start, Math.PI/2 *i , 0, Math.PI /2);
i++
i%=4
if (i==1) origin[1] -= last
if (i==2) origin[0] += last
if (i==3) origin[1] += last
if (i==0) origin[0] -= last
;[last, start] = [start, start + last]
}
ctx.stroke();
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 7
ctx.strokeStyle = "red";
ctx.lineTo(400, 400)
ctx.stroke()
<canvas width="500" height="500" style="border:1px solid #000000;"></canvas>
解决方案
我不确定您希望螺旋如何截取该点。有twp选项。
- 旋转螺旋线到截点
- 将螺旋缩放到截点
此答案使用方法 1 解决。方法 2 存在一些问题,因为如果我们不对截点的位置设置限制,则转数会呈指数增长,从而使渲染变得非常缓慢。
不是螺旋
您提供的代码没有在数学定义中绘制螺旋线,而只是一组连接的椭球体。
这意味着在这些连接曲线上定义一个点的函数不止一个。求解一个点需要一定的复杂性,因为必须求解每条可能的曲线,然后对解决方案进行审查以找到正确的曲线。在这些椭球之上,我发现会产生一些非常丑陋的数学。
螺旋函数
如果我们将曲线定义为一个函数,其中螺旋半径由角度定义,则很容易求解。
半径的函数可以是形式的简化多项式,Ax^P+C
其中 x 是角度,P
是螺旋度(因为需要更好的术语),A
是比例(再次因为需要更好的术语)并且C
是起始角度
C
如果您想让螺旋的步距角为设定长度,例如 1 px 将是angle += 1 / (Ax^P+C)
如果 C 为 0,那么1/0
将导致无限循环。
绘制螺旋
正如上面所定义的,有许多类型的螺旋可以被渲染,所以应该有一种接近你所拥有的螺旋。
螺旋线上的任意一点如下所示
x = cos(angle) * f(angle) + origin.x
y = sin(angle) * f(angle) + origin.y
其中 f 是多边形f(x) = Ax^P+C
以下函数绘制一个基本的线性螺旋 f(x) = 1*x^1+0.1
function drawSpiral(origin) {
ctx.strokeStyle = "black";
ctx.lineWidth = 3;
ctx.beginPath();
let i = 0;
while (i < 5) {
const r = i + 0.1; // f(x) = 1*x^1+0.1
ctx.lineTo(
Math.cos(i) * r + origin.x,
Math.sin(i) * r + origin.y
);
i += 0.1
}
ctx.stroke();
}
解决通过点
为了求解一个点,我们将 转换为point
相对于 的极坐标origin
。见功能pointDist
,pointAngle
。Ax^P+C = dist
然后我们根据x
(角度)和与dist
的距离来求解origin
。然后减去该点的角度以获得螺旋方向。(注意 ^ 表示力量,其余答案使用 JavaScripts **
)
求解任意多项式会变得相当复杂,这就是我使用简化版本的原因。
该函数A * x ** P + C = pointDist(point)
需要根据 重新排列pointDist(point)
。
这给x = ((pointDist(point) - C) / A) ** (1 / P)
然后减去极角x = ((pointDist(point)- C) / A) ** (1 / P) - pointAngle(point)
,我们就有了角度偏移量,这样螺旋线就会与该点相交。
例子
一个工作示例,以防上述情况是 TLDR 或有太多像行话这样的数学。
该示例通过半径函数 A、C 和 P 的系数定义螺旋。
有 3 个示例螺旋黑色、蓝色和绿色。
绘制一个螺旋线,直到其半径大于到画布角的对角线距离。原点是画布的中心。
截取点由页面上的鼠标位置设置。
仅当鼠标位置更改时才会渲染螺旋。
简化多项式的解在函数的步骤中显示
startAngle
。
当我编写代码时,我缝合失去了方向,因此需要在起始角度(Math.PI)上增加 180 度,否则该点会在旋臂之间的中间结束。
const ctx = canvas.getContext("2d");
const mouse = {x : 0, y : 0}, mouseOld = {x : undefined, y : undefined};
document.addEventListener("mousemove", (e) => { mouse.x = e.pageX; mouse.y = e.pageY });
requestAnimationFrame(loop);
const TURNS = 4 * Math.PI * 2;
let origin = {x: canvas.width / 2, y: canvas.height / 2};
scrollTo(0, origin.y - innerHeight / 2);
const maxRadius = (origin.x ** 2 + origin.y ** 2) ** 0.5; // dist from origin to corner
const pointDist = (p1, p2) => Math.hypot(p1.x - p2.x, p1.y - p2.y);
const pointAngle = (p1, p2) => Math.atan2(p1.y - p2.y, p1.x - p2.x);
const radius = (x, spiral) => spiral.A * x ** spiral.P + spiral.C;
const startAngle = (origin, point, spiral) => {
const dist = pointDist(origin, point);
const ang = pointAngle(origin, point);
// Da math
// from radius function A * x ** P + C
// where x is ang
// A * x ** P + C = dist
// A * x ** P = dist - C
// x ** P = (dist - C) / A
// x = ((dist - C) / A) ** (1 / p)
return ((dist - spiral.C) / spiral.A) ** (1 / spiral.P) - ang;
}
// F for Fibonacci
const startAngleF = (origin, point, spiral) => {
const dist = pointDist(origin, point);
const ang = pointAngle(origin, point);
return (1 / spiral.P) * Math.log(dist / spiral.A) - ang;
}
const radiusF = (x, spiral) => spiral.A * Math.E ** (spiral.P * x);
const spiral = (P, A, C, rFc = radius, aFc = startAngle) => ({P, A, C, rFc, aFc});
const spirals = [
spiral(2, 1, 0.1),
spiral(3, 0.25, 0.1),
spiral(0.3063489,0.2972713047, null, radiusF, startAngleF),
spiral(0.8,4, null, radiusF, startAngleF),
];
function drawSpiral(origin, point, spiral, col) {
const start = spiral.aFc(origin, point, spiral);
ctx.strokeStyle = col;
ctx.beginPath();
let i = 0;
while (i < TURNS) {
const r = spiral.rFc(i, spiral);
const ang = i - start - Math.PI;
ctx.lineTo(
Math.cos(ang) * r + origin.x,
Math.sin(ang) * r + origin.y
);
if (r > maxRadius) { break }
i += 0.1
}
ctx.stroke();
}
loop()
function loop() {
if (mouse.x !== mouseOld.x || mouse.y !== mouseOld.y) {
ctx.clearRect(0, 0, 500, 500);
ctx.lineWidth = 1;
drawSpiral(origin, mouse, spirals[0], "#FFF");
drawSpiral(origin, mouse, spirals[1], "#0FF");
ctx.lineWidth = 4;
drawSpiral(origin, mouse, spirals[2], "#FF0");
drawSpiral(origin, mouse, spirals[3], "#AF0");
ctx.beginPath();
ctx.lineCap = "round";
ctx.lineWidth = 7;
ctx.strokeStyle = "red";
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
Object.assign(mouseOld, mouse);
}
requestAnimationFrame(loop);
}
canvas { position : absolute; top : 0px; left : 0px; background: black }
<canvas id="canvas" width = "500" height = "500"></canvas>
更新
按照评论中的要求
我在示例中添加了斐波那契螺旋线
半径函数为
radiusF
找到截取点的起始角度的函数是
startAngleF
两条新的斐波那契螺旋线颜色为石灰绿和黄色
要使用斐波那契螺旋线,您必须包含函数
radiusF
,并且startAngleF
在定义spiral
例如spiral(1, 1, 0, radiusF, startAngleF)
请注意,第三个参数未使用,在上面的示例中为零。因为我认为你不需要它
推荐阅读
- javascript - 更改日期格式(Javascript / Angular)
- javascript - 如何从赛普拉斯的测试文件中抽象出常用功能
- elasticsearch - Elasticsearch如何在java API中的must_not中添加布尔查询并具有多个匹配?
- html - 将表格单元格转换为可编辑输入
- angular - Angular - rxjs_Observable__WEBPACK_IMPORTED_MODULE_2__.Observable.of 不是函数
- r - 使用 R studio 对 .csv 文件进行操作
- javascript - 在Vue和Ajax中获取请求的http状态码
- android - Azure 给 403(禁止)与 android(API 级别 19)
- reporting-services - 如何为子报表添加 SSRS 文档地图书签
- java - Spring boot - 如何在 Process Builder 中指定 Java 路径