四旋翼无人机的动力核心:电机与桨叶的科学原理——仿真模拟器代码

对于初学者来说,无人机不再是简单的玩具,而是集机械、电子和空气动力学于一体的科技产品。本文将深入解析四旋翼无人机的动力系统,包括电机类型、桨叶参数及其科学原理,通过可理解的理论计算和互动模拟,帮助你掌握无人机飞行的核心奥秘。

一、无人机的 "心脏":电机系统

1.1 从有刷到无刷:电机技术的进化

无人机的电机经历了从有刷到无刷的技术飞跃。有刷电机结构简单,就像玩具车里的小马达,通过电刷和换向器传递电流,但存在摩擦大、效率低的缺点。而无刷直流电机(BLDC) 则采用电子换向技术,彻底解决了这些问题。

无刷电机的优势可以用三个物理量来量化:

  • 效率:无刷电机效率可达 85%-90%,比有刷电机高 20%-30%
  • 寿命:无刷电机寿命可达数万小时,是有刷电机的 10 倍以上
  • 响应速度:无刷电机的电流变化率更快,能在毫秒级时间内调整转速

这就像自行车从普通刹车升级到碟刹,不仅更省力,制动效果也更精准。

1.2 无刷电机的关键参数:KV 值的物理意义

KV 值是无刷电机最重要的参数,定义为 "电机在 1 伏特电压下,空载时的每分钟转速"。计算公式为:

实际转速 = KV值 × 工作电压

例如:3000KV 的电机在 12V 电压下工作时,理论转速为 3000×12=36000 转 / 分钟。这个转速有多快?换算成每秒就是 600 转,相当于每秒能完成 300 次完整的圆周运动!

不同 KV 值的电机适用场景不同:

  • 高 KV 电机(2500KV 以上):转速快,适合小桨叶,用于竞速无人机
  • 中 KV 电机(1500-2500KV):平衡了转速和扭矩,适合大多数航拍无人机
  • 低 KV 电机(1500KV 以下):转速慢但扭矩大,适合大桨叶,用于载重无人机

选择电机时要考虑 "扭矩 - 转速平衡":高 KV 电机转速快但扭矩小,就像短跑运动员擅长冲刺但不适合举重;低 KV 电机则相反,类似举重运动员力量大但速度慢。

1.3 电机尺寸与功率的关系

电机尺寸通常用 "直径 × 长度" 表示(单位:毫米),如 2208 电机即直径 22mm、长度 8mm。功率与尺寸的关系近似为:

功率 ∝ 直径² × 长度

这意味着直径增加 1 倍,功率可增加 4 倍。2208 电机的功率约为 20-30 瓦,而 3510 电机功率可达 100-150 瓦,足以带动 1 公斤以上的载荷。

二、无人机的 "翅膀":桨叶的空气动力学

2.1 桨叶参数的科学解读

桨叶的核心参数是长度(L)螺距(P),通常用四位数字表示,如 5045 桨叶:

  • 前两位 "50" 表示长度 5.0 英寸(1 英寸 = 25.4mm)
  • 后两位 "45" 表示螺距 4.5 英寸

螺距是个关键概念,它是桨叶旋转一圈理论上前进的距离,就像螺丝旋转一圈前进的距离。当桨叶以角速度 ω 旋转时,前进速度 v 与螺距的关系为:

v = (P × ω) / (2π)

其中 ω 的单位是弧度 / 分钟(1 转 = 2π 弧度)。这个公式表明,在相同转速下,螺距越大,无人机的前进速度越快。

2.2 桨叶效率的影响因素

桨叶产生的升力可以用简化的空气动力学公式表示:

升力(L) = 0.5 × ρ × v² × S × C_L

  • ρ:空气密度(标准状态下约 1.225kg/m³)
  • v:桨叶弦线处的线速度
  • S:桨叶总面积
  • C_L:升力系数(与桨叶形状和攻角相关)

对于旋转的桨叶,线速度 v 随半径变化(v=ω×r),因此总升力需要通过积分计算。简化后,四旋翼无人机的总升力与各参数的关系为:

总升力 ∝ n² × L⁴ × P

  • n:电机转速(转 / 秒)
  • L:桨叶长度
  • P:螺距

这个公式揭示了一个重要规律:桨叶长度对升力的影响最大(四次方关系),这就是为什么载重无人机通常采用大尺寸桨叶。

2.3 桨叶与电机的匹配原则

电机和桨叶的匹配需要满足功率平衡方程:

电机输出功率 = 桨叶消耗功率

桨叶消耗功率的经验公式为:

功率(P) = K × n³ × L⁴ × P

其中 K 是比例常数(与空气密度等因素相关)。这个公式表明:

  • 转速提高 1 倍,功率需求增加 8 倍(立方关系)
  • 桨叶长度增加 1 倍,功率需求增加 16 倍(四次方关系)

这就是为什么高速无人机需要大功率电机,而大桨叶无人机更注重扭矩而非转速。

三、四旋翼的平衡控制原理

四旋翼无人机通过调整四个电机的转速实现飞行控制,其基本原理基于牛顿第三定律(作用力与反作用力)。

3.1 基本飞行姿态控制

  • 垂直升降:同时增加 / 减小四个电机转速,改变总升力
  • 前后飞行:调整前后电机转速差(如前减后增使机身前倾)
  • 左右飞行:调整左右电机转速差
  • 偏航:利用反扭矩(顺时针旋转的电机与逆时针旋转的电机转速差)

以偏航运动为例,当左上角和右下角电机(顺时针旋转)转速大于另外两个(逆时针旋转)时,顺时针方向的扭矩占优,无人机将向右偏航。

3.2 平衡控制的数学基础

四旋翼的姿态控制可以用欧拉角(俯仰角 θ、横滚角 φ、偏航角 ψ)描述,其动力学方程为:

∑F = m × a

∑M = I × β

  • ∑F:合力(决定上升 / 下降加速度)
  • ∑M:合扭矩(决定角加速度)
  • I:转动惯量
  • β:角加速度

飞控系统通过陀螺仪实时检测姿态变化,再根据这些方程计算需要调整的电机转速,形成闭环控制。这个过程每秒要进行数百次,才能保证无人机的稳定飞行。

四、互动模拟:参数变化对飞行的影响

下面的 3D 模拟器将帮助你直观理解各参数对无人机飞行的影响。通过调整滑块,你可以观察电机转速、桨叶长度和螺距变化时,无人机的高度、速度和姿态如何变化。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>四旋翼无人机参数模拟器</title>
    <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
            background: linear-gradient(135deg, #1a2a6c, #2c3e50);
            color: #fff;
            overflow: hidden;
            height: 100vh;
            display: flex;
            flex-direction: column;
        }

        .header {
            background: rgba(0, 0, 0, 0.7);
            padding: 15px 30px;
            text-align: center;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
            z-index: 10;
        }

        .header h1 {
            font-size: 2.2rem;
            margin-bottom: 5px;
            color: #4fc3f7;
            text-shadow: 0 0 10px rgba(79, 195, 247, 0.5);
        }

        .header p {
            font-size: 1rem;
            opacity: 0.8;
            max-width: 800px;
            margin: 0 auto;
            line-height: 1.5;
        }

        .container {
            display: flex;
            flex: 1;
            overflow: hidden;
            padding: 15px;
            gap: 15px;
        }

        .controls {
            width: 320px;
            background: rgba(25, 35, 60, 0.8);
            border-radius: 15px;
            padding: 20px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
            backdrop-filter: blur(10px);
            border: 1px solid rgba(79, 195, 247, 0.2);
            overflow-y: auto;
            display: flex;
            flex-direction: column;
            gap: 25px;
        }

        .control-group {
            background: rgba(20, 30, 50, 0.7);
            border-radius: 12px;
            padding: 20px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
            border: 1px solid rgba(79, 195, 247, 0.15);
        }

        .control-group h3 {
            color: #4fc3f7;
            margin-bottom: 15px;
            font-size: 1.3rem;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .control-group h3::before {
            content: "›";
            font-size: 1.5rem;
        }

        label {
            display: block;
            margin: 15px 0 8px;
            color: #bbdefb;
            font-size: 0.95rem;
        }

        input[type="range"] {
            width: 100%;
            height: 8px;
            -webkit-appearance: none;
            background: rgba(100, 150, 200, 0.2);
            border-radius: 10px;
            outline: none;
        }

        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 20px;
            height: 20px;
            background: #4fc3f7;
            border-radius: 50%;
            cursor: pointer;
            box-shadow: 0 0 10px rgba(79, 195, 247, 0.8);
        }

        .value-display {
            background: rgba(0, 30, 60, 0.5);
            padding: 12px 15px;
            border-radius: 8px;
            margin-top: 8px;
            font-weight: 600;
            color: #e3f2fd;
            display: flex;
            justify-content: space-between;
            border: 1px solid rgba(79, 195, 247, 0.2);
        }

        .value-display .value {
            color: #4fc3f7;
            font-weight: bold;
        }

        .physics-info {
            background: rgba(20, 40, 80, 0.7);
            border-radius: 12px;
            padding: 20px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
            border: 1px solid rgba(79, 195, 247, 0.15);
        }

        .physics-info h3 {
            color: #4fc3f7;
            margin-bottom: 15px;
            font-size: 1.3rem;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .physics-info h3::before {
            content: "›";
            font-size: 1.5rem;
        }

        .formula {
            background: rgba(0, 20, 40, 0.6);
            padding: 15px;
            border-radius: 8px;
            margin: 12px 0;
            font-family: 'Courier New', monospace;
            color: #bbdefb;
            border: 1px solid rgba(79, 195, 247, 0.2);
            line-height: 1.6;
        }

        .note {
            font-size: 0.85rem;
            color: #90a4ae;
            margin-top: 5px;
            font-style: italic;
        }

        .status-panel {
            background: rgba(20, 30, 50, 0.7);
            border-radius: 12px;
            padding: 20px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
            border: 1px solid rgba(79, 195, 247, 0.15);
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 15px;
        }

        .status-item {
            background: rgba(0, 30, 60, 0.5);
            padding: 15px;
            border-radius: 8px;
            text-align: center;
            border: 1px solid rgba(79, 195, 247, 0.2);
        }

        .status-item h4 {
            color: #90a4ae;
            font-size: 0.9rem;
            margin-bottom: 8px;
        }

        .status-value {
            font-size: 1.4rem;
            font-weight: bold;
            color: #4fc3f7;
        }

        .status-unit {
            font-size: 0.9rem;
            color: #bbdefb;
            margin-left: 3px;
        }

        #simulation {
            flex: 1;
            position: relative;
            border-radius: 15px;
            overflow: hidden;
            box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4);
            border: 1px solid rgba(79, 195, 247, 0.3);
        }

        #instructions {
            position: absolute;
            bottom: 20px;
            left: 20px;
            background: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 15px 20px;
            border-radius: 10px;
            font-size: 0.9rem;
            max-width: 320px;
            line-height: 1.6;
            backdrop-filter: blur(5px);
            border: 1px solid rgba(79, 195, 247, 0.3);
        }

        #instructions strong {
            color: #4fc3f7;
            display: block;
            margin-bottom: 8px;
            font-size: 1.1rem;
        }

        .propeller {
            position: absolute;
            width: 100%;
            height: 100%;
            pointer-events: none;
        }

        .propeller-circle {
            position: absolute;
            border-radius: 50%;
            border: 2px dashed rgba(79, 195, 247, 0.7);
            transform: translate(-50%, -50%);
        }

        @media (max-width: 1100px) {
            .container {
                flex-direction: column;
            }
            .controls {
                width: 100%;
                max-height: 40vh;
            }
            #simulation {
                min-height: 50vh;
            }
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>四旋翼无人机参数模拟器</h1>
        <p>调整参数实时观察无人机性能变化 - 基于Three.js的物理模拟系统</p>
    </div>

    <div class="container">
        <div class="controls">
            <div class="control-group">
                <h3>电机参数</h3>
                <label for="kv">电机KV值 (1000-3000)</label>
                <input type="range" id="kv" min="1000" max="3000" value="2200" step="100">
                <div class="value-display">
                    <span>当前KV值:</span>
                    <span class="value" id="kv-value">2200</span>
                </div>

                <label for="voltage">电池电压 (7.4-14.8V)</label>
                <input type="range" id="voltage" min="74" max="148" value="111" step="1">
                <div class="value-display">
                    <span>当前电压:</span>
                    <span class="value" id="voltage-value">11.1V</span>
                </div>

                <label for="rpm">电机转速</label>
                <div class="value-display">
                    <span>计算值:</span>
                    <span class="value" id="rpm-value">24420 RPM</span>
                </div>
            </div>

            <div class="control-group">
                <h3>桨叶参数</h3>
                <label for="length">桨叶长度 (4.0-7.0英寸)</label>
                <input type="range" id="length" min="40" max="70" value="50" step="1">
                <div class="value-display">
                    <span>当前长度:</span>
                    <span class="value" id="length-value">5.0英寸</span>
                </div>

                <label for="pitch">桨叶螺距 (3.0-6.0英寸)</label>
                <input type="range" id="pitch" min="30" max="60" value="45" step="1">
                <div class="value-display">
                    <span>当前螺距:</span>
                    <span class="value" id="pitch-value">4.5英寸</span>
                </div>
            </div>

            <div class="status-panel">
                <div class="status-item">
                    <h4>升力</h4>
                    <div class="status-value" id="lift-value">4.92<span class="status-unit">N</span></div>
                </div>
                <div class="status-item">
                    <h4>高度</h4>
                    <div class="status-value" id="height-value">5.0<span class="status-unit">m</span></div>
                </div>
                <div class="status-item">
                    <h4>前进速度</h4>
                    <div class="status-value" id="speed-value">18.6<span class="status-unit">km/h</span></div>
                </div>
                <div class="status-item">
                    <h4>功耗</h4>
                    <div class="status-value" id="power-value">112<span class="status-unit">W</span></div>
                </div>
            </div>

            <div class="physics-info">
                <h3>物理公式参考</h3>
                <div>1. 电机转速计算:</div>
                <div class="formula">转速(RPM) = KV值 × 电压(V)</div>
                
                <div>2. 升力与参数关系:</div>
                <div class="formula">升力 ∝ 转速² × 桨长⁴ × 螺距</div>
                
                <div>3. 前进速度估算:</div>
                <div class="formula">速度 ≈ (螺距 × 转速) / 1056</div>
                <div class="note">单位转换: 英寸/分钟 → 公里/小时</div>
                
                <div>4. 功耗估算:</div>
                <div class="formula">功率(W) = (电压² × 0.0005) + (转速 × 0.002)</div>
            </div>
        </div>

        <div id="simulation">
            <div id="instructions">
                <strong>操作提示:</strong>
                - 鼠标拖动: 旋转视角<br>
                - 滚轮: 缩放视图<br>
                - WASD键: 移动相机<br>
                - 空格键: 重置无人机位置<br>
                观察参数变化时,注意无人机的高度、速度和姿态变化
            </div>
        </div>
    </div>

    <script>
        // 初始化Three.js场景
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0x0a192f);
        scene.fog = new THREE.Fog(0x0a192f, 20, 100);

        // 创建地面
        const groundGeometry = new THREE.PlaneGeometry(100, 100);
        const groundMaterial = new THREE.MeshPhongMaterial({
            color: 0x1e3c5a,
            side: THREE.DoubleSide
        });
        const ground = new THREE.Mesh(groundGeometry, groundMaterial);
        ground.rotation.x = -Math.PI / 2;
        ground.position.y = -0.1;
        scene.add(ground);

        // 添加网格和坐标轴
        const gridHelper = new THREE.GridHelper(50, 50, 0x2a4a6c, 0x1e3c5a);
        scene.add(gridHelper);

        // 添加光源
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
        scene.add(ambientLight);

        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
        directionalLight.position.set(10, 20, 15);
        directionalLight.castShadow = true;
        scene.add(directionalLight);

        // 相机设置
        const container = document.getElementById('simulation');
        const camera = new THREE.PerspectiveCamera(60, container.clientWidth / container.clientHeight, 0.1, 1000);
        camera.position.set(15, 10, 15);

        // 渲染器设置
        const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
        renderer.setSize(container.clientWidth, container.clientHeight);
        renderer.shadowMap.enabled = true;
        container.appendChild(renderer.domElement);

        // 控制器
        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;
        controls.screenSpacePanning = false;
        controls.minDistance = 5;
        controls.maxDistance = 50;

        // 创建无人机模型
        function createDrone() {
            const droneGroup = new THREE.Group();
            droneGroup.name = "drone";
            
            // 机身
            const bodyGeometry = new THREE.CylinderGeometry(0.6, 0.6, 0.4, 8);
            const bodyMaterial = new THREE.MeshPhongMaterial({ 
                color: 0xe74c3c,
                shininess: 80
            });
            const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
            body.position.y = 0.2;
            body.castShadow = true;
            droneGroup.add(body);

            // 机臂
            const armGeometry = new THREE.CylinderGeometry(0.15, 0.15, 5, 6);
            const armMaterial = new THREE.MeshPhongMaterial({ 
                color: 0x34495e,
                shininess: 60
            });
            
            const armPositions = [
                {x: 2.5, z: 2.5, rot: Math.PI/4},    // 前右
                {x: 2.5, z: -2.5, rot: -Math.PI/4},  // 后右
                {x: -2.5, z: -2.5, rot: -3*Math.PI/4}, // 后左
                {x: -2.5, z: 2.5, rot: 3*Math.PI/4}   // 前左
            ];

            const arms = [];
            armPositions.forEach(pos => {
                const arm = new THREE.Mesh(armGeometry, armMaterial);
                arm.position.set(pos.x, 0, pos.z);
                arm.rotation.y = pos.rot;
                arm.castShadow = true;
                droneGroup.add(arm);
                arms.push(arm);

                // 电机座
                const motorHousing = new THREE.Mesh(
                    new THREE.CylinderGeometry(0.4, 0.4, 0.3, 16),
                    new THREE.MeshPhongMaterial({ color: 0x2c3e50 })
                );
                motorHousing.position.set(pos.x, 0.25, pos.z);
                motorHousing.rotation.x = Math.PI / 2;
                droneGroup.add(motorHousing);

                // 螺旋桨
                const propeller = new THREE.Mesh(
                    new THREE.CylinderGeometry(1.2, 1.2, 0.05, 8),
                    new THREE.MeshPhongMaterial({ 
                        color: 0x95a5a6,
                        emissive: 0x222222,
                        side: THREE.DoubleSide
                    })
                );
                propeller.position.set(pos.x, 0.5, pos.z);
                propeller.userData.originalY = 0.5;
                propeller.name = `propeller-${droneGroup.children.length}`;
                droneGroup.add(propeller);
            });

            // 添加相机和传感器(装饰)
            const cameraBox = new THREE.Mesh(
                new THREE.BoxGeometry(0.4, 0.3, 0.5),
                new THREE.MeshPhongMaterial({ color: 0x1a1a1a })
            );
            cameraBox.position.set(0, 0.2, 1.2);
            droneGroup.add(cameraBox);

            const lens = new THREE.Mesh(
                new THREE.CylinderGeometry(0.1, 0.1, 0.1, 16),
                new THREE.MeshPhongMaterial({ color: 0x1e88e5 })
            );
            lens.position.set(0, 0.2, 1.45);
            lens.rotation.x = Math.PI / 2;
            droneGroup.add(lens);

            droneGroup.position.set(0, 5, 0);
            scene.add(droneGroup);
            
            return droneGroup;
        }

        // 创建环境
        function createEnvironment() {
            // 添加一些随机树木
            const treeGeometry = new THREE.ConeGeometry(2, 5, 8);
            const treeMaterial = new THREE.MeshPhongMaterial({ color: 0x2e7d32 });
            
            for (let i = 0; i < 20; i++) {
                const tree = new THREE.Mesh(treeGeometry, treeMaterial);
                const angle = Math.random() * Math.PI * 2;
                const radius = 25 + Math.random() * 25;
                tree.position.set(
                    Math.cos(angle) * radius,
                    2.5,
                    Math.sin(angle) * radius
                );
                tree.castShadow = true;
                scene.add(tree);
                
                // 树干
                const trunk = new THREE.Mesh(
                    new THREE.CylinderGeometry(0.4, 0.4, 3, 8),
                    new THREE.MeshPhongMaterial({ color: 0x5d4037 })
                );
                trunk.position.set(tree.position.x, -1.5, tree.position.z);
                trunk.castShadow = true;
                scene.add(trunk);
            }
            
            // 添加山脉背景
            const mountainGroup = new THREE.Group();
            for (let i = 0; i < 5; i++) {
                const mountain = new THREE.Mesh(
                    new THREE.ConeGeometry(15, 20 + Math.random() * 15, 4, 1),
                    new THREE.MeshPhongMaterial({ 
                        color: 0x37474f,
                        flatShading: true
                    })
                );
                mountain.position.set(
                    -40 + i * 20,
                    -10,
                    -40
                );
                mountain.rotation.y = Math.PI / 4;
                mountainGroup.add(mountain);
            }
            mountainGroup.position.y = -0.1;
            scene.add(mountainGroup);
        }

        // 创建无人机和场景
        const drone = createDrone();
        createEnvironment();

        // 获取DOM元素
        const kvSlider = document.getElementById('kv');
        const voltageSlider = document.getElementById('voltage');
        const lengthSlider = document.getElementById('length');
        const pitchSlider = document.getElementById('pitch');
        
        const kvValue = document.getElementById('kv-value');
        const voltageValue = document.getElementById('voltage-value');
        const rpmValue = document.getElementById('rpm-value');
        const lengthValue = document.getElementById('length-value');
        const pitchValue = document.getElementById('pitch-value');
        
        const liftValue = document.getElementById('lift-value');
        const heightValue = document.getElementById('height-value');
        const speedValue = document.getElementById('speed-value');
        const powerValue = document.getElementById('power-value');

        // 初始参数
        let params = {
            kv: 2200,
            voltage: 11.1,
            length: 5.0,
            pitch: 4.5,
            rpm: 24420,
            lift: 4.92,
            height: 5.0,
            speed: 0,
            power: 112
        };

        // 更新参数显示
        function updateDisplays() {
            kvValue.textContent = params.kv;
            voltageValue.textContent = params.voltage.toFixed(1) + 'V';
            rpmValue.textContent = params.rpm.toLocaleString() + ' RPM';
            lengthValue.textContent = params.length.toFixed(1) + '英寸';
            pitchValue.textContent = params.pitch.toFixed(1) + '英寸';
            
            liftValue.innerHTML = params.lift.toFixed(2) + '<span class="status-unit">N</span>';
            heightValue.innerHTML = params.height.toFixed(1) + '<span class="status-unit">m</span>';
            speedValue.innerHTML = params.speed.toFixed(1) + '<span class="status-unit">km/h</span>';
            powerValue.innerHTML = params.power.toFixed(0) + '<span class="status-unit">W</span>';
        }

        // 计算无人机参数
        function calculateParams() {
            // 计算转速
            params.rpm = params.kv * params.voltage;
            
            // 计算升力 (基于转速、桨叶长度和螺距)
            const liftFactor = Math.pow(params.rpm / 25000, 2) * 
                              Math.pow(params.length / 5.0, 4) * 
                              (params.pitch / 4.5);
            params.lift = 4.9 * liftFactor; // 4.9N 相当于0.5kg物体的重力
            
            // 计算速度 (km/h)
            params.speed = (params.pitch * params.rpm) / 1056;
            
            // 计算功耗
            params.power = (Math.pow(params.voltage, 2) * 0.0005) + (params.rpm * 0.002);
            
            // 更新显示
            updateDisplays();
        }

        // 事件监听器
        kvSlider.addEventListener('input', function() {
            params.kv = parseInt(this.value);
            calculateParams();
        });

        voltageSlider.addEventListener('input', function() {
            params.voltage = parseInt(this.value) / 10;
            calculateParams();
        });

        lengthSlider.addEventListener('input', function() {
            params.length = parseInt(this.value) / 10;
            calculateParams();
        });

        pitchSlider.addEventListener('input', function() {
            params.pitch = parseInt(this.value) / 10;
            calculateParams();
        });

        // 初始化计算
        calculateParams();
        updateDisplays();

        // 键盘控制
        const keyboard = {};
        document.addEventListener('keydown', (event) => {
            keyboard[event.key.toLowerCase()] = true;
            
            // 空格键重置无人机位置
            if (event.key === ' ') {
                drone.position.set(0, 5, 0);
                drone.rotation.set(0, 0, 0);
                params.height = 5.0;
                updateDisplays();
            }
        });

        document.addEventListener('keyup', (event) => {
            keyboard[event.key.toLowerCase()] = false;
        });

        // 动画循环
        let lastTime = 0;
        const droneProps = {
            height: 5.0,
            velocity: 0,
            rotation: 0
        };

        function animate(time) {
            requestAnimationFrame(animate);
            
            // 计算时间增量
            const delta = Math.min(0.1, (time - lastTime) / 1000);
            lastTime = time;
            
            // 更新控制器
            controls.update();
            
            // 更新无人机物理状态
            if (drone) {
                // 计算高度变化(基于升力和重力)
                const gravity = 9.8;
                const mass = 0.5; // kg
                const netForce = params.lift - (mass * gravity);
                const acceleration = netForce / mass;
                
                droneProps.velocity += acceleration * delta;
                droneProps.height += droneProps.velocity * delta;
                
                // 限制高度
                droneProps.height = Math.max(1.0, droneProps.height);
                params.height = droneProps.height;
                
                // 更新无人机位置
                drone.position.y = droneProps.height;
                
                // 更新旋转(基于速度)
                droneProps.rotation += (params.speed / 20) * delta;
                drone.rotation.y = droneProps.rotation;
                
                // 前后倾斜效果(基于加速度)
                const tilt = Math.min(Math.max(acceleration * 0.1, -0.3), 0.3);
                drone.rotation.x = tilt;
                
                // 更新螺旋桨旋转
                drone.children.forEach(child => {
                    if (child.name && child.name.startsWith('propeller')) {
                        child.rotation.y += (params.rpm / 2000) * delta;
                    }
                });
                
                // 更新状态显示
                updateDisplays();
            }
            
            // 相机移动
            const cameraSpeed = 15 * delta;
            if (keyboard['w']) camera.position.z -= cameraSpeed;
            if (keyboard['s']) camera.position.z += cameraSpeed;
            if (keyboard['a']) camera.position.x -= cameraSpeed;
            if (keyboard['d']) camera.position.x += cameraSpeed;
            
            renderer.render(scene, camera);
        }

        // 响应窗口大小变化
        window.addEventListener('resize', function() {
            camera.aspect = container.clientWidth / container.clientHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(container.clientWidth, container.clientHeight);
        });

        // 启动动画
        animate(0);
    </script>
</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值