204 lines
7.3 KiB
TypeScript
204 lines
7.3 KiB
TypeScript
import Model from "./Model";
|
||
import CollisionGrid from "./CollisionGrid";
|
||
|
||
const { ccclass, property } = cc._decorator;
|
||
type BezierObject = {
|
||
pos1: cc.Vec2,
|
||
pos2: cc.Vec2,
|
||
pos3: cc.Vec2,
|
||
pos4: cc.Vec2,
|
||
con1: cc.Vec2,
|
||
con2: cc.Vec2,
|
||
con3: cc.Vec2,
|
||
con4: cc.Vec2,
|
||
}
|
||
@ccclass
|
||
export default class MetaballMgr extends cc.Component {
|
||
@property(cc.Node)
|
||
targerParent: cc.Node = null;
|
||
|
||
_grid: CollisionGrid;
|
||
private _graphics: cc.Graphics;
|
||
private _waters: Array<cc.Node>;
|
||
|
||
onLoad() {
|
||
var size = cc.view.getVisibleSize();
|
||
this._grid = new CollisionGrid(size.width, size.height, 30);
|
||
}
|
||
|
||
start() {
|
||
this._graphics = this.getComponent(cc.Graphics);
|
||
this.draw();
|
||
}
|
||
set waters(value) {
|
||
this._waters = value;
|
||
}
|
||
get waters() {
|
||
return this._waters;
|
||
}
|
||
|
||
draw() {
|
||
if (Model.setting.showWaterEff == false) return
|
||
this.checkCollision2()
|
||
}
|
||
|
||
checkCollision() {
|
||
this._grid.assign(this._waters)
|
||
var numChecks: number = this._grid.checks.length;
|
||
this._graphics.clear();
|
||
this._graphics.fillColor = cc.color(14, 137, 240);
|
||
|
||
for (var i = 0; i < numChecks; i += 2) {
|
||
var ball = this._grid.checks[i]
|
||
|
||
let bassPos1 = ball.position;
|
||
this._graphics.circle(bassPos1.x, bassPos1.y, 15);
|
||
this._graphics.fill();
|
||
var anotherBall = this._grid.checks[i + 1]
|
||
let bassPos2 = anotherBall.position;
|
||
let bezierObj: BezierObject = null;
|
||
if (bassPos1.y < bassPos2.y) {
|
||
bezierObj = this.metaball(15, 15, bassPos1, bassPos2);
|
||
} else {
|
||
bezierObj = this.metaball(15, 15, bassPos2, bassPos1);
|
||
}
|
||
|
||
if (bezierObj) {
|
||
this._graphics.moveTo(bezierObj.pos1.x, bezierObj.pos1.y);
|
||
this._graphics.bezierCurveTo(bezierObj.con1.x, bezierObj.con1.y, bezierObj.con3.x, bezierObj.con3.y, bezierObj.pos3.x, bezierObj.pos3.y);
|
||
this._graphics.lineTo(bezierObj.pos4.x, bezierObj.pos4.y);
|
||
this._graphics.bezierCurveTo(bezierObj.con4.x, bezierObj.con4.y, bezierObj.con2.x, bezierObj.con2.y, bezierObj.pos2.x, bezierObj.pos2.y);
|
||
this._graphics.lineTo(bezierObj.pos1.x, bezierObj.pos1.y);
|
||
this._graphics.fill();
|
||
}
|
||
}
|
||
}
|
||
checkCollision2() {
|
||
this._graphics.clear();
|
||
this._graphics.fillColor = cc.color(14, 137, 240);
|
||
|
||
for (let i = 0; i < this._waters.length; i++) {
|
||
let ball = this._waters[i];
|
||
let bassPos1 = ball.position;
|
||
|
||
let radius1 = 15;
|
||
|
||
this._graphics.circle(bassPos1.x, bassPos1.y, radius1);
|
||
this._graphics.fill();
|
||
for (let j = i; j < this._waters.length; j++) {
|
||
if (i === j) {
|
||
continue;
|
||
}
|
||
let anotherBall = this._waters[j];
|
||
let bassPos2 = anotherBall.position;
|
||
let radius2 = 15;
|
||
|
||
let bezierObj: BezierObject = null;
|
||
if (bassPos1.y < bassPos2.y) {
|
||
bezierObj = this.metaball(radius1, radius2, bassPos1, bassPos2);
|
||
} else {
|
||
bezierObj = this.metaball(radius2, radius1, bassPos2, bassPos1);
|
||
}
|
||
|
||
if (bezierObj) {
|
||
this._graphics.moveTo(bezierObj.pos1.x, bezierObj.pos1.y);
|
||
this._graphics.bezierCurveTo(bezierObj.con1.x, bezierObj.con1.y, bezierObj.con3.x, bezierObj.con3.y, bezierObj.pos3.x, bezierObj.pos3.y);
|
||
this._graphics.lineTo(bezierObj.pos4.x, bezierObj.pos4.y);
|
||
this._graphics.bezierCurveTo(bezierObj.con4.x, bezierObj.con4.y, bezierObj.con2.x, bezierObj.con2.y, bezierObj.pos2.x, bezierObj.pos2.y);
|
||
this._graphics.lineTo(bezierObj.pos1.x, bezierObj.pos1.y);
|
||
this._graphics.fill();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
metaball(radius1, radius2, center1, center2, handleSize = 2.4): BezierObject {
|
||
const HALF_PI = Math.PI / 2;
|
||
const d = center1.sub(center2).mag()
|
||
const maxDist = radius1 + radius2 * 1.9;
|
||
|
||
const v = (maxDist - d) / maxDist * 2.2 + 0.4
|
||
|
||
let u1, u2;
|
||
// No blob if a radius is 0
|
||
// or if distance between the circles is larger than max-dist
|
||
// or if circle2 is completely inside circle1
|
||
if (radius1 === 0 || radius2 === 0 || d > maxDist || d <= Math.abs(radius1 - radius2)) {
|
||
return null;
|
||
}
|
||
|
||
// Calculate u1 and u2 if the circles are overlapping
|
||
if (d < radius1 + radius2) {
|
||
u1 = Math.acos((radius1 * radius1 + d * d - radius2 * radius2) / (2 * radius1 * d));
|
||
u2 = Math.acos((radius2 * radius2 + d * d - radius1 * radius1) / (2 * radius2 * d));
|
||
} else {
|
||
// Else set u1 and u2 to zero
|
||
u1 = 0; u2 = 0;
|
||
}
|
||
|
||
// Calculate the max spread
|
||
let angleBetweenCenters = center1.sub(center2).angle(cc.v2(-1, 0));
|
||
if (center1.y > center2.y) {
|
||
angleBetweenCenters = -angleBetweenCenters;
|
||
}
|
||
const maxSpread = Math.acos((radius1 - radius2) / d);
|
||
|
||
// Angles for the points
|
||
const angle1 = angleBetweenCenters + u1 + (maxSpread - u1) * v;
|
||
const angle2 = angleBetweenCenters - u1 - (maxSpread - u1) * v;
|
||
const angle3 = angleBetweenCenters + Math.PI - u2 - (Math.PI - u2 - maxSpread) * v;
|
||
const angle4 = angleBetweenCenters - Math.PI + u2 + (Math.PI - u2 - maxSpread) * v;
|
||
// Point locations
|
||
const p1 = this.getVector(center1, angle1, radius1);
|
||
const p2 = this.getVector(center1, angle2, radius1);
|
||
const p3 = this.getVector(center2, angle3, radius2);
|
||
const p4 = this.getVector(center2, angle4, radius2);
|
||
|
||
// Define handle length by the distance between both ends of the curve
|
||
const totalRadius = radius1 + radius2;
|
||
const d2Base = Math.min(v * handleSize, p1.sub(p3).mag() / totalRadius);
|
||
|
||
// Take into account when circles are overlapping
|
||
const d2 = d2Base * Math.min(1, d * 2 / (radius1 + radius2));
|
||
|
||
// Length of the handles
|
||
const r1 = radius1 * d2;
|
||
const r2 = radius2 * d2;
|
||
|
||
// Handle locations
|
||
const h1 = this.getVector(p1, angle1 - HALF_PI, r1);
|
||
const h2 = this.getVector(p2, angle2 + HALF_PI, r1);
|
||
const h3 = this.getVector(p3, angle3 + HALF_PI, r2);
|
||
const h4 = this.getVector(p4, angle4 - HALF_PI, r2);
|
||
|
||
// Generate the connector path
|
||
|
||
return {
|
||
pos1: p1,
|
||
pos2: p2,
|
||
pos3: p3,
|
||
pos4: p4,
|
||
con1: h1,
|
||
con2: h2,
|
||
con3: h3,
|
||
con4: h4,
|
||
};
|
||
}
|
||
metaballToPath(p1, p2, p3, p4, h1, h2, h3, h4, escaped, r) {
|
||
//C: 曲线 A:弧线 C 曲线
|
||
// return ['M', p1, 'C', h1, h3, p3, 'A', r, r, 0, escaped ? 1 : 0, 0, p4, 'C', h4, h3, p4].join(' ');
|
||
return [p1, p2, p3, p4, h1, h2, h3, h4, escaped ? 1 : 0, r];
|
||
}
|
||
dist(p1, p2) {
|
||
return ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5;
|
||
}
|
||
|
||
getVector(vec: cc.Vec2, angle: number, radius: number) {
|
||
let offX = radius * Math.cos(angle);
|
||
let offY = radius * Math.sin(angle);
|
||
|
||
return cc.v2(vec.x + offX, vec.y + offY);
|
||
}
|
||
}
|