
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🌀 一、分形几何:无限细节的数学世界
📚 1.1 分形的历史与概念
分形(Fractal)一词由数学家本华·曼德博(Benoît Mandelbrot)于1975年创造,源自拉丁语"fractus",意为"破碎的"或"不规则的"。分形是一种在不同尺度上具有自相似性的几何形状。
分形发展里程碑:
| 年份 | 人物 | 贡献 |
|---|---|---|
| 17世纪 | 莱布尼茨 | 递归自相似概念 |
| 1883 | 康托尔 | 康托尔集 |
| 1904 | 科赫 | 科赫雪花 |
| 1915 | 谢尔宾斯基 | 谢尔宾斯基三角形 |
| 1918 | 朱利亚 | 朱利亚集合 |
| 1980 | 曼德博 | 曼德博集合 |
| 现代 | 计算机 | 实时渲染与艺术应用 |
分形的核心特征:
分形的定义特征:
1. 自相似性 (Self-similarity)
- 整体与局部相似
- 不同尺度下结构相同
- 可以是精确的或统计的
2. 无限细节 (Infinite detail)
- 无论放大多少倍
- 总能看到新的细节
- 永远不会"平滑"
3. 分数维度 (Fractional dimension)
- 维度不是整数
- 介于线和面之间
- 科赫曲线维度 ≈ 1.26
4. 简单规则生成复杂形态
- 简单的迭代公式
- 产生极其复杂的图案
- 混沌与秩序的统一
分形在自然界中的存在:
自然界中的分形结构:
植物:
- 树枝的分叉模式
- 蕨类叶片的形态
- 花椰菜的结构
- 闪电的分支
地理:
- 海岸线的形状
- 河流的水系
- 山脉的轮廓
- 云朵的边缘
生物:
- 血管网络
- 支气管树
- 神经系统
- 细胞分裂
这些自然分形启发了数学分形的研究!
🔬 1.2 曼德博集合:数学最美的发现
曼德博集合的定义:
曼德博集合 M 定义为:
M = { c ∈ ℂ : zₙ₊₁ = zₙ² + c, z₀ = 0, n→∞ 时 zₙ 有界 }
简单来说:
- 对于复平面上的每个点 c
- 迭代 z = z² + c,从 z = 0 开始
- 如果迭代结果不发散到无穷,则 c 属于曼德博集合
迭代过程:
z₀ = 0
z₁ = z₀² + c = c
z₂ = z₁² + c = c² + c
z₃ = z₂² + c = (c² + c)² + c
...
判断是否发散:
- 如果 |z| > 2,则一定会发散
- 如果经过 N 次迭代后 |z| ≤ 2,认为 c 属于集合
曼德博集合的数学性质:
曼德博集合的神奇性质:
1. 连通性
- 集合是连通的(一个整体)
- 但边界极其复杂
- 边界维度约为 2
2. 自相似性
- 边界上存在无限多个"小曼德博"
- 每个小曼德博都与整体相似
- 但不完全相同
3. 普适性
- 包含所有可能的朱利亚集合
- 每个点 c 对应一个朱利亚集合
- 曼德博集合是朱利亚集合的"目录"
4. 周期轨道
- 主心形区域:周期1
- 圆盘:周期2
- 更小的圆盘:更高周期
曼德博集合的关键区域:
曼德博集合的主要结构:
╭─────────────────╮
╱ ╲
╱ 主心形区域 ╲
│ (周期1轨道) │
│ ○ │
╲ 周期2圆盘 ╱
╲ (●) ╱
╲ ╱
╲ ╱
╰───────────╯
触角和分支
主心形区域(Cardioid):
- 方程:r = (1 - cos(θ))/2
- 内部点迭代收敛到固定点
- 周期1轨道
周期2圆盘:
- 圆心在 c = -1
- 半径 1/4
- 迭代在两个值之间振荡
触角(Antennae):
- 从主集合延伸出的细丝
- 包含无限多的小曼德博集合
- 是最复杂的区域
🎨 1.3 朱利亚集合:曼德博的镜像
朱利亚集合的定义:
朱利亚集合 J(c) 定义为:
对于固定的复数 c:
J(c) = { z₀ ∈ ℂ : zₙ₊₁ = zₙ² + c, n→∞ 时 zₙ 有界 }
与曼德博集合的区别:
- 曼德博:固定 z₀ = 0,变化 c
- 朱利亚:固定 c,变化 z₀
每个 c 值对应一个不同的朱利亚集合!
朱利亚集合的分类:
1. 连通朱利亚集合(c 在曼德博集合内部)
- 形成一个连通的整体
- 边界复杂但完整
2. 康托尔朱利亚集合(c 在曼德博集合外部)
- 分散成无数个点
- 像尘埃一样分布
经典朱利亚集合形态:
不同 c 值产生的朱利亚集合:
c = -0.4 + 0.6i(龙形):
╱╲╱╲╱╲╱╲
╱ ╲ ╲
╱ ╲ ╲
╱ ╲ ╲
╱ ╲ ╱
╱ ╲╱╱
连通的龙形图案
c 在曼德博集合内部
c = 0.285 + 0.01i(螺旋):
╭───────╮
╱ ╭───╮ ╲
│ ╱ ╲ │
│ │ ○ │ │
│ ╲ ╱ │
╲ ╰───╯ ╱
╰───────╯
美丽的螺旋结构
接近曼德博边界
c = -0.8 + 0.156i(树枝):
╱╲
╱ ╲
╱ ╲
╱ ╱╲ ╲
╱ ╱ ╲ ╲
╱ ╱ ╲ ╲
树枝状分形
典型的自相似结构
c = -2(直线):
────────────────
退化为直线
在曼德博边界上
朱利亚集合与曼德博集合的关系:
曼德博集合是朱利亚集合的"地图":
曼德博集合内部:
- c 在主心形内 → 朱利亚集合是变形的圆
- c 在周期2圆盘 → 朱利亚集合有对称结构
- c 在小圆盘内 → 朱利亚集合有对应周期结构
曼德博集合边界:
- 朱利亚集合最复杂
- 边界分形维度最高
- 视觉效果最丰富
曼德博集合外部:
- 朱利亚集合是康托尔尘埃
- 分散成无数孤立点
- 不形成连通区域
通过曼德博集合可以"导航"所有朱利亚集合!
📐 1.4 其他经典分形
科赫雪花(Koch Snowflake):
科赫雪花的构造过程:
第0代(等边三角形):
╱╲
╱ ╲
╱ ╲
╱ ╲
╱────────╲
第1代:
╱╲
╱ ╲
╱ ╱╲ ╲
╱ ╲
╱────────╲
第2代:
╱╲
╱ ╲
╱╱╲╱╲╲
╱ ╲
╱ ╱╲╱╲ ╱╲ ╲
╱ ╲
╱──────────╲
构造规则:
- 每条边分成三等份
- 中间部分向外突出成等边三角形
- 无限重复
数学性质:
- 周长:无限(每代增加 4/3 倍)
- 面积:有限(收敛到初始三角形的 8/5 倍)
- 维度:log(4)/log(3) ≈ 1.26
谢尔宾斯基三角形(Sierpinski Triangle):
谢尔宾斯基三角形的构造:
第0代: 第1代: 第2代:
▲ ▲ ▲
▲▲ ▲ ▲ ▲ ▲
▲▲▲▲ ▲ ▲ ▲ ▲
▲▲▲▲▲▲ ▲▲▲ ▲▲▲ ▲▲▲ ▲▲▲
▲ ▲ ▲ ▲
▲▲▲ ▲▲▲ ▲▲▲ ▲▲▲
构造规则:
- 从实心三角形开始
- 移除中心的倒三角形
- 对剩余的三个小三角形重复
数学性质:
- 面积:趋于 0
- 维度:log(3)/log(2) ≈ 1.58
- 自相似:每个小三角形与整体相同
巴恩斯利蕨(Barnsley Fern):
巴恩斯利蕨的迭代函数系统:
使用四个仿射变换:
f₁(x,y) = (0, 0.16y) 概率 1%
f₂(x,y) = (0.85x + 0.04y, -0.04x + 0.85y + 1.6) 概率 85%
f₃(x,y) = (0.2x - 0.26y, 0.23x + 0.22y + 1.6) 概率 7%
f₄(x,y) = (-0.15x + 0.28y, 0.26x + 0.24y + 0.44) 概率 7%
结果:
🌿
🌿🌿
🌿 🌿
🌿🌿🌿🌿
🌿 🌿
🌿🌿 🌿🌿
🌿 🌿 🌿 🌿
🌿🌿🌿🌿🌿🌿🌿🌿
🌿 🌿
- 完美模拟真实蕨类植物
- 展示了分形在自然界中的普遍性
- 简单规则产生复杂形态
🎵 二、分形与音乐
🎼 2.1 分形音乐的概念
音乐中的分形结构:
音乐天然具有分形特性:
1. 节奏层次
全音符 → 二分音符 → 四分音符 → 八分音符
每个层次都是上一层的细分
自相似的时间结构
2. 旋律结构
乐句 → 乐段 → 乐章 → 整部作品
不同尺度上的相似模式
主题的变奏与重复
3. 和声进行
和弦 → 和弦进行 → 调性区域 → 整体调性布局
多层次的组织结构
4. 音色频谱
基频 → 泛音列 → 谐波结构
频率域的分形特征
分形音乐生成方法:
使用分形原理生成音乐:
1. 一维映射(音高)
将分形迭代值映射到音高
zₙ 的实部或虚部 → MIDI音符号
示例:
z = 0.5 + 0.3i → 音高 C4
z = 0.7 + 0.1i → 音高 E4
z = -0.2 + 0.4i → 音高 G4
2. 二维映射(音高+时间)
实部 → 音高
虚部 → 时间或节奏
3. 逃逸时间映射
迭代次数 → 音高或力度
快速逃逸 → 高音/强音
慢速逃逸 → 低音/弱音
4. 自相似旋律
使用 L-系统生成
规则:A → AB, B → A
结果:A, AB, ABA, ABAAB, ...
🎚️ 2.2 音频驱动的分形可视化
音频参数映射到分形参数:
将音频特征映射到分形参数:
1. 能量 → 迭代深度
能量高 → 更多迭代 → 更多细节
能量低 → 较少迭代 → 简单图案
2. 频率分布 → 颜色映射
低频 → 红色系
中频 → 绿色系
高频 → 蓝色系
3. 节奏 → 缩放动画
强拍 → 快速缩放
弱拍 → 缓慢变化
4. 音色 → 分形类型
纯净音色 → 曼德博集合
复杂音色 → 朱利亚集合
噪声音色 → 混沌图案
5. 音高 → 复平面位置
低音 → 左下区域
高音 → 右上区域
实时音频分形系统:
实时音频分形可视化流程:
┌─────────────┐
│ 音频输入 │
└──────┬──────┘
↓
┌─────────────┐
│ FFT分析 │ → 频谱数据
└──────┬──────┘
↓
┌─────────────┐
│ 特征提取 │ → 能量、频率、节奏
└──────┬──────┘
↓
┌─────────────┐
│ 参数映射 │ → 分形参数
└──────┬──────┘
↓
┌─────────────┐
│ 分形渲染 │ → 图像输出
└─────────────┘
关键参数:
- c 值:由音频特征决定
- 迭代次数:由能量决定
- 缩放中心:由主频决定
- 颜色方案:由音色决定
📊 2.3 分形维度与音乐复杂度
音乐复杂度的分形度量:
使用分形维度分析音乐复杂度:
1. 旋律线维度
- 简单旋律:维度接近 1
- 复杂旋律:维度接近 1.5
- 随机旋律:维度接近 2
2. 节奏模式维度
- 规则节奏:维度接近 1
- 切分节奏:维度 1.2-1.5
- 复杂节奏:维度接近 2
3. 频谱维度
- 纯音:维度接近 0
- 乐音:维度 0.5-1
- 噪音:维度接近 2
计算方法:
使用盒子计数法(Box Counting)
N(ε) ∝ ε^(-D)
其中 ε 是盒子大小,N 是覆盖所需的盒子数
🔧 三、Dart/Flutter 中的分形实现
🌀 3.1 曼德博集合计算器
dart
import 'dart:math';
class Complex {
final double real;
final double imag;
const Complex(this.real, this.imag);
Complex operator +(Complex other) => Complex(real + other.real, imag + other.imag);
Complex operator *(Complex other) => Complex(real * other.real - imag * other.imag, real * other.imag + imag * other.real);
double get magnitude => sqrt(real * real + imag * imag);
Complex squared() => Complex(real * real - imag * imag, 2 * real * imag);
}
class MandelbrotCalculator {
final int maxIterations;
final double escapeRadius;
MandelbrotCalculator({this.maxIterations = 100, this.escapeRadius = 2.0});
int calculateEscapeTime(Complex c) {
var z = const Complex(0, 0);
for (int i = 0; i < maxIterations; i++) {
z = z.squared() + c;
if (z.magnitude > escapeRadius) {
return i;
}
}
return maxIterations;
}
bool isInSet(Complex c) => calculateEscapeTime(c) == maxIterations;
double calculateSmoothEscapeTime(Complex c) {
var z = const Complex(0, 0);
for (int i = 0; i < maxIterations; i++) {
z = z.squared() + c;
if (z.magnitude > escapeRadius) {
return i + 1 - log(log(z.magnitude)) / log(2);
}
}
return maxIterations.toDouble();
}
}
🎨 3.2 朱利亚集合计算器
dart
class JuliaCalculator {
final Complex c;
final int maxIterations;
final double escapeRadius;
JuliaCalculator({required this.c, this.maxIterations = 100, this.escapeRadius = 2.0});
int calculateEscapeTime(Complex z0) {
var z = z0;
for (int i = 0; i < maxIterations; i++) {
z = z.squared() + c;
if (z.magnitude > escapeRadius) {
return i;
}
}
return maxIterations;
}
double calculateSmoothEscapeTime(Complex z0) {
var z = z0;
for (int i = 0; i < maxIterations; i++) {
z = z.squared() + c;
if (z.magnitude > escapeRadius) {
return i + 1 - log(log(z.magnitude)) / log(2);
}
}
return maxIterations.toDouble();
}
static List<Complex> get presetCValues => [
const Complex(-0.4, 0.6), // 龙形
const Complex(0.285, 0.01), // 螺旋
const Complex(-0.8, 0.156), // 树枝
const Complex(-0.70176, -0.3842), // 闪电
const Complex(-0.835, -0.2321), // 雪花
const Complex(0.45, 0.1428), // 星云
];
}
🎵 3.3 音频驱动的分形控制器
dart
import 'dart:typed_data';
class AudioFractalController {
Complex center = const Complex(-0.5, 0);
double zoom = 1.0;
int maxIterations = 100;
Complex juliaC = const Complex(-0.4, 0.6);
Float32List audioData = Float32List(64);
double energy = 0;
double bass = 0;
double mid = 0;
double treble = 0;
void updateFromAudio(Float32List data) {
audioData = data;
double total = 0, bassE = 0, midE = 0, trebleE = 0;
for (int i = 0; i < data.length; i++) {
total += data[i];
if (i < data.length * 0.25) {
bassE += data[i];
} else if (i < data.length * 0.6) {
midE += data[i];
} else {
trebleE += data[i];
}
}
energy = total / data.length;
bass = bassE / (data.length * 0.25);
mid = midE / (data.length * 0.35);
treble = trebleE / (data.length * 0.4);
}
Complex get dynamicJuliaC => Complex(
juliaC.real + (mid - 0.5) * 0.2,
juliaC.imag + (treble - 0.5) * 0.2,
);
int get dynamicMaxIterations => (maxIterations * (1 + energy * 0.5)).toInt();
double get dynamicZoom => zoom * (1 + bass * 2);
}
💻 四、完整代码实现
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';
import 'dart:ui' as ui;
import 'dart:async';
void main() {
runApp(const FractalApp());
}
class FractalApp extends StatelessWidget {
const FractalApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '分形几何',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, brightness: Brightness.dark),
useMaterial3: true,
),
home: const FractalHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class Complex {
final double real;
final double imag;
const Complex(this.real, this.imag);
Complex operator +(Complex other) => Complex(real + other.real, imag + other.imag);
Complex operator *(Complex other) => Complex(real * other.real - imag * other.imag, real * other.imag + imag * other.real);
double get magnitude => sqrt(real * real + imag * imag);
Complex squared() => Complex(real * real - imag * imag, 2 * real * imag);
@override
String toString() => 'Complex($real, $imag)';
}
class FractalHomePage extends StatelessWidget {
const FractalHomePage({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.blur_on, color: Colors.deepPurple,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const MandelbrotDemo()))),
_buildCard(context, title: '朱利亚集合', description: '曼德博的镜像世界', icon: Icons.filter_vintage, color: Colors.purple,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const JuliaSetDemo()))),
_buildCard(context, title: '音乐分形', description: '音频驱动的动态分形', icon: Icons.music_note, color: Colors.indigo,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const MusicFractalDemo()))),
_buildCard(context, title: '科赫雪花', description: '无限边界的有限面积', icon: Icons.ac_unit, color: Colors.cyan,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const KochSnowflakeDemo()))),
_buildCard(context, title: '谢尔宾斯基', description: '自相似的三角形', icon: Icons.change_history, color: Colors.teal,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SierpinskiDemo()))),
_buildCard(context, title: '分形树', description: '递归生长的树形结构', icon: Icons.nature, color: Colors.green,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const FractalTreeDemo()))),
_buildCard(context, title: '巴恩斯利蕨', description: '自然界的分形', icon: Icons.eco, color: Colors.lightGreen,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const BarnsleyFernDemo()))),
_buildCard(context, title: '分形动画', description: '缩放与颜色动画', icon: Icons.animation, color: Colors.pink,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const FractalAnimationDemo()))),
],
),
);
}
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 MandelbrotDemo extends StatefulWidget {
const MandelbrotDemo({super.key});
@override
State<MandelbrotDemo> createState() => _MandelbrotDemoState();
}
class _MandelbrotDemoState extends State<MandelbrotDemo> {
Complex _center = const Complex(-0.5, 0);
double _zoom = 1.0;
int _maxIterations = 100;
int _colorScheme = 0;
bool _isRendering = false;
ui.Image? _cachedImage;
final TextEditingController _realController = TextEditingController(text: '-0.5');
final TextEditingController _imagController = TextEditingController(text: '0');
@override
void initState() {
super.initState();
_renderFractal();
}
void _renderFractal() {
if (_isRendering) return;
setState(() => _isRendering = true);
Future.delayed(const Duration(milliseconds: 50), () {
if (!mounted) return;
_generateImage().then((image) {
if (mounted) {
setState(() {
_cachedImage = image;
_isRendering = false;
});
}
});
});
}
Future<ui.Image> _generateImage() async {
const width = 800;
const height = 800;
final pixels = Int32List(width * height);
final scale = 3.0 / (_zoom * width);
for (int py = 0; py < height; py++) {
for (int px = 0; px < width; px++) {
final x = (px - width / 2) * scale + _center.real;
final y = (py - height / 2) * scale + _center.imag;
final escapeTime = _calculateMandelbrot(Complex(x, y), _maxIterations);
pixels[py * width + px] = _getColor(escapeTime, _maxIterations);
}
}
final completer = Completer<ui.Image>();
ui.decodeImageFromPixels(
Uint8List.view(pixels.buffer),
width,
height,
ui.PixelFormat.rgba8888,
completer.complete,
);
return completer.future;
}
int _calculateMandelbrot(Complex c, int maxIter) {
var z = const Complex(0, 0);
for (int i = 0; i < maxIter; i++) {
z = z.squared() + c;
if (z.magnitude > 2) return i;
}
return maxIter;
}
int _getColor(int escapeTime, int maxIter) {
if (escapeTime == maxIter) return 0xFF000000;
final t = escapeTime / maxIter;
int r, g, b;
switch (_colorScheme) {
case 0:
r = (9 * (1 - t) * t * t * t * 255).toInt();
g = (15 * (1 - t) * (1 - t) * t * t * 255).toInt();
b = (8.5 * (1 - t) * (1 - t) * (1 - t) * t * 255).toInt();
break;
case 1:
r = (t * 255).toInt();
g = ((1 - t) * 255).toInt();
b = (sin(t * pi) * 255).toInt();
break;
default:
final hue = t * 360;
r = ((1 + sin(hue * pi / 180)) * 127.5).toInt();
g = ((1 + sin((hue - 120) * pi / 180)) * 127.5).toInt();
b = ((1 + sin((hue - 240) * pi / 180)) * 127.5).toInt();
}
return 0xFF000000 | (r << 16) | (g << 8) | b;
}
void _zoomIn() {
setState(() {
_zoom *= 1.5;
_maxIterations = (100 + _zoom * 10).toInt();
});
_renderFractal();
}
void _zoomOut() {
setState(() {
_zoom /= 1.5;
if (_zoom < 0.1) _zoom = 0.1;
_maxIterations = (100 + _zoom * 10).toInt();
});
_renderFractal();
}
void _goToLocation(double real, double imag) {
setState(() {
_center = Complex(real, imag);
_zoom = 100;
_maxIterations = 200;
});
_renderFractal();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('曼德博集合')),
body: Stack(children: [
GestureDetector(
onTapDown: (details) {
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
final dx = details.localPosition.dx - size.width / 2;
final dy = details.localPosition.dy - size.height / 2;
final scale = 3.0 / (_zoom * min(size.width, size.height));
setState(() {
_center = Complex(_center.real + dx * scale, _center.imag + dy * scale);
});
_renderFractal();
},
child: CustomPaint(
painter: ImagePainter(_cachedImage),
size: Size.infinite,
),
),
if (_isRendering)
const Center(child: CircularProgressIndicator()),
Positioned(top: 20, left: 20, child: _buildInfo()),
Positioned(bottom: 20, left: 20, right: 20, child: _buildControls()),
]),
);
}
Widget _buildInfo() {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(8)),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('缩放: ${_zoom.toStringAsFixed(1)}x', style: const TextStyle(color: Colors.white, fontSize: 14)),
Text('中心: (${_center.real.toStringAsFixed(4)}, ${_center.imag.toStringAsFixed(4)})', style: const TextStyle(color: Colors.white70, fontSize: 12)),
Text('迭代: $_maxIterations', style: const TextStyle(color: Colors.white70, fontSize: 12)),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.8), borderRadius: BorderRadius.circular(12)),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
Expanded(child: TextField(controller: _realController, decoration: const InputDecoration(labelText: '实部', labelStyle: TextStyle(color: Colors.white70)),
style: const TextStyle(color: Colors.white), keyboardType: TextInputType.number)),
const SizedBox(width: 8),
Expanded(child: TextField(controller: _imagController, decoration: const InputDecoration(labelText: '虚部', labelStyle: TextStyle(color: Colors.white70)),
style: const TextStyle(color: Colors.white), keyboardType: TextInputType.number)),
IconButton(icon: const Icon(Icons.my_location, color: Colors.deepPurple),
onPressed: () => _goToLocation(double.tryParse(_realController.text) ?? -0.5, double.tryParse(_imagController.text) ?? 0)),
]),
const SizedBox(height: 8),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
IconButton(icon: const Icon(Icons.add, color: Colors.deepPurple), onPressed: _zoomIn),
IconButton(icon: const Icon(Icons.remove, color: Colors.deepPurple), onPressed: _zoomOut),
IconButton(icon: const Icon(Icons.refresh, color: Colors.deepPurple), onPressed: () {
setState(() {
_center = const Complex(-0.5, 0);
_zoom = 1.0;
_maxIterations = 100;
});
_renderFractal();
}),
IconButton(icon: const Icon(Icons.palette, color: Colors.deepPurple), onPressed: () {
setState(() => _colorScheme = (_colorScheme + 1) % 3);
_renderFractal();
}),
]),
const SizedBox(height: 8),
Wrap(spacing: 8, children: [
_buildPresetChip('海马谷', -0.75, 0.1),
_buildPresetChip('螺旋', -0.745, 0.113),
_buildPresetChip('闪电', -1.25066, 0.02012),
_buildPresetChip('象谷', 0.275, 0.0),
]),
]),
);
}
Widget _buildPresetChip(String label, double real, double imag) {
return ActionChip(
label: Text(label, style: const TextStyle(color: Colors.white, fontSize: 11)),
backgroundColor: Colors.deepPurple.withOpacity(0.5),
onPressed: () => _goToLocation(real, imag),
);
}
@override
void dispose() {
_realController.dispose();
_imagController.dispose();
super.dispose();
}
}
class ImagePainter extends CustomPainter {
final ui.Image? image;
ImagePainter(this.image);
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = Colors.black);
if (image != null) {
canvas.drawImageRect(image!, Rect.fromLTWH(0, 0, image!.width.toDouble(), image!.height.toDouble()),
Rect.fromLTWH(0, 0, size.width, size.height), Paint());
}
}
@override
bool shouldRepaint(covariant ImagePainter old) => image != old.image;
}
class JuliaSetDemo extends StatefulWidget {
const JuliaSetDemo({super.key});
@override
State<JuliaSetDemo> createState() => _JuliaSetDemoState();
}
class _JuliaSetDemoState extends State<JuliaSetDemo> {
Complex _c = const Complex(-0.4, 0.6);
int _maxIterations = 100;
bool _isRendering = false;
ui.Image? _cachedImage;
final List<Map<String, dynamic>> _presets = [
{'name': '龙形', 'c': const Complex(-0.4, 0.6)},
{'name': '螺旋', 'c': const Complex(0.285, 0.01)},
{'name': '树枝', 'c': const Complex(-0.8, 0.156)},
{'name': '闪电', 'c': const Complex(-0.70176, -0.3842)},
{'name': '雪花', 'c': const Complex(-0.835, -0.2321)},
{'name': '星云', 'c': const Complex(0.45, 0.1428)},
];
@override
void initState() {
super.initState();
_renderFractal();
}
void _renderFractal() {
if (_isRendering) return;
setState(() => _isRendering = true);
Future.delayed(const Duration(milliseconds: 50), () {
if (!mounted) return;
_generateImage().then((image) {
if (mounted) {
setState(() {
_cachedImage = image;
_isRendering = false;
});
}
});
});
}
Future<ui.Image> _generateImage() async {
const width = 800;
const height = 800;
final pixels = Int32List(width * height);
const scale = 4.0 / width;
for (int py = 0; py < height; py++) {
for (int px = 0; px < width; px++) {
final x = (px - width / 2) * scale;
final y = (py - height / 2) * scale;
final escapeTime = _calculateJulia(Complex(x, y), _c, _maxIterations);
pixels[py * width + px] = _getColor(escapeTime, _maxIterations);
}
}
final completer = Completer<ui.Image>();
ui.decodeImageFromPixels(
Uint8List.view(pixels.buffer),
width,
height,
ui.PixelFormat.rgba8888,
completer.complete,
);
return completer.future;
}
int _calculateJulia(Complex z0, Complex c, int maxIter) {
var z = z0;
for (int i = 0; i < maxIter; i++) {
z = z.squared() + c;
if (z.magnitude > 2) return i;
}
return maxIter;
}
int _getColor(int escapeTime, int maxIter) {
if (escapeTime == maxIter) return 0xFF000000;
final t = escapeTime / maxIter;
final hue = t * 360;
final r = ((1 + sin(hue * pi / 180)) * 127.5).toInt();
final g = ((1 + sin((hue - 120) * pi / 180)) * 127.5).toInt();
final b = ((1 + sin((hue - 240) * pi / 180)) * 127.5).toInt();
return 0xFF000000 | (r << 16) | (g << 8) | b;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('朱利亚集合')),
body: Stack(children: [
CustomPaint(painter: ImagePainter(_cachedImage), size: Size.infinite),
if (_isRendering) const Center(child: CircularProgressIndicator()),
Positioned(top: 20, left: 20, child: _buildInfo()),
Positioned(bottom: 20, left: 20, right: 20, child: _buildControls()),
]),
);
}
Widget _buildInfo() {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(8)),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('c = ${_c.real.toStringAsFixed(4)} + ${_c.imag.toStringAsFixed(4)}i', style: const TextStyle(color: Colors.white, fontSize: 14)),
Text('迭代: $_maxIterations', style: const TextStyle(color: Colors.white70, fontSize: 12)),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.8), borderRadius: BorderRadius.circular(12)),
child: Column(children: [
Wrap(spacing: 8, runSpacing: 8, children: _presets.map((p) => ActionChip(
label: Text(p['name'], style: const TextStyle(color: Colors.white, fontSize: 11)),
backgroundColor: _c == p['c'] ? Colors.purple : Colors.purple.withOpacity(0.3),
onPressed: () {
setState(() => _c = p['c'] as Complex);
_renderFractal();
},
)).toList()),
const SizedBox(height: 8),
Row(children: [
Expanded(child: Slider(value: _c.real, min: -2, max: 2, onChanged: (v) {
setState(() => _c = Complex(v, _c.imag));
_renderFractal();
}, activeColor: Colors.purple)),
Expanded(child: Slider(value: _c.imag, min: -2, max: 2, onChanged: (v) {
setState(() => _c = Complex(_c.real, v));
_renderFractal();
}, activeColor: Colors.purple)),
]),
]),
);
}
}
class MusicFractalDemo extends StatefulWidget {
const MusicFractalDemo({super.key});
@override
State<MusicFractalDemo> createState() => _MusicFractalDemoState();
}
class _MusicFractalDemoState extends State<MusicFractalDemo> with TickerProviderStateMixin {
late AnimationController _animController;
late AudioPlayer _audioPlayer;
Complex _c = const Complex(-0.4, 0.6);
double _time = 0;
bool _isPlaying = false;
Float32List _audioData = Float32List(64);
double _energy = 0, _bass = 0, _mid = 0, _treble = 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: 33))..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));
try { await _audioPlayer.setUrl(_audioUrl); } catch (e) { debugPrint('加载失败: $e'); }
}
void _update() {
_time += 0.033;
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;
_c = Complex(-0.4 + (_mid - 0.5) * 0.3, 0.6 + (_treble - 0.5) * 0.3);
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: MusicFractalPainter(_c, _energy, _time, _bass, _mid, _treble), 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.indigo),
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.indigo : Colors.grey[800], borderRadius: BorderRadius.circular(12)),
child: Text(_isPlaying ? '播放中' : '暂停', style: const TextStyle(color: Colors.white, fontSize: 12))),
]),
const SizedBox(height: 12),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
IconButton(icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.indigo, size: 36),
onPressed: () => _isPlaying ? _audioPlayer.pause() : _audioPlayer.play()),
]),
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)),
]),
]),
);
}
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: 30, decoration: BoxDecoration(color: Colors.grey[800], borderRadius: BorderRadius.circular(4)),
child: Align(alignment: Alignment.bottomCenter,
child: AnimatedContainer(duration: const Duration(milliseconds: 50), height: (value * 30).clamp(2.0, 30.0),
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(4))))),
]);
}
}
class MusicFractalPainter extends CustomPainter {
final Complex c;
final double energy;
final double time;
final double bass;
final double mid;
final double treble;
MusicFractalPainter(this.c, this.energy, this.time, this.bass, this.mid, this.treble);
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final scale = min(size.width, size.height) * 0.2;
final bgColor = Color.lerp(const Color(0xFF050510), const Color(0xFF100520), energy)!;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = bgColor);
final maxIter = (50 + energy * 100).toInt();
final pixelScale = 3.0 / (size.width * 0.4);
for (int py = 0; py < size.height; py += 2) {
for (int px = 0; px < size.width; px += 2) {
final x = (px - size.width / 2) * pixelScale;
final y = (py - size.height / 2) * pixelScale;
var z = Complex(x, y);
int iter = 0;
for (; iter < maxIter; iter++) {
z = z.squared() + c;
if (z.magnitude > 2) break;
}
if (iter < maxIter) {
final t = iter / maxIter;
final hue = (t * 180 + time * 30) % 360;
final alpha = (0.3 + t * 0.5) * (0.5 + energy * 0.5);
canvas.drawRect(Rect.fromLTWH(px.toDouble(), py.toDouble(), 2, 2), Paint()..color = HSVColor.fromAHSV(alpha, hue, 0.9, 1).toColor());
}
}
}
}
@override
bool shouldRepaint(covariant MusicFractalPainter old) => true;
}
class KochSnowflakeDemo extends StatefulWidget {
const KochSnowflakeDemo({super.key});
@override
State<KochSnowflakeDemo> createState() => _KochSnowflakeDemoState();
}
class _KochSnowflakeDemoState extends State<KochSnowflakeDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
int _iterations = 3;
double _time = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_controller.addListener(() {
_time += 0.016;
setState(() {});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('科赫雪花')),
body: Stack(children: [
CustomPaint(painter: KochSnowflakePainter(_iterations, _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: Column(children: [
Text('迭代次数: $_iterations', style: const TextStyle(color: Colors.white70, fontSize: 12)),
Slider(value: _iterations.toDouble(), min: 0, max: 6, divisions: 6,
onChanged: (v) => setState(() => _iterations = v.toInt()), activeColor: Colors.cyan),
Text('边数: ${3 * pow(4, _iterations)}', style: const TextStyle(color: Colors.white54, fontSize: 10)),
]),
);
}
}
class KochSnowflakePainter extends CustomPainter {
final int iterations;
final double time;
KochSnowflakePainter(this.iterations, this.time);
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final center = Offset(size.width / 2, size.height / 2);
final radius = min(size.width, size.height) * 0.35;
final p1 = center + Offset(0, -radius);
final p2 = center + Offset(radius * cos(pi / 6), radius * sin(pi / 6));
final p3 = center + Offset(-radius * cos(pi / 6), radius * sin(pi / 6));
final points = <Offset>[];
points.addAll(_generateKochCurve(p1, p2, iterations));
points.addAll(_generateKochCurve(p2, p3, iterations));
points.addAll(_generateKochCurve(p3, p1, iterations));
final path = Path()..moveTo(points.first.dx, points.first.dy);
for (final point in points.skip(1)) {
path.lineTo(point.dx, point.dy);
}
path.close();
final hue = (time * 20) % 360;
canvas.drawPath(path, Paint()..color = HSVColor.fromAHSV(0.8, hue, 0.7, 1).toColor()..style = PaintingStyle.stroke..strokeWidth = 1.5);
canvas.drawPath(path, Paint()..color = HSVColor.fromAHSV(0.2, hue, 0.5, 1).toColor()..style = PaintingStyle.fill);
}
List<Offset> _generateKochCurve(Offset p1, Offset p2, int depth) {
if (depth == 0) return [p1, p2];
final dx = p2.dx - p1.dx;
final dy = p2.dy - p1.dy;
final pA = Offset(p1.dx + dx / 3, p1.dy + dy / 3);
final pB = Offset(p1.dx + dx * 2 / 3, p1.dy + dy * 2 / 3);
final angle = -pi / 3;
final pC = Offset(
pA.dx + (pB.dx - pA.dx) * cos(angle) - (pB.dy - pA.dy) * sin(angle),
pA.dy + (pB.dx - pA.dx) * sin(angle) + (pB.dy - pA.dy) * cos(angle),
);
return [
..._generateKochCurve(p1, pA, depth - 1),
..._generateKochCurve(pA, pC, depth - 1),
..._generateKochCurve(pC, pB, depth - 1),
..._generateKochCurve(pB, p2, depth - 1),
];
}
@override
bool shouldRepaint(covariant KochSnowflakePainter old) => true;
}
class SierpinskiDemo extends StatefulWidget {
const SierpinskiDemo({super.key});
@override
State<SierpinskiDemo> createState() => _SierpinskiDemoState();
}
class _SierpinskiDemoState extends State<SierpinskiDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
int _iterations = 5;
double _time = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_controller.addListener(() {
_time += 0.016;
setState(() {});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('谢尔宾斯基三角形')),
body: Stack(children: [
CustomPaint(painter: SierpinskiPainter(_iterations, _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: Column(children: [
Text('迭代次数: $_iterations', style: const TextStyle(color: Colors.white70, fontSize: 12)),
Slider(value: _iterations.toDouble(), min: 0, max: 8, divisions: 8,
onChanged: (v) => setState(() => _iterations = v.toInt()), activeColor: Colors.teal),
Text('三角形数: ${pow(3, _iterations)}', style: const TextStyle(color: Colors.white54, fontSize: 10)),
]),
);
}
}
class SierpinskiPainter extends CustomPainter {
final int iterations;
final double time;
SierpinskiPainter(this.iterations, this.time);
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final center = Offset(size.width / 2, size.height / 2);
final radius = min(size.width, size.height) * 0.4;
final p1 = center + Offset(0, -radius);
final p2 = center + Offset(radius * cos(pi / 6), radius * sin(pi / 6));
final p3 = center + Offset(-radius * cos(pi / 6), radius * sin(pi / 6));
_drawSierpinski(canvas, p1, p2, p3, iterations);
}
void _drawSierpinski(Canvas canvas, Offset p1, Offset p2, Offset p3, int depth) {
if (depth == 0) {
final hue = ((p1.dx + p1.dy) * 0.5 + time * 20) % 360;
final path = Path()..moveTo(p1.dx, p1.dy)..lineTo(p2.dx, p2.dy)..lineTo(p3.dx, p3.dy)..close();
canvas.drawPath(path, Paint()..color = HSVColor.fromAHSV(0.7, hue, 0.8, 1).toColor());
return;
}
final m1 = Offset((p1.dx + p2.dx) / 2, (p1.dy + p2.dy) / 2);
final m2 = Offset((p2.dx + p3.dx) / 2, (p2.dy + p3.dy) / 2);
final m3 = Offset((p3.dx + p1.dx) / 2, (p3.dy + p1.dy) / 2);
_drawSierpinski(canvas, p1, m1, m3, depth - 1);
_drawSierpinski(canvas, m1, p2, m2, depth - 1);
_drawSierpinski(canvas, m3, m2, p3, depth - 1);
}
@override
bool shouldRepaint(covariant SierpinskiPainter old) => true;
}
class FractalTreeDemo extends StatefulWidget {
const FractalTreeDemo({super.key});
@override
State<FractalTreeDemo> createState() => _FractalTreeDemoState();
}
class _FractalTreeDemoState extends State<FractalTreeDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
int _depth = 9;
double _angle = 25;
double _lengthRatio = 0.7;
double _time = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_controller.addListener(() {
_time += 0.016;
setState(() {});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('分形树')),
body: Stack(children: [
CustomPaint(painter: FractalTreePainter(_depth, _angle, _lengthRatio, _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: Column(children: [
Row(children: [
Expanded(child: Column(children: [
Text('深度: $_depth', style: const TextStyle(color: Colors.white70, fontSize: 11)),
Slider(value: _depth.toDouble(), min: 1, max: 12, divisions: 11,
onChanged: (v) => setState(() => _depth = v.toInt()), activeColor: Colors.green),
])),
Expanded(child: Column(children: [
Text('角度: ${_angle.toStringAsFixed(0)}°', style: const TextStyle(color: Colors.white70, fontSize: 11)),
Slider(value: _angle, min: 10, max: 60,
onChanged: (v) => setState(() => _angle = v), activeColor: Colors.green),
])),
]),
Text('长度比: ${_lengthRatio.toStringAsFixed(2)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
Slider(value: _lengthRatio, min: 0.5, max: 0.9,
onChanged: (v) => setState(() => _lengthRatio = v), activeColor: Colors.green),
]),
);
}
}
class FractalTreePainter extends CustomPainter {
final int depth;
final double angle;
final double lengthRatio;
final double time;
FractalTreePainter(this.depth, this.angle, this.lengthRatio, this.time);
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final startX = size.width / 2;
final startY = size.height * 0.85;
final startLength = size.height * 0.25;
_drawBranch(canvas, Offset(startX, startY), -pi / 2, startLength, depth);
}
void _drawBranch(Canvas canvas, Offset start, double angle, double length, int currentDepth) {
if (currentDepth == 0 || length < 2) return;
final end = Offset(start.dx + length * cos(angle), start.dy + length * sin(angle));
final hue = (120 + (depth - currentDepth) * 15 + time * 10) % 360;
final strokeWidth = (currentDepth * 1.5).clamp(1.0, 15.0);
canvas.drawLine(start, end, Paint()
..color = HSVColor.fromAHSV(0.8, hue, 0.8, 1).toColor()
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round);
final windOffset = sin(time * 2 + start.dy * 0.01) * 0.05;
final newAngle = angle * 180 / pi;
_drawBranch(canvas, end, (newAngle - this.angle + windOffset * 30) * pi / 180, length * lengthRatio, currentDepth - 1);
_drawBranch(canvas, end, (newAngle + this.angle + windOffset * 30) * pi / 180, length * lengthRatio, currentDepth - 1);
}
@override
bool shouldRepaint(covariant FractalTreePainter old) => true;
}
class BarnsleyFernDemo extends StatefulWidget {
const BarnsleyFernDemo({super.key});
@override
State<BarnsleyFernDemo> createState() => _BarnsleyFernDemoState();
}
class _BarnsleyFernDemoState extends State<BarnsleyFernDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
int _points = 50000;
double _time = 0;
List<Offset> _fernPoints = [];
@override
void initState() {
super.initState();
_generateFern();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_controller.addListener(() {
_time += 0.016;
setState(() {});
});
}
void _generateFern() {
_fernPoints = [];
double x = 0, y = 0;
final random = Random(42);
for (int i = 0; i < _points; i++) {
final r = random.nextDouble();
double newX, newY;
if (r < 0.01) {
newX = 0;
newY = 0.16 * y;
} else if (r < 0.86) {
newX = 0.85 * x + 0.04 * y;
newY = -0.04 * x + 0.85 * y + 1.6;
} else if (r < 0.93) {
newX = 0.2 * x - 0.26 * y;
newY = 0.23 * x + 0.22 * y + 1.6;
} else {
newX = -0.15 * x + 0.28 * y;
newY = 0.26 * x + 0.24 * y + 0.44;
}
x = newX;
y = newY;
_fernPoints.add(Offset(x, y));
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('巴恩斯利蕨')),
body: Stack(children: [
CustomPaint(painter: BarnsleyFernPainter(_fernPoints, _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: Column(children: [
Text('点数: $_points', style: const TextStyle(color: Colors.white70, fontSize: 12)),
Slider(value: _points.toDouble(), min: 10000, max: 100000, divisions: 9,
onChanged: (v) {
setState(() => _points = v.toInt());
_generateFern();
}, activeColor: Colors.lightGreen),
]),
);
}
}
class BarnsleyFernPainter extends CustomPainter {
final List<Offset> fernPoints;
final double time;
BarnsleyFernPainter(this.fernPoints, this.time);
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final scale = min(size.width, size.height) / 12;
final offsetX = size.width / 2;
final offsetY = size.height * 0.95;
for (int i = 0; i < fernPoints.length; i++) {
final p = fernPoints[i];
final x = offsetX + p.dx * scale;
final y = offsetY - p.dy * scale;
final hue = (120 + p.dy * 10 + time * 5) % 360;
final alpha = 0.3 + (i / fernPoints.length) * 0.5;
canvas.drawCircle(Offset(x, y), 1, Paint()..color = HSVColor.fromAHSV(alpha, hue, 0.8, 0.8).toColor());
}
}
@override
bool shouldRepaint(covariant BarnsleyFernPainter old) => true;
}
class FractalAnimationDemo extends StatefulWidget {
const FractalAnimationDemo({super.key});
@override
State<FractalAnimationDemo> createState() => _FractalAnimationDemoState();
}
class _FractalAnimationDemoState extends State<FractalAnimationDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
double _time = 0;
int _fractalType = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_controller.addListener(() {
_time += 0.016;
setState(() {});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('分形动画')),
body: Stack(children: [
CustomPaint(painter: FractalAnimationPainter(_time, _fractalType), 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: Column(children: [
Text('分形类型', style: const TextStyle(color: Colors.white70, fontSize: 12)),
const SizedBox(height: 8),
Wrap(spacing: 8, children: [
ActionChip(label: const Text('螺旋', style: TextStyle(color: Colors.white, fontSize: 11)),
backgroundColor: _fractalType == 0 ? Colors.pink : Colors.pink.withOpacity(0.3),
onPressed: () => setState(() => _fractalType = 0)),
ActionChip(label: const Text('漩涡', style: TextStyle(color: Colors.white, fontSize: 11)),
backgroundColor: _fractalType == 1 ? Colors.pink : Colors.pink.withOpacity(0.3),
onPressed: () => setState(() => _fractalType = 1)),
ActionChip(label: const Text('星系', style: TextStyle(color: Colors.white, fontSize: 11)),
backgroundColor: _fractalType == 2 ? Colors.pink : Colors.pink.withOpacity(0.3),
onPressed: () => setState(() => _fractalType = 2)),
]),
]),
);
}
}
class FractalAnimationPainter extends CustomPainter {
final double time;
final int fractalType;
FractalAnimationPainter(this.time, this.fractalType);
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF050510));
final center = Offset(size.width / 2, size.height / 2);
final scale = min(size.width, size.height) * 0.4;
switch (fractalType) {
case 0:
_drawSpiralFractal(canvas, center, scale);
break;
case 1:
_drawVortexFractal(canvas, center, scale);
break;
case 2:
_drawGalaxyFractal(canvas, center, scale);
break;
}
}
void _drawSpiralFractal(Canvas canvas, Offset center, double scale) {
for (int i = 0; i < 500; i++) {
final t = i / 500;
final angle = t * 10 * pi + time;
final r = t * scale;
final x = center.dx + r * cos(angle);
final y = center.dy + r * sin(angle);
final hue = (t * 360 + time * 50) % 360;
final alpha = 0.3 + t * 0.5;
canvas.drawCircle(Offset(x, y), 2 + t * 3, Paint()..color = HSVColor.fromAHSV(alpha, hue, 0.8, 1).toColor());
}
}
void _drawVortexFractal(Canvas canvas, Offset center, double scale) {
for (int ring = 0; ring < 20; ring++) {
final ringRadius = (ring / 20) * scale;
final pointsInRing = 20 + ring * 5;
for (int i = 0; i < pointsInRing; i++) {
final angle = (i / pointsInRing) * 2 * pi + time * (1 + ring * 0.1);
final r = ringRadius + sin(time * 3 + ring) * 10;
final x = center.dx + r * cos(angle);
final y = center.dy + r * sin(angle);
final hue = (ring * 18 + time * 30) % 360;
canvas.drawCircle(Offset(x, y), 2, Paint()..color = HSVColor.fromAHSV(0.7, hue, 0.9, 1).toColor());
}
}
}
void _drawGalaxyFractal(Canvas canvas, Offset center, double scale) {
final arms = 4;
for (int arm = 0; arm < arms; arm++) {
final armAngle = (arm / arms) * 2 * pi;
for (int i = 0; i < 200; i++) {
final t = i / 200;
final spiralAngle = armAngle + t * 4 * pi + time * 0.5;
final r = t * scale;
final spread = sin(t * 20 + time * 2) * 20 * t;
final x = center.dx + r * cos(spiralAngle) + spread * cos(spiralAngle + pi / 2);
final y = center.dy + r * sin(spiralAngle) + spread * sin(spiralAngle + pi / 2);
final hue = (arm * 90 + t * 60 + time * 20) % 360;
final alpha = 0.2 + (1 - t) * 0.6;
canvas.drawCircle(Offset(x, y), 1 + (1 - t) * 2, Paint()..color = HSVColor.fromAHSV(alpha, hue, 0.8, 1).toColor());
}
}
for (int i = 0; i < 100; i++) {
final angle = (i / 100) * 2 * pi;
final r = 20 + sin(time * 5 + i * 0.5) * 10;
final x = center.dx + r * cos(angle);
final y = center.dy + r * sin(angle);
canvas.drawCircle(Offset(x, y), 1, Paint()..color = Colors.white.withOpacity(0.5));
}
}
@override
bool shouldRepaint(covariant FractalAnimationPainter old) => true;
}
🎯 五、总结与展望
📝 5.1 本文要点回顾
分形几何核心概念:
1. 自相似性
- 整体与局部的相似
- 不同尺度下的重复结构
- 数学与自然的统一
2. 曼德博集合
- z = z² + c 迭代
- 无限复杂的边界
- 包含所有朱利亚集合
3. 朱利亚集合
- 固定 c 值的迭代
- 与曼德博集合的对应关系
- 丰富的视觉形态
4. 经典分形
- 科赫雪花:无限周长有限面积
- 谢尔宾斯基:自相似三角形
- 巴恩斯利蕨:自然形态模拟
5. 音乐与分形
- 音乐的分形结构
- 音频驱动的分形可视化
- 分形维度与音乐复杂度
🚀 5.2 实际应用场景
分形几何的应用领域:
1. 艺术与设计
- 生成艺术
- 纹理设计
- 建筑设计
2. 科学研究
- 自然现象建模
- 信号处理
- 数据压缩
3. 音乐可视化
- 实时音频分形
- 音乐视频特效
- 沉浸式体验
4. 教育工具
- 数学可视化
- 概念演示
- 交互式学习
📚 5.3 扩展阅读
- 《The Fractal Geometry of Nature》- Benoît Mandelbrot
- 《Chaos and Fractals: New Frontiers of Science》
- 曼德博集合在线探索工具
- 分形音乐生成研究
💡 提示:分形几何是数学与艺术的完美结合,通过简单的迭代规则可以产生无限复杂的美丽图案。在音乐可视化中,分形提供了一种将声音转化为视觉的独特方式。