Harmony Flutter 跨平台开发实战:鸿蒙与音乐律动艺术、混沌理论与奇异吸引子:从洛伦兹到音乐的动态艺术

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


🌀 一、混沌理论:确定性与不可预测性的统一

📚 1.1 混沌理论的诞生

混沌理论是20世纪最重要的科学发现之一,它揭示了看似随机的现象背后可能隐藏着确定性的规律。

历史里程碑

年份 人物 贡献
1890 庞加莱 三体问题中的混沌现象
1963 洛伦兹 发现洛伦兹吸引子,蝴蝶效应
1975 李天岩-约克 首次提出"混沌"术语
1976 罗斯勒 罗斯勒吸引子
1976 费根鲍姆 普适常数发现
1983 格拉斯伯格-普罗卡西亚 GP算法,吸引子重构

洛伦兹的发现

复制代码
1963年,气象学家爱德华·洛伦兹在研究大气对流时,
发现了一个惊人的现象:

初始条件的微小差异,会导致结果的巨大不同。

这就是著名的"蝴蝶效应":

"一只南美洲亚马逊河流域热带雨林中的蝴蝶,
偶尔扇动几下翅膀,可以在两周以后引起
美国得克萨斯州的一场龙卷风。"

------ 爱德华·洛伦兹(1917-2008)

🔬 1.2 混沌系统的特征

混沌系统具有以下核心特征:

1. 对初始条件的敏感依赖性

复制代码
两个初始条件极其接近的点,随着时间演化,
它们的轨迹会指数级分离。

数学描述:
|δZ(t)| ≈ e^(λt)|δZ(0)|

其中 λ 是李雅普诺夫指数:
- λ > 0:混沌系统
- λ < 0:稳定系统
- λ = 0:临界状态

2. 确定性与不可预测性

复制代码
混沌系统是完全确定性的(由确定性方程描述),
但长期行为不可预测。

这不是因为方程有随机性,
而是因为初始条件无法无限精确测量。

示例:
双摆系统 - 完全确定,但长期不可预测

3. 奇异吸引子

复制代码
混沌系统在相空间中的轨迹会被吸引到一个
复杂的几何结构上,称为奇异吸引子。

特点:
- 分形结构(自相似性)
- 非整数维数
- 轨迹永不重复
- 有界但非周期

4. 分岔与倍周期

复制代码
随着参数变化,系统会经历分岔:
周期1 → 周期2 → 周期4 → ... → 混沌

费根鲍姆常数:
δ = 4.669201609...

这是自然界的一个普适常数!

📐 1.3 相空间与吸引子

相空间的概念

复制代码
相空间是描述系统状态的多维空间。

对于三维系统,相空间中的每个点代表:
(x, y, z) → 系统的一个状态

轨迹:系统状态随时间的演化路径

吸引子:轨迹最终收敛到的区域

吸引子类型

类型 维数 特征 示例
固定点 0 稳定平衡态 阻尼振子
极限环 1 周期运动 心跳
极限环面 2 准周期运动 耦合振子
奇异吸引子 分数 混沌运动 洛伦兹

🦋 二、洛伦兹吸引子:混沌的标志

📊 2.1 洛伦兹方程

洛伦兹方程是混沌理论的基石,描述了大气对流的简化模型:

方程组

复制代码
dx/dt = σ(y - x)
dy/dt = x(ρ - z) - y
dz/dt = xy - βz

经典参数:
σ = 10    (普朗特数)
ρ = 28    (瑞利数)
β = 8/3   (几何因子)

当 ρ > 24.74 时,系统进入混沌状态!

参数的物理意义

复制代码
σ (sigma) - 普朗特数:
描述动量扩散与热扩散的比值
σ = ν/α (粘性系数/热扩散系数)

ρ (rho) - 瑞利数:
描述对流强度
ρ = Ra/Rac (实际瑞利数/临界瑞利数)

β (beta) - 几何因子:
与对流层的纵横比相关

🎨 2.2 洛伦兹吸引子的几何特征

吸引子的形状

复制代码
洛伦兹吸引子呈蝴蝶状,有两个"翅膀":

        左翼          右翼
       ╱    ╲        ╱    ╲
      ╱      ╲      ╱      ╲
     ╱   ●    ╲    ╱    ●   ╲
    ╱          ╲  ╱          ╲
   ╱            ╲╱            ╲
  ╱              ●              ╲
 ╱              ╱╲              ╲
╱              ╱  ╲              ╲

轨迹在两个翼之间随机跳跃,
永远不会重复相同的路径!

分形维数

复制代码
洛伦兹吸引子的维数约为 2.06

这意味着它比二维平面"厚"一点,
但比三维空间"薄"很多。

计算方法(盒计数法):
D = lim(log(N(ε))/log(1/ε))
    ε→0

其中 N(ε) 是覆盖吸引子所需的边长为 ε 的盒子数

💻 2.3 洛伦兹吸引子的数值求解

四阶龙格-库塔方法

dart 复制代码
// 洛伦兹方程的数值求解
class LorenzSolver {
  final double sigma, rho, beta;
  
  LorenzSolver({
    this.sigma = 10,
    this.rho = 28,
    this.beta = 8 / 3,
  });
  
  // 计算导数
  List<double> derivatives(List<double> state) {
    final x = state[0], y = state[1], z = state[2];
    return [
      sigma * (y - x),           // dx/dt
      x * (rho - z) - y,         // dy/dt
      x * y - beta * z,          // dz/dt
    ];
  }
  
  // 四阶龙格-库塔积分
  List<double> step(List<double> state, double dt) {
    final k1 = derivatives(state);
  
    final k2 = derivatives([
      state[0] + k1[0] * dt / 2,
      state[1] + k1[1] * dt / 2,
      state[2] + k1[2] * dt / 2,
    ]);
  
    final k3 = derivatives([
      state[0] + k2[0] * dt / 2,
      state[1] + k2[1] * dt / 2,
      state[2] + k2[2] * dt / 2,
    ]);
  
    final k4 = derivatives([
      state[0] + k3[0] * dt,
      state[1] + k3[1] * dt,
      state[2] + k3[2] * dt,
    ]);
  
    return [
      state[0] + (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]) * dt / 6,
      state[1] + (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]) * dt / 6,
      state[2] + (k1[2] + 2 * k2[2] + 2 * k3[2] + k4[2]) * dt / 6,
    ];
  }
}

🔄 三、其他经典奇异吸引子

🌀 3.1 罗斯勒吸引子

罗斯勒吸引子是比洛伦兹更简单的混沌系统:

方程组

复制代码
dx/dt = -y - z
dy/dt = x + ay
dz/dt = b + z(x - c)

经典参数:
a = 0.2
b = 0.2
c = 5.7

特点:
- 只有一个非线性项
- 单螺旋结构
- 更容易分析

几何特征

复制代码
罗斯勒吸引子呈螺旋带状:

         ╱╲
        ╱  ╲
       ╱    ╲
      ╱      ╲
     ╱        ╲
    ╱          ╲
   ╱            ╲
  ╱              ╲
 ╱                ╲
╱                  ╲

轨迹在螺旋上旋转,同时向外扩展,
然后突然跳回内部,重新开始循环。

💫 3.2 陈氏吸引子

陈氏吸引子是洛伦兹系统的变体:

方程组

复制代码
dx/dt = a(y - x)
dy/dt = (c - a)x - xz + cy
dz/dt = xy - bz

经典参数:
a = 40
b = 3
c = 28

特点:
- 双螺旋结构
- 比洛伦兹更复杂的混沌行为
- 由陈关荣于1999年发现

🎭 3.3 埃农映射

埃农映射是二维离散混沌系统:

迭代方程

复制代码
x(n+1) = 1 - a·x(n)² + y(n)
y(n+1) = b·x(n)

经典参数:
a = 1.4
b = 0.3

特点:
- 离散系统
- 分形结构
- 可视化简单

分形结构

复制代码
埃农映射产生的点集具有自相似性:

放大任意局部,都能看到相似的结构:

┌────────────────┐
│  ·  ·    ·  ·  │
│ ·    ·  ·    · │
│   ·    ·    ·  │
│  ·  ·    ·  ·  │
│ ·    ·  ·    · │
└────────────────┘

每次放大都会发现新的细节!

🌊 3.4 双摆混沌

双摆是经典的物理混沌系统:

运动方程

复制代码
θ₁'' = [−g(2m₁+m₂)sinθ₁ − m₂g sin(θ₁−2θ₂) 
        − 2sin(θ₁−θ₂)m₂(θ₂'²L₂ + θ₁'²L₁cos(θ₁−θ₂))]
       / [L₁(2m₁+m₂−m₂cos(2θ₁−2θ₂))]

θ₂'' = [2sin(θ₁−θ₂)(θ₁'²L₁(m₁+m₂) 
        + g(m₁+m₂)cosθ₁ + θ₂'²L₂m₂cos(θ₁−θ₂))]
       / [L₂(2m₁+m₂−m₂cos(2θ₁−2θ₂))]

特点:
- 真实物理系统
- 对初始条件极度敏感
- 轨迹永不重复

🎵 四、混沌与音乐的结合

🎼 4.1 混沌音乐生成

混沌系统可以用来生成音乐:

音高映射

复制代码
将混沌系统的输出映射到音高:

x(t) → 音高 (MIDI 音符)
范围:[−20, 20] → [48, 84] (C3 到 C6)

映射公式:
note = 48 + (x + 20) / 40 * 36

示例:
x = -20 → note = 48 (C3)
x = 0   → note = 66 (F4)
x = 20  → note = 84 (C6)

节奏映射

复制代码
将混沌系统的输出映射到节奏:

z(t) → 音符持续时间
范围:[0, 50] → [八分音符, 全音符]

映射公式:
duration = 0.25 + z / 50 * 0.75 (拍)

示例:
z = 0  → 0.25 拍 (八分音符)
z = 25 → 0.625 拍
z = 50 → 1.0 拍 (全音符)

力度映射

复制代码
将混沌系统的输出映射到力度:

y(t) → 力度 (MIDI velocity)
范围:[−30, 30] → [30, 127]

映射公式:
velocity = 30 + (y + 30) / 60 * 97

示例:
y = -30 → velocity = 30 (弱)
y = 0   → velocity = 78 (中)
y = 30  → velocity = 127 (强)

🎚️ 4.2 音频驱动的混沌可视化

将音频特征映射到混沌系统参数:

能量映射

复制代码
音频能量 → 混沌参数

能量低:ρ = 20 (周期行为)
能量高:ρ = 35 (强混沌)

映射公式:
ρ = 20 + energy * 15

效果:
- 低能量时轨迹稳定
- 高能量时轨迹剧烈变化

频率映射

复制代码
音频频率 → 混沌参数

低频主导:σ = 8 (缓慢变化)
高频主导:σ = 15 (快速变化)

映射公式:
σ = 8 + treble * 7

效果:
- 低频时轨迹平滑
- 高频时轨迹抖动

🌈 4.3 颜色与混沌

轨迹颜色映射

dart 复制代码
// 根据混沌状态计算颜色
Color getChaosColor(double x, double y, double z, double time) {
  // 使用状态变量计算色相
  final hue = (atan2(y, x) * 180 / pi + 180 + time * 10) % 360;
  
  // 使用 z 坐标计算饱和度
  final saturation = 0.5 + (z / 50) * 0.5;
  
  // 使用速度计算亮度
  final speed = sqrt(x * x + y * y + z * z);
  final brightness = 0.5 + (speed / 50) * 0.5;
  
  return HSVColor.fromAHSV(1, hue, saturation, brightness).toColor();
}

📊 五、完整代码实现

以下是完整的可运行代码,可以直接替换到 main.dart 中使用:

dart 复制代码
import 'package:flutter/material.dart';
import 'package:just_audio_ohos/just_audio_ohos.dart';
import 'package:audio_session/audio_session.dart';
import 'dart:math';
import 'dart:typed_data';

void main() {
  runApp(const ChaosApp());
}

class ChaosApp extends StatelessWidget {
  const ChaosApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '混沌吸引子',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.cyan, brightness: Brightness.dark),
        useMaterial3: true,
      ),
      home: const ChaosHomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class ChaosHomePage extends StatelessWidget {
  const ChaosHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('🌀 混沌吸引子'), backgroundColor: Theme.of(context).colorScheme.inversePrimary),
      body: ListView(padding: const EdgeInsets.all(16), children: [
        _buildCard(context, title: '洛伦兹吸引子', description: '蝴蝶效应的经典展示', icon: Icons.flutter_dash, color: Colors.cyan,
            onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const LorenzAttractorDemo()))),
        _buildCard(context, title: '罗斯勒吸引子', description: '单螺旋混沌系统', icon: Icons.all_inclusive, color: Colors.purple,
            onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const RosslerAttractorDemo()))),
        _buildCard(context, title: '陈氏吸引子', description: '双螺旋混沌变体', icon: Icons.sync_alt, color: Colors.orange,
            onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ChenAttractorDemo()))),
        _buildCard(context, title: '埃农映射', description: '离散混沌分形', icon: Icons.scatter_plot, color: Colors.pink,
            onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const HenonMapDemo()))),
        _buildCard(context, title: '音乐混沌', description: '音频驱动的混沌可视化', icon: Icons.music_note, color: Colors.teal,
            onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const MusicChaosDemo()))),
        _buildCard(context, title: '双摆模拟', description: '物理混沌系统', icon: Icons.accessibility, color: Colors.amber,
            onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const DoublePendulumDemo()))),
      ]),
    );
  }

  Widget _buildCard(BuildContext context, {required String title, required String description, required IconData icon, 
      required Color color, required VoidCallback onTap}) {
    return Card(
      margin: const EdgeInsets.only(bottom: 12),
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(16),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Row(children: [
            Container(width: 56, height: 56, decoration: BoxDecoration(color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12)),
                child: Icon(icon, color: color, size: 28)),
            const SizedBox(width: 16),
            Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
              Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
              const SizedBox(height: 4),
              Text(description, style: TextStyle(color: Colors.grey[600], fontSize: 14)),
            ])),
            Icon(Icons.chevron_right, color: Colors.grey[400]),
          ]),
        ),
      ),
    );
  }
}

/// 洛伦兹吸引子演示
class LorenzAttractorDemo extends StatefulWidget {
  const LorenzAttractorDemo({super.key});
  @override
  State<LorenzAttractorDemo> createState() => _LorenzAttractorDemoState();
}

class _LorenzAttractorDemoState extends State<LorenzAttractorDemo> with TickerProviderStateMixin {
  late AnimationController _animController;
  late AudioPlayer _audioPlayer;
  
  double _sigma = 10, _rho = 28, _beta = 8 / 3;
  double _x = 1, _y = 1, _z = 1;
  final List<Offset> _trajectory = [];
  final List<double> _zValues = [];
  int _maxPoints = 5000;
  
  bool _isPlaying = false;
  Duration _position = Duration.zero;
  Duration _duration = Duration.zero;
  double _energy = 0, _bass = 0;
  double _rotationY = 0;
  double _scale = 15;
  
  static const String _audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';

  @override
  void initState() {
    super.initState();
    _initAudio();
    _animController = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
    _animController.addListener(_update);
  }
  
  Future<void> _initAudio() async {
    _audioPlayer = AudioPlayer();
    final session = await AudioSession.instance;
    await session.configure(const AudioSessionConfiguration.music());
  
    _audioPlayer.playerStateStream.listen((s) => setState(() => _isPlaying = s.playing));
    _audioPlayer.positionStream.listen((p) => setState(() => _position = p));
    _audioPlayer.durationStream.listen((d) => setState(() => _duration = d ?? Duration.zero));
  
    try { await _audioPlayer.setUrl(_audioUrl); } catch (e) { debugPrint('加载失败: $e'); }
  }
  
  void _update() {
    final effectiveRho = _rho + _energy * 10;
    final effectiveSigma = _sigma + _bass * 5;
  
    final dt = 0.005;
    for (int i = 0; i < 3; i++) {
      final k1x = effectiveSigma * (_y - _x);
      final k1y = _x * (effectiveRho - _z) - _y;
      final k1z = _x * _y - _beta * _z;
    
      final k2x = effectiveSigma * ((_y + k1y * dt / 2) - (_x + k1x * dt / 2));
      final k2y = (_x + k1x * dt / 2) * (effectiveRho - (_z + k1z * dt / 2)) - (_y + k1y * dt / 2);
      final k2z = (_x + k1x * dt / 2) * (_y + k1y * dt / 2) - _beta * (_z + k1z * dt / 2);
    
      final k3x = effectiveSigma * ((_y + k2y * dt / 2) - (_x + k2x * dt / 2));
      final k3y = (_x + k2x * dt / 2) * (effectiveRho - (_z + k2z * dt / 2)) - (_y + k2y * dt / 2);
      final k3z = (_x + k2x * dt / 2) * (_y + k2y * dt / 2) - _beta * (_z + k2z * dt / 2);
    
      final k4x = effectiveSigma * ((_y + k3y * dt) - (_x + k3x * dt));
      final k4y = (_x + k3x * dt) * (effectiveRho - (_z + k3z * dt)) - (_y + k3y * dt);
      final k4z = (_x + k3x * dt) * (_y + k3y * dt) - _beta * (_z + k3z * dt);
    
      _x += (k1x + 2 * k2x + 2 * k3x + k4x) * dt / 6;
      _y += (k1y + 2 * k2y + 2 * k3y + k4y) * dt / 6;
      _z += (k1z + 2 * k2z + 2 * k3z + k4z) * dt / 6;
    }
  
    _rotationY += 0.003;
  
    final px = _x * _scale;
    final py = _z * _scale * 0.5 - _y * _scale * 0.3;
  
    _trajectory.add(Offset(px, py));
    _zValues.add(_z);
  
    if (_trajectory.length > _maxPoints) {
      _trajectory.removeAt(0);
      _zValues.removeAt(0);
    }
  
    if (_isPlaying) {
      _energy = _energy * 0.95 + (sin(DateTime.now().millisecondsSinceEpoch * 0.001) * 0.5 + 0.5) * 0.05;
      _bass = _bass * 0.95 + (sin(DateTime.now().millisecondsSinceEpoch * 0.0005) * 0.5 + 0.5) * 0.05;
    } else {
      _energy *= 0.98;
      _bass *= 0.98;
    }
  
    setState(() {});
  }

  @override
  void dispose() {
    _animController.dispose();
    _audioPlayer.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('洛伦兹吸引子')),
      body: Stack(children: [
        CustomPaint(painter: LorenzPainter(_trajectory, _zValues, _energy), size: Size.infinite),
        Positioned(bottom: 30, left: 20, right: 20, child: _buildControls()),
      ]),
    );
  }
  
  Widget _buildControls() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(color: Colors.black.withOpacity(0.8), borderRadius: BorderRadius.circular(16)),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Row(children: [
            IconButton(icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.cyan, size: 36),
                onPressed: () => _isPlaying ? _audioPlayer.pause() : _audioPlayer.play()),
            const SizedBox(width: 16),
            Expanded(child: Slider(value: _duration.inMilliseconds > 0 ? _position.inMilliseconds.toDouble().clamp(0, _duration.inMilliseconds.toDouble()) : 0,
                max: _duration.inMilliseconds > 0 ? _duration.inMilliseconds.toDouble() : 1,
                onChanged: (v) => _audioPlayer.seek(Duration(milliseconds: v.toInt())))),
          ]),
          Row(children: [
            Expanded(child: _buildParamSlider('ρ', _rho, 15, 40, (v) => _rho = v, Colors.orange)),
            Expanded(child: _buildParamSlider('σ', _sigma, 5, 15, (v) => _sigma = v, Colors.purple)),
            Expanded(child: _buildParamSlider('β', _beta, 1, 5, (v) => _beta = v, Colors.teal)),
          ]),
        ],
      ),
    );
  }
  
  Widget _buildParamSlider(String label, double value, double min, double max, Function(double) onChanged, Color color) {
    return Column(children: [
      Text('$label: ${value.toStringAsFixed(1)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
      Slider(value: value, min: min, max: max, onChanged: (v) => setState(() => onChanged(v)), activeColor: color),
    ]);
  }
}

class LorenzPainter extends CustomPainter {
  final List<Offset> trajectory;
  final List<double> zValues;
  final double energy;
  
  LorenzPainter(this.trajectory, this.zValues, this.energy);
  
  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
  
    final bgPaint = Paint()..shader = RadialGradient(
      colors: [const Color(0xFF0a0a20), const Color(0xFF050510)],
    ).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), bgPaint);
  
    if (trajectory.length < 2) return;
  
    for (int i = 1; i < trajectory.length; i++) {
      final p1 = trajectory[i - 1] + center;
      final p2 = trajectory[i] + center;
    
      final z = zValues[i];
      final hue = (z * 3 + i * 0.02) % 360;
      final alpha = (i / trajectory.length * 0.8 + 0.2).clamp(0.0, 1.0);
    
      final paint = Paint()
        ..color = HSVColor.fromAHSV(alpha, hue, 0.8, 1).toColor()
        ..strokeWidth = 1 + energy * 2
        ..strokeCap = StrokeCap.round;
    
      canvas.drawLine(p1, p2, paint);
    }
  
    if (trajectory.isNotEmpty) {
      final lastPoint = trajectory.last + center;
      canvas.drawCircle(lastPoint, 4 + energy * 3, Paint()..color = Colors.white);
      canvas.drawCircle(lastPoint, 2 + energy * 2, Paint()..color = Colors.cyan);
    }
  }
  
  @override
  bool shouldRepaint(covariant LorenzPainter old) => true;
}

/// 罗斯勒吸引子演示
class RosslerAttractorDemo extends StatefulWidget {
  const RosslerAttractorDemo({super.key});
  @override
  State<RosslerAttractorDemo> createState() => _RosslerAttractorDemoState();
}

class _RosslerAttractorDemoState extends State<RosslerAttractorDemo> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  
  double _a = 0.2, _b = 0.2, _c = 5.7;
  double _x = 1, _y = 1, _z = 1;
  final List<Offset> _trajectory = [];
  final List<double> _zValues = [];
  double _time = 0;
  double _scale = 8;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
    _controller.addListener(() {
      _time += 0.016;
      _updateAttractor();
      setState(() {});
    });
  }
  
  void _updateAttractor() {
    final dt = 0.01;
    for (int i = 0; i < 3; i++) {
      final k1x = -_y - _z;
      final k1y = _x + _a * _y;
      final k1z = _b + _z * (_x - _c);
    
      final k2x = -(_y + k1y * dt / 2) - (_z + k1z * dt / 2);
      final k2y = (_x + k1x * dt / 2) + _a * (_y + k1y * dt / 2);
      final k2z = _b + (_z + k1z * dt / 2) * ((_x + k1x * dt / 2) - _c);
    
      final k3x = -(_y + k2y * dt / 2) - (_z + k2z * dt / 2);
      final k3y = (_x + k2x * dt / 2) + _a * (_y + k2y * dt / 2);
      final k3z = _b + (_z + k2z * dt / 2) * ((_x + k2x * dt / 2) - _c);
    
      final k4x = -(_y + k3y * dt) - (_z + k3z * dt);
      final k4y = (_x + k3x * dt) + _a * (_y + k3y * dt);
      final k4z = _b + (_z + k3z * dt) * ((_x + k3x * dt) - _c);
    
      _x += (k1x + 2 * k2x + 2 * k3x + k4x) * dt / 6;
      _y += (k1y + 2 * k2y + 2 * k3y + k4y) * dt / 6;
      _z += (k1z + 2 * k2z + 2 * k3z + k4z) * dt / 6;
    }
  
    final px = _x * _scale;
    final py = _z * _scale * 0.5 - _y * _scale * 0.3;
  
    _trajectory.add(Offset(px, py));
    _zValues.add(_z);
  
    if (_trajectory.length > 4000) {
      _trajectory.removeAt(0);
      _zValues.removeAt(0);
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('罗斯勒吸引子')),
      body: Stack(children: [
        CustomPaint(painter: RosslerPainter(_trajectory, _zValues, _time), size: Size.infinite),
        Positioned(bottom: 20, left: 20, right: 20, child: _buildControls()),
      ]),
    );
  }
  
  Widget _buildControls() {
    return Container(
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(12)),
      child: Row(children: [
        Expanded(child: _buildSlider('a', _a, 0.1, 0.4, (v) => setState(() => _a = v))),
        Expanded(child: _buildSlider('b', _b, 0.1, 0.4, (v) => setState(() => _b = v))),
        Expanded(child: _buildSlider('c', _c, 4, 8, (v) => setState(() => _c = v))),
      ]),
    );
  }
  
  Widget _buildSlider(String label, double value, double min, double max, Function(double) onChanged) {
    return Column(children: [
      Text('$label: ${value.toStringAsFixed(2)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
      Slider(value: value, min: min, max: max, onChanged: onChanged),
    ]);
  }
}

class RosslerPainter extends CustomPainter {
  final List<Offset> trajectory;
  final List<double> zValues;
  final double time;
  
  RosslerPainter(this.trajectory, this.zValues, this.time);
  
  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
  
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
  
    if (trajectory.length < 2) return;
  
    for (int i = 1; i < trajectory.length; i++) {
      final p1 = trajectory[i - 1] + center;
      final p2 = trajectory[i] + center;
    
      final hue = (zValues[i] * 20 + time * 10) % 360;
      final alpha = (i / trajectory.length * 0.7 + 0.3).clamp(0.0, 1.0);
    
      canvas.drawLine(p1, p2, Paint()..color = HSVColor.fromAHSV(alpha, hue, 0.8, 1).toColor()..strokeWidth = 1.5);
    }
  
    if (trajectory.isNotEmpty) {
      canvas.drawCircle(trajectory.last + center, 3, Paint()..color = Colors.white);
    }
  }
  
  @override
  bool shouldRepaint(covariant RosslerPainter old) => true;
}

/// 陈氏吸引子演示
class ChenAttractorDemo extends StatefulWidget {
  const ChenAttractorDemo({super.key});
  @override
  State<ChenAttractorDemo> createState() => _ChenAttractorDemoState();
}

class _ChenAttractorDemoState extends State<ChenAttractorDemo> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  
  double _a = 40, _b = 3, _c = 28;
  double _x = -0.1, _y = 0.5, _z = -0.6;
  final List<Offset> _trajectory = [];
  final List<double> _zValues = [];
  double _time = 0;
  double _scale = 6;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
    _controller.addListener(() {
      _time += 0.016;
      _updateAttractor();
      setState(() {});
    });
  }
  
  void _updateAttractor() {
    final dt = 0.002;
    for (int i = 0; i < 5; i++) {
      final k1x = _a * (_y - _x);
      final k1y = (_c - _a) * _x - _x * _z + _c * _y;
      final k1z = _x * _y - _b * _z;
    
      _x += k1x * dt;
      _y += k1y * dt;
      _z += k1z * dt;
    }
  
    final px = _x * _scale;
    final py = _z * _scale * 0.5 - _y * _scale * 0.3;
  
    _trajectory.add(Offset(px, py));
    _zValues.add(_z);
  
    if (_trajectory.length > 4000) {
      _trajectory.removeAt(0);
      _zValues.removeAt(0);
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('陈氏吸引子')),
      body: Stack(children: [
        CustomPaint(painter: ChenPainter(_trajectory, _zValues, _time), size: Size.infinite),
        Positioned(bottom: 20, left: 20, right: 20, child: _buildControls()),
      ]),
    );
  }
  
  Widget _buildControls() {
    return Container(
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(12)),
      child: Row(children: [
        Expanded(child: _buildSlider('a', _a, 30, 50, (v) => setState(() => _a = v))),
        Expanded(child: _buildSlider('b', _b, 1, 5, (v) => setState(() => _b = v))),
        Expanded(child: _buildSlider('c', _c, 20, 35, (v) => setState(() => _c = v))),
      ]),
    );
  }
  
  Widget _buildSlider(String label, double value, double min, double max, Function(double) onChanged) {
    return Column(children: [
      Text('$label: ${value.toStringAsFixed(1)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
      Slider(value: value, min: min, max: max, onChanged: onChanged),
    ]);
  }
}

class ChenPainter extends CustomPainter {
  final List<Offset> trajectory;
  final List<double> zValues;
  final double time;
  
  ChenPainter(this.trajectory, this.zValues, this.time);
  
  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
  
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0510));
  
    if (trajectory.length < 2) return;
  
    for (int i = 1; i < trajectory.length; i++) {
      final p1 = trajectory[i - 1] + center;
      final p2 = trajectory[i] + center;
    
      final hue = (zValues[i] * 5 + time * 15) % 360;
      final alpha = (i / trajectory.length * 0.7 + 0.3).clamp(0.0, 1.0);
    
      canvas.drawLine(p1, p2, Paint()..color = HSVColor.fromAHSV(alpha, hue, 0.9, 1).toColor()..strokeWidth = 1.5);
    }
  
    if (trajectory.isNotEmpty) {
      canvas.drawCircle(trajectory.last + center, 3, Paint()..color = Colors.white);
    }
  }
  
  @override
  bool shouldRepaint(covariant ChenPainter old) => true;
}

/// 埃农映射演示
class HenonMapDemo extends StatefulWidget {
  const HenonMapDemo({super.key});
  @override
  State<HenonMapDemo> createState() => _HenonMapDemoState();
}

class _HenonMapDemoState extends State<HenonMapDemo> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  
  double _a = 1.4, _b = 0.3;
  double _x = 0.1, _y = 0.1;
  final List<Offset> _points = [];
  int _iterations = 0;
  double _time = 0;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
    _controller.addListener(() {
      _time += 0.016;
      _iterate();
      setState(() {});
    });
  }
  
  void _iterate() {
    for (int i = 0; i < 50; i++) {
      final newX = 1 - _a * _x * _x + _y;
      final newY = _b * _x;
      _x = newX;
      _y = newY;
    
      _points.add(Offset(_x * 150, _y * 150));
      _iterations++;
    }
  
    if (_points.length > 50000) {
      _points.removeRange(0, 1000);
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('埃农映射')),
      body: Stack(children: [
        CustomPaint(painter: HenonPainter(_points, _time), size: Size.infinite),
        Positioned(top: 20, left: 20, child: Text('迭代: $_iterations', style: const TextStyle(color: Colors.white70))),
        Positioned(bottom: 20, left: 20, right: 20, child: _buildControls()),
      ]),
    );
  }
  
  Widget _buildControls() {
    return Container(
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(12)),
      child: Row(children: [
        Expanded(child: Column(children: [
          Text('a: ${_a.toStringAsFixed(2)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
          Slider(value: _a, min: 1, max: 1.5, onChanged: (v) => setState(() { _a = v; _points.clear(); _x = 0.1; _y = 0.1; _iterations = 0; })),
        ])),
        Expanded(child: Column(children: [
          Text('b: ${_b.toStringAsFixed(2)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
          Slider(value: _b, min: 0.1, max: 0.5, onChanged: (v) => setState(() { _b = v; _points.clear(); _x = 0.1; _y = 0.1; _iterations = 0; })),
        ])),
      ]),
    );
  }
}

class HenonPainter extends CustomPainter {
  final List<Offset> points;
  final double time;
  
  HenonPainter(this.points, this.time);
  
  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
  
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF050510));
  
    for (int i = 0; i < points.length; i++) {
      final p = points[i] + center;
      final hue = (i * 0.01 + time * 5) % 360;
      canvas.drawCircle(p, 0.5, Paint()..color = HSVColor.fromAHSV(0.7, hue, 0.9, 1).toColor());
    }
  }
  
  @override
  bool shouldRepaint(covariant HenonPainter old) => true;
}

/// 音乐混沌演示
class MusicChaosDemo extends StatefulWidget {
  const MusicChaosDemo({super.key});
  @override
  State<MusicChaosDemo> createState() => _MusicChaosDemoState();
}

class _MusicChaosDemoState extends State<MusicChaosDemo> with TickerProviderStateMixin {
  late AnimationController _animController;
  late AudioPlayer _audioPlayer;
  
  double _x = 1, _y = 1, _z = 1;
  final List<Offset> _trajectory = [];
  final List<double> _zValues = [];
  
  bool _isPlaying = false;
  Duration _position = Duration.zero;
  Duration _duration = Duration.zero;
  Float32List _audioData = Float32List(64);
  double _energy = 0, _bass = 0, _mid = 0, _treble = 0;
  double _time = 0;
  
  static const String _audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';

  @override
  void initState() {
    super.initState();
    _initAudio();
    _animController = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
    _animController.addListener(_update);
  }
  
  Future<void> _initAudio() async {
    _audioPlayer = AudioPlayer();
    final session = await AudioSession.instance;
    await session.configure(const AudioSessionConfiguration.music());
  
    _audioPlayer.playerStateStream.listen((s) => setState(() => _isPlaying = s.playing));
    _audioPlayer.positionStream.listen((p) => setState(() => _position = p));
    _audioPlayer.durationStream.listen((d) => setState(() => _duration = d ?? Duration.zero));
  
    try { await _audioPlayer.setUrl(_audioUrl); } catch (e) { debugPrint('加载失败: $e'); }
  }
  
  void _update() {
    _time += 0.016;
  
    for (int i = 0; i < 64; i++) {
      if (_isPlaying) {
        final freq = (i / 64) * 8 + 1;
        _audioData[i] = _audioData[i] * 0.85 + (sin(_time * freq) * 0.5 + 0.5) * 0.15;
      } else {
        _audioData[i] *= 0.95;
      }
    }
  
    double total = 0, bassE = 0, midE = 0, trebleE = 0;
    for (int i = 0; i < 64; i++) {
      total += _audioData[i];
      if (i < 16) bassE += _audioData[i];
      else if (i < 40) midE += _audioData[i];
      else trebleE += _audioData[i];
    }
    _energy = total / 64;
    _bass = bassE / 16;
    _mid = midE / 24;
    _treble = trebleE / 24;
  
    final sigma = 10 + _treble * 10;
    final rho = 28 + _energy * 15;
    final beta = 8 / 3 + _bass * 2;
  
    final dt = 0.005;
    for (int i = 0; i < 3; i++) {
      final k1x = sigma * (_y - _x);
      final k1y = _x * (rho - _z) - _y;
      final k1z = _x * _y - beta * _z;
    
      _x += k1x * dt;
      _y += k1y * dt;
      _z += k1z * dt;
    }
  
    final scale = 12 + _energy * 5;
    final px = _x * scale;
    final py = _z * scale * 0.5 - _y * scale * 0.3;
  
    _trajectory.add(Offset(px, py));
    _zValues.add(_z);
  
    if (_trajectory.length > 3000) {
      _trajectory.removeAt(0);
      _zValues.removeAt(0);
    }
  
    setState(() {});
  }

  @override
  void dispose() {
    _animController.dispose();
    _audioPlayer.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('音乐混沌')),
      body: Stack(children: [
        CustomPaint(painter: MusicChaosPainter(_trajectory, _zValues, _energy, _time), size: Size.infinite),
        Positioned(bottom: 30, left: 20, right: 20, child: _buildControls()),
      ]),
    );
  }
  
  Widget _buildControls() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(color: Colors.black.withOpacity(0.8), borderRadius: BorderRadius.circular(16)),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Row(children: [
            const Icon(Icons.music_note, color: Colors.teal),
            const SizedBox(width: 8),
            const Expanded(child: Text('SoundHelix - Song 1', style: TextStyle(color: Colors.white, fontSize: 14))),
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
              decoration: BoxDecoration(
                color: _isPlaying ? Colors.teal : Colors.grey[800],
                borderRadius: BorderRadius.circular(12),
              ),
              child: Text(_isPlaying ? '播放中' : '暂停', style: const TextStyle(color: Colors.white, fontSize: 12)),
            ),
          ]),
          const SizedBox(height: 12),
          Slider(
            value: _duration.inMilliseconds > 0 ? _position.inMilliseconds.toDouble().clamp(0, _duration.inMilliseconds.toDouble()) : 0,
            max: _duration.inMilliseconds > 0 ? _duration.inMilliseconds.toDouble() : 1,
            onChanged: (v) => _audioPlayer.seek(Duration(milliseconds: v.toInt())),
            activeColor: Colors.teal,
          ),
          Row(mainAxisAlignment: MainAxisAlignment.center, children: [
            IconButton(icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.teal, size: 36),
                onPressed: () => _isPlaying ? _audioPlayer.pause() : _audioPlayer.play()),
          ]),
          _buildBandMeters(),
        ],
      ),
    );
  }
  
  Widget _buildBandMeters() {
    return Row(children: [
      Expanded(child: _buildMeter('低频', _bass, Colors.red)),
      const SizedBox(width: 8),
      Expanded(child: _buildMeter('中频', _mid, Colors.yellow)),
      const SizedBox(width: 8),
      Expanded(child: _buildMeter('高频', _treble, Colors.cyan)),
      const SizedBox(width: 8),
      Expanded(child: _buildMeter('能量', _energy, Colors.teal)),
    ]);
  }
  
  Widget _buildMeter(String label, double value, Color color) {
    return Column(children: [
      Text(label, style: const TextStyle(color: Colors.white70, fontSize: 10)),
      const SizedBox(height: 4),
      Container(
        height: 40,
        decoration: BoxDecoration(color: Colors.grey[800], borderRadius: BorderRadius.circular(4)),
        child: Align(
          alignment: Alignment.bottomCenter,
          child: AnimatedContainer(
            duration: const Duration(milliseconds: 50),
            height: (value * 40).clamp(2.0, 40.0),
            decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(4)),
          ),
        ),
      ),
    ]);
  }
}

class MusicChaosPainter extends CustomPainter {
  final List<Offset> trajectory;
  final List<double> zValues;
  final double energy;
  final double time;
  
  MusicChaosPainter(this.trajectory, this.zValues, this.energy, this.time);
  
  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
  
    final bgColor = Color.lerp(const Color(0xFF050510), const Color(0xFF0a1520), energy)!;
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = bgColor);
  
    if (trajectory.length < 2) return;
  
    for (int i = 1; i < trajectory.length; i++) {
      final p1 = trajectory[i - 1] + center;
      final p2 = trajectory[i] + center;
    
      final z = zValues[i];
      final hue = (z * 5 + time * 20) % 360;
      final alpha = (i / trajectory.length * 0.8 + 0.2).clamp(0.0, 1.0);
    
      canvas.drawLine(p1, p2, Paint()..color = HSVColor.fromAHSV(alpha, hue, 0.9, 1).toColor()..strokeWidth = 1.5 + energy);
    }
  
    if (trajectory.isNotEmpty) {
      final lastPoint = trajectory.last + center;
      canvas.drawCircle(lastPoint, 5 + energy * 3, Paint()..color = Colors.white.withOpacity(0.9));
      canvas.drawCircle(lastPoint, 3 + energy * 2, Paint()..color = Colors.teal);
    }
  }
  
  @override
  bool shouldRepaint(covariant MusicChaosPainter old) => true;
}

/// 双摆模拟演示
class DoublePendulumDemo extends StatefulWidget {
  const DoublePendulumDemo({super.key});
  @override
  State<DoublePendulumDemo> createState() => _DoublePendulumDemoState();
}

class _DoublePendulumDemoState extends State<DoublePendulumDemo> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  
  // 双摆参数
  double _L1 = 150, _L2 = 150;
  double _m1 = 10, _m2 = 10;
  double _theta1 = pi / 2, _theta2 = pi / 2;
  double _omega1 = 0, _omega2 = 0;
  double _g = 9.81;
  
  final List<Offset> _trail = [];
  double _time = 0;
  bool _showTrail = true;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
    _controller.addListener(() {
      _time += 0.016;
      _updatePendulum();
      setState(() {});
    });
  }
  
  void _updatePendulum() {
    final dt = 0.05;
  
    for (int i = 0; i < 3; i++) {
      final delta = _theta1 - _theta2;
    
      final den1 = (_m1 + _m2) * _L1 - _m2 * _L1 * cos(delta) * cos(delta);
      final den2 = (_L2 / _L1) * den1;
    
      final alpha1 = (_m2 * _L1 * _omega1 * _omega1 * sin(delta) * cos(delta)
          + _m2 * _g * sin(_theta2) * cos(delta)
          + _m2 * _L2 * _omega2 * _omega2 * sin(delta)
          - (_m1 + _m2) * _g * sin(_theta1)) / den1;
    
      final alpha2 = (-_m2 * _L2 * _omega2 * _omega2 * sin(delta) * cos(delta)
          + (_m1 + _m2) * _g * sin(_theta1) * cos(delta)
          - (_m1 + _m2) * _L1 * _omega1 * _omega1 * sin(delta)
          - (_m1 + _m2) * _g * sin(_theta2)) / den2;
    
      _omega1 += alpha1 * dt;
      _omega2 += alpha2 * dt;
      _theta1 += _omega1 * dt;
      _theta2 += _omega2 * dt;
    }
  
    // 记录轨迹
    final x2 = _L1 * sin(_theta1) + _L2 * sin(_theta2);
    final y2 = _L1 * cos(_theta1) + _L2 * cos(_theta2);
    _trail.add(Offset(x2, y2));
  
    if (_trail.length > 2000) {
      _trail.removeAt(0);
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('双摆模拟')),
      body: Stack(children: [
        CustomPaint(
          painter: DoublePendulumPainter(
            _L1, _L2, _theta1, _theta2, _trail, _time, _showTrail,
          ),
          size: Size.infinite,
        ),
        Positioned(top: 20, right: 20, child: _buildToggle()),
        Positioned(bottom: 20, left: 20, right: 20, child: _buildControls()),
      ]),
    );
  }
  
  Widget _buildToggle() {
    return GestureDetector(
      onTap: () => setState(() => _showTrail = !_showTrail),
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
        decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(8)),
        child: Text(_showTrail ? '隐藏轨迹' : '显示轨迹', style: const TextStyle(color: Colors.white70, fontSize: 12)),
      ),
    );
  }
  
  Widget _buildControls() {
    return Container(
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(12)),
      child: Row(children: [
        Expanded(child: Column(children: [
          Text('长度1: ${_L1.toStringAsFixed(0)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
          Slider(value: _L1, min: 50, max: 200, onChanged: (v) => setState(() => _L1 = v)),
        ])),
        Expanded(child: Column(children: [
          Text('长度2: ${_L2.toStringAsFixed(0)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
          Slider(value: _L2, min: 50, max: 200, onChanged: (v) => setState(() => _L2 = v)),
        ])),
        Expanded(child: Column(children: [
          Text('重置', style: const TextStyle(color: Colors.white70, fontSize: 11)),
          IconButton(icon: const Icon(Icons.refresh, color: Colors.amber), onPressed: () {
            setState(() {
              _theta1 = pi / 2;
              _theta2 = pi / 2;
              _omega1 = 0;
              _omega2 = 0;
              _trail.clear();
            });
          }),
        ])),
      ]),
    );
  }
}

class DoublePendulumPainter extends CustomPainter {
  final double L1, L2, theta1, theta2;
  final List<Offset> trail;
  final double time;
  final bool showTrail;
  
  DoublePendulumPainter(this.L1, this.L2, this.theta1, this.theta2, this.trail, this.time, this.showTrail);
  
  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 3);
  
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
  
    // 绘制轨迹
    if (showTrail && trail.length > 1) {
      for (int i = 1; i < trail.length; i++) {
        final p1 = trail[i - 1] + center;
        final p2 = trail[i] + center;
        final hue = (i / trail.length * 120 + time * 10) % 360;
        final alpha = i / trail.length * 0.8;
        canvas.drawLine(p1, p2, Paint()..color = HSVColor.fromAHSV(alpha, hue, 0.9, 1).toColor()..strokeWidth = 1);
      }
    }
  
    // 计算摆球位置
    final x1 = L1 * sin(theta1);
    final y1 = L1 * cos(theta1);
    final x2 = x1 + L2 * sin(theta2);
    final y2 = y1 + L2 * cos(theta2);
  
    // 绘制支点
    canvas.drawCircle(center, 8, Paint()..color = Colors.grey);
  
    // 绘制第一根杆
    canvas.drawLine(center, center + Offset(x1, y1), Paint()..color = Colors.white..strokeWidth = 3);
  
    // 绘制第一个摆球
    canvas.drawCircle(center + Offset(x1, y1), 15, Paint()..color = Colors.cyan);
  
    // 绘制第二根杆
    canvas.drawLine(center + Offset(x1, y1), center + Offset(x2, y2), Paint()..color = Colors.white..strokeWidth = 3);
  
    // 绘制第二个摆球
    canvas.drawCircle(center + Offset(x2, y2), 12, Paint()..color = Colors.orange);
  }
  
  @override
  bool shouldRepaint(covariant DoublePendulumPainter old) => true;
}

📝 六、总结

本篇文章深入探讨了混沌理论在音乐可视化中的应用,从洛伦兹吸引子到各种奇异吸引子,构建了音乐驱动的动态混沌艺术系统。

✅ 核心知识点回顾

知识点 说明
🌀混沌理论 确定性系统中的不可预测行为
🦋洛伦兹吸引子 蝴蝶效应的经典模型
🔄罗斯勒吸引子 单螺旋混沌系统
💫陈氏吸引子 双螺旋混沌变体
🎭埃农映射 离散混沌分形
🌊双摆系统 物理混沌实例
🎵音频驱动 音乐参数映射混沌

⭐ 最佳实践要点

  • ✅ 使用龙格-库塔方法进行数值积分
  • ✅ 音频能量映射到混沌参数实现动态效果
  • ✅ 轨迹颜色基于状态变量动态计算
  • ✅ 合理设置轨迹长度避免性能问题
  • ✅ 提供参数调节交互增强用户体验

相关推荐
lbb 小魔仙2 小时前
鸿蒙跨平台实战:React Native在OpenHarmony上的Font字体降级策略详解
react native·华为·harmonyos
2501_921930832 小时前
进阶实战 Flutter for OpenHarmony:高性能列表虚拟化系统 - 大数据量渲染优化实现
flutter
hqk2 小时前
鸿蒙项目实战:手把手带你从零架构 WanAndroid 鸿蒙版
前端·架构·harmonyos
2501_921930833 小时前
进阶实战 Flutter for OpenHarmony:自定义渲染引擎系统 - RenderObject 底层绘制实现
flutter
早點睡3903 小时前
Harmony Flutter 跨平台开发实战:鸿蒙与音乐律动艺术、粒子物理引力场:万有引力与排斥逻辑
flutter·华为·harmonyos
九狼3 小时前
Riverpod 2.0 代码生成与依赖注入
flutter·设计模式·github
空白诗3 小时前
Harmony Flutter 跨平台开发实战:鸿蒙与音乐律动艺术、傅里叶变换与频谱:从时域到频域的视觉翻译
flutter
lbb 小魔仙4 小时前
鸿蒙跨平台实战:React Native在OpenHarmony上的Font字体加载管理详解
react native·华为·harmonyos
2601_949593654 小时前
Harmony Flutter 跨平台开发实战:鸿蒙与音乐律动艺术、极坐标对称投影:万花筒般的几何韵律
flutter·华为·harmonyos