关于fabricjs object与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).renderAll();

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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值