如何在二次曲线上跟踪坐标 签名哪里结果功能用法

如何解决如何在二次曲线上跟踪坐标 签名哪里结果功能用法

我在Polyline(html5画布)上有一个HiDPICanvas。当我左右移动鼠标时,我会跟踪其坐标,并在折线上相同X坐标的相应点上绘制Circle。您现在可以尝试查看结果。

// Create a canvas
var HiDPICanvas = function(container_id,color,w,h) {
    /*
    objects are objects on the canvas,first elements of dictionary are background elements,last are on the foreground
    canvas will be placed in the container
    canvas will have width w and height h
    */
    var objects = {
        box     : [],borders : [],circles : [],polyline: []
    }
    var doNotMove = ['borders']
    // is mouse down & its coords
    var mouseDown = false
    lastX = window.innerWidth/2
    lastY = window.innerHeight/2

    // return pixel ratio
    var getRatio = function() {
        var ctx = document.createElement("canvas").getContext("2d");
        var dpr = window.devicePixelRatio || 1;
        var bsr = ctx.webkitBackingStorePixelRatio ||
                    ctx.mozBackingStorePixelRatio ||
                    ctx.msBackingStorePixelRatio ||
                    ctx.oBackingStorePixelRatio ||
                    ctx.backingStorePixelRatio || 1;
    
        return dpr / bsr;
    }

    // return high dots per inch canvas
    var createHiDPICanvas = function() {
        var ratio = getRatio();
        var chart_container = document.getElementById(container_id);
        var can             = document.createElement("canvas");
        can.style.backgroundColor = color
        can.width           = w * ratio;
        can.height          = h * ratio;
        can.style.width     = w + "px";
        can.style.height    = h + "px";
        can.getContext("2d").setTransform(ratio,ratio,0);
        chart_container.appendChild(can);
        return can;
    }

    // add object to the canvas
    var add = function(object,category) {
        objects[category].push(object)
    }

    // clear canvas
    var clearCanvas = function(x0,y0,x1,y1) {
        ctx.clearRect(x0,y1);
        ctx.beginPath();
        ctx.globalCompositeOperation = "source-over";
        ctx.globalAlpha = 1;
        ctx.closePath();
    }

    // check function do I can move this group of objects
    var canMove = function(groupname) {
        for (var i = 0; i < doNotMove.length; i++) {
            var restricted = doNotMove[i]
            if (restricted == groupname) {
                return false
            }
        }
        return true
    }

    // refresh all objects on the canvas
    var refresh = function() {
        clearCanvas(0,h)
        var object
        for (var key in objects) {
            for (var i = 0; i < objects[key].length; i++) {
                object = objects[key][i]
                object.refresh()
            }
        }
    }

    // shift all objects on the canvas except left and down borders and its content
    var shiftObjects = function(event) {
        event.preventDefault()
        // if mouse clicked now -> we can move canvas view left\right
        if (mouseDown) {
            var object
            for (var key in objects) {
                if (canMove(key)) {
                    for (var i = 0; i < objects[key].length; i++) {
                        object = objects[key][i]
                        object.move(event.movementX,event.movementY)
                    }   
                }
            }
            cci.refresh()
        }
    }
    
    // transfer x to canvas drawing zone x coord (for not drawing on borders of the canvas)
    var transferX = function(x) {
        return objects.borders[0].width + x
    }

    var transferCoords = function(x,y) {
        // no need to transfer y because borders are only at the left
        return {
            x : transferX(x),y : y
        }
    }

    // change mouse state on the opposite
    var toggleMouseState = function() {
        mouseDown = !mouseDown
    }

    // make mouseDown = false,(bug removal function when mouse down & leaving the canvas)
    var refreshMouseState = function() {
        mouseDown = false
    }

    // print information about all objects on the canvas
    var print = function() {
        var groupLogged = true
        console.log("Objects on the canvas:")
        for (var key in objects) {
            groupLogged = !groupLogged
            if (!groupLogged) {console.log(key,":"); groupLogged = !groupLogged}
            for (var i = 0 ; i < objects[key].length; i++) {
                console.log(objects[key][i])
            }
        }
    }

    var restrictEdges = function() {
        console.log("offsetLeft",objects['borders'][0])
    }

    var getMouseCoords = function() {
        return {
            x : lastX,y : lastY
        }
    }

    var addCircleTracker = function() {
        canvas.addEventListener("mousemove",(e) => {
            var polyline = objects.polyline[0]
            var mouseCoords = getMouseCoords()
            var adjNodes = polyline.get2NeighbourNodes(mouseCoords.x)
            if (adjNodes != -1) {
                var prevNode   = adjNodes.prev
                var currNode   = adjNodes.curr
                var cursorNode = polyline.linearInterpolation(prevNode,currNode,mouseCoords.x)
                // cursorNode.cursorX,cursorNode.cursorY are coords
                // for circle that should be drawn on the polyline
                // between the closest neighbour nodes
                var circle = objects.circles[0]
                circle.changePos(cursorNode.x,cursorNode.y)
                refresh()
            }
        })
    }

    // create canvas
    var canvas = createHiDPICanvas()
    addCircleTracker()
    
    // we created canvas so we can track mouse coords
    var trackMouse = function(event) {
        lastX = event.offsetX || (event.pageX - canvas.offsetLeft)
        lastY = event.offsetY || (event.pageY - canvas.offsetTop)
    }

    // 2d context
    var ctx = canvas.getContext("2d")
    // add event listeners to the canvas
    canvas.addEventListener("mousemove",shiftObjects         )
    canvas.addEventListener("mousemove",(e) =>{ trackMouse(e)       })
    canvas.addEventListener("mousedown",() => { toggleMouseState () })
    canvas.addEventListener("mouseup",() => { toggleMouseState () })
    canvas.addEventListener("mouseleave",() => { refreshMouseState() })
    canvas.addEventListener("mouseenter",() => { refreshMouseState() })

    return {
        // base objects
        canvas : canvas,ctx    : ctx,// sizes of the canvas
        width  : w,height : h,color  : color,// add object on the canvas for redrawing
        add    : add,print  : print,// refresh canvas
        refresh: refresh,// objects on the canvas
        objects: objects,// get mouse coords
        getMouseCoords : getMouseCoords
    }

}

// cci -> canvas ctx info (dict)
var cci = HiDPICanvas("lifespanChart","bisque",780,640)
var ctx           = cci.ctx
var canvas        = cci.canvas


var Polyline = function(path,color) {
    
    var create = function() {
        if (this.path === undefined) {
            this.path = path
            this.color = color
        }
        ctx.save()
        ctx.beginPath()
        p = this.path
        ctx.fillStyle = color
        ctx.moveTo(p[0].x,p[0].y)
        for (var i = 0; i < p.length - 1; i++) {
            var currentNode = p[i]
            var nextNode    = p[i+1]
            
            // draw smooth polyline
            // var xc = (currentNode.x + nextNode.x) / 2;
            // var yc = (currentNode.y + nextNode.y) / 2;
            // taken from https://stackoverflow.com/a/7058606/13727076
            // ctx.quadraticCurveTo(currentNode.x,currentNode.y,xc,yc);
            
            // draw rough polyline
            ctx.lineTo(currentNode.x,currentNode.y)
        }
        ctx.stroke()
        ctx.restore()
        ctx.closePath()
    }
    // circle that will track mouse coords and be
    // on the corresponding X coord on the path
    // following mouse left\right movements
    var circle = new Circle(50,50,5,"purple")
    cci.add(circle,"circles")
    create()

    var get2NeighbourNodes = function(x) {
        // x,y are cursor coords on the canvas 
        //
        // Get 2 (left and right) neighbour nodes to current cursor x,y
        // N are path nodes,* is Node we search coords for
        //
        // N-----------*----------N
        //
        for (var i = 1; i < this.path.length; i++) {
            var prevNode = this.path[i-1]
            var currNode = this.path[i]
            if ( prevNode.x <= x && currNode.x >= x ) {
                return {
                    prev : prevNode,curr : currNode
                }
            }
        }
        return -1
    }

    var linearInterpolation = function(prevNode,cursorX) {
        // calculate x,y for the node between 2 nodes
        // on the path using linearInterpolation
        // https://en.wikipedia.org/wiki/Linear_interpolation
        var cursorY = prevNode.y + (cursorX - prevNode.x) * ((currNode.y - prevNode.y)/(currNode.x - prevNode.x))
        
        return {
            x : cursorX,y : cursorY
        }
    }

    var move = function(diff_x,diff_y) {
        for (var i = 0; i < this.path.length; i++) {
            this.path[i].x += diff_x
            this.path[i].y += diff_y
        }
    }
    
    return {
        create : create,refresh: create,move   : move,get2NeighbourNodes : get2NeighbourNodes,linearInterpolation : linearInterpolation,path   : path,color  : color
    }


}

var Circle = function(x,y,radius,fillStyle) {
    
    var create = function() {
        if (this.x === undefined) {
            this.x = x
            this.y = y
            this.radius = radius
            this.fillStyle = fillStyle
        }
        ctx.save()
        ctx.beginPath()
        ctx.arc(this.x,this.y,2*Math.PI)
        ctx.fillStyle = fillStyle
        ctx.strokeStyle = fillStyle
        ctx.fill()
        ctx.stroke()
        ctx.closePath()
        ctx.restore()
    }
    create()

    var changePos = function(new_x,new_y) {
        this.x = new_x
        this.y = new_y
    }

    var move = function(diff_x,diff_y) {
        this.x += diff_x
        this.y += diff_y
    }

    return {
        refresh : create,create  : create,changePos: changePos,move    : move,radius  : radius,x       : this.x,y       : this.y
    }
}

var Node = function(x,y) {
    this.x = x
    this.y = y
    return {
        x : this.x,y : this.y
    }
} 

var poly   = new Polyline([
    Node(30,30),Node(150,150),Node(290,Node(320,200),Node(350,350),Node(390,250),Node(450,140)
],"green")

cci.add(poly,"polyline")
<div>
        <div id="lifespanChart"></div>
    </div>

但是,如果您转到下面的注释draw smooth polyline和取消注释代码(以及用于绘制粗折线的注释线)-现在它将绘制平滑的折线(二次贝塞尔曲线)。但是,当您尝试左右移动鼠标时-Circle有时会超出折线范围。

二次曲线之前:

enter image description here

二次曲线之后:

enter image description here

这是一个问题:我使用linear interpolation在粗折线上计算了x,y的{​​{1}}坐标,但是如何计算{{1} }在平滑quadratic curve上的Circle坐标?

ADD 1 :在对折线进行平滑处理时,以Beizer curve为基础的QuadraticCurve

ADD 2 对于那些对实施有些坚持的人,我发现并保存了here中更简单的解决方案,例如:

x,y
Circle
var canvas = document.getElementById("canv")
var canvasRect = canvas.getBoundingClientRect()
var ctx = canvas.getContext('2d')


var p0 = {x : 30,y : 30}
var p1 = {x : 20,y :100}
var p2 = {x : 200,y :100}
var p3 = {x : 200,y :20}

// Points are objects with x and y properties
// p0: start point
// p1: handle of start point
// p2: handle of end point
// p3: end point
// t: progression along curve 0..1
// returns an object containing x and y values for the given t
// link https://stackoverflow.com/questions/14174252/how-to-find-out-y-coordinate-of-specific-point-in-bezier-curve-in-canvas
var BezierCubicXY = function(p0,p1,p2,p3,t) {
    var ret = {};
    var coords = ['x','y'];
    var i,k;

    for (i in coords) {
        k = coords[i];
        ret[k] = Math.pow(1 - t,3) * p0[k] + 3 * Math.pow(1 - t,2) * t * p1[k] + 3 * (1 - t) * Math.pow(t,2) * p2[k] + Math.pow(t,3) * p3[k];
    }

    return ret;
}

var draw_poly = function () {
  ctx.beginPath()
  ctx.lineWidth=2
  ctx.strokeStyle="white"
  ctx.moveTo(p0.x,p0.y)// start point
  //                 cont        cont        end
  ctx.bezierCurveTo(p1.x,p1.y,p2.x,p2.y,p3.x,p3.y)
  ctx.stroke()
  ctx.closePath()
}
var clear_canvas = function () {
  ctx.clearRect(0,300,300);
  ctx.beginPath();
  ctx.globalCompositeOperation = "source-over";
  ctx.globalAlpha = 1;
  ctx.closePath();
};
var draw_circle = function(x,y) {
    ctx.save();
    // semi-transparent arua around the circle
    ctx.globalCompositeOperation = "source-over";
    ctx.beginPath()
    ctx.fillStyle = "white"
    ctx.strokeStyle = "white"
    ctx.arc(x,2 * Math.PI);
    ctx.stroke();
    ctx.closePath();
    ctx.restore();
}
var refresh = function(circle_x,circle_y) {
  clear_canvas()
  draw_circle(circle_x,circle_y)
  draw_poly()
}

var dist = function(mouse,point) {
  return Math.abs(mouse.x - point.x)
  // return ((mouse.x - point.x)**2 + (mouse.y - point.y)**2)**0.5
}

var returnClosest = function(curr,prev) {
  if (curr < prev) {
    return curr
  }
  return prev
}

refresh(30,30)
canvas.addEventListener("mousemove",(e) => {
  var mouse = {
     x : e.clientX - canvasRect.left,y : e.clientY - canvasRect.top
  }
  
  var Point = BezierCubicXY(p0,0)
  for (var t = 0; t < 1; t += 0.01) {
    var nextPoint = BezierCubicXY(p0,t)
    if (dist(mouse,Point) > dist(mouse,nextPoint)) {
      Point = nextPoint
    }
    // console.log(Point)
  }
  
  refresh(Point.x,Point.y)
  
})

只需遍历曲线的所有线并使用此图案即可找到最接近的位置

解决方法

可以像使用行一样使用迭代搜索来完成。

顺便说一句,线上有很多better way to find the closest point,其复杂度为 O(1),而不是 O(n),其中 n 是线段的长度。

搜索最近的点

以下函数可用于二次方和三次方贝塞尔曲线,并将贝塞尔曲线上最近点的单位位置返回给定坐标。

该函数还具有属性foundPoint,该属性具有找到的点的位置

该函数使用定义2D坐标的对象Point

签名

该函数具有两个签名,一个签名用于二次贝塞尔曲线,另一个签名用于三次方。

  1. closestPointOnBezier(point,resolution,p1,p2,cp1)
  2. closestPointOnBezier(point,cp1,cp2)

哪里

  • pointPoint的位置
  • resolution as Number搜索贝塞尔曲线的近似分辨率。如果为0,则固定为DEFAULT_SCAN_RESOLUTION,否则为起点和终点之间的距离乘以resolution IE,如果resolution = 1则近似扫描为1px,如果resolution = 2则近似扫描是1 / 2px
  • p1p2Point是贝塞尔曲线的起点和终点
  • cp1cp2Point是贝塞尔曲线的第一个和/或第二个控制点

结果

  • 它们都返回Number,即最接近点的贝塞尔曲线上的单位pos。值将为0
  • 函数属性closestPointOnBezier.foundPointPoint,具有贝塞尔曲线上最近点的坐标,可用于计算到贝塞尔曲线上的点的距离。

功能

const Point = (x = 0,y = 0) => ({x,y});
const MAX_RESOLUTION = 2048;
const DEFAULT_SCAN_RESOLUTION = 256;
closestPointOnBezier.foundPoint = Point();
function closestPointOnBezier(point,cp2) {  
    var unitPos,a,b,b1,c,i,vx,vy,closest = Infinity;
    const v1 = Point(p1.x - point.x,p1.y - point.y);
    const v2 = Point(p2.x - point.x,p2.y - point.y);
    const v3 = Point(cp1.x - point.x,cp1.y - point.y);
    resolution = resolution > 0 && reolution < MAX_RESOLUTION ? (Math.hypot(p1.x - p2.x,p1.y - p2.y) + 1) * resolution : 100;
    const fp = closestPointOnBezier.foundPoint;
    const step = 1 / resolution;
    const end = 1 + step / 2;
    const checkClosest = (e = (vx * vx + vy * vy) ** 0.5) => {
        if (e < closest ){
            unitPos = i;
            closest = e;
            fp.x = vx;
            fp.y = vy;
        }        
    }
    if (cp2 === undefined) {  // find quadratic 
        for (i = 0; i <= end; i += step) {
            a = (1 - i); 
            c = i * i; 
            b = a*2*i;
            a *= a;  
            vx = v1.x * a + v3.x * b + v2.x * c;
            vy = v1.y * a + v3.y * b + v2.y * c;
            checkClosest();
        }
    } else { // find cubic
        const v4 = Point(cp2.x - point.x,cp2.y - point.y);
        for (i = 0; i <= end; i += step) { 
            a = (1 - i); 
            c = i * i; 
            b = 3 * a * a * i; 
            b1 = 3 * c * a; 
            a = a * a * a;
            c *= i; 
            vx = v1.x * a + v3.x * b + v4.x * b1 + v2.x * c;
            vy = v1.y * a + v3.y * b + v4.y * b1 + v2.y * c;
            checkClosest();
        }
    }    
    return unitPos < 1 ? unitPos : 1; // unit pos on bezier. clamped 
}

用法

在两个贝塞尔曲线上找到最接近点的示例用法

定义的几何形状

const bzA = {
    p1: Point(10,100),// start point
    p2: Point(200,400),// control point
    p3: Point(410,500),// end point
};
const bzB = {
    p1: bzA.p3,// end point
};
const mouse = Point(?,?);

找到最近的

// Find first point
closestPointOnBezier(mouse,2,bzA.p1,bzA.p3,bzA.p2);

// copy point
var found = Point(closestPointOnBezier.foundPoint.x,closestPointOnBezier.foundPoint.y);

// get distance to mouse
var dist = Math.hypot(found.x - mouse.x,found.y - mouse.y);

// find point on second bezier
closestPointOnBezier(mouse,bzB.p1,bzB.p3,bzB.p2);

// get distance of second found point
const distB = Math.hypot(closestPointOnBezier.foundPoint.x - mouse.x,closestPointOnBezier.foundPoint.y - mouse.y);

// is closer
if (distB < dist) {
    found.x = closestPointOnBezier.foundPoint.x;
    found.y = closestPointOnBezier.foundPoint.y;
    dist = distB;
}

壁橱点为foundPoint

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res