# 【螺旋线】

螺旋线应用十分广泛。

  1. 螺旋
  2. 漩涡
  3. 蚊香
  4. 卷轴
  5. 内卷

以下是直线扭曲成螺旋线可视化:

module vp
{
    /** 卷轴螺旋线组件 */
    export class ComponentScroll extends Phaser.GameObjects.Container
    {
        // 原点
        public oriX: number = 370;
        public oriY: number = 370;

        // 开始旋转半径
        public minRadiu:number = 10;
        // 结束旋转半径
        public maxRadiu:number = 40;
        // 总旋转弧度
        public scrollRadin:number = 2 * Math.PI * 2;
        /** 步进长度,控制螺旋线的顺滑程度,越小越顺滑,性能消耗越大 */
        // public stepLen:number = 5;
        public stepCount = 600;
        // 线条粗细
        public lineThickness:number = 2;
        // 线条颜色
        public lineColor:number = 0x6e789e;

        private spiralGraphics:Phaser.GameObjects.Graphics;
        private centerGraphics:Phaser.GameObjects.Graphics;
        constructor(scene: Phaser.Scene)
        {
            super(scene);
            this.spiralGraphics = new Phaser.GameObjects.Graphics(this.scene);
            this.centerGraphics = new Phaser.GameObjects.Graphics(this.scene);
            this.centerGraphics.fillStyle(0xff0000,1.0);
            this.centerGraphics.fillCircle(0,0,3);
            this.centerGraphics.fill();
            this.add(this.spiralGraphics);
            this.add(this.centerGraphics);
            this.updateSpiralGraphics();
        }

        /** 0为直线 1为完全螺旋线 */
        public scrollPos:number = 1;

        /** 圆上的切线方向 */
        public normalizeTangentVector(ax, ay)
        {
            // 计算点a到圆心的距离
            var dist = Math.sqrt(ax * ax + ay * ay);
            // 计算连线向量
            var vector = {x: ax/dist, y: ay/dist};
            // 计算切线向量
            var tangentVector = {x: -vector.y, y: vector.x};
            // 归一化切线向量
            var normalizedTangentVector = {x: tangentVector.x/dist, y: tangentVector.y/dist};
            return normalizedTangentVector;
        }

        /** 刷新图形的显示 */
        public updateSpiralGraphics():void
        {
            this.spiralGraphics.clear();
            this.spiralGraphics.lineStyle(this.lineThickness,this.lineColor,1.0);
            // 螺旋线长度
            let spiralLength = this.calculateSpiralLength(this.minRadiu,this.maxRadiu,this.scrollRadin);
            let stepLen = spiralLength/this.stepCount;
            for (let i = 0; i < this.stepCount; i++) {
                let spLen = (i+1) * stepLen;
                // 进度 0~1,0代表直线,1代表全漩涡
                let bi = spLen/spiralLength;
                if(bi > this.scrollPos){
                    // 螺旋线结束,显示直线
                    // 直线长度
                    let straightLineLen = (1-bi) * spiralLength;
                    if(straightLineLen > 0){
                        // 螺旋切线方向
                        let radin = this.scrollRadin * bi;
                        let radiu = this.minRadiu + (this.maxRadiu - this.minRadiu) * bi;
                        let posX = Math.cos(radin) * radiu;
                        let posY = Math.sin(radin) * radiu;
                        this.centerGraphics.x = posX;
                        this.centerGraphics.y = posY;

                        let dirObj = this.normalizeTangentVector(Math.cos(radin),Math.sin(radin));
                        let dirX = dirObj.x;
                        let dirY = dirObj.y;
                        let endX = posX + dirX * straightLineLen;
                        let endY = posY + dirY * straightLineLen;
                        this.spiralGraphics.lineTo(endX,endY);
                        break;
                    }
                }else{
                    let xyObj = this.calculateSpiralXY(this.minRadiu,this.maxRadiu,this.scrollRadin,spLen);
                    if(i == 0){
                        if(xyObj){
                            this.spiralGraphics.moveTo(xyObj.x,xyObj.y);
                        }
                    }else{
                        if(xyObj){
                            this.spiralGraphics.lineTo(xyObj.x,xyObj.y);
                        }
                    }
                }
            }
            this.spiralGraphics.stroke();
        }

        private _spiralLength:number = -1;
        /** 
         * 计算螺旋线长度
         */
        public calculateSpiralLength(startRadius:number, endRadius:number, totalRotation:number):number
        {
            if(this._spiralLength != -1)return this._spiralLength;
            // 螺旋线的总旋转圈数
            var totalRevolutions = totalRotation / (2 * Math.PI);
            // 螺旋线的半径差
            var radiusDiff = endRadius - startRadius;
            // 螺旋线的长度
            var length = 0;
            // 每计算一小段弧长的长度
            var delta = 0.0001;
            // 通过积分计算螺旋线的长度
            let temp1 = totalRevolutions * 2 * Math.PI;
            for (var t = 0; t < temp1; t += delta) {
                var radius = startRadius + (radiusDiff * t) / temp1;
                var dS = Math.sqrt(1 + radius * radius) * delta;
                length += dS;
            }
            // 返回螺旋线的长度
            this._spiralLength = length;
            return length;
        }

        /** 计算螺旋线上的坐标点 */
        public calculateSpiralXY(startRadius:number, endRadius:number, totalRotation:number, desiredLength:number) 
        {
            const totalLength = this.calculateSpiralLength(startRadius, endRadius, totalRotation);
            const numTurns = totalRotation / (2 * Math.PI); // 总旋转弧度除以2π,得到螺旋线的圈数

            const rateOfIncrease = (endRadius - startRadius) / numTurns; // 螺旋线半径的增加率
            // 计算指定长度时的旋转弧度
            const desiredRotation = (desiredLength / totalLength) * totalRotation;
            // 计算指定长度时的螺旋线半径
            const desiredRadius = startRadius + (desiredRotation / (2 * Math.PI)) * rateOfIncrease;
            // 根据指定长度时的螺旋线半径计算坐标点xy
            const x = desiredRadius * Math.cos(desiredRotation);
            const y = desiredRadius * Math.sin(desiredRotation);
            return { x, y };
        }
    }
}