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

1060

被折叠的 条评论
为什么被折叠?



