js 切割圆形

2020-05-07  本文已影响0人  CODERLIHAO
2020-05-07 17.16.55.gif
这种类型就是线段与圆的交点问题
724B5348538A85F0E7CE3AD597AD829A.png
设线段就是P1P2就是我们的线段,P1(x1,y1) , P2(x2,y2)
交点是P(x,y)
那么向量 OP = 向量O P1 + u(P1P2),u是一个系数

x = x1+u(x2−x1)
y = y1+u(y2−y1)
由于P也在圆上,所以
(x−x3)2+(y−y3)2=r2
带入方程,求解
Au2+Bu+C=0
A = (x2−x1)2+(y2−y1)2
B = 2((x2−x1)(x1−x3)+(y2−y1)(y1−y3))
C = (x3)2+(y3)2+(x1)2+(y1)2−2(x3x1+y3y1)−r2

解一元二次方程
u = (-B ±√(B2 - 4AC))/2A
B2 - 4AC < 0 没有解
B2 - 4AC = 0 只有一个解
B2 - 4AC > 0 有两个解
我们的要求的是找到两个不同的点,所以只有B2 - 4AC>0满足条件
求出的u可能大于等于1 或者小于等于0,都是不满足的

先定义我们的剪切线段

    class CutLine {
        sx = 0; //起始点x
        sy = 0; //起始点y
        ex = 0; //终点x
        ey = 0; //终点y
        strokeWidth = 0; //线条宽度
        strokeColor = 0; //线条颜色

        constructor(option) {
            this.strokeWidth = option.strokeWidth;
            this.strokeColor = option.strokeColor;
        }

        updateStartPoint(x, y) {
            this.sx = x;
            this.sy = y;
        }

        updateEndPoint(x, y) {
            this.ex = x;
            this.ey = y;
        }

        draw(ctx) {
            ctx.beginPath();
            ctx.lineWidth = this.strokeWidth;
            ctx.strokeStyle = this.strokeColor;
            ctx.moveTo(this.sx, this.sy);
            ctx.lineTo(this.ex, this.ey);
            ctx.stroke();
        }
    }

定义一个抽象的shape

class Shape {
        cutLine = null;
        isCut = false;
        child = [];
        hitChild = null;
        oldX = 0;
        oldY = 0;

        constructor(option) {
            this.cutLine = new CutLine({
                strokeWidth: option.lineStrokeWidth,
                strokeColor: option.lineStrokeColor
            });
        }

        onTouchDown(event, ctx) {
            ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
            this.oldX = event.x;
            this.oldY = event.y;
            if (!this.isCut) {
                this.cutLine.updateStartPoint(event.x, event.y);
                this.drawSelf(ctx);
            } else {
                this.hitChild = this.hitTest(event);
                this.drawChild(ctx);
            }
        }

        onTouchMove(event, ctx) {
            ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
            if (this.isCut && this.hitChild) {
                const x = event.x;
                const y = event.y;
                const deltaX = x - this.oldX;
                const deltaY = y - this.oldY;
                this.hitChild.x += deltaX;
                this.hitChild.y += deltaY;
                this.oldX = x;
                this.oldY = y;
            }

            if (!this.isCut) {
                this.cutLine.updateEndPoint(event.x, event.y);
                this.cutLine.draw(ctx);
                this.drawSelf(ctx);
            } else {
                this.drawChild(ctx);
            }
        }

        onTouchUp(event, ctx) {
            ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        }

        drawChild(ctx) {
            if (this.child) {
                this.child.forEach((shape) => {
                    shape.draw(ctx);
                });
            }
        }

        cutShape(cutLine) {

        }

        drawSelf(ctx) {

        }

        hitTest(event) {
            const x = event.x;
            const y = event.y;
            for (let i = this.child.length - 1; i >= 0; i--) {
                const childShape = this.child[i];
                if (x >= childShape.x && x <= childShape.x + childShape.w && y >= childShape.y && y <= childShape.y + childShape.h) {
                    const image = childShape.image;
                    if (!image) {
                        continue;
                    }
                    const pxData = Utils.getPixel(image, x - childShape.x, y - childShape.y, childShape.w, childShape.h);
                    if (pxData[3] < 1) {
                        continue;
                    }
                    return childShape;
                }
            }
        }
    }

用圆继承这个Shape

class CircleShape extends Shape {

       cx = 0;
       cy = 0;
       cr = 0;
       strokeColor = "#098231";
       fillColor = "#098231";
       cutFillColor = "#098231";
       strokeWidth = 0;

       constructor(option) {
           super(option);
           this.cx = option.cx;
           this.cy = option.cy;
           this.cr = option.cr;
           this.strokeColor = option.strokeColor;
           this.fillColor = option.fillColor;
           this.cutFillColor = option.cutFillColor;
           this.strokeWidth = option.strokeWidth;
       }

       reset(ctx) {
           this.hitChild = null;
           this.child.length = 0;
           this.isCut = false;
           this.drawSelf(ctx);
       }

       onTouchUp(event, ctx) {
           super.onTouchUp(event, ctx);
           this.hitChild = null;
           if (!this.isCut) {
               const thetas = this.getTheta(this.cx, this.cy, this.cr, this.cutLine.sx, this.cutLine.sy, this.cutLine.ex, this.cutLine.ey);
               if (thetas) {
                   this.isCut = true;
                   this.cutShape(ctx, thetas);
                   this.drawChild(ctx);
               } else {
                   this.drawCircle(ctx, this.cx, this.cy, this.cr, this.strokeWidth, this.strokeColor, this.fillColor);
               }
           } else {
               this.drawChild(ctx);
           }
       }

       drawSelf(ctx) {
           ctx.beginPath();
           ctx.fillStyle = this.fillColor;
           ctx.arc(this.cx, this.cy, this.cr - this.strokeWidth / 2, 0, 2 * Math.PI);
           ctx.fill();
           ctx.beginPath();
           ctx.lineWidth = this.strokeWidth;
           ctx.strokeStyle = this.strokeColor;
           ctx.arc(this.cx, this.cy, this.cr - this.strokeWidth / 2, 0, 2 * Math.PI);
           ctx.stroke();
       }


       cutShape(ctx, thetas) {
           const image1 = Utils.toCircleDataURL(this.cr, thetas[0], thetas[1], this.strokeColor, this.fillColor, this.strokeWidth, false);
           const child1 = new ChildShape(this.cx - this.cr, this.cy - this.cr, 2 * this.cr, 2 * this.cr, image1);
           this.child.push(child1);

           const image2 = Utils.toCircleDataURL(this.cr, thetas[0], thetas[1], this.strokeColor, this.cutFillColor, this.strokeWidth, true);
           const child2 = new ChildShape(this.cx - this.cr, this.cy - this.cr, 2 * this.cr, 2 * this.cr, image2);
           this.child.push(child2);
       }

       getTheta(cx, cy, cr, x1, y1, x2, y2) {

           if (Math.sqrt(Math.pow(cx - x1, 2) + Math.pow(cy - y1, 2)) <= cr
               || Math.sqrt(Math.pow(cx - x2, 2) + Math.pow(cy - y2, 2)) <= cr) {
               return;
           }

           //设线段的两个端点分别是P1(x1,y1)和P2(x2,y2),圆的圆心在P3(x3,y3),半径为r,那么如果有交点P(x,y)的话
           //向量P = 向量P1 +u(向量P2 − 向量P1)
           // x = x1+u(x2−x1)
           // y = y1+u(y2−y1)
           //由于P也在圆上,所以
           //(x−x3)^2+(y−y3)^2=r^2
           // Au^2+Bu+C=0
           // A = (x2−x1)^2+(y2−y1)^2
           // B = 2((x2−x1)(x1−x3)+(y2−y1)(y1−y3))
           // C = (x3)^2+(y3)^2+(x1)^2+(y1)^2−2(x3x1+y3y1)−r^2

           // u = (-B ±√(B^2 - 4AC))/2A

           const A = Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2);
           const B = 2 * ((x2 - x1) * (x1 - cx) + (y2 - y1) * (y1 - cy));
           const C = Math.pow(cx, 2) + Math.pow(cy, 2) + Math.pow(x1, 2) + Math.pow(y1, 2) - 2 * (cx * x1 + cy * y1) - Math.pow(cr, 2);
           const t = Math.pow(B, 2) - 4 * A * C;
           if (t <= 0) {
               //没有交点或者只有一个交点
               return;
           }
           const u1 = (-B + Math.sqrt(t)) / 2 / A;
           const u2 = (-B - Math.sqrt(t)) / 2 / A;

           if (Math.abs(u1) >= 1 || Math.abs(u2) >= 1) {
               return;
           }
           const p1X = x1 + u1 * (x2 - x1);
           const p1Y = y1 + u1 * (y2 - y1);

           const p2X = x1 + u2 * (x2 - x1);
           const p2Y = y1 + u2 * (y2 - y1);

           const theta1 = Math.atan2(p1Y - cy, p1X - cx);
           const theta2 = Math.atan2(p2Y - cy, p2X - cx);
           return [theta1, theta2];
       }
   }

定义一个工具类,将shape变为图片,每次点击时,获取图片位置的像素值,要是透明的就不接受事件

static getPixel(image, x, y, w, h) {
            if (!Utils.cacheCanvas) {
                Utils.cacheCanvas = document.createElement("canvas");
            }
            var ctx = Utils.cacheCanvas.getContext('2d');
            Utils.cacheCanvas.width = w;
            Utils.cacheCanvas.height = h;
            ctx.drawImage(image, 0, 0);
            return ctx.getImageData(x, y, 1, 1).data;
        }

        static toCircleDataURL(cr, angleStart, angleEnd, strokeColor, fillColor, lineWidth, anticlockwise = false) {
            if (!Utils.cacheCanvas) {
                Utils.cacheCanvas = document.createElement("canvas");
            }
            var ctx = Utils.cacheCanvas.getContext('2d');
            ctx.clearRect(0, 0, Utils.cacheCanvas.width, Utils.cacheCanvas.height);
            Utils.cacheCanvas.width = 2 * cr;
            Utils.cacheCanvas.height = 2 * cr;
            ctx.beginPath();
            ctx.fillStyle = fillColor;
            ctx.arc(cr, cr, cr - lineWidth / 2, angleStart, angleEnd, anticlockwise);
            ctx.closePath();
            ctx.fill();

            ctx.beginPath();
            ctx.lineWidth = lineWidth;
            ctx.strokeStyle = strokeColor;
            ctx.arc(cr, cr, cr - lineWidth / 2, angleStart, angleEnd, anticlockwise);
            ctx.closePath();
            ctx.stroke();
            return Utils.cacheCanvas.toDataURL("image/png");
        }

Demo

上一篇下一篇

猜你喜欢

热点阅读