欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

关于fabricjs object与object间的包含关系与碰撞

程序员文章站 2022-03-20 18:47:15
最近项目需要前段处理图片,涉及到对象与对象之间的碰撞关系,fabricjs也提供了相应的方法,但是他们的提供的方法都是基于矩形的边来碰撞的,比如一个圆,他会以包裹圆的矩形为基准去碰撞,包括不规则的多边形是也是一样,请看官方提供的例子:碰撞检测从例子很容易看出,对圆和三角形没有触碰到他们的边缘就开始与其包裹的矩形发生碰撞了。官方apiintersectsWithObject检查对象是否碰撞与包含的关系,不过是基于包裹object的矩形,具体也不多说,自己看源码就知道了。下面的代码我也是根据源码改造出来.....

最近项目需要前端处理图片,用的是fabricjs,涉及到对象与对象之间的碰撞关系,fabricjs也提供了相应的方法,但是他们的提供的方法都是基于矩形的边来碰撞的,比如一个圆,他会以包裹圆的矩形为基准去碰撞,包括不规则的多边形是也是一样,请看官方提供的例子:碰撞检测从例子很容易看出,对圆和三角形没有触碰到他们的边缘就开始与其包裹的矩形发生碰撞了。

官方api intersectsWithObject 检查对象是否碰撞与包含的关系,不过是基于包裹object的矩形,具体也不多说,自己看源码就知道了。下面的代码我也是根据源码改造出来的。

fabric.util.object.extend(fabric.Object.prototype, {
  // 将圆分成N等份的多边形提高碰撞精度
  getCirclePoints: function(number) {
    number = number || 8;
    const [x, y, r] = [this.left, this.top, this.radius];
    const angle = 360 / number;
    const points = [];
    for(let i = 0; i < number; i++) {
      points.push({
        x: x + r * Math.cos(angle * i * Math.PI / 180),
        y: y + r * Math.sin(angle * i * Math.PI / 180)
      })
    }
    return points;
  },
  // 获取多边形的相对画布的绝对位置点
  getPolygonPoints: function() {
    return this.get('points').map(p => {
    // @ts-ignore
      return fabric.util.transformPoint({
        // @ts-ignore
        x: ~~ (p.x - this.pathOffset.x),
        // @ts-ignore
        y: ~~ (p.y - this.pathOffset.y)
      }, this.calcTransformMatrix());
    });
  },
  pointToLines: function(points) {
    const lines = {}
    points.forEach((item, index) => {
      lines[`line${index}`] = {
        d: item,
        o: points[(index + 1) % points.length]
      }
    });
    return lines;
  },
  // 讲多边形的点换成线,方便对象的包含关系判断
  getPolygonToLines: function() {
    return this.pointToLines(this.getPolygonPoints());
  },
  checkCrossPoints: function(point, lines) {
    let b1, b2, a1, a2, xi, // yi,
        xcount = 0,
        iLine;
    for (let lineKey in lines) {
      iLine = lines[lineKey];
      // optimisation 1: line below point. no cross
      if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) {
        continue;
      }
      // optimisation 2: line above point. no cross
      if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) {
        continue;
      }
      // optimisation 3: vertical line case
      if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) {
        xi = iLine.o.x;
        // yi = point.y;
      }
      // calculate the intersection point
      else {
        b1 = 0;
        b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x);
        a1 = point.y - b1 * point.x;
        a2 = iLine.o.y - b2 * iLine.o.x;
        xi = -(a1 - a2) / (b1 - b2);
        // yi = a1 + b1 * xi;
      }
      // dont count xi < point.x cases
      if (xi >= point.x) {
        xcount += 1;
      }
    }
    return (xcount !== 0 && xcount % 2 === 1);
  },
  // 判断一个对象是否再一个对象内,包括任意形状的多边形
  checkObjectIsInOtherObject: function(other) {
    let type = this.type, lines, points, oType = other.type;
    if (type === 'circle') { // 获取圆的点数,默认8个点
      points = this.getCirclePoints();
    } else if (type === 'polygon') {
      points = this.getPolygonPoints();
    } else {
      points = this.getCoords();
    }
    if (oType === 'circle') {
      lines = other.pointToLines(other.getCirclePoints);
    } else if (oType === 'polygon') {
      lines = other.getPolygonToLines();
    } else {
      other.setCoords();
      lines = other._getImageLines(other.aCoords);
    }
    for (let i =0, len = points.length; i < len; i++) {
      if (!other.checkCrossPoints(points[i], lines)) {
        return false;
      }
    }
    return true;
  },
  // 检测是否再某个对象里面 intersection 判断是否要相交
  isIntersectsWithObject: function(other, intersection) {
    let intersectionRes = null;
    if (intersection) {
      let type = this.type, oType = other.type, points, oPoints;
      if (type === 'polygon') {
        points = this.getPolygonPoints();
      } else if (type === 'circle') {
        points = this.getCirclePoints();
      } else {
        this.setCoords();
        points = this.getCoords();
      }
      if (oType === 'polygon') {
        oPoints = other.getPolygonPoints();
      } else if (oType === 'circle') {
        oPoints = other.getCirclePoints();
      } else {
        this.setCoords();
        oPoints = other.getCoords();
      }
      intersectionRes = fabric.Intersection.intersectPolygonPolygon(points, oPoints);
      console.log(intersectionRes, 'insterction')
    }
    return intersectionRes && intersectionRes.status === 'Intersection' || this.checkObjectIsInOtherObject(other) 
  }
})

 具体使用如下:


const canvas = new fabric.Canvas('c');
fabric.Object.prototype.transparentCorners = false;
const polygon = new fabric.Polygon([
  {x: 100, y: 100}, 
  {x: 200, y: 200}, 
  {x: 350, y: 100}, 
  {x: 300, y: 300},  
  {x: 250, y: 200},
  {x: 100, y: 300}
], {
  left: 300,
  top: 100,
  fill: 'rgba(0, 0, 0, 0.5)'
})


const circle = new fabric.Circle({
  radius: 10, left: 400, top: 200, fill: '#aac',
        originX: 'center',
        originY: 'center',
});

const rect = new fabric.Rect({
  left: 400,
  top: 300,
  width: 100,
  height: 100,
  fill: 'red'
});

canvas.add(rect, circle, polygon);

function check () {
  canvas.forEachObject(obj => {
    if (obj != polygon) {
      console.log(obj)
      const res = obj.isIntersectsWithObject(polygon, true);
      console.log(res);
    }
  })
}

document.getElementById('btn').onclick = check

我只写了多边形、圆,三角形等没有去写,其实原理都是一样的,就照样画葫芦吧。

fabricjs的资源太少了,开发过程中只能是摸着石头过河,其实里面的方法有部分是从源码中拿过来修改下直接用的。多看源码就能解决我们问题。

本文地址:https://blog.csdn.net/mengdasheng/article/details/110949571