2019-11-20

// http://www.emanueleferonato.com/2011/08/05/slicing-splitting-and-cutting-objects-with-box2d-part-4-using-real-graphics/

const EPSILON = 0.1;

const POINT_SQR_EPSILON = 5;

function compare(a, b) {

    if (a.fraction > b.fraction) {

        return 1;

    } else if (a.fraction < b.fraction) {

        return -1;

    }

    return 0;

}

function equals (a, b, epsilon) {

    epsilon = epsilon === undefined ? EPSILON : epsilon;

    return Math.abs(a-b) < epsilon;

}

function equalsVec2(a,b, epsilon) {

    return equals(a.x, b.x, epsilon) && equals(a.y, b.y, epsilon);

}

//Intersection 辅助类,用于测试形状与形状是否相交.  pointLineDistance 计算点到直线的距离。

//计算点到直线的距离。如果这是一条线段并且垂足不在线段内,则会计算点到线段端点的距离。

function pointInLine (point, a, b) {

    return cc.Intersection.pointLineDistance(point, a, b, true) < 1;

}

cc.Class({

    extends: cc.Component,

    //碰撞检测

    onEnable: function () {

        this.debugDrawFlags = cc.director.getPhysicsManager().debugDrawFlags;//开启物理系统

        //指定物理系统需要绘制哪些调试信息

        //绘制关节链接信息

        //绘制形状

        cc.director.getPhysicsManager().debugDrawFlags =

            cc.PhysicsManager.DrawBits.e_jointBit |

            cc.PhysicsManager.DrawBits.e_shapeBit

            ;

          //cc.log("cutting-objects:onEnable"); 

    },

    onDisable: function () {

        cc.director.getPhysicsManager().debugDrawFlags = this.debugDrawFlags;

        //cc.log("cutting-objects:onDisable"); 

    },

    // use this for initialization

    onLoad: function () {

        var canvas = cc.find('Canvas');

        //触摸监听

        canvas.on(cc.Node.EventType.TOUCH_START, this.onTouchStart, this);

        canvas.on(cc.Node.EventType.TOUCH_END, this.onTouchEnd, this);

        canvas.on(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this);

        //线

        this.ctx = this.getComponent(cc.Graphics);

    },

    onTouchStart: function (event) {

        //cc.log("cutting-objects:onTouchStart");

        this.touching = true;

        this.r1 = this.r2 = this.results = null;

        this.touchStartPoint = this.touchPoint = cc.v2( event.touch.getLocation() );

    },

    onTouchMove: function (event) {

        //cc.log("cutting-objects:onTouchMove"); 

        this.touchPoint = cc.v2( event.touch.getLocation() );

    },

    onTouchEnd: function (event) {

        //cc.log("cutting-objects:onTouchEnd");

        this.touchPoint = cc.v2( event.touch.getLocation() );

        this.recalcResults();

        this.touching = false;

        let point = cc.v2( event.touch.getLocation() );

        if ( equals(this.touchStartPoint.sub(point).magSqr(), 0) ) return;

        // fraction 射线与碰撞体相交的点占射线长度的分数

        // recalculate fraction, make fraction from one direction

        this.r2.forEach(r => {

            r.fraction = 1 - r.fraction;

        });

        //results 是 射线碰撞 矩阵

        let results = this.results;

        let pairs = [];

        cc.log("results.length:"+results.length);

        for (let i = 0; i < results.length; i++) {

            let find = false;

            let result = results[i];

            cc.log("pairs.length:"+pairs.length);

            for (let j = 0; j < pairs.length; j++) {

                let pair = pairs[j];

                //碰撞体

                cc.log("pair[0]||result.collider||pair[0].collider:"+pair[0]+","+result.collider.name+","+pair[0].collider.name);

                if (pair[0] && result.collider === pair[0].collider) {

                    find = true;

                    // one collider may contains several fixtures, so raycast may through the inner fixture side

                    // we need remove them from the result

                    let r = pair.find((r) => {

                        return r.point.sub(result.point).magSqr() <= POINT_SQR_EPSILON;

                    });

                    if (r) {

                        pair.splice(pair.indexOf(r), 1);

                    }

                    else {

                        pair.push(result);

                    }

                    break;

                }

            }

            if (!find) {

                pairs.push([result]);

            }

        }

        for (let i = 0; i < pairs.length; i++) {

            let pair = pairs[i];

            if (pair.length < 2) {

                continue;

            }

            // sort pair with fraction

            pair = pair.sort(compare);

            let splitResults = [];

            // first calculate all results, not split collider right now

            for (let j = 0; j < (pair.length - 1); j +=2) {

                let r1 = pair[j];

                let r2 = pair[j+1];

                if (r1 && r2) {

                    this.split(r1.collider, r1.point, r2.point, splitResults);

                }

            }

            if (splitResults.length <= 0) {

                continue;

            }

            let collider = pair[0].collider;

            let maxPointsResult;

            for (let j = 0; j < splitResults.length; j++) {

                let splitResult = splitResults[j];

                for (let k = 0; k < splitResult.length; k++) {

                    if (typeof splitResult[k] === 'number') {

                        splitResult[k] = collider.points[splitResult[k]];

                    }

                }

                if (!maxPointsResult || splitResult.length > maxPointsResult.length) {

                    maxPointsResult = splitResult;

                }

            }

            if (maxPointsResult.length < 3) {

                continue;

            }

            // keep max length points to origin collider

            collider.points = maxPointsResult;

            collider.apply();

            let body = collider.body;

            for (let j = 0; j < splitResults.length; j++) {

                let splitResult = splitResults[j];

                if (splitResult.length < 3) continue;

                if (splitResult == maxPointsResult) continue;

                // create new body

                let node = new cc.Node();

                node.position = body.getWorldPosition();

                node.rotation = body.getWorldRotation();

                node.parent = cc.director.getScene();


                node.addComponent(cc.RigidBody);


                let newCollider = node.addComponent(cc.PhysicsPolygonCollider);

                newCollider.points = splitResult;

                newCollider.apply();

            }


        }

    },

    //切割:碰撞体 点1 点2

    split: function (collider, p1, p2, splitResults) {

        //collider.body 碰撞体会在初始化时查找节点上是否存在刚体,如果查找成功则赋值到这个属性上

        let body = collider.body;      //碰撞盒 的 刚体 rigidbody

        let points = collider.points;  //碰撞盒 的 Vec2[] 多边形顶点数组

        cc.log("split-1:"+collider.name+","+p1+","+p2+","+splitResults.length);

        cc.log("split-2:"+body.type+","+points);

        // The manager.rayCast() method returns points in world coordinates, so use the body.getLocalPoint() to convert them to local coordinates.

      //将 p1 p2 转换成碰撞体的坐标系

        p1 = body.getLocalPoint(p1);

        p2 = body.getLocalPoint(p2);

        //p1-p2 p2-p1

        let newSplitResult1 = [p1, p2];

        let newSplitResult2 = [p2, p1];

        //

        let index1, index2;

        //遍历碰撞体的点,

        for (let i = 0; i < points.length; i++) {

            let pp1 = points[i];//当前顶点坐标 给 p1

            let pp2 = i === points.length - 1 ? points[0] : points[i+1]; //如果i是最后一个,第一个坐标 给 p2,否则给下一个点坐标

            //点p1 到 线段pp1-pp2 的距离;如果in1没赋值 && p1到线段有距离

            if (index1 === undefined && pointInLine(p1, pp1, pp2)) { index1 = i; }

            //点p2 到 线段pp1-pp2 的距离

            else if (index2 === undefined && pointInLine(p2, pp1, pp2)) { index2 = i; }

            if (index1 !== undefined && index2 !== undefined) { break; }

        }

        // console.log(index1 + ' : ' + index2);

        if (index1 === undefined || index2 === undefined) {

            debugger

            return;

        }

        let splitResult, indiceIndex1 = index1, indiceIndex2 = index2;

        cc.log("splitResults.length:"+splitResults.length);

        if (splitResults.length > 0) {

            for (let i = 0; i < splitResults.length; i++) {

                let indices = splitResults[i];

                indiceIndex1 = indices.indexOf(index1);

                indiceIndex2 = indices.indexOf(index2);

                if (indiceIndex1 !== -1 && indiceIndex2 !== -1) {

                    splitResult = splitResults.splice(i, 1)[0];

                    break;

                }

            }

        }

        if (!splitResult) {

            splitResult = points.map((p, i) => {

                return i;

            });

        }

        for (let i = indiceIndex1 + 1; i !== (indiceIndex2+1); i++) {

            if (i >= splitResult.length) {

                i = 0;

            }

            let p = splitResult[i];

            p = typeof p === 'number' ? points[p] : p;


            if (p.sub(p1).magSqr() < POINT_SQR_EPSILON || p.sub(p2).magSqr() < POINT_SQR_EPSILON) {

                continue;

            }

            newSplitResult2.push(splitResult[i]);

        }

        for (let i = indiceIndex2 + 1; i !== indiceIndex1+1; i++) {

            if (i >= splitResult.length) {

                i = 0;

            }

            let p = splitResult[i];

            p = typeof p === 'number' ? points[p] : p;


            if (p.sub(p1).magSqr() < POINT_SQR_EPSILON || p.sub(p2).magSqr() < POINT_SQR_EPSILON) {

                continue;

            }

            newSplitResult1.push(splitResult[i]);

        }

        splitResults.push(newSplitResult1);

        splitResults.push(newSplitResult2);

    },

    recalcResults: function () {

        //cc.log("recalcResults");

        if (!this.touching) return;

        let startPoint = this.touchStartPoint;

        let point = this.touchPoint;

        this.ctx.clear();

        this.ctx.moveTo(this.touchStartPoint.x, this.touchStartPoint.y);

        this.ctx.lineTo(point.x, point.y);

        this.ctx.stroke();

        let manager = cc.director.getPhysicsManager();

        // manager.rayCast() method calls this function only when it sees that a given line gets into the body - it doesnt see when the line gets out of it.

        // I must have 2 intersection points with a body so that it can be sliced, thats why I use manager.rayCast() again, but this time from B to A - that way the point, at which BA enters the body is the point at which AB leaves it!

        //检测哪些碰撞体在给定射线的路径上,射线检测将忽略包含起始点的碰撞体

        let r1 = manager.rayCast(this.touchStartPoint, point, cc.RayCastType.All);

        let r2 = manager.rayCast(point, this.touchStartPoint, cc.RayCastType.All);

        //concat拼接两个矩阵,将结果保存到 out 矩阵。

        //这个函数不创建任何内存,你需要先创建 AffineTransform 对象用来存储结果,并作为第一个参数传入函数。 out = t1 * t2

        let results = r1.concat(r2);

        //circle 绘制圆形路径

        for (let i = 0; i < results.length; i++) {

            let p = results[i].point;

            this.ctx.circle(p.x, p.y, 5);

        } 

        this.ctx.fill();

        this.r1 = r1;

        this.r2 = r2;

        this.results = results;

    },

    // called every frame, uncomment this function to activate update callback

    update: function (dt) {

        // body maybe moving, need calc raycast results in update

        this.recalcResults();

    },

});

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容