贝塞尔曲线的定义我就不详细说了,网上很多资料都有介绍可以用递归方法求曲线点也可以用迭代法求解,这里使用迭代法求点,总的来说就是第一个控制点到第二个控制点的细分点和第二个到第三个控制点连成的线,其他的更高阶的以此类推,知道迭代到点的集合的长度为1,
这里采用左键单击画布就会生成一个控制点,根据控制点进行步进生成点进而绘制出来:
const {ccclass, property,requireComponent} = cc._decorator;
export interface Vector2 {
x: number,
y: number
}
@ccclass
@requireComponent(cc.Graphics)
export default class BezierTest extends cc.Component {
@property(cc.Prefab)
dotPrefab: cc.Prefab = null;
@property(cc.Prefab)
settingPrefab: cc.Prefab = null;
@property(cc.Graphics)
pen: cc.Graphics = null;
@property(cc.Node)
box: cc.Node = null;
@property({
type: cc.Integer,
tooltip: '步进长度,帧率,控制t'
})
stepFrame: number = 60;
/** 控制点数组 */
private _controllPoints: Vector2[] = [];
/** 生成的目标点信息 即贝塞尔曲线的信息 */
private _targetPoints: Vector2[] = [];
private _controllNodes: cc.Node[] = [];
onLoad () {
}
protected onEnable(): void {
this.node.on(cc.Node.EventType.TOUCH_END,this.tapEnd,this);
cc.director.on('repaint',this.repaint,this);
cc.director.on('exportData',this.exportData,this);
cc.director.on('clear',this.clearAllData,this);
}
protected onDisable(): void {
this.node.off(cc.Node.EventType.TOUCH_END,this.tapEnd,this);
cc.director.off('repaint',this.repaint,this);
cc.director.off('exportData',this.exportData,this);
cc.director.off('clear',this.clearAllData,this);
}
exportData(): void {
console.log('export');
if(this._targetPoints.length > 0) {
this._targetPoints = this._targetPoints.map(item => ({
x: item.x,
y: item.y,
rotate: 0
}))
let len = this._targetPoints.length;
for(let i = 0; i < len - 1; i++) {
let cur = this._targetPoints[i];
let next = this._targetPoints[i + 1];
let offsetY = next.y - cur.y;
let offsetX = next.x - cur.x;
let tanRes = offsetY / offsetX;
let r = Math.atan(tanRes);
let angle = 180 * r / Math.PI;
// 设置经过该点的角度值
cur['rotate'] = angle;
}
this._targetPoints[len - 1]['rotate'] = this._targetPoints[len - 2]['rotate'];
console.log('targetPoint is ',this._targetPoints);
const blob = new Blob([JSON.stringify(this._targetPoints)],{type: 'application/json'});
const a = document.createElement('a');
a.href = window.webkitURL.createObjectURL(blob);
a.download = '';
a.click();
}
}
clearAllData(): void {
this.pen.clear();
this._controllNodes.length = 0;
this._controllPoints.length = 0;
}
/** 重新绘制 */
repaint(): void {
this._controllPoints = [];
this._controllNodes.forEach(item => {
this._controllPoints.push(item.position);
});
this.checkControllPoints();
}
tapEnd(event: cc.Event.EventTouch) {
// 生成点
const localPos = this.node.convertToNodeSpaceAR(event.getLocation());
if(event.target == this.node) {
const dotNode = cc.instantiate(this.dotPrefab);
this.node.addChild(dotNode);
dotNode.setPosition(localPos);
this._controllPoints.push(localPos);
this._controllNodes.push(dotNode);
}
this.checkControllPoints();
}
checkControllPoints() {
/** 清空路径点 */
this._targetPoints = [];
if(this._controllPoints.length > 1) {
// 绘制
for(let i = 0; i <= this.stepFrame; i++) {
this.changeT(i / this.stepFrame);
}
console.log('targetpoints is',this._targetPoints);
this.pen.clear();
this.drawControllPoints();
if(this._targetPoints.length > 1) {
this.pen.strokeColor = cc.color().fromHEX('#ff0000');
this._targetPoints.forEach((item,index) => {
if(!index) {
this.pen.moveTo(item.x,item.y);
} else {
this.pen.lineTo(item.x,item.y);
}
});
this.pen.stroke();
}
}
}
drawControllPoints(): void {
this.pen.strokeColor = cc.color().fromHEX('#dcdcdc');
this._controllPoints.forEach((item,index) => {
if(!index) {
this.pen.moveTo(item.x,item.y);
} else {
this.pen.lineTo(item.x,item.y);
}
});
this.pen.stroke();
}
/** 改变时间参数t 关键代码 */
changeT(ratio: number): void {
console.log('ratio is ',ratio);
let tempPosArr: Vector2[] = [];
for(let i = 0,len = this._controllPoints.length; i < len - 1; i++) {
let deltaX = this._controllPoints[i + 1].x - this._controllPoints[i].x;
let deltaY = this._controllPoints[i + 1].y - this._controllPoints[i].y;
let x = this._controllPoints[i].x + deltaX * ratio;
let y = this._controllPoints[i].y + deltaY * ratio;
let targetPoint = {x,y};
tempPosArr.push(targetPoint);
}
console.log('tempPosArr is ',tempPosArr);
// 一直迭代直到tempPosArr的长度为1
while(tempPosArr.length > 1) {
let posArrLen = tempPosArr.length;
for(let i = 0, len = tempPosArr.length; i < len - 1; i++) {
let deltaX = tempPosArr[i + 1].x - tempPosArr[i].x;
let deltaY = tempPosArr[i + 1].y - tempPosArr[i].y;
let x = tempPosArr[i].x + deltaX * ratio;
let y = tempPosArr[i].y + deltaY * ratio;
let tp = {x,y};
tempPosArr.push(tp);
}
tempPosArr.splice(0,posArrLen);
}
if(tempPosArr.length === 1) {
this._targetPoints.push(tempPosArr[0]);
}
}
async play() {
this.box.active = true;
for(let item of this._targetPoints) {
this.box.setPosition(cc.v2(item.x,item.y));
this.box.angle = item['rotate'];
await new Promise((resolve) => {
setTimeout(resolve,100);
})
}
}
settingsEvent() {
const settingNode = cc.instantiate(this.settingPrefab);
this.node.addChild(settingNode);
}
start () {
}
// update (dt) {}
}
导出数据的时候需要计算每个点需要旋转的角度值,这里做的比较粗略,因为曲线上的点比较密集,就粗略计算两个点之间的坐标差值求正切得到角度值,导出的时候需要用到blob对象来创建下载链接
const blob = new Blob([JSON.stringify(this._targetPoints)],{type: 'application/json'});
const a = document.createElement('a');
a.href = window.webkitURL.createObjectURL(blob);
a.download = '';
a.click();
|