【问题标题】:Prevent collision or intersection of canvas objects防止画布对象发生碰撞或相交
【发布时间】:2014-02-21 12:28:58
【问题描述】:

我正在画布上绘制 n 个矩形。矩形是可拖动和可缩放的。 我想防止它们重叠或相交。最好的情况是,如果它们只是相互对齐。

我想办法检查十字路口。在我的示例中,我将触摸对象的不透明度设置为 0.1。

巧合的是,在我尝试解决此问题时,我的对象在接触另一个对象时无法释放。见http://jsfiddle.net/gcollect/jZw7P/ 这是因为第 91 行没有执行警报。 alert(math.abs(distx));

实际上它是一种解决方案,但绝对不是一个优雅的解决方案。

有什么想法吗?

【问题讨论】:

  • 嗨。您的警报未执行,因为数学函数区分大小写。请替换 alert(math.abs(distx));通过警报(Math.abs(distx)); 'M' 大写...

标签: canvas html5-canvas collision-detection intersection fabricjs


【解决方案1】:

这是基于 gco 的回答,更新后可与 FabricJS 1.5.0 一起使用,具有以下改进:

  • 形状不重叠。
  • 捕捉反应更快。
  • 形状包含在画布中。

JS 小提琴:https://jsfiddle.net/aphillips8/31qbr0vn/1/

var canvas = new fabric.Canvas('canvas'),
canvasWidth = document.getElementById('canvas').width,
canvasHeight = document.getElementById('canvas').height,
counter = 0,
rectLeft = 0,
snap = 20; //Pixels to snap

canvas.selection = false;
plusrect();
plusrect();
plusrect();

function plusrect(top, left, width, height, fill) {
    var rect = new fabric.Rect({
        top: 300,
        name: 'rectangle ' + counter,
        left: 0 + rectLeft,
        width: 100,
        height: 100,
        fill: 'rgba(' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ', 0.75)',
        lockRotation: true,
        originX: 'left',
        originY: 'top',
        cornerSize: 15,
        hasRotatingPoint: false,
        perPixelTargetFind: true,
        minScaleLimit: 1,
        maxWidth: canvasWidth,
        maxHeight: canvasHeight
    });

    rect.custom = {};
    rect.custom.counter = counter;

    canvas.add(rect);
    counter++;
    rectLeft += 200;
}

function findNewPos(distX, distY, target, obj) {
    // See whether to focus on X or Y axis
    if(Math.abs(distX) > Math.abs(distY)) {
        if (distX > 0) {
            target.setLeft(obj.getLeft() - target.getWidth());
        } else {
            target.setLeft(obj.getLeft() + obj.getWidth());
        }
    } else {
        if (distY > 0) {
            target.setTop(obj.getTop() - target.getHeight());
        } else {
            target.setTop(obj.getTop() + obj.getHeight());
        }
    }
}

canvas.on('object:moving', function (options) {
    // Sets corner position coordinates based on current angle, width and height
    options.target.setCoords();

    // Don't allow objects off the canvas
    if(options.target.getLeft() < snap) {
        options.target.setLeft(0);
    }

    if(options.target.getTop() < snap) {
        options.target.setTop(0);
    }

    if((options.target.getWidth() + options.target.getLeft()) > (canvasWidth - snap)) {
        options.target.setLeft(canvasWidth - options.target.getWidth());
    }

    if((options.target.getHeight() + options.target.getTop()) > (canvasHeight - snap)) {
        options.target.setTop(canvasHeight - options.target.getHeight());
    }

    // Loop through objects
    canvas.forEachObject(function (obj) {
        if (obj === options.target) return;

        // If objects intersect
        if (options.target.isContainedWithinObject(obj) || options.target.intersectsWithObject(obj) || obj.isContainedWithinObject(options.target)) {

            var distX = ((obj.getLeft() + obj.getWidth()) / 2) - ((options.target.getLeft() + options.target.getWidth()) / 2);
            var distY = ((obj.getTop() + obj.getHeight()) / 2) - ((options.target.getTop() + options.target.getHeight()) / 2);

            // Set new position
            findNewPos(distX, distY, options.target, obj);
        }

        // Snap objects to each other horizontally

        // If bottom points are on same Y axis
        if(Math.abs((options.target.getTop() + options.target.getHeight()) - (obj.getTop() + obj.getHeight())) < snap) {
            // Snap target BL to object BR
            if(Math.abs(options.target.getLeft() - (obj.getLeft() + obj.getWidth())) < snap) {
                options.target.setLeft(obj.getLeft() + obj.getWidth());
                options.target.setTop(obj.getTop() + obj.getHeight() - options.target.getHeight());
            }

            // Snap target BR to object BL
            if(Math.abs((options.target.getLeft() + options.target.getWidth()) - obj.getLeft()) < snap) {
                options.target.setLeft(obj.getLeft() - options.target.getWidth());
                options.target.setTop(obj.getTop() + obj.getHeight() - options.target.getHeight());
            }
        }

        // If top points are on same Y axis
        if(Math.abs(options.target.getTop() - obj.getTop()) < snap) {
            // Snap target TL to object TR
            if(Math.abs(options.target.getLeft() - (obj.getLeft() + obj.getWidth())) < snap) {
                options.target.setLeft(obj.getLeft() + obj.getWidth());
                options.target.setTop(obj.getTop());
            }

            // Snap target TR to object TL
            if(Math.abs((options.target.getLeft() + options.target.getWidth()) - obj.getLeft()) < snap) {
                options.target.setLeft(obj.getLeft() - options.target.getWidth());
                options.target.setTop(obj.getTop());
            }
        }

        // Snap objects to each other vertically

        // If right points are on same X axis
        if(Math.abs((options.target.getLeft() + options.target.getWidth()) - (obj.getLeft() + obj.getWidth())) < snap) {
            // Snap target TR to object BR
            if(Math.abs(options.target.getTop() - (obj.getTop() + obj.getHeight())) < snap) {
                options.target.setLeft(obj.getLeft() + obj.getWidth() - options.target.getWidth());
                options.target.setTop(obj.getTop() + obj.getHeight());
            }

            // Snap target BR to object TR
            if(Math.abs((options.target.getTop() + options.target.getHeight()) - obj.getTop()) < snap) {
                options.target.setLeft(obj.getLeft() + obj.getWidth() - options.target.getWidth());
                options.target.setTop(obj.getTop() - options.target.getHeight());
            }
        }

        // If left points are on same X axis
        if(Math.abs(options.target.getLeft() - obj.getLeft()) < snap) {
            // Snap target TL to object BL
            if(Math.abs(options.target.getTop() - (obj.getTop() + obj.getHeight())) < snap) {
                options.target.setLeft(obj.getLeft());
                options.target.setTop(obj.getTop() + obj.getHeight());
            }

            // Snap target BL to object TL
            if(Math.abs((options.target.getTop() + options.target.getHeight()) - obj.getTop()) < snap) {
                options.target.setLeft(obj.getLeft());
                options.target.setTop(obj.getTop() - options.target.getHeight());
            }
        }
    });

    options.target.setCoords();

    // If objects still overlap

    var outerAreaLeft = null,
    outerAreaTop = null,
    outerAreaRight = null,
    outerAreaBottom = null;

    canvas.forEachObject(function (obj) {
        if (obj === options.target) return;

        if (options.target.isContainedWithinObject(obj) || options.target.intersectsWithObject(obj) || obj.isContainedWithinObject(options.target)) {

            var intersectLeft = null,
            intersectTop = null,
            intersectWidth = null,
            intersectHeight = null,
            intersectSize = null,
            targetLeft = options.target.getLeft(),
            targetRight = targetLeft + options.target.getWidth(),
            targetTop = options.target.getTop(),
            targetBottom = targetTop + options.target.getHeight(),
            objectLeft = obj.getLeft(),
            objectRight = objectLeft + obj.getWidth(),
            objectTop = obj.getTop(),
            objectBottom = objectTop + obj.getHeight();

            // Find intersect information for X axis
            if(targetLeft >= objectLeft && targetLeft <= objectRight) {
                intersectLeft = targetLeft;
                intersectWidth = obj.getWidth() - (intersectLeft - objectLeft);

            } else if(objectLeft >= targetLeft && objectLeft <= targetRight) {
                intersectLeft = objectLeft;
                intersectWidth = options.target.getWidth() - (intersectLeft - targetLeft);
            }

            // Find intersect information for Y axis
            if(targetTop >= objectTop && targetTop <= objectBottom) {
                intersectTop = targetTop;
                intersectHeight = obj.getHeight() - (intersectTop - objectTop);

            } else if(objectTop >= targetTop && objectTop <= targetBottom) {
                intersectTop = objectTop;
                intersectHeight = options.target.getHeight() - (intersectTop - targetTop);
            }

            // Find intersect size (this will be 0 if objects are touching but not overlapping)
            if(intersectWidth > 0 && intersectHeight > 0) {
                intersectSize = intersectWidth * intersectHeight;
            }

            // Set outer snapping area
            if(obj.getLeft() < outerAreaLeft || outerAreaLeft == null) {
                outerAreaLeft = obj.getLeft();
            }

            if(obj.getTop() < outerAreaTop || outerAreaTop == null) {
                outerAreaTop = obj.getTop();
            }

            if((obj.getLeft() + obj.getWidth()) > outerAreaRight || outerAreaRight == null) {
                outerAreaRight = obj.getLeft() + obj.getWidth();
            }

            if((obj.getTop() + obj.getHeight()) > outerAreaBottom || outerAreaBottom == null) {
                outerAreaBottom = obj.getTop() + obj.getHeight();
            }

            // If objects are intersecting, reposition outside all shapes which touch
            if(intersectSize) {
                var distX = (outerAreaRight / 2) - ((options.target.getLeft() + options.target.getWidth()) / 2);
                var distY = (outerAreaBottom / 2) - ((options.target.getTop() + options.target.getHeight()) / 2);

                // Set new position
                findNewPos(distX, distY, options.target, obj);
            }
        }
    });
});

【讨论】:

  • 感谢您的工作,但您能告诉我如何处理圈子吗?
  • 不抱歉,这听起来很棘手。您可能必须使用 pi 进行一些计算。
【解决方案2】:

这些解决方案非常好,但它们都不适用于旋转对象。 我想出了这个。它可以防止对象相交..甚至旋转。

这个小提琴是我编的。看看吧。

[Fiddle](http://jsfiddle.net/m0jjc23v/9)

解释:

解决方法是您必须在移动对象时保存最后一个非交叉点的顶部和左侧位置。当对象与另一个对象相交或包含时,只需使用先前保存的顶部和左侧位置并重新定位对象。有很多可能的改进。随意使用此代码并进行改进。

代码:

//handle moving object
this.canvas.on('object:moving', function(event) {
        var obj = event.target;
        intersectingCheck(obj);
});

function intersectingCheck(activeObject) {
    activeObject.setCoords();
    if(typeof activeObject.refreshLast != 'boolean') {
        activeObject.refreshLast = true
    };

    //loop canvas objects
    activeObject.canvas.forEachObject(function (targ) {
        if (targ === activeObject) return; //bypass self

        //check intersections with every object in canvas
        if (activeObject.intersectsWithObject(targ) 
            || activeObject.isContainedWithinObject(targ) 
            || targ.isContainedWithinObject(activeObject)) {
                //objects are intersecting - deny saving last non-intersection position and break loop
                if(typeof activeObject.lastLeft == 'number') {
                    activeObject.left = activeObject.lastLeft;
                    activeObject.top = activeObject.lastTop;
                    activeObject.refreshLast = false;
                    return;
                }
       }
       else {
           activeObject.refreshLast = true;
       }
   });

   if(activeObject.refreshLast) {
       //save last non-intersecting position if possible
       activeObject.lastLeft = activeObject.left
       activeObject.lastTop = activeObject.top;
   }
}

【讨论】:

  • 不要添加链接,而是你的代码和关于它的解释。
  • 无论如何,它正在工作。但请解释你的代码。对我来说似乎有点滞后。
【解决方案3】:

对于那些仍然对解决方案感兴趣的人:我在这里解决了:https://stackoverflow.com/a/22649022/3207478 参见 jsfiddle:http://jsfiddle.net/gcollect/FD53A/

使用

.oCoords.tl .tr .bl. and .br solved it.

【讨论】:

  • 您可以使用折线、圆形甚至直线,而不是矩形吗?
【解决方案4】:

我想出了如何防止 x 轴上的碰撞。通过添加这些行:

canvas.forEachObject(function (obj) {
                if (obj === options.target) return;
                if (options.target.isContainedWithinObject(obj)||options.target.intersectsWithObject(obj)||obj.isContainedWithinObject(options.target)) {
                    var distx = ((obj.left + obj.width)/2) - ((options.target.left + options.target.width)/2);
                    var disty = ((obj.top + obj.height)/2) - ((options.target.top + options.target.height)/2);                  

                    if (distx > 0){
                        options.target.left = obj.left - options.target.width;
                    } else {
                        options.target.left = obj.left + obj.width;
                    }

见JSFiddle。 实际上这很酷,对象在 x 轴上相互对齐。麻烦制造者现在是 y 轴。从顶部或下方的方法会将对象移动到左侧或右侧边缘......将解决这个问题。

【讨论】:

    【解决方案5】:

    我一直在考虑一个类似的问题,我 + 来自@kangax 的最后一个小提琴。如果一个人有一个路径碰撞检测算法,比如在此处找到的多边形相交代码:http://www.kevlindev.com/geometry/2D/intersections/index.htm,甚至可以使用这种类型的机制对路径本身进行碰撞检测。

    但我不喜欢这种“粘性”解决方案的一点是,对我来说,对象实际上应该为我的应用程序沿着表面滑动,而不是强迫用户“解开”对象。对于这种效果,我意识到 2D 物理引擎可能会在启用此类功能方面做得很好,http://brm.io/matter-js/ 的一些示例用法演示了解决问题,定位和旋转应该能够映射到 fabric.js。但是,如果需要固定轮换,看起来成功的概率会降低。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-07
      • 2013-04-19
      • 2015-11-11
      • 2018-06-23
      相关资源
      最近更新 更多