249 lines
8.4 KiB
TypeScript
249 lines
8.4 KiB
TypeScript
import * as cc from 'cc';
|
|
|
|
/** 贝塞尔曲线 */
|
|
class BezierCurve {
|
|
constructor(pointAs_?: cc.Vec3[]) {
|
|
this.pointV3S = pointAs_;
|
|
this._resetData();
|
|
}
|
|
/* --------------- private --------------- */
|
|
private _distanceNS: number[] = [];
|
|
private _funcFSS: Function[][] = [];
|
|
/** 控制点 */
|
|
private _pointV3S!: cc.Vec3[];
|
|
/* --------------- public --------------- */
|
|
/** 控制点 */
|
|
get pointV3S() {
|
|
return this._pointV3S;
|
|
}
|
|
set pointV3S(valueV3S) {
|
|
this._pointV3S = valueV3S;
|
|
this._resetData();
|
|
}
|
|
/* ------------------------------- 功能函数 ------------------------------- */
|
|
/** 重置数据 */
|
|
private _resetData(): void {
|
|
if (this._pointV3S.length < 2) {
|
|
return;
|
|
}
|
|
/** 首尾相等 */
|
|
let equalsB = this._pointV3S[0].strictEquals(this._pointV3S[this._pointV3S.length - 1]);
|
|
/** 总距离 */
|
|
let sumDistanceN = 0;
|
|
/** 临时变量 */
|
|
let tempV3: cc.Vec3;
|
|
let temp2V3: cc.Vec3;
|
|
let temp3V3: cc.Vec3;
|
|
let temp4V3: cc.Vec3;
|
|
for (let kN = 0, lenN = this._pointV3S.length - 1; kN < lenN; kN++) {
|
|
if (kN === 0) {
|
|
tempV3 = equalsB ? this._pointV3S[this._pointV3S.length - 2] : this._pointV3S[0];
|
|
} else {
|
|
tempV3 = this._pointV3S[kN - 1];
|
|
}
|
|
temp2V3 = this._pointV3S[kN];
|
|
temp3V3 = this._pointV3S[kN + 1];
|
|
|
|
if (kN + 1 === this._pointV3S.length - 1) {
|
|
temp4V3 = equalsB ? this._pointV3S[1] : this._pointV3S[this._pointV3S.length - 1];
|
|
} else {
|
|
temp4V3 = this._pointV3S[kN + 2];
|
|
}
|
|
|
|
this._funcFSS[kN] = [];
|
|
[this._funcFSS[kN][0], this._funcFSS[kN][1]] = this._curve(tempV3, temp2V3, temp3V3, temp4V3);
|
|
|
|
sumDistanceN += this._gaussLegendre(this._funcFSS[kN][1] as any, 0, 1);
|
|
this._distanceNS[kN] = sumDistanceN;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 递归阶乘
|
|
* @param valueN_
|
|
* @returns
|
|
*/
|
|
private _factorial(valueN_: number): number {
|
|
let resultN = 1;
|
|
for (let kN = 2; kN <= valueN_; ++kN) {
|
|
resultN *= kN;
|
|
}
|
|
return resultN;
|
|
}
|
|
|
|
/**
|
|
* 高斯—勒让德积分公式可以用较少节点数得到高精度的计算结果
|
|
* @param valueF_ 曲线长度变化率,用于匀速曲线运动
|
|
* @param valueN_ 左区间
|
|
* @param value2N_ 右区间
|
|
* @returns
|
|
*/
|
|
private _gaussLegendre(valueF_: (vN: number) => number, valueN_: number, value2N_: number): number {
|
|
// 3次系数
|
|
let gauFactor = {
|
|
0.7745966692: 0.555555556,
|
|
0: 0.8888888889
|
|
};
|
|
// 5次系数
|
|
// let GauFactor = {0.9061798459:0.2369268851,0.5384693101:0.4786286705,0:0.5688888889}
|
|
// 积分
|
|
let gauSumN = 0;
|
|
let keyN: number;
|
|
for (let key in gauFactor) {
|
|
if (Object.prototype.hasOwnProperty.call(gauFactor, key)) {
|
|
keyN = Number(key);
|
|
let v = gauFactor[key];
|
|
let t = ((value2N_ - valueN_) * keyN + valueN_ + value2N_) / 2;
|
|
let der = valueF_(t);
|
|
gauSumN = gauSumN + der * v;
|
|
if (keyN > 0) {
|
|
t = ((value2N_ - valueN_) * -key + valueN_ + value2N_) / 2;
|
|
der = valueF_(t);
|
|
gauSumN = gauSumN + der * v;
|
|
}
|
|
}
|
|
}
|
|
return (gauSumN * (value2N_ - valueN_)) / 2;
|
|
}
|
|
|
|
private _curve(pointV3_: cc.Vec3, point2V3_: cc.Vec3, point3V3_: cc.Vec3, point4V3_: cc.Vec3) {
|
|
// 基本样条线插值算法
|
|
// 弹性
|
|
let sN = 0.5;
|
|
// 计算三次样条线函数系数
|
|
let bV3 = pointV3_
|
|
.clone()
|
|
.multiplyScalar(-sN)
|
|
.add(point2V3_.clone().multiplyScalar(2 - sN))
|
|
.add(point3V3_.clone().multiplyScalar(sN - 2))
|
|
.add(point4V3_.clone().multiplyScalar(sN));
|
|
let b2V3 = pointV3_
|
|
.clone()
|
|
.multiplyScalar(2 * sN)
|
|
.add(point2V3_.clone().multiplyScalar(sN - 3))
|
|
.add(point3V3_.clone().multiplyScalar(3 - 2 * sN))
|
|
.add(point4V3_.clone().multiplyScalar(-sN));
|
|
let b3V3 = pointV3_.clone().multiplyScalar(-sN).add(point3V3_.clone().multiplyScalar(sN));
|
|
let b4V3 = point2V3_;
|
|
|
|
// 函数曲线
|
|
function fx(xN: number) {
|
|
return bV3
|
|
.clone()
|
|
.multiplyScalar(Math.pow(xN, 3))
|
|
.add(b2V3.clone().multiplyScalar(Math.pow(xN, 2)))
|
|
.add(b3V3.clone().multiplyScalar(xN))
|
|
.add(b4V3.clone());
|
|
}
|
|
// 曲线长度变化率,用于匀速曲线运动
|
|
function ds(xN: number) {
|
|
let derV3 = bV3
|
|
.clone()
|
|
.multiplyScalar(3 * Math.pow(xN, 2))
|
|
.add(b2V3.clone().multiplyScalar(2 * xN))
|
|
.add(b3V3.clone());
|
|
return Math.sqrt(Math.pow(derV3.x, 2) + Math.pow(derV3.y, 2) + Math.pow(derV3.z, 2));
|
|
}
|
|
return [fx, ds];
|
|
}
|
|
|
|
/**
|
|
* 获取曲线上某点的位置
|
|
* @param posN_ min: 0, max: 1
|
|
*/
|
|
point(posN_: number): cc.Vec3 | null {
|
|
let posN = posN_;
|
|
if (this._pointV3S.length < 2) {
|
|
return null;
|
|
}
|
|
|
|
if (posN < 0 || posN > 1) {
|
|
posN = posN < 0 ? 0 : 1;
|
|
}
|
|
|
|
// 首个和最后点直接返回
|
|
if (posN === 0) {
|
|
return this._pointV3S[0];
|
|
} else if (posN === 1) {
|
|
return this._pointV3S[this._pointV3S.length - 1];
|
|
}
|
|
|
|
let resultV3 = cc.v3();
|
|
let indexN = this._pointV3S.length - 1;
|
|
this._pointV3S.forEach((v, kS) => {
|
|
if (!kS) {
|
|
resultV3.x += v.x * Math.pow(1 - posN, indexN - kS) * Math.pow(posN, kS);
|
|
resultV3.y += v.y * Math.pow(1 - posN, indexN - kS) * Math.pow(posN, kS);
|
|
resultV3.z += v.z * Math.pow(1 - posN, indexN - kS) * Math.pow(posN, kS);
|
|
} else {
|
|
resultV3.x +=
|
|
(this._factorial(indexN) / this._factorial(kS) / this._factorial(indexN - kS)) *
|
|
v.x *
|
|
Math.pow(1 - posN, indexN - kS) *
|
|
Math.pow(posN, kS);
|
|
resultV3.y +=
|
|
(this._factorial(indexN) / this._factorial(kS) / this._factorial(indexN - kS)) *
|
|
v.y *
|
|
Math.pow(1 - posN, indexN - kS) *
|
|
Math.pow(posN, kS);
|
|
resultV3.z +=
|
|
(this._factorial(indexN) / this._factorial(kS) / this._factorial(indexN - kS)) *
|
|
v.z *
|
|
Math.pow(1 - posN, indexN - kS) *
|
|
Math.pow(posN, kS);
|
|
}
|
|
});
|
|
return resultV3;
|
|
}
|
|
|
|
/** 匀速点 */
|
|
uniformPoint(posN_: number): cc.Vec3 | null {
|
|
let posN = posN_;
|
|
if (this._pointV3S.length < 2) {
|
|
return null;
|
|
}
|
|
|
|
if (posN < 0 || posN > 1) {
|
|
posN = posN < 0 ? 0 : 1;
|
|
}
|
|
|
|
// 首个和最后点直接返回
|
|
if (posN === 0) {
|
|
return this._pointV3S[0];
|
|
} else if (posN === 1) {
|
|
return this._pointV3S[this._pointV3S.length - 1];
|
|
}
|
|
|
|
// 平均距离
|
|
let averDistN = posN * this._distanceNS[this._pointV3S.length - 2];
|
|
let indexN = 0;
|
|
let beyondN = 0;
|
|
let percentN = 0;
|
|
for (let kN = 0; kN < this._pointV3S.length - 1; kN++) {
|
|
if (averDistN < this._distanceNS[kN]) {
|
|
let preDis = kN === 0 ? 0 : this._distanceNS[kN - 1];
|
|
indexN = kN;
|
|
beyondN = averDistN - preDis;
|
|
percentN = beyondN / (this._distanceNS[kN] - preDis);
|
|
break;
|
|
}
|
|
}
|
|
// 牛顿切线法求根
|
|
let aN = percentN;
|
|
let bN: number;
|
|
// 最多迭代6次
|
|
for (let i = 0; i < 6; i++) {
|
|
let actualLen = this._gaussLegendre(this._funcFSS[indexN][1] as any, 0, aN);
|
|
bN = aN - (actualLen - beyondN) / this._funcFSS[indexN][1](aN);
|
|
if (Math.abs(aN - bN) < 0.0001) {
|
|
break;
|
|
}
|
|
aN = bN;
|
|
}
|
|
percentN = bN;
|
|
return this._funcFSS[indexN][0](percentN);
|
|
}
|
|
}
|
|
|
|
export default BezierCurve;
|