引言:为什么学习角度(Angle)如此重要?

在现代网页开发中,角度(Angle)作为一个核心概念,不仅在几何学中占据重要地位,更在计算机图形学、游戏开发、UI设计以及数据可视化等领域发挥着关键作用。无论你是初学者还是有经验的开发者,掌握角度的计算和应用都能显著提升你的项目质量和效率。本文将从基础到进阶,提供一个全方位的学习路径,并结合实用技巧和代码示例,帮助你快速掌握角度相关的知识。

第一部分:角度的基础知识

1.1 角度的定义与单位

角度是两条射线(或线段)从一个共同端点(顶点)出发所形成的图形。常见的角度单位包括度(°)和弧度(rad)。

  • 度(Degree):将一个圆周分为360等份,每份为1度。这是最常用的角度单位,尤其在日常生活中。
  • 弧度(Radian):弧度是国际单位制中的角度单位。1弧度定义为圆周上弧长等于半径时的圆心角。一个完整的圆周为2π弧度。

代码示例:度与弧度的转换

在编程中,经常需要在度和弧度之间进行转换。以下是JavaScript中的转换函数:

// 度转弧度
function degreesToRadians(degrees) {
    return degrees * (Math.PI / 180);
}

// 弧度转度
function radiansToDegrees(radians) {
    return radians * (180 / Math.PI);
}

console.log(degreesToRadians(180)); // 输出:3.141592653589793 (π)
console.log(radiansToDegrees(Math.PI)); // 输出:180

1.2 角度的基本类型

根据角度的大小,可以分为以下几种类型:

  • 锐角:小于90°的角。
  • 直角:等于90°的角。
  • 钝角:大于90°但小于180°的角。
  • 平角:等于180°的角。
  • 周角:等于360°的角。

1.3 角度的加减法

角度的加减法是基础运算,尤其在旋转和方向计算中非常常见。

示例:计算两个角度的和与差。

function addAngles(angle1, angle2) {
    let sum = angle1 + angle2;
    // 角度超过360°时,取模运算
    return sum % 360;
}

function subtractAngles(angle1, angle2) {
    let difference = angle1 - angle2;
    // 角度小于0时,加上360°
    return (difference + 360) % 360;
}

console.log(addAngles(270, 100)); // 输出:10 (270+100=370, 370%360=10)
console.log(subtractAngles(50, 100)); // 输出:310 (50-100=-50, -50+360=310)

第二部分:角度的进阶应用

2.1 三角函数与角度

三角函数是角度在数学中的核心应用,包括正弦(sin)、余弦(cos)、正切(tan)等。这些函数在图形学、物理模拟和游戏开发中至关重要。

代码示例:计算三角函数值

// 将角度转换为弧度后计算三角函数
function calculateSin(angleDegrees) {
    const angleRadians = degreesToRadians(angleDegrees);
    return Math.sin(angleRadians);
}

function calculateCos(angleDegrees) {
    const angleRadians = degreesToRadians(angleDegrees);
    return Math.cos(angleRadians);
}

function calculateTan(angleDegrees) {
    const angleRadians = degreesToRadians(angleDegrees);
    return Math.tan(angleRadians);
}

console.log(calculateSin(30)); // 输出:0.5
console.log(calculateCos(60)); // 0.5
console.log(calculateTan(45)); // 1

2.2 角度的旋转与方向计算

在2D空间中,角度常用于表示物体的旋转或方向。例如,在游戏开发中,角色的朝向可以用角度表示。

代码示例:计算旋转后的新坐标

假设一个点 (x, y) 绕原点旋转 angle 度后的新坐标 (x’, y’),可以使用以下公式:

\[ x' = x \cdot \cos(\theta) - y \cdot \sin(\theta) \]

\[ y' = x \cdot \sin(\theta) + y \cdot \cos(\theta) \]

function rotatePoint(x, y, angleDegrees) {
    const angleRadians = degreesToRadians(angleDegrees);
    const cos = Math.cos(angleRadians);
    const sin = Math.sin(angleRadians);

    const newX = x * cos - y * sin;
    const newY = x * sin + y * sin;

    return { x: newX, y: newY };
}

// 示例:将点 (1, 0) 旋转 90 度
console.log(rotatePoint(1, 0, 90)); // 输出:{ x: 0, y: 1 }

2.3 角度的模运算与规范化

在实际应用中,角度可能超出0-360°的范围,需要进行规范化处理。

代码示例:角度规范化

function normalizeAngle(angle) {
    // 将角度限制在0-360°之间
    return ((angle % 360) + 360) % 360;
}

console.log(normalizeAngle(400)); // 输出:40
console.log(normalizeAngle(-50)); // 输出:310

第三部分:实用技巧与最佳实践

3.1 使用角度进行UI动画

在UI设计中,角度可以用来创建旋转动画,例如加载指示器或旋转菜单。

代码示例:CSS旋转动画

@keyframes rotate {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}

.loading-spinner {
    width: 50px;
    loading: 50px;
    border: 5px solid #f3f3f3;
    border-top: 5px solid #3498db;
    border-radius: 50%;
    animation: rotate 2s linear infinite;
}

3.2 角度在游戏开发中的应用

在游戏开发中,角度用于计算子弹的发射方向、敌人的移动路径等。

代码示例:计算子弹发射方向

假设角色位置为 (playerX, playerY),目标位置为 (targetX, targetY),计算发射角度:

function calculateAngle(playerX, playerY, targetX, targetY) {
    const dx = targetX - playerX;
    const dy = targetY - playerY;
    // 使用Math.atan2计算弧度,再转换为度
    const angleRadians = Math.atan2(dy, dx);
    const angleDegrees = radiansToDegrees(angleRadians);
    return normalizeAngle(angleDegrees);
}

// 示例:角色在 (0,0),目标在 (1,1)
console.log(calculateAngle(0, 0, 1, 1)); // 输出:45

3.3 角度的性能优化

在大量计算角度时,预计算三角函数值或使用查找表可以提高性能。

代码示例:使用查找表优化三角函数

// 预计算0-360度的sin和cos值
const sinTable = [];
const cosTable = [];
for (let i = 0; i < 360; i++) {
    const rad = degreesToRadians(i);
    sinTable[i] = Math.sin(rad);
    cosTable[i] = Math.cos(rad);
}

function fastSin(angle) {
    return sinTable[normalizeAngle(angle)];
}

function fastCos(angle) {
    return cosTable[normalizeAngle(angle)];
}

console.log(fastSin(30)); // 输出:0.5
console.log(fastCos(60)); // 0.5

第四部分:常见问题与解决方案

4.1 角度计算中的精度问题

由于浮点数精度限制,角度计算可能出现微小误差。解决方案是使用容差比较。

代码示例:角度比较

function areAnglesEqual(angle1, angle2, tolerance = 0.001) {
    const diff = Math.abs(normalizeAngle(angle1) - normalizeAngle(angle2));
    return diff < tolerance || Math.abs(diff - 360) < tolerance;
}

console.log(areAnglesEqual(45.0001, 45)); // true

4.2 处理角度的循环性

角度是循环的,0°和360°表示相同的方向。在比较或插值时需要考虑这一点。

代码示例:角度插值

function interpolateAngles(angle1, angle2, t) {
    // 确保角度在0-360之间
    angle1 = normalizeAngle(angle1);
    angle2 = normalizeAngle(angle2);

    // 计算最短路径
    let diff = angle2 - angle1;
    if (diff > 180) diff -= 360;
    if (diff < -180) diff += 360;

    return normalizeAngle(angle1 + diff * t);
}

// 从350°到10°的插值(经过0°)
console.log(interpolateAngles(350, 10, 0.5)); // 输出:0 (350 + 10 = 360, 360/2=180, 350+180=530, 530%360=170? 等等,这里可能需要修正)

修正:上述插值函数可能需要调整,以下是一个更可靠的版本:

function interpolateAngles(angle1, angle2, t) {
    angle1 = normalizeAngle(angle1);
    angle2 = normalizeAngle(angle2);

    // 计算两个方向的差值
    let diff = angle2 - angle1;
    if (diff > 180) diff -= 360;
    if (diff < -180) diff += 360;

    return normalizeAngle(angle1 + diff * t);
}

console.log(interpolateAngles(350, 10, 0.5)); // 输出:0 (350 + (-20)*0.5 = 350 -10 = 340? 不对,应该是350 + (10-350) = 350 -340 = 10, 10/2=5, 350+5=355? 等等,这里需要仔细计算)

正确计算:从350°到10°,最短路径是顺时针20°(350→360→10)。因此,t=0.5时,应为350 + 10 = 360(即0°)。

function interpolateAngles(angle1, angle2, t) {
    angle1 = normalizeAngle(angle1);
    angle2 = normalizeAngle(angle2);

    let diff = angle2 - angle1;
    if (diff > 180) diff -= 360;
    if (diff < -180) diff += 360;

    return normalizeAngle(angle1 + diff * t);
}

console.log(interpolateAngles(350, 10, 0.5)); // 输出:0

第五部分:总结与学习路径建议

5.1 学习路径总结

  1. 基础阶段:掌握角度的定义、单位转换和基本运算。
  2. 进阶阶段:学习三角函数、旋转计算和角度规范化。
  3. 应用阶段:将角度应用于UI动画、游戏开发和性能优化。
  4. 高级阶段:解决精度问题、循环性问题和复杂插值。

5.2 推荐资源

  • 书籍:《3D数学基础:图形与游戏开发》
  • 在线课程:Coursera上的“计算机图形学”课程
  • 工具:使用Desmos或GeoGebra进行角度可视化

5.3 实践建议

  • 小项目:尝试用角度计算实现一个简单的旋转木马动画。
  • 开源贡献:参与游戏开发或图形库的开源项目,学习角度在实际中的应用。
  • 持续学习:关注计算机图形学和游戏开发的最新技术。

通过本文的详细解析和代码示例,相信你已经对角度的计算和应用有了全面的了解。从基础到进阶,不断实践和探索,你将能够在各种项目中灵活运用角度知识,创造出更加生动和交互性强的应用程序。# 角度教学图解从基础到进阶的全方位学习路径与实用技巧解析

引言:角度在现代技术中的核心地位

角度作为几何学的基本概念,在现代计算机科学中扮演着至关重要的角色。从网页动画到游戏开发,从机器人导航到数据可视化,角度的精确计算和应用无处不在。本文将为您提供一个从基础到进阶的完整学习路径,配合详细的代码示例和实用技巧,帮助您彻底掌握角度相关的知识体系。

第一部分:角度基础知识体系

1.1 角度的本质与数学定义

角度是两条射线从共同端点(顶点)出发所形成的几何图形。在计算机科学中,我们主要使用两种角度单位:

度(Degree)

  • 将圆周分为360等份,每份为1度
  • 直观易懂,适合人类理解
  • 广泛应用于工程、设计和日常计算

弧度(Radian)

  • 国际单位制中的标准角度单位
  • 定义为弧长等于半径时的圆心角
  • 一个完整圆周为2π弧度
  • 在数学计算和编程中更为精确

1.2 角度单位转换的完整实现

在实际编程中,角度转换是最基础也是最频繁的操作。以下是完整的转换函数实现:

// 角度转换工具类
class AngleConverter {
    // 度转弧度
    static degreesToRadians(degrees) {
        return degrees * (Math.PI / 180);
    }

    // 弧度转度
    static radiansToDegrees(radians) {
        return radians * (180 / Math.PI);
    }

    // 批量转换示例
    static batchConvertDegreesToRadians(degreesArray) {
        return degreesArray.map(deg => this.degreesToRadians(deg));
    }

    // 验证转换的准确性
    static testConversion() {
        const testCases = [
            { deg: 0, rad: 0 },
            { deg: 90, rad: Math.PI / 2 },
            { deg: 180, rad: Math.PI },
            { deg: 270, rad: (3 * Math.PI) / 2 },
            { deg: 360, rad: 2 * Math.PI }
        ];

        testCases.forEach(test => {
            const result = this.degreesToRadians(test.deg);
            const diff = Math.abs(result - test.rad);
            console.log(`度: ${test.deg} -> 弧度: ${result.toFixed(6)} (误差: ${diff})`);
        });
    }
}

// 使用示例
console.log("=== 角度转换测试 ===");
AngleConverter.testConversion();

// 批量转换
const angles = [30, 45, 60, 90, 120];
const radians = AngleConverter.batchConvertDegreesToRadians(angles);
console.log("\n批量转换结果:", radians);

1.3 角度的基本分类与识别

根据角度大小,我们可以将其分为以下类型,并提供识别函数:

// 角度分类器
class AngleClassifier {
    static classify(angle) {
        // 规范化角度到0-360范围
        const normalized = this.normalizeAngle(angle);
        
        if (normalized === 0) return "零角";
        if (normalized === 90) return "直角";
        if (normalized === 180) return "平角";
        if (normalized === 270) return "负直角";
        if (normalized === 360) return "周角";
        
        if (normalized > 0 && normalized < 90) return "锐角";
        if (normalized > 90 && normalized < 180) return "钝角";
        if (normalized > 180 && normalized < 270) return "优角";
        if (normalized > 270 && normalized < 360) return "负锐角";
        
        return "未知类型";
    }

    static normalizeAngle(angle) {
        return ((angle % 360) + 360) % 360;
    }

    static batchClassify(angles) {
        return angles.map(angle => ({
            angle,
            normalized: this.normalizeAngle(angle),
            type: this.classify(angle)
        }));
    }
}

// 测试分类器
const testAngles = [-45, 30, 90, 135, 180, 225, 270, 315, 400, -100];
console.log("\n=== 角度分类测试 ===");
const classified = AngleClassifier.batchClassify(testAngles);
classified.forEach(item => {
    console.log(`${item.angle}° -> ${item.normalized}°: ${item.type}`);
});

第二部分:角度的进阶运算与应用

2.1 角度的加减运算与循环处理

角度具有循环性(0°=360°),这在加减运算中需要特殊处理:

// 角度运算器
class AngleCalculator {
    // 规范化角度到0-360范围
    static normalize(angle) {
        return ((angle % 360) + 360) % 360;
    }

    // 角度加法(自动处理循环)
    static add(angle1, angle2) {
        return this.normalize(angle1 + angle2);
    }

    // 角度减法(自动处理循环)
    static subtract(angle1, angle2) {
        return this.normalize(angle1 - angle2);
    }

    // 计算两个角度之间的最短差值(考虑方向)
    static difference(angle1, angle2) {
        const diff = this.normalize(angle1) - this.normalize(angle2);
        // 调整到[-180, 180]范围
        if (diff > 180) return diff - 360;
        if (diff < -180) return diff + 360;
        return diff;
    }

    // 判断两个角度是否相等(考虑循环)
    static areEqual(angle1, angle2, tolerance = 0.001) {
        const diff = Math.abs(this.difference(angle1, angle2));
        return diff <= tolerance;
    }

    // 计算角度的平均值(考虑循环)
    static average(angles) {
        if (angles.length === 0) return 0;
        
        // 将所有角度转换为向量并求和
        let sumX = 0;
        let sumY = 0;
        
        angles.forEach(angle => {
            const rad = AngleConverter.degreesToRadians(this.normalize(angle));
            sumX += Math.cos(rad);
            sumY += Math.sin(rad);
        });
        
        // 计算平均向量的角度
        const avgRad = Math.atan2(sumY, sumX);
        return AngleConverter.radiansToDegrees(avgRad);
    }
}

// 运算测试
console.log("\n=== 角度运算测试 ===");
console.log("270° + 100° =", AngleCalculator.add(270, 100), "°"); // 10°
console.log("50° - 100° =", AngleCalculator.subtract(50, 100), "°"); // 310°
console.log("350° 和 10° 的差值:", AngleCalculator.difference(350, 10), "°"); // -20°
console.log("350° 和 10° 是否相等:", AngleCalculator.areEqual(350, 10)); // true

// 平均值测试
const angles = [10, 350, 20]; // 这些角度应该平均为0°左右
console.log("角度 [10, 350, 20] 的平均值:", AngleCalculator.average(angles), "°");

2.2 三角函数与角度计算

三角函数是角度计算的核心,在图形学、物理模拟中应用广泛:

// 三角函数计算器
class TrigonometricCalculator {
    // 安全的sin函数(处理浮点误差)
    static sin(angleDegrees) {
        const rad = AngleConverter.degreesToRadians(angleDegrees);
        const result = Math.sin(rad);
        // 处理常见的浮点误差
        return Math.abs(result) < 1e-10 ? 0 : result;
    }

    // 安全的cos函数
    static cos(angleDegrees) {
        const rad = AngleConverter.degreesToRadians(angleDegrees);
        const result = Math.cos(rad);
        return Math.abs(result) < 1e-10 ? 0 : result;
    }

    // 安全的tan函数
    static tan(angleDegrees) {
        const rad = AngleConverter.degreesToRadians(angleDegrees);
        const result = Math.tan(rad);
        return Math.abs(result) < 1e-10 ? 0 : result;
    }

    // 反三角函数(返回度)
    static asin(value) {
        return AngleConverter.radiansToDegrees(Math.asin(value));
    }

    static acos(value) {
        return AngleConverter.radiansToDegrees(Math.acos(value));
    }

    static atan(value) {
        return AngleConverter.radiansToDegrees(Math.atan(value));
    }

    static atan2(y, x) {
        return AngleConverter.radiansToDegrees(Math.atan2(y, x));
    }

    // 计算两点之间的角度
    static angleBetweenPoints(x1, y1, x2, y2) {
        return this.atan2(y2 - y1, x2 - x1);
    }

    // 计算三角函数值表(用于验证)
    static generateTable() {
        const angles = [0, 30, 45, 60, 90, 120, 135, 150, 180];
        console.log("\n角度\t\tSin\t\tCos\t\tTan");
        console.log("─".repeat(50));
        
        angles.forEach(angle => {
            const sin = this.sin(angle);
            const cos = this.cos(angle);
            const tan = this.tan(angle);
            console.log(`${angle}°\t\t${sin.toFixed(4)}\t\t${cos.toFixed(4)}\t\t${tan.toFixed(4)}`);
        });
    }
}

// 三角函数测试
console.log("\n=== 三角函数测试 ===");
TrigonometricCalculator.generateTable();

// 两点间角度计算
const angle = TrigonometricCalculator.angleBetweenPoints(0, 0, 1, 1);
console.log(`\n点(0,0)到点(1,1)的角度: ${angle}°`);

2.3 角度的旋转与坐标变换

旋转是角度最重要的应用之一,广泛用于计算机图形学和游戏开发:

// 旋转计算器
class RotationCalculator {
    // 2D点绕原点旋转
    static rotatePoint(x, y, angleDegrees) {
        const rad = AngleConverter.degreesToRadians(angleDegrees);
        const cos = Math.cos(rad);
        const sin = Math.sin(rad);
        
        return {
            x: x * cos - y * sin,
            y: x * sin + y * cos
        };
    }

    // 2D点绕指定中心旋转
    static rotatePointAroundCenter(x, y, centerX, centerY, angleDegrees) {
        // 先平移到原点
        const translatedX = x - centerX;
        const translatedY = y - centerY;
        
        // 旋转
        const rotated = this.rotatePoint(translatedX, translatedY, angleDegrees);
        
        // 平移回原位置
        return {
            x: rotated.x + centerX,
            y: rotated.y + centerY
        };
    }

    // 批量旋转点
    static rotatePoints(points, angleDegrees, centerX = 0, centerY = 0) {
        return points.map(point => 
            this.rotatePointAroundCenter(point.x, point.y, centerX, centerY, angleDegrees)
        );
    }

    // 3D点绕Z轴旋转(2D旋转的扩展)
    static rotate3DPointZ(x, y, z, angleDegrees) {
        const rotated = this.rotatePoint(x, y, angleDegrees);
        return { x: rotated.x, y: rotated.y, z };
    }

    // 计算旋转后的边界框
    static getRotatedBoundingBox(width, height, angleDegrees) {
        const corners = [
            { x: -width/2, y: -height/2 },
            { x: width/2, y: -height/2 },
            { x: width/2, y: height/2 },
            { x: -width/2, y: height/2 }
        ];

        const rotatedCorners = corners.map(corner => 
            this.rotatePoint(corner.x, corner.y, angleDegrees)
        );

        const xs = rotatedCorners.map(p => p.x);
        const ys = rotatedCorners.map(p => p.y);

        return {
            minX: Math.min(...xs),
            maxX: Math.max(...xs),
            minY: Math.min(...ys),
            maxY: Math.max(...ys),
            width: Math.max(...xs) - Math.min(...xs),
            height: Math.max(...ys) - Math.min(...ys)
        };
    }
}

// 旋转测试
console.log("\n=== 旋转计算测试 ===");
const point = { x: 1, y: 0 };
const rotated = RotationCalculator.rotatePoint(point.x, point.y, 90);
console.log(`点(1,0)绕原点旋转90°: (${rotated.x.toFixed(2)}, ${rotated.y.toFixed(2)})`);

// 绕中心旋转
const rotatedAroundCenter = RotationCalculator.rotatePointAroundCenter(2, 2, 1, 1, 90);
console.log(`点(2,2)绕(1,1)旋转90°: (${rotatedAroundCenter.x.toFixed(2)}, ${rotatedAroundCenter.y.toFixed(2)})`);

// 边界框计算
const bbox = RotationCalculator.getRotatedBoundingBox(10, 6, 45);
console.log(`10x6矩形旋转45°后的边界框: 宽度=${bbox.width.toFixed(2)}, 高度=${bbox.height.toFixed(2)}`);

第三部分:实用技巧与最佳实践

3.1 UI动画中的角度应用

在网页和移动应用开发中,角度常用于创建流畅的旋转动画:

<!DOCTYPE html>
<html>
<head>
<style>
    /* 旋转动画示例 */
    .spinner {
        width: 60px;
        height: 60px;
        border: 4px solid #f3f3f3;
        border-top: 4px solid #3498db;
        border-radius: 50%;
        animation: spin 1s linear infinite;
    }

    @keyframes spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
    }

    /* 指针动画 */
    .gauge {
        width: 200px;
        height: 100px;
        background: #f0f0f0;
        border-radius: 100px 100px 0 0;
        position: relative;
        overflow: hidden;
    }

    .needle {
        width: 2px;
        height: 90px;
        background: #e74c3c;
        position: absolute;
        bottom: 0;
        left: 50%;
        transform-origin: bottom center;
        transition: transform 0.5s ease;
    }

    /* 旋转菜单 */
    .rotating-menu {
        position: relative;
        width: 200px;
        height: 200px;
    }

    .menu-item {
        position: absolute;
        width: 40px;
        height: 40px;
        background: #3498db;
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        color: white;
        font-weight: bold;
        transition: all 0.3s ease;
    }

    .menu-item:hover {
        transform: scale(1.2);
        background: #2980b9;
    }
</style>
</head>
<body>

<h3>1. 加载旋转器</h3>
<div class="spinner"></div>

<h3>2. 仪表盘指针</h3>
<div class="gauge">
    <div class="needle" id="gaugeNeedle"></div>
</div>
<button onclick="updateGauge(45)">45°</button>
<button onclick="updateGauge(90)">90°</button>
<button onclick="updateGauge(135)">135°</button>

<h3>3. 旋转菜单</h3>
<div class="rotating-menu" id="rotatingMenu"></div>
<button onclick="rotateMenu()">旋转菜单</button>

<script>
    // 仪表盘控制
    function updateGauge(angle) {
        const needle = document.getElementById('gaugeNeedle');
        // 将角度转换为仪表盘范围(-90°到90°)
        const gaugeAngle = -90 + (angle / 180) * 180;
        needle.style.transform = `rotate(${gaugeAngle}deg)`;
    }

    // 旋转菜单生成
    function createRotatingMenu() {
        const menu = document.getElementById('rotatingMenu');
        const items = 6;
        const radius = 70;
        
        for (let i = 0; i < items; i++) {
            const angle = (360 / items) * i;
            const rad = angle * Math.PI / 180;
            const x = 80 + radius * Math.cos(rad);
            const y = 80 + radius * Math.sin(rad);
            
            const item = document.createElement('div');
            item.className = 'menu-item';
            item.textContent = i + 1;
            item.style.left = x + 'px';
            item.style.top = y + 'px';
            item.dataset.angle = angle;
            
            menu.appendChild(item);
        }
    }

    // 旋转菜单
    let currentRotation = 0;
    function rotateMenu() {
        const menu = document.getElementById('rotatingMenu');
        currentRotation += 60;
        menu.style.transform = `rotate(${currentRotation}deg)`;
        menu.style.transition = 'transform 0.5s ease';
        
        // 反向旋转子项以保持直立
        const items = menu.querySelectorAll('.menu-item');
        items.forEach(item => {
            item.style.transform = `rotate(${-currentRotation}deg)`;
        });
    }

    // 初始化
    createRotatingMenu();
</script>

</body>
</html>

3.2 游戏开发中的角度应用

在游戏开发中,角度用于计算方向、弹道、AI行为等:

// 游戏角度工具类
class GameAngleUtils {
    // 计算两点间的角度(用于瞄准)
    static calculateAimAngle(fromX, fromY, toX, toY) {
        return TrigonometricCalculator.atan2(toY - fromY, toX - fromX);
    }

    // 计算弹道偏转(考虑重力)
    static calculateProjectileAngle(startX, startY, targetX, targetY, gravity = 9.8) {
        const dx = targetX - startX;
        const dy = targetY - startY;
        const distance = Math.sqrt(dx * dx + dy * dy);
        
        // 简化的弹道计算(假设初速度固定)
        const velocity = 20; // 初速度
        const time = distance / velocity;
        
        // 考虑重力的垂直偏移
        const verticalOffset = 0.5 * gravity * time * time;
        
        // 调整角度
        const adjustedY = targetY + verticalOffset;
        return this.calculateAimAngle(startX, startY, targetX, adjustedY);
    }

    // AI视野角度检查
    static isInFieldOfView(lookAngle, targetAngle, fov = 90) {
        const diff = AngleCalculator.difference(lookAngle, targetAngle);
        return Math.abs(diff) <= fov / 2;
    }

    // 计算反射角度(用于碰撞)
    static calculateReflection(incidentAngle, surfaceAngle) {
        // 入射角与法线的夹角
        const normalAngle = surfaceAngle + 90;
        const incidence = AngleCalculator.difference(incidentAngle, normalAngle);
        
        // 反射角 = 法线角 - 入射角
        return AngleCalculator.subtract(normalAngle, incidence);
    }

    // 平滑角度插值(用于动画)
    static smoothAngleInterpolation(fromAngle, toAngle, factor) {
        const diff = AngleCalculator.difference(toAngle, fromAngle);
        return AngleCalculator.add(fromAngle, diff * factor);
    }

    // 生成随机角度(用于粒子效果)
    static randomAngleInCone(centerAngle, coneWidth) {
        const halfCone = coneWidth / 2;
        const randomOffset = (Math.random() - 0.5) * coneWidth;
        return AngleCalculator.add(centerAngle, randomOffset);
    }
}

// 游戏角度测试
console.log("\n=== 游戏角度应用测试 ===");

// 瞄准计算
const aimAngle = GameAngleUtils.calculateAimAngle(0, 0, 100, 50);
console.log(`从(0,0)瞄准(100,50)的角度: ${aimAngle.toFixed(2)}°`);

// 视野检查
const playerAngle = 45;
const enemyAngle = 80;
const inView = GameAngleUtils.isInFieldOfView(playerAngle, enemyAngle, 60);
console.log(`敌人在视野内: ${inView}`); // false

// 反射计算
const incident = 30; // 入射角
const surface = 120; // 表面角度
const reflection = GameAngleUtils.calculateReflection(incident, surface);
console.log(`入射角${incident}°, 表面${surface}° -> 反射角${reflection}°`);

// 平滑插值
const from = 350;
const to = 10;
const interpolated = GameAngleUtils.smoothAngleInterpolation(from, to, 0.5);
console.log(`从${from}°到${to}°的50%插值: ${interpolated.toFixed(2)}°`);

3.3 性能优化技巧

在大量角度计算时,性能优化至关重要:

// 性能优化的角度计算
class OptimizedAngleCalculator {
    constructor() {
        // 预计算常用角度的三角函数值
        this.sinCache = new Map();
        this.cosCache = new Map();
        this.tanCache = new Map();
        
        // 缓存大小限制
        this.maxCacheSize = 1000;
    }

    // 带缓存的sin计算
    sin(angle) {
        const key = Math.round(angle * 100) / 100; // 保留两位小数作为key
        if (this.sinCache.has(key)) {
            return this.sinCache.get(key);
        }
        
        const result = TrigonometricCalculator.sin(angle);
        this.sinCache.set(key, result);
        
        // 清理过大的缓存
        if (this.sinCache.size > this.maxCacheSize) {
            const firstKey = this.sinCache.keys().next().value;
            this.sinCache.delete(firstKey);
        }
        
        return result;
    }

    // 批量角度计算优化
    static batchRotatePoints(points, angle) {
        // 预计算三角函数
        const rad = AngleConverter.degreesToRadians(angle);
        const cos = Math.cos(rad);
        const sin = Math.sin(rad);
        
        // 使用TypedArray提升性能(适用于大量点)
        const result = new Float32Array(points.length * 2);
        
        for (let i = 0; i < points.length; i++) {
            const x = points[i * 2];
            const y = points[i * 2 + 1];
            
            result[i * 2] = x * cos - y * sin;
            result[i * 2 + 1] = x * sin + y * cos;
        }
        
        return result;
    }

    // 使用Web Workers进行并行角度计算
    static async parallelAngleCalculation(angles, workerCount = 4) {
        const chunkSize = Math.ceil(angles.length / workerCount);
        const workers = [];
        
        // 创建Worker代码
        const workerCode = `
            self.onmessage = function(e) {
                const angles = e.data;
                const result = angles.map(angle => {
                    // 执行角度计算
                    const rad = angle * Math.PI / 180;
                    return {
                        sin: Math.sin(rad),
                        cos: Math.cos(rad),
                        normalized: ((angle % 360) + 360) % 360
                    };
                });
                self.postMessage(result);
            };
        `;
        
        const blob = new Blob([workerCode], { type: 'application/javascript' });
        const workerUrl = URL.createObjectURL(blob);
        
        // 分发任务
        for (let i = 0; i < workerCount; i++) {
            const start = i * chunkSize;
            const end = Math.min(start + chunkSize, angles.length);
            const chunk = angles.slice(start, end);
            
            const worker = new Worker(workerUrl);
            const promise = new Promise((resolve) => {
                worker.onmessage = (e) => {
                    resolve(e.data);
                    worker.terminate();
                };
            });
            
            worker.postMessage(chunk);
            workers.push(promise);
        }
        
        // 等待所有任务完成
        const results = await Promise.all(workers);
        return results.flat();
    }

    // 使用SIMD优化(如果可用)
    static simdBatchNormalize(angles) {
        // 模拟SIMD操作
        const result = new Float32Array(angles.length);
        for (let i = 0; i < angles.length; i++) {
            result[i] = ((angles[i] % 360) + 360) % 360;
        }
        return result;
    }
}

// 性能测试
console.log("\n=== 性能优化测试 ===");

// 测试大量角度计算
const largeAngleArray = new Float32Array(10000);
for (let i = 0; i < largeAngleArray.length; i++) {
    largeAngleArray[i] = Math.random() * 720 - 360; // -360到360
}

console.time("批量规范化");
const normalized = OptimizedAngleCalculator.simdBatchNormalize(largeAngleArray);
console.timeEnd("批量规范化");
console.log(`规范化了 ${normalized.length} 个角度`);

// 缓存测试
const optimizer = new OptimizedAngleCalculator();
console.time("缓存计算");
for (let i = 0; i < 1000; i++) {
    optimizer.sin(i % 360);
}
console.timeEnd("缓存计算");

第四部分:常见问题与解决方案

4.1 精度问题与浮点误差处理

浮点数精度问题是角度计算中的常见陷阱:

// 精度处理工具
class PrecisionHandler {
    // 安全的角度比较
    static safeAngleCompare(angle1, angle2, tolerance = 1e-10) {
        const diff = Math.abs(AngleCalculator.difference(angle1, angle2));
        return diff <= tolerance;
    }

    // 处理三角函数的浮点误差
    static safeTrigonometricValue(value, threshold = 1e-10) {
        return Math.abs(value) < threshold ? 0 : value;
    }

    // 高精度角度规范化
    static highPrecisionNormalize(angle) {
        // 使用模运算避免累积误差
        const quotient = Math.floor(angle / 360);
        return angle - quotient * 360;
    }

    // 检测并修复精度问题
    static detectPrecisionIssues(angles) {
        const issues = [];
        
        angles.forEach((angle, index) => {
            const normalized = this.highPrecisionNormalize(angle);
            const standard = ((angle % 360) + 360) % 360;
            
            if (Math.abs(normalized - standard) > 1e-12) {
                issues.push({
                    index,
                    original: angle,
                    normalized,
                    standard,
                    difference: Math.abs(normalized - standard)
                });
            }
        });
        
        return issues;
    }

    // 创建高精度角度对象
    static createHighPrecisionAngle(degree) {
        return {
            degrees: degree,
            radians: AngleConverter.degreesToRadians(degree),
            normalize: function() {
                return PrecisionHandler.highPrecisionNormalize(this.degrees);
            },
            add: function(other) {
                const result = this.degrees + other.degrees;
                return PrecisionHandler.createHighPrecisionAngle(result);
            },
            equals: function(other, tolerance = 1e-10) {
                return PrecisionHandler.safeAngleCompare(this.degrees, other.degrees, tolerance);
            }
        };
    }
}

// 精度问题测试
console.log("\n=== 精度问题测试 ===");

// 检测常见精度问题
const problematicAngles = [0.1 + 0.2, 360.0000000000001, -0.000000000001];
const issues = PrecisionHandler.detectPrecisionIssues(problematicAngles);
console.log("检测到的精度问题:", issues);

// 高精度对象测试
const hpAngle1 = PrecisionHandler.createHighPrecisionAngle(45);
const hpAngle2 = PrecisionHandler.createHighPrecisionAngle(45);
console.log("高精度角度相等:", hpAngle1.equals(hpAngle2));

// 三角函数精度处理
const sinValue = Math.sin(Math.PI); // 应该是0,但可能有微小误差
const safeSin = PrecisionHandler.safeTrigonometricValue(sinValue);
console.log(`sin(π) = ${sinValue}, 安全值 = ${safeSin}`);

4.2 角度循环性问题的解决方案

角度的循环性(0°=360°)会导致许多逻辑错误:

// 循环角度处理器
class CircularAngleHandler {
    // 检测角度跳跃(用于动画平滑)
    static detectAngleJump(previous, current, threshold = 180) {
        const diff = Math.abs(AngleCalculator.difference(previous, current));
        return diff > threshold;
    }

    // 平滑角度过渡(处理循环)
    static smoothAngleTransition(previous, current, factor) {
        // 计算最短路径
        let diff = AngleCalculator.difference(current, previous);
        
        // 如果跳跃太大,选择另一方向
        if (Math.abs(diff) > 180) {
            diff = diff > 0 ? diff - 360 : diff + 360;
        }
        
        return AngleCalculator.add(previous, diff * factor);
    }

    // 角度插值(处理循环)
    static interpolateCircular(angle1, angle2, t) {
        // 确保角度在0-360之间
        angle1 = AngleCalculator.normalize(angle1);
        angle2 = AngleCalculator.normalize(angle2);
        
        // 计算两个方向的距离
        const directDiff = angle2 - angle1;
        const wrapDiff = directDiff > 0 ? directDiff - 360 : directDiff + 360;
        
        // 选择较短路径
        const diff = Math.abs(directDiff) < Math.abs(wrapDiff) ? directDiff : wrapDiff;
        
        return AngleCalculator.add(angle1, diff * t);
    }

    // 生成角度序列(处理循环)
    static generateAngleSequence(start, end, steps, includeEnd = true) {
        const sequence = [];
        const diff = AngleCalculator.difference(end, start);
        
        for (let i = 0; i <= steps; i++) {
            const t = i / steps;
            const angle = this.interpolateCircular(start, end, t);
            sequence.push(angle);
        }
        
        if (!includeEnd) {
            sequence.pop();
        }
        
        return sequence;
    }

    // 检测角度范围重叠(用于碰撞检测)
    static rangesOverlap(range1Start, range1End, range2Start, range2End) {
        // 规范化所有角度
        range1Start = AngleCalculator.normalize(range1Start);
        range1End = AngleCalculator.normalize(range1End);
        range2Start = AngleCalculator.normalize(range2Start);
        range2End = AngleCalculator.normalize(range2End);
        
        // 检查是否跨越0度
        const range1Crosses = range1End < range1Start;
        const range2Crosses = range2End < range2Start;
        
        if (!range1Crosses && !range2Crosses) {
            // 都不跨越0度
            return (range1Start <= range2End && range1End >= range2Start);
        } else if (range1Crosses && range2Crosses) {
            // 都跨越0度
            return true; // 总是重叠
        } else if (range1Crosses) {
            // range1跨越0度
            return (range1Start <= range2End || range1End >= range2Start);
        } else {
            // range2跨越0度
            return (range2Start <= range1End || range2End >= range1Start);
        }
    }
}

// 循环角度测试
console.log("\n=== 循环角度处理测试 ===");

// 平滑过渡测试
const prevAngle = 350;
const currAngle = 10;
const smoothed = CircularAngleHandler.smoothAngleTransition(prevAngle, currAngle, 0.5);
console.log(`从${prevAngle}°平滑过渡到${currAngle}°的50%: ${smoothed.toFixed(2)}°`);

// 插值序列
const sequence = CircularAngleHandler.generateAngleSequence(350, 10, 5);
console.log("角度序列:", sequence.map(a => a.toFixed(2)));

// 范围重叠检测
const overlap1 = CircularAngleHandler.rangesOverlap(350, 30, 20, 40);
const overlap2 = CircularAngleHandler.rangesOverlap(350, 30, 340, 10);
console.log(`范围[350,30]和[20,40]重叠: ${overlap1}`);
console.log(`范围[350,30]和[340,10]重叠: ${overlap2}`);

4.3 复杂场景下的角度计算

在实际项目中,我们经常需要处理复杂的多角度场景:

// 复杂角度场景处理器
class ComplexAngleHandler {
    // 计算多边形内角
    static calculatePolygonInteriorAngles(vertices) {
        const angles = [];
        const n = vertices.length;
        
        for (let i = 0; i < n; i++) {
            const prev = vertices[(i - 1 + n) % n];
            const curr = vertices[i];
            const next = vertices[(i + 1) % n];
            
            // 计算两个向量的角度
            const angle1 = TrigonometricCalculator.atan2(curr.y - prev.y, curr.x - prev.x);
            const angle2 = TrigonometricCalculator.atan2(next.y - curr.y, next.x - curr.x);
            
            // 计算内角
            const interior = AngleCalculator.difference(angle2, angle1);
            angles.push(interior);
        }
        
        return angles;
    }

    // 计算旋转后的点集(批量)
    static rotatePointSet(points, angle, center = { x: 0, y: 0 }) {
        const rad = AngleConverter.degreesToRadians(angle);
        const cos = Math.cos(rad);
        const sin = Math.sin(rad);
        
        return points.map(p => {
            const dx = p.x - center.x;
            const dy = p.y - center.y;
            
            return {
                x: center.x + dx * cos - dy * sin,
                y: center.y + dx * sin + dy * cos
            };
        });
    }

    // 计算角度的统计信息
    static angleStatistics(angles) {
        const normalized = angles.map(a => AngleCalculator.normalize(a));
        
        // 平均角度(考虑循环)
        const avg = AngleCalculator.average(angles);
        
        // 角度分布(直方图)
        const histogram = new Array(36).fill(0); // 每10度一个桶
        normalized.forEach(angle => {
            const bucket = Math.floor(angle / 10);
            histogram[bucket]++;
        });
        
        // 标准差(简化计算)
        const variance = normalized.reduce((sum, angle) => {
            const diff = AngleCalculator.difference(angle, avg);
            return sum + diff * diff;
        }, 0) / normalized.length;
        
        return {
            average: avg,
            count: angles.length,
            histogram,
            stdDev: Math.sqrt(variance)
        };
    }

    // 检测角度模式(用于识别形状)
    static detectAnglePattern(angles, tolerance = 5) {
        // 检测等边(所有角度相等)
        const allEqual = angles.every(angle => 
            Math.abs(AngleCalculator.difference(angle, angles[0])) <= tolerance
        );
        
        // 检测互补(和为180)
        const isComplementary = angles.length === 2 && 
            Math.abs(AngleCalculator.add(angles[0], angles[1]) - 180) <= tolerance;
        
        // 检测互余(和为90)
        const isComplementary90 = angles.length === 2 && 
            Math.abs(AngleCalculator.add(angles[0], angles[1]) - 90) <= tolerance;
        
        // 检测对顶角(相等)
        const isVertical = angles.length === 2 && 
            Math.abs(AngleCalculator.difference(angles[0], angles[1])) <= tolerance;
        
        return {
            allEqual,
            isComplementary,
            isComplementary90,
            isVertical,
            pattern: allEqual ? "等边" : 
                    isComplementary ? "互补" : 
                    isComplementary90 ? "互余" : 
                    isVertical ? "对顶" : "无特殊模式"
        };
    }

    // 计算旋转矩阵
    static getRotationMatrix(angle) {
        const rad = AngleConverter.degreesToRadians(angle);
        const cos = Math.cos(rad);
        const sin = Math.sin(rad);
        
        return [
            [cos, -sin],
            [sin, cos]
        ];
    }

    // 应用旋转矩阵
    static applyRotationMatrix(point, matrix) {
        return {
            x: point.x * matrix[0][0] + point.y * matrix[0][1],
            y: point.x * matrix[1][0] + point.y * matrix[1][1]
        };
    }
}

// 复杂场景测试
console.log("\n=== 复杂场景测试 ===");

// 多边形内角计算
const squareVertices = [
    { x: 0, y: 0 },
    { x: 1, y: 0 },
    { x: 1, y: 1 },
    { x: 0, y: 1 }
];
const interiorAngles = ComplexAngleHandler.calculatePolygonInteriorAngles(squareVertices);
console.log("正方形内角:", interiorAngles.map(a => a.toFixed(2)), "°");

// 角度统计
const randomAngles = [10, 350, 20, 340, 30, 330];
const stats = ComplexAngleHandler.angleStatistics(randomAngles);
console.log("角度统计:", {
    average: stats.average.toFixed(2),
    count: stats.count,
    stdDev: stats.stdDev.toFixed(2)
});

// 模式检测
const pattern1 = ComplexAngleHandler.detectAnglePattern([45, 45, 45]);
const pattern2 = ComplexAngleHandler.detectAnglePattern([30, 150]);
console.log("模式1:", pattern1.pattern);
console.log("模式2:", pattern2.pattern);

// 旋转矩阵
const matrix = ComplexAngleHandler.getRotationMatrix(90);
const rotatedPoint = ComplexAngleHandler.applyRotationMatrix({ x: 1, y: 0 }, matrix);
console.log("旋转矩阵应用:", rotatedPoint); // { x: 0, y: 1 }

第五部分:完整项目示例与学习路径

5.1 综合项目:角度可视化工具

以下是一个完整的角度可视化工具,整合了前面学到的所有知识:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>角度可视化工具</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            background: #f5f5f5;
        }
        .container {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
            max-width: 1200px;
            margin: 0 auto;
        }
        .panel {
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        canvas {
            border: 1px solid #ddd;
            background: white;
            cursor: crosshair;
        }
        .controls {
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        .control-group {
            display: flex;
            align-items: center;
            gap: 10px;
        }
        label {
            min-width: 120px;
            font-weight: bold;
        }
        input[type="range"] {
            flex: 1;
        }
        input[type="number"] {
            width: 80px;
            padding: 5px;
        }
        button {
            padding: 8px 16px;
            background: #3498db;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:hover {
            background: #2980b9;
        }
        .info {
            background: #ecf0f1;
            padding: 10px;
            border-radius: 4px;
            font-family: monospace;
            font-size: 12px;
            white-space: pre-wrap;
        }
        h2 {
            color: #2c3e50;
            margin-top: 0;
        }
        .highlight {
            background: #fff3cd;
            padding: 2px 4px;
            border-radius: 3px;
        }
    </style>
</head>
<body>

<div class="container">
    <div class="panel">
        <h2>角度可视化画布</h2>
        <canvas id="angleCanvas" width="400" height="400"></canvas>
        <div class="info" id="angleInfo">点击画布设置角度</div>
    </div>

    <div class="panel">
        <h2>控制面板</h2>
        <div class="controls">
            <div class="control-group">
                <label>主角度 (°):</label>
                <input type="range" id="mainAngle" min="0" max="360" value="45">
                <input type="number" id="mainAngleInput" value="45" min="0" max="360">
            </div>
            
            <div class="control-group">
                <label>旋转速度 (°/s):</label>
                <input type="range" id="rotationSpeed" min="-360" max="360" value="0">
                <input type="number" id="rotationSpeedInput" value="0" min="-360" max="360">
            </div>

            <div class="control-group">
                <label>辅助角度 (°):</label>
                <input type="range" id="helperAngle" min="0" max="360" value="90">
                <input type="number" id="helperAngleInput" value="90" min="0" max="360">
            </div>

            <div class="control-group">
                <label>操作模式:</label>
                <select id="operationMode">
                    <option value="single">单角度显示</option>
                    <option value="add">角度相加</option>
                    <option value="subtract">角度相减</option>
                    <option value="rotate">旋转点</option>
                    <option value="triangle">三角函数</option>
                </select>
            </div>

            <div class="control-group">
                <button id="resetBtn">重置</button>
                <button id="animateBtn">开始动画</button>
                <button id="snapBtn">吸附到45°倍数</button>
            </div>

            <div class="control-group">
                <label>显示选项:</label>
                <input type="checkbox" id="showGrid" checked> 网格
                <input type="checkbox" id="showLabels" checked> 标签
                <input type="checkbox" id="showVector"> 向量
            </div>
        </div>
    </div>

    <div class="panel">
        <h2>计算结果</h2>
        <div class="info" id="calculationResult">等待计算...</div>
    </div>

    <div class="panel">
        <h2>批量处理</h2>
        <div class="controls">
            <div class="control-group">
                <label>角度列表 (逗号分隔):</label>
                <input type="text" id="batchAngles" value="30, 45, 60, 90, 120" style="flex: 1;">
            </div>
            <div class="control-group">
                <button id="batchNormalize">规范化</button>
                <button id="batchAverage">求平均</button>
                <button id="batchClassify">分类</button>
            </div>
            <div class="info" id="batchResult">批量结果将显示在这里</div>
        </div>
    </div>
</div>

<script>
// 核心角度计算类(来自前面的代码)
class AngleConverter {
    static degreesToRadians(degrees) {
        return degrees * (Math.PI / 180);
    }
    static radiansToDegrees(radians) {
        return radians * (180 / Math.PI);
    }
}

class AngleCalculator {
    static normalize(angle) {
        return ((angle % 360) + 360) % 360;
    }
    static add(angle1, angle2) {
        return this.normalize(angle1 + angle2);
    }
    static subtract(angle1, angle2) {
        return this.normalize(angle1 - angle2);
    }
    static difference(angle1, angle2) {
        const diff = this.normalize(angle1) - this.normalize(angle2);
        if (diff > 180) return diff - 360;
        if (diff < -180) return diff + 360;
        return diff;
    }
    static average(angles) {
        if (angles.length === 0) return 0;
        let sumX = 0, sumY = 0;
        angles.forEach(angle => {
            const rad = AngleConverter.degreesToRadians(this.normalize(angle));
            sumX += Math.cos(rad);
            sumY += Math.sin(rad);
        });
        const avgRad = Math.atan2(sumY, sumX);
        return AngleConverter.radiansToDegrees(avgRad);
    }
}

class TrigonometricCalculator {
    static sin(angle) {
        const rad = AngleConverter.degreesToRadians(angle);
        const result = Math.sin(rad);
        return Math.abs(result) < 1e-10 ? 0 : result;
    }
    static cos(angle) {
        const rad = AngleConverter.degreesToRadians(angle);
        const result = Math.cos(rad);
        return Math.abs(result) < 1e-10 ? 0 : result;
    }
    static tan(angle) {
        const rad = AngleConverter.degreesToRadians(angle);
        const result = Math.tan(rad);
        return Math.abs(result) < 1e-10 ? 0 : result;
    }
    static atan2(y, x) {
        return AngleConverter.radiansToDegrees(Math.atan2(y, x));
    }
}

// 应用状态
const state = {
    mainAngle: 45,
    helperAngle: 90,
    rotationSpeed: 0,
    mode: 'single',
    isAnimating: false,
    lastTime: 0,
    point: { x: 100, y: 0 },
    clickPoint: null
};

// 画布设置
const canvas = document.getElementById('angleCanvas');
const ctx = canvas.getContext('2d');
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;

// 绘制函数
function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // 保存状态
    ctx.save();
    ctx.translate(centerX, centerY);
    
    // 绘制网格
    if (document.getElementById('showGrid').checked) {
        drawGrid();
    }
    
    // 根据模式绘制
    switch(state.mode) {
        case 'single':
            drawSingleAngle();
            break;
        case 'add':
            drawAddition();
            break;
        case 'subtract':
            drawSubtraction();
            break;
        case 'rotate':
            drawRotation();
            break;
        case 'triangle':
            drawTriangle();
            break;
    }
    
    // 绘制点击点
    if (state.clickPoint) {
        ctx.fillStyle = '#e74c3c';
        ctx.beginPath();
        ctx.arc(state.clickPoint.x - centerX, state.clickPoint.y - centerY, 5, 0, Math.PI * 2);
        ctx.fill();
        
        if (document.getElementById('showLabels').checked) {
            ctx.fillStyle = '#e74c3c';
            ctx.font = '12px Arial';
            ctx.fillText(`点击: ${Math.round(state.clickPoint.x - centerX)}, ${Math.round(state.clickPoint.y - centerY)}`, 
                        state.clickPoint.x - centerX + 10, state.clickPoint.y - centerY - 10);
        }
    }
    
    ctx.restore();
}

function drawGrid() {
    ctx.strokeStyle = '#e0e0e0';
    ctx.lineWidth = 1;
    
    // 坐标轴
    ctx.beginPath();
    ctx.moveTo(-centerX, 0);
    ctx.lineTo(centerX, 0);
    ctx.moveTo(0, -centerY);
    ctx.lineTo(0, centerY);
    ctx.stroke();
    
    // 45度线
    ctx.strokeStyle = '#f0f0f0';
    for (let angle = 0; angle < 360; angle += 45) {
        const rad = AngleConverter.degreesToRadians(angle);
        ctx.beginPath();
        ctx.moveTo(0, 0);
        ctx.lineTo(Math.cos(rad) * centerX, Math.sin(rad) * centerY);
        ctx.stroke();
    }
}

function drawSingleAngle() {
    const angle = state.mainAngle;
    const rad = AngleConverter.degreesToRadians(angle);
    
    // 绘制角度弧
    ctx.strokeStyle = '#3498db';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(0, 0, 80, 0, -rad, rad < 0);
    ctx.stroke();
    
    // 绘制射线
    ctx.strokeStyle = '#2c3e50';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(Math.cos(rad) * 100, Math.sin(rad) * 100);
    ctx.stroke();
    
    // 标签
    if (document.getElementById('showLabels').checked) {
        ctx.fillStyle = '#3498db';
        ctx.font = 'bold 14px Arial';
        ctx.fillText(`${angle}°`, Math.cos(rad) * 90, Math.sin(rad) * 90);
    }
}

function drawAddition() {
    const angle1 = state.mainAngle;
    const angle2 = state.helperAngle;
    const sum = AngleCalculator.add(angle1, angle2);
    
    // 绘制两个角度
    ctx.strokeStyle = '#3498db';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(0, 0, 60, 0, -AngleConverter.degreesToRadians(angle1), angle1 < 0);
    ctx.stroke();
    
    ctx.strokeStyle = '#e74c3c';
    ctx.beginPath();
    ctx.arc(0, 0, 80, -AngleConverter.degreesToRadians(angle1), 
            -AngleConverter.degreesToRadians(angle1 + angle2), angle2 < 0);
    ctx.stroke();
    
    // 绘制结果
    ctx.strokeStyle = '#27ae60';
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.moveTo(0, 0);
    const rad = AngleConverter.degreesToRadians(sum);
    ctx.lineTo(Math.cos(rad) * 110, Math.sin(rad) * 110);
    ctx.stroke();
    
    if (document.getElementById('showLabels').checked) {
        ctx.fillStyle = '#27ae60';
        ctx.font = 'bold 14px Arial';
        ctx.fillText(`${angle1}° + ${angle2}° = ${sum}°`, -60, -100);
    }
}

function drawSubtraction() {
    const angle1 = state.mainAngle;
    const angle2 = state.helperAngle;
    const diff = AngleCalculator.subtract(angle1, angle2);
    
    // 绘制角度1
    ctx.strokeStyle = '#3498db';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(0, 0, 60, 0, -AngleConverter.degreesToRadians(angle1), angle1 < 0);
    ctx.stroke();
    
    // 绘制角度2(反向)
    ctx.strokeStyle = '#e74c3c';
    ctx.beginPath();
    ctx.arc(0, 0, 80, -AngleConverter.degreesToRadians(angle1), 
            -AngleConverter.degreesToRadians(angle1 - angle2), true);
    ctx.stroke();
    
    // 绘制结果
    ctx.strokeStyle = '#27ae60';
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.moveTo(0, 0);
    const rad = AngleConverter.degreesToRadians(diff);
    ctx.lineTo(Math.cos(rad) * 110, Math.sin(rad) * 110);
    ctx.stroke();
    
    if (document.getElementById('showLabels').checked) {
        ctx.fillStyle = '#27ae60';
        ctx.font = 'bold 14px Arial';
        ctx.fillText(`${angle1}° - ${angle2}° = ${diff}°`, -60, -100);
    }
}

function drawRotation() {
    const angle = state.mainAngle;
    
    // 绘制原始点
    ctx.fillStyle = '#3498db';
    ctx.beginPath();
    ctx.arc(state.point.x, state.point.y, 5, 0, Math.PI * 2);
    ctx.fill();
    
    // 计算旋转后的点
    const rad = AngleConverter.degreesToRadians(angle);
    const rotatedX = state.point.x * Math.cos(rad) - state.point.y * Math.sin(rad);
    const rotatedY = state.point.x * Math.sin(rad) + state.point.y * Math.cos(rad);
    
    // 绘制旋转后的点
    ctx.fillStyle = '#e74c3c';
    ctx.beginPath();
    ctx.arc(rotatedX, rotatedY, 5, 0, Math.PI * 2);
    ctx.fill();
    
    // 绘制旋转弧
    ctx.strokeStyle = '#95a5a6';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.arc(0, 0, 40, 0, -rad, rad < 0);
    ctx.stroke();
    
    if (document.getElementById('showLabels').checked) {
        ctx.fillStyle = '#3498db';
        ctx.font = '12px Arial';
        ctx.fillText(`原点: (${Math.round(state.point.x)}, ${Math.round(state.point.y)})`, state.point.x + 10, state.point.y - 10);
        
        ctx.fillStyle = '#e74c3c';
        ctx.fillText(`旋转后: (${Math.round(rotatedX)}, ${Math.round(rotatedY)})`, rotatedX + 10, rotatedY - 10);
        
        ctx.fillStyle = '#2c3e50';
        ctx.fillText(`旋转: ${angle}°`, -20, -80);
    }
}

function drawTriangle() {
    const angle = state.mainAngle;
    
    // 绘制直角三角形
    const hypotenuse = 80;
    const adjacent = hypotenuse * TrigonometricCalculator.cos(angle);
    const opposite = hypotenuse * TrigonometricCalculator.sin(angle);
    
    ctx.strokeStyle = '#2c3e50';
    ctx.lineWidth = 2;
    
    // 绘制三角形
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(adjacent, 0);
    ctx.lineTo(adjacent, opposite);
    ctx.closePath();
    ctx.stroke();
    
    // 填充角度区域
    ctx.fillStyle = 'rgba(52, 152, 219, 0.2)';
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(30, 0);
    ctx.arc(0, 0, 30, 0, -AngleConverter.degreesToRadians(angle), angle < 0);
    ctx.closePath();
    ctx.fill();
    
    // 显示三角函数值
    if (document.getElementById('showLabels').checked) {
        ctx.fillStyle = '#2c3e50';
        ctx.font = '12px Arial';
        ctx.fillText(`sin(${angle}°) = ${TrigonometricCalculator.sin(angle).toFixed(3)}`, -80, -60);
        ctx.fillText(`cos(${angle}°) = ${TrigonometricCalculator.cos(angle).toFixed(3)}`, -80, -45);
        ctx.fillText(`tan(${angle}°) = ${TrigonometricCalculator.tan(angle).toFixed(3)}`, -80, -30);
        
        // 边长标注
        ctx.fillStyle = '#e74c3c';
        ctx.fillText(`对边: ${opposite.toFixed(1)}`, adjacent + 5, opposite / 2);
        ctx.fillStyle = '#3498db';
        ctx.fillText(`邻边: ${adjacent.toFixed(1)}`, adjacent / 2, -5);
    }
}

// 事件处理
function setupEventListeners() {
    // 滑块和输入框同步
    const syncInput = (sliderId, inputId, stateKey) => {
        const slider = document.getElementById(sliderId);
        const input = document.getElementById(inputId);
        
        slider.addEventListener('input', (e) => {
            const value = parseFloat(e.target.value);
            input.value = value;
            state[stateKey] = value;
            draw();
            updateInfo();
        });
        
        input.addEventListener('input', (e) => {
            const value = parseFloat(e.target.value);
            slider.value = value;
            state[stateKey] = value;
            draw();
            updateInfo();
        });
    };

    syncInput('mainAngle', 'mainAngleInput', 'mainAngle');
    syncInput('helperAngle', 'helperAngleInput', 'helperAngle');
    syncInput('rotationSpeed', 'rotationSpeedInput', 'rotationSpeed');

    // 模式选择
    document.getElementById('operationMode').addEventListener('change', (e) => {
        state.mode = e.target.value;
        draw();
        updateInfo();
    });

    // 画布点击
    canvas.addEventListener('click', (e) => {
        const rect = canvas.getBoundingClientRect();
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;
        
        state.clickPoint = { x, y };
        
        // 计算点击位置的角度
        const dx = x - centerX;
        const dy = y - centerY;
        const angle = TrigonometricCalculator.atan2(dy, dx);
        
        // 更新主角度
        state.mainAngle = AngleCalculator.normalize(angle);
        document.getElementById('mainAngle').value = state.mainAngle;
        document.getElementById('mainAngleInput').value = state.mainAngle;
        
        draw();
        updateInfo();
    });

    // 按钮事件
    document.getElementById('resetBtn').addEventListener('click', () => {
        state.mainAngle = 45;
        state.helperAngle = 90;
        state.rotationSpeed = 0;
        state.clickPoint = null;
        state.isAnimating = false;
        
        document.getElementById('mainAngle').value = 45;
        document.getElementById('mainAngleInput').value = 45;
        document.getElementById('helperAngle').value = 90;
        document.getElementById('helperAngleInput').value = 90;
        document.getElementById('rotationSpeed').value = 0;
        document.getElementById('rotationSpeedInput').value = 0;
        
        draw();
        updateInfo();
    });

    document.getElementById('animateBtn').addEventListener('click', () => {
        state.isAnimating = !state.isAnimating;
        document.getElementById('animateBtn').textContent = state.isAnimating ? '停止动画' : '开始动画';
        if (state.isAnimating) {
            state.lastTime = performance.now();
            animate();
        }
    });

    document.getElementById('snapBtn').addEventListener('click', () => {
        const snapped = Math.round(state.mainAngle / 45) * 45;
        state.mainAngle = snapped;
        document.getElementById('mainAngle').value = snapped;
        document.getElementById('mainAngleInput').value = snapped;
        draw();
        updateInfo();
    });

    // 批量处理
    document.getElementById('batchNormalize').addEventListener('click', () => {
        const angles = parseBatchAngles();
        const normalized = angles.map(a => AngleCalculator.normalize(a));
        document.getElementById('batchResult').textContent = 
            `规范化结果: ${normalized.join(', ')}°`;
    });

    document.getElementById('batchAverage').addEventListener('click', () => {
        const angles = parseBatchAngles();
        const avg = AngleCalculator.average(angles);
        document.getElementById('batchResult').textContent = 
            `平均角度: ${avg.toFixed(2)}°\n原始: ${angles.join(', ')}°`;
    });

    document.getElementById('batchClassify').addEventListener('click', () => {
        const angles = parseBatchAngles();
        const classified = angles.map(angle => ({
            angle,
            type: classifyAngle(angle)
        }));
        const result = classified.map(c => `${c.angle}°: ${c.type}`).join('\n');
        document.getElementById('batchResult').textContent = result;
    });

    // 显示选项
    ['showGrid', 'showLabels', 'showVector'].forEach(id => {
        document.getElementById(id).addEventListener('change', draw);
    });
}

function parseBatchAngles() {
    const input = document.getElementById('batchAngles').value;
    return input.split(',').map(s => parseFloat(s.trim())).filter(n => !isNaN(n));
}

function classifyAngle(angle) {
    const normalized = AngleCalculator.normalize(angle);
    if (normalized === 0) return "零角";
    if (normalized === 90) return "直角";
    if (normalized === 180) return "平角";
    if (normalized === 360) return "周角";
    if (normalized > 0 && normalized < 90) return "锐角";
    if (normalized > 90 && normalized < 180) return "钝角";
    if (normalized > 180 && normalized < 270) return "优角";
    return "负锐角";
}

function updateInfo() {
    const info = document.getElementById('angleInfo');
    const result = document.getElementById('calculationResult');
    
    let infoText = `主角度: ${state.mainAngle}°\n`;
    infoText += `辅助角度: ${state.helperAngle}°\n`;
    infoText += `模式: ${state.mode}\n`;
    
    if (state.clickPoint) {
        const dx = state.clickPoint.x - centerX;
        const dy = state.clickPoint.y - centerY;
        const clickAngle = TrigonometricCalculator.atan2(dy, dx);
        infoText += `点击角度: ${AngleCalculator.normalize(clickAngle).toFixed(2)}°\n`;
    }
    
    info.textContent = infoText;
    
    // 计算结果
    let calcText = '';
    switch(state.mode) {
        case 'add':
            const sum = AngleCalculator.add(state.mainAngle, state.helperAngle);
            calcText = `${state.mainAngle}° + ${state.helperAngle}° = ${sum}°`;
            break;
        case 'subtract':
            const diff = AngleCalculator.subtract(state.mainAngle, state.helperAngle);
            calcText = `${state.mainAngle}° - ${state.helperAngle}° = ${diff}°`;
            break;
        case 'rotate':
            const rad = AngleConverter.degreesToRadians(state.mainAngle);
            const rotatedX = state.point.x * Math.cos(rad) - state.point.y * Math.sin(rad);
            const rotatedY = state.point.x * Math.sin(rad) + state.point.y * Math.cos(rad);
            calcText = `旋转 (${state.point.x}, ${state.point.y}) ${state.mainAngle}°\n= (${rotatedX.toFixed(2)}, ${rotatedY.toFixed(2)})`;
            break;
        case 'triangle':
            calcText = `sin(${state.mainAngle}°) = ${TrigonometricCalculator.sin(state.mainAngle).toFixed(4)}\n`;
            calcText += `cos(${state.mainAngle}°) = ${TrigonometricCalculator.cos(state.mainAngle).toFixed(4)}\n`;
            calcText += `tan(${state.mainAngle}°) = ${TrigonometricCalculator.tan(state.mainAngle).toFixed(4)}`;
            break;
        default:
            calcText = `当前角度: ${state.mainAngle}°\n分类: ${classifyAngle(state.mainAngle)}`;
    }
    
    result.textContent = calcText;
}

function animate() {
    if (!state.isAnimating) return;
    
    const currentTime = performance.now();
    const deltaTime = (currentTime - state.lastTime) / 1000; // 转换为秒
    state.lastTime = currentTime;
    
    // 更新角度
    if (state.rotationSpeed !== 0) {
        state.mainAngle = AngleCalculator.add(state.mainAngle, state.rotationSpeed * deltaTime);
        document.getElementById('mainAngle').value = state.mainAngle;
        document.getElementById('mainAngleInput').value = Math.round(state.mainAngle);
    }
    
    draw();
    updateInfo();
    
    requestAnimationFrame(animate);
}

// 初始化
setupEventListeners();
draw();
updateInfo();

</script>

</body>
</html>

5.2 学习路径建议

初学者阶段(1-2周)

  1. 掌握基础概念

    • 理解角度的定义和单位
    • 熟练掌握度与弧度的转换
    • 识别不同类型的角
  2. 基础运算练习

    • 角度的加减法
    • 角度的规范化
    • 简单的三角函数计算
  3. 实践项目

    • 编写角度转换器
    • 创建简单的旋转动画
    • 计算两点间的角度

中级阶段(2-4周)

  1. 进阶数学

    • 深入理解三角函数
    • 掌握向量与角度的关系
    • 学习旋转矩阵
  2. 应用开发

    • 实现UI旋转动画
    • 开发简单的游戏瞄准系统
    • 创建角度可视化工具
  3. 性能优化

    • 学习缓存技术
    • 掌握批量计算方法
    • 了解Web Workers

高级阶段(4周以上)

  1. 复杂场景处理

    • 多边形角度计算
    • 3D空间中的角度
    • 物理模拟中的角度
  2. 算法优化

    • 高精度计算
    • 并行处理
    • SIMD优化
  3. 实际项目

    • 游戏引擎开发
    • CAD软件开发
    • 机器人导航系统

5.3 推荐学习资源

在线工具

  • Desmos: 用于可视化角度和三角函数
  • GeoGebra: 交互式几何学习
  • Wolfram Alpha: 角度计算和验证

开源库

  • Three.js: 3D角度和旋转
  • Paper.js: 2D图形和角度
  • Math.js: 数学计算库

书籍推荐

  • 《3D数学基础:图形与游戏开发》
  • 《计算机图形学原理及实践》
  • 《算法导论》(几何算法部分)

5.4 常见面试问题

  1. 如何处理角度的循环性?

    • 使用模运算规范化
    • 计算最短路径差值
    • 插值时考虑方向
  2. 如何优化大量角度计算?

    • 预计算三角函数
    • 使用TypedArray
    • 并行处理
  3. 浮点精度问题如何解决?

    • 使用容差比较
    • 避免累积误差
    • 采用高精度数据类型

结论

角度计算是计算机科学中的基础技能,掌握它对于图形学、游戏开发、机器人学等领域至关重要。通过本文的学习路径,从基础概念到高级应用,配合详细的代码示例和实用技巧,您应该能够:

  1. 熟练处理角度的基本运算和转换
  2. 应用角度解决实际问题
  3. 优化角度计算的性能
  4. 处理复杂场景下的角度问题

记住,理论学习必须配合大量实践。建议您按照学习路径,逐步完成每个阶段的练习和项目,最终形成完整的知识体系。角度的世界既精确又美妙,祝您在学习中发现更多乐趣!