首页 > 解决方案 > 使螺旋线通过任意点的最简单方法?

问题描述

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第一个代码片段和第二个代码片段之间的唯一区别是初始值laststart变量。也欢迎其他重写整个事情的解决方案。

        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>

标签: javascripthtml5-canvas

解决方案


我不确定您希望螺旋如何截取该点。有twp选项。

  1. 旋转螺旋线到截点
  2. 将螺旋缩放到截点

此答案使用方法 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。见功能pointDistpointAngleAx^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)

    请注意,第三个参数未使用,在上面的示例中为零。因为我认为你不需要它


推荐阅读