games/Legend-of-the-Water-Cup/assets/scripts/MetaballMgr.ts

204 lines
7.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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