# 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

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();

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();

},

});