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

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🌳 一、分形树:自然界的递归之美
📚 1.1 分形基础概念
分形(Fractal)是一种在不同尺度上呈现自相似性的几何结构。自然界中的树木、河流、闪电、海岸线等都展现出分形特征。
分形定义:
分形特征:
1. 自相似性:局部与整体相似
2. 分数维度:维度可以是分数
3. 无限细节:放大后仍有结构
4. 简单规则:复杂形态源于简单规则
分形维度:
D = log(N) / log(S)
其中:
N = 自相似片数
S = 缩放比例
例子:
科赫曲线:D = log(4)/log(3) ≈ 1.26
康托集:D = log(2)/log(3) ≈ 0.63
谢尔宾斯基三角形:D = log(3)/log(2) ≈ 1.58
分形树示意:
/\
/ \
/ \
/\ /\
/ \ / \
/\ /\/\ /\
/ \/ \/ \
每一级分支都是上一级的缩小复制
递归深度决定细节层次
📐 1.2 L-系统原理
L-系统(Lindenmayer系统)是由生物学家Aristid Lindenmayer于1968年提出的并行重写系统,用于模拟植物生长。
L-系统组成:
L-系统 = (V, ω, P)
V = 字母表(符号集合)
ω = 公理(初始字符串)
P = 产生式规则
例子:科赫曲线
V = {F, +, -}
ω = F
P = F → F+F-F-F+F
解释:
F = 向前画线
+ = 右转
- = 左转
常用符号:
| 符号 | 含义 |
|---|---|
| F | 向前画线 |
| f | 向前移动(不画线) |
| + | 右转指定角度 |
| - | 左转指定角度 |
| [ | 保存当前状态(压栈) |
| ] | 恢复保存状态(出栈) |
| X | 变量(不画图) |
🔬 1.3 经典分形树类型
简单分形树:
规则:F → F[+F]F[-F]F
角度:25°
迭代过程:
n=0: F
n=1: F[+F]F[-F]F
n=2: F[+F]F[-F]F[+F[+F]F[-F]F]F[+F]F[-F]F[-F[+F]F[-F]F]F[+F]F[-F]F
...
生成二叉树状结构
随机分形树:
引入随机性:
- 角度随机变化
- 长度随机变化
- 分支数随机
使树更自然:
角度 = 基础角度 ± 随机偏移
长度 = 基础长度 × (0.7 + 随机因子)
常见分形树规则:
| 名称 | 规则 | 角度 |
|---|---|---|
| 二叉树 | F → F[+F][-F] | 30° |
| 龙胆树 | F → FF+[+F-F-F]-[-F+F+F] | 22.5° |
| 随机树 | F → F[+F]F[-F][F] | 20° |
| 蕨类 | X → F+[[X]-X]-F[-FX]+X, F → FF | 25° |
各类型特点:
二叉树:
- 最基础的分形树类型
- 每个节点产生两个分支
- 完美对称,适合教学演示
龙胆树:
- 模拟真实龙血树的伞形树冠
- FF使主干持续生长
- 分支层次丰富
随机树:
- 引入随机性增加自然感
- 角度、长度随机变化
- 模拟真实环境中的不规则生长
蕨类:
- 使用变量X(不画图)和F(绘制)
- 属于迭代函数系统(IFS)
- 高度自相似性,迭代4次即可呈现完整形态
🎯 1.4 分形树的数学性质
维度计算:
分形树维度:
对于二叉分形树:
- 每级产生2个分支
- 分支长度为上一级的r倍
维度 D 满足:
2 × r^D = 1
解得:
D = -log(2) / log(r)
例子:
r = 0.5: D = 1(线段)
r = 0.7: D ≈ 1.51
r = 0.8: D ≈ 1.16
生长规律:
分支长度递减:
L_n = L_0 × r^n
分支数量增长:
N_n = 2^n
总长度:
L_total = L_0 × (1 - r^n) / (1 - r)
当 n → ∞:
L_total = L_0 / (1 - r)
🔧 二、L-系统的 Dart 实现
🧮 2.1 L-系统核心类
dart
import 'dart:math';
/// L-系统
class LSystem {
final String axiom;
final Map<String, String> rules;
final double angle;
LSystem({
required this.axiom,
required this.rules,
required this.angle,
});
String generate(int iterations) {
var current = axiom;
for (int i = 0; i < iterations; i++) {
final buffer = StringBuffer();
for (final char in current.split('')) {
buffer.write(rules[char] ?? char);
}
current = buffer.toString();
}
return current;
}
List<TurtleCommand> parse(String str) {
return str.split('').map((char) {
switch (char) {
case 'F':
return TurtleCommand.draw;
case 'f':
return TurtleCommand.move;
case '+':
return TurtleCommand.turnRight;
case '-':
return TurtleCommand.turnLeft;
case '[':
return TurtleCommand.push;
case ']':
return TurtleCommand.pop;
default:
return TurtleCommand.none;
}
}).toList();
}
}
/// 海龟命令
enum TurtleCommand {
draw,
move,
turnRight,
turnLeft,
push,
pop,
none,
}
/// 海龟状态
class TurtleState {
double x;
double y;
double angle;
double length;
TurtleState({
required this.x,
required this.y,
required this.angle,
required this.length,
});
TurtleState copy() => TurtleState(
x: x,
y: y,
angle: angle,
length: length,
);
}
/// 海龟绘图器
class Turtle {
TurtleState state;
final List<TurtleState> stack = [];
final List<Offset> points = [];
final List<List<Offset>> branches = [];
List<Offset> currentBranch = [];
Turtle({
required double startX,
required double startY,
required double startAngle,
required double startLength,
}) : state = TurtleState(
x: startX,
y: startY,
angle: startAngle,
length: startLength,
);
void execute(TurtleCommand command, double angle, double lengthFactor) {
switch (command) {
case TurtleCommand.draw:
final newX = state.x + state.length * cos(state.angle);
final newY = state.y + state.length * sin(state.angle);
currentBranch.add(Offset(state.x, state.y));
currentBranch.add(Offset(newX, newY));
state.x = newX;
state.y = newY;
break;
case TurtleCommand.move:
state.x += state.length * cos(state.angle);
state.y += state.length * sin(state.angle);
break;
case TurtleCommand.turnRight:
state.angle += angle;
break;
case TurtleCommand.turnLeft:
state.angle -= angle;
break;
case TurtleCommand.push:
stack.add(state.copy());
if (currentBranch.isNotEmpty) {
branches.add(List.from(currentBranch));
currentBranch = [];
}
break;
case TurtleCommand.pop:
if (stack.isNotEmpty) {
if (currentBranch.isNotEmpty) {
branches.add(List.from(currentBranch));
}
state = stack.removeLast();
currentBranch = [];
}
break;
case TurtleCommand.none:
break;
}
}
List<List<Offset>> draw(LSystem lsystem, int iterations, double lengthFactor) {
final str = lsystem.generate(iterations);
final commands = lsystem.parse(str);
for (final command in commands) {
execute(command, lsystem.angle, lengthFactor);
state.length *= lengthFactor;
}
if (currentBranch.isNotEmpty) {
branches.add(currentBranch);
}
return branches;
}
}
⚡ 2.2 分形树生成器
dart
/// 分形树生成器
class FractalTreeGenerator {
final Random _random = Random();
double randomness = 0.0;
double branchRatio = 0.7;
double branchAngle = 25.0;
int maxDepth = 10;
List<Branch> generate(double startX, double startY, double startLength, double startAngle) {
final branches = <Branch>[];
_generateRecursive(branches, startX, startY, startLength, startAngle, 0);
return branches;
}
void _generateRecursive(
List<Branch> branches,
double x,
double y,
double length,
double angle,
int depth,
) {
if (depth >= maxDepth || length < 1) return;
final endX = x + length * cos(angle);
final endY = y + length * sin(angle);
branches.add(Branch(
start: Offset(x, y),
end: Offset(endX, endY),
depth: depth,
));
final newLength = length * branchRatio * (1 + randomness * (_random.nextDouble() - 0.5));
final angleVariation = branchAngle * (pi / 180) * (1 + randomness * (_random.nextDouble() - 0.5));
_generateRecursive(branches, endX, endY, newLength, angle - angleVariation, depth + 1);
_generateRecursive(branches, endX, endY, newLength, angle + angleVariation, depth + 1);
}
}
/// 分支
class Branch {
final Offset start;
final Offset end;
final int depth;
Branch({
required this.start,
required this.end,
required this.depth,
});
double get length => (end - start).distance;
Offset get midpoint => Offset((start.dx + end.dx) / 2, (start.dy + end.dy) / 2);
}
/// 预定义L-系统
class LSystemPresets {
static LSystem binaryTree() => LSystem(
axiom: 'F',
rules: {'F': 'FF+[+F-F-F]-[-F+F+F]'},
angle: 22.5 * pi / 180,
);
static LSystem dragonTree() => LSystem(
axiom: 'F',
rules: {'F': 'F[+F]F[-F]F'},
angle: 25.7 * pi / 180,
);
static LSystem fern() => LSystem(
axiom: 'X',
rules: {
'X': 'F+[[X]-X]-F[-FX]+X',
'F': 'FF',
},
angle: 25 * pi / 180,
);
static LSystem plant() => LSystem(
axiom: 'X',
rules: {
'X': 'F[+X][-X]FX',
'F': 'FF',
},
angle: 29.7 * pi / 180,
);
static LSystem seaweed() => LSystem(
axiom: 'F',
rules: {'F': 'FF-[-F+F+F]+[+F-F-F]'},
angle: 22.5 * pi / 180,
);
static LSystem tree2D() => LSystem(
axiom: 'X',
rules: {
'X': 'F[+X]F[-X]+X',
'F': 'FF',
},
angle: 30 * pi / 180,
);
}
🎨 2.3 可视化组件
dart
import 'package:flutter/material.dart';
/// 分形树绘制器
class FractalTreePainter extends CustomPainter {
final List<Branch> branches;
final Color trunkColor;
final Color leafColor;
final double maxDepth;
FractalTreePainter({
required this.branches,
required this.trunkColor,
required this.leafColor,
required this.maxDepth,
});
@override
void paint(Canvas canvas, Size size) {
for (final branch in branches) {
final progress = branch.depth / maxDepth;
final color = Color.lerp(trunkColor, leafColor, progress)!;
final width = (1 - progress * 0.7) * 4;
final paint = Paint()
..color = color
..strokeWidth = width
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
canvas.drawLine(branch.start, branch.end, paint);
}
}
@override
bool shouldRepaint(covariant FractalTreePainter old) =>
branches.length != old.branches.length;
}
/// L-系统绘制器
class LSystemPainter extends CustomPainter {
final List<List<Offset>> branches;
final Color color;
LSystemPainter({
required this.branches,
this.color = Colors.green,
});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..strokeWidth = 1.5
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
for (final branch in branches) {
if (branch.length < 2) continue;
final path = Path();
path.moveTo(branch[0].dx, branch[0].dy);
for (int i = 1; i < branch.length; i++) {
path.lineTo(branch[i].dx, branch[i].dy);
}
canvas.drawPath(path, paint);
}
}
@override
bool shouldRepaint(covariant LSystemPainter old) =>
branches.length != old.branches.length;
}
📦 三、完整示例代码
以下是完整的分形树可视化示例代码:
dart
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(const FractalTreeApp());
}
class FractalTreeApp extends StatelessWidget {
const FractalTreeApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '分形树',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green, brightness: Brightness.dark),
useMaterial3: true,
),
home: const FractalTreeHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class FractalTreeHomePage extends StatelessWidget {
const FractalTreeHomePage({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.nature, color: Colors.green,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const RecursiveTreeDemo()))),
_buildCard(context, title: 'L-系统树', description: 'Lindenmayer系统生成', icon: Icons.account_tree, color: Colors.teal,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const LSystemTreeDemo()))),
_buildCard(context, title: '随机森林', description: '随机参数自然树', icon: Icons.forest, color: Colors.brown,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const RandomForestDemo()))),
_buildCard(context, title: '动画生长', description: '动态生长动画', icon: Icons.animation, color: Colors.lime,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const AnimatedTreeDemo()))),
],
),
);
}
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 RecursiveTreeDemo extends StatefulWidget {
const RecursiveTreeDemo({super.key});
@override
State<RecursiveTreeDemo> createState() => _RecursiveTreeDemoState();
}
class _RecursiveTreeDemoState extends State<RecursiveTreeDemo> {
double _branchAngle = 25.0;
double _branchRatio = 0.7;
int _maxDepth = 10;
List<Branch> _branches = [];
@override
void initState() {
super.initState();
_generateTree();
}
void _generateTree() {
_branches.clear();
_generateRecursive(0, 0, 100, -pi / 2, 0);
setState(() {});
}
void _generateRecursive(double x, double y, double length, double angle, int depth) {
if (depth >= _maxDepth || length < 2) return;
final endX = x + length * cos(angle);
final endY = y + length * sin(angle);
_branches.add(Branch(start: Offset(x, y), end: Offset(endX, endY), depth: depth));
final newLength = length * _branchRatio;
final angleRad = _branchAngle * pi / 180;
_generateRecursive(endX, endY, newLength, angle - angleRad, depth + 1);
_generateRecursive(endX, endY, newLength, angle + angleRad, depth + 1);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('递归分形树')),
body: Column(children: [
Expanded(child: CustomPaint(painter: TreePainter(_branches, _maxDepth), size: Size.infinite)),
_buildControls(),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Colors.grey[900], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
const Text('分支角度: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _branchAngle, min: 10, max: 60, onChanged: (v) { _branchAngle = v; _generateTree(); })),
Text('${_branchAngle.toInt()}°', style: const TextStyle(color: Colors.green)),
]),
Row(children: [
const Text('缩放比例: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _branchRatio, min: 0.5, max: 0.9, onChanged: (v) { _branchRatio = v; _generateTree(); })),
Text(_branchRatio.toStringAsFixed(2), style: const TextStyle(color: Colors.green)),
]),
Row(children: [
const Text('递归深度: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _maxDepth.toDouble(), min: 1, max: 14, divisions: 13, onChanged: (v) { _maxDepth = v.toInt(); _generateTree(); })),
Text('$_maxDepth', style: const TextStyle(color: Colors.green)),
]),
]),
);
}
}
class Branch {
final Offset start, end;
final int depth;
Branch({required this.start, required this.end, required this.depth});
}
class TreePainter extends CustomPainter {
final List<Branch> branches;
final int maxDepth;
TreePainter(this.branches, this.maxDepth);
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final cx = size.width / 2;
final cy = size.height * 0.85;
final scale = size.height / 300;
for (final b in branches) {
final progress = b.depth / maxDepth;
final color = Color.lerp(Colors.brown, Colors.green, progress)!;
final width = (1 - progress * 0.8) * 5 * scale;
final paint = Paint()..color = color..strokeWidth = width..strokeCap = StrokeCap.round..style = PaintingStyle.stroke;
final start = Offset(cx + b.start.dx * scale, cy + b.start.dy * scale);
final end = Offset(cx + b.end.dx * scale, cy + b.end.dy * scale);
canvas.drawLine(start, end, paint);
}
}
@override
bool shouldRepaint(covariant TreePainter old) => true;
}
/// L-系统树演示
class LSystemTreeDemo extends StatefulWidget {
const LSystemTreeDemo({super.key});
@override
State<LSystemTreeDemo> createState() => _LSystemTreeDemoState();
}
class _LSystemTreeDemoState extends State<LSystemTreeDemo> {
int _presetIndex = 0;
int _iterations = 4;
List<List<Offset>> _branches = [];
final List<_LPreset> _presets = [
_LPreset('龙胆树', 'F', {'F': 'FF+[+F-F-F]-[-F+F+F]'}, 22.5),
_LPreset('简单树', 'F', {'F': 'F[+F]F[-F]F'}, 25.7),
_LPreset('蕨类', 'X', {'X': 'F+[[X]-X]-F[-FX]+X', 'F': 'FF'}, 25),
_LPreset('植物', 'X', {'X': 'F[+X][-X]FX', 'F': 'FF'}, 29.7),
_LPreset('海草', 'F', {'F': 'FF-[-F+F+F]+[+F-F-F]'}, 22.5),
];
@override
void initState() {
super.initState();
_generate();
}
void _generate() {
final preset = _presets[_presetIndex];
String current = preset.axiom;
for (int i = 0; i < _iterations; i++) {
final buffer = StringBuffer();
for (final c in current.split('')) {
buffer.write(preset.rules[c] ?? c);
}
current = buffer.toString();
}
_branches = _interpret(current, preset.angle);
setState(() {});
}
List<List<Offset>> _interpret(String str, double angleDeg) {
final branches = <List<Offset>>[];
final stack = <_TState>[];
var state = _TState(0, 0, -90);
var currentBranch = <Offset>[];
final angle = angleDeg * pi / 180;
final length = 5.0;
for (final c in str.split('')) {
switch (c) {
case 'F':
final newX = state.x + length * cos(state.angle);
final newY = state.y + length * sin(state.angle);
currentBranch.add(Offset(state.x, state.y));
currentBranch.add(Offset(newX, newY));
state = _TState(newX, newY, state.angle);
case '+':
state = _TState(state.x, state.y, state.angle + angle);
case '-':
state = _TState(state.x, state.y, state.angle - angle);
case '[':
stack.add(state);
if (currentBranch.isNotEmpty) branches.add(List.from(currentBranch));
currentBranch = [];
case ']':
if (currentBranch.isNotEmpty) branches.add(List.from(currentBranch));
currentBranch = [];
if (stack.isNotEmpty) state = stack.removeLast();
}
}
if (currentBranch.isNotEmpty) branches.add(currentBranch);
return branches;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('L-系统树')),
body: Column(children: [
Expanded(child: CustomPaint(painter: LTreePainter(_branches), size: Size.infinite)),
_buildControls(),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Colors.grey[900], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
const Text('类型: ', style: TextStyle(color: Colors.white70)),
Expanded(child: SegmentedButton(selected: {_presetIndex}, segments: List.generate(_presets.length, (i) => ButtonSegment(value: i, label: Text(_presets[i].name))), onSelectionChanged: (s) { _presetIndex = s.first; _generate(); })),
]),
const SizedBox(height: 8),
Row(children: [
const Text('迭代: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _iterations.toDouble(), min: 1, max: 6, divisions: 5, onChanged: (v) { _iterations = v.toInt(); _generate(); })),
Text('$_iterations', style: const TextStyle(color: Colors.teal)),
]),
]),
);
}
}
class _TState {
final double x, y, angle;
_TState(this.x, this.y, this.angle);
}
class _LPreset {
final String name, axiom;
final Map<String, String> rules;
final double angle;
_LPreset(this.name, this.axiom, this.rules, this.angle);
}
class LTreePainter extends CustomPainter {
final List<List<Offset>> branches;
LTreePainter(this.branches);
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final cx = size.width / 2;
final cy = size.height * 0.95;
final scale = size.height / 400;
final colors = [Colors.green, Colors.teal, Colors.cyan, Colors.lime, Colors.lightGreen];
for (int i = 0; i < branches.length; i++) {
final branch = branches[i];
if (branch.length < 2) continue;
final path = Path();
for (int j = 0; j < branch.length; j++) {
final x = cx + branch[j].dx * scale;
final y = cy + branch[j].dy * scale;
if (j == 0) path.moveTo(x, y);
else path.lineTo(x, y);
}
canvas.drawPath(path, Paint()..color = colors[i % colors.length]..strokeWidth = 1.5..style = PaintingStyle.stroke..strokeCap = StrokeCap.round);
}
}
@override
bool shouldRepaint(covariant LTreePainter old) => true;
}
/// 随机森林演示
class RandomForestDemo extends StatefulWidget {
const RandomForestDemo({super.key});
@override
State<RandomForestDemo> createState() => _RandomForestDemoState();
}
class _RandomForestDemoState extends State<RandomForestDemo> {
Random _random = Random(42);
List<_Tree> _trees = [];
double _randomness = 0.3;
int _treeCount = 5;
@override
void initState() {
super.initState();
_generateForest();
}
void _generateForest() {
_trees.clear();
for (int i = 0; i < _treeCount; i++) {
final x = (i + 0.5) / _treeCount;
final height = 80 + _random.nextDouble() * 40;
_trees.add(_Tree(x, height, _randomness, _random));
}
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('随机森林')),
body: Column(children: [
Expanded(child: CustomPaint(painter: ForestPainter(_trees), size: Size.infinite)),
_buildControls(),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Colors.grey[900], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
const Text('随机性: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _randomness, min: 0, max: 0.6, onChanged: (v) { _randomness = v; _generateForest(); })),
Text(_randomness.toStringAsFixed(2), style: const TextStyle(color: Colors.brown)),
]),
Row(children: [
const Text('树木数: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _treeCount.toDouble(), min: 1, max: 10, divisions: 9, onChanged: (v) { _treeCount = v.toInt(); _generateForest(); })),
Text('$_treeCount', style: const TextStyle(color: Colors.brown)),
]),
ElevatedButton(onPressed: () { _random = Random(DateTime.now().millisecondsSinceEpoch); _generateForest(); }, child: const Text('重新生成')),
]),
);
}
}
class _Tree {
final double x, height, randomness;
final Random random;
final List<Branch> branches = [];
_Tree(this.x, this.height, this.randomness, this.random) {
_generate(0, 0, height, -pi / 2, 0);
}
void _generate(double x, double y, double len, double ang, int depth) {
if (depth > 9 || len < 3) return;
final endX = x + len * cos(ang);
final endY = y + len * sin(ang);
branches.add(Branch(start: Offset(x, y), end: Offset(endX, endY), depth: depth));
final newLen = len * (0.65 + randomness * (random.nextDouble() - 0.5));
final angVar = (25 + randomness * random.nextDouble() * 20) * pi / 180;
_generate(endX, endY, newLen, ang - angVar, depth + 1);
_generate(endX, endY, newLen, ang + angVar, depth + 1);
}
}
class ForestPainter extends CustomPainter {
final List<_Tree> trees;
ForestPainter(this.trees);
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
for (final tree in trees) {
final baseX = tree.x * size.width;
final baseY = size.height * 0.9;
final scale = size.height / 200;
for (final b in tree.branches) {
final progress = b.depth / 10;
final color = Color.lerp(Color(0xFF4a2c0a), Colors.green, progress)!;
final width = (1 - progress * 0.8) * 4;
final paint = Paint()..color = color..strokeWidth = width..strokeCap = StrokeCap.round..style = PaintingStyle.stroke;
final start = Offset(baseX + b.start.dx * scale, baseY + b.start.dy * scale);
final end = Offset(baseX + b.end.dx * scale, baseY + b.end.dy * scale);
canvas.drawLine(start, end, paint);
}
}
}
@override
bool shouldRepaint(covariant ForestPainter old) => true;
}
/// 动画生长演示
class AnimatedTreeDemo extends StatefulWidget {
const AnimatedTreeDemo({super.key});
@override
State<AnimatedTreeDemo> createState() => _AnimatedTreeDemoState();
}
class _AnimatedTreeDemoState extends State<AnimatedTreeDemo> with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
double _progress = 0;
List<Branch> _allBranches = [];
double _branchAngle = 25;
double _branchRatio = 0.7;
@override
void initState() {
super.initState();
_generateAllBranches();
_ctrl = AnimationController(vsync: this, duration: const Duration(seconds: 3))..repeat();
_ctrl.addListener(() {
setState(() => _progress = _ctrl.value);
});
}
void _generateAllBranches() {
_allBranches.clear();
_generateRecursive(0, 0, 100, -pi / 2, 0);
}
void _generateRecursive(double x, double y, double length, double angle, int depth) {
if (depth > 10 || length < 2) return;
final endX = x + length * cos(angle);
final endY = y + length * sin(angle);
_allBranches.add(Branch(start: Offset(x, y), end: Offset(endX, endY), depth: depth));
final newLength = length * _branchRatio;
final angleRad = _branchAngle * pi / 180;
_generateRecursive(endX, endY, newLength, angle - angleRad, depth + 1);
_generateRecursive(endX, endY, newLength, angle + angleRad, depth + 1);
}
List<Branch> _getVisibleBranches() {
final totalDepth = 11;
final visibleDepth = (_progress * totalDepth).floor();
return _allBranches.where((b) => b.depth < visibleDepth).toList();
}
@override
void dispose() { _ctrl.dispose(); super.dispose(); }
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('动画生长')),
body: Column(children: [
Expanded(child: CustomPaint(painter: TreePainter(_getVisibleBranches(), 10), size: Size.infinite)),
_buildControls(),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Colors.grey[900], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
const Text('分支角度: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _branchAngle, min: 10, max: 60, onChanged: (v) { _branchAngle = v; _generateAllBranches(); })),
Text('${_branchAngle.toInt()}°', style: const TextStyle(color: Colors.lime)),
]),
Row(children: [
const Text('缩放比例: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _branchRatio, min: 0.5, max: 0.9, onChanged: (v) { _branchRatio = v; _generateAllBranches(); })),
Text(_branchRatio.toStringAsFixed(2), style: const TextStyle(color: Colors.lime)),
]),
]),
);
}
}
📝 四、数学原理深入解析
📐 4.1 分形维度
Hausdorff维度:
定义:
对于自相似分形,若由N个相似比为r的
部分组成,则维度:
D = log(N) / log(1/r)
分形树维度:
二叉树:每级2分支,缩放比r
D = log(2) / log(1/r)
当 r = 0.7:
D = log(2) / log(1/0.7)
D ≈ 1.51
介于线段(D=1)和面(D=2)之间
盒子计数法:
用边长为ε的盒子覆盖分形
计数非空盒子数 N(ε)
维度:
D = lim(ε→0) log(N(ε)) / log(1/ε)
数值计算:
对不同的ε,计算N(ε)
拟合直线斜率即为维度
🔄 4.2 L-系统数学
形式语言理论:
L-系统是形式文法的一种
文法 G = (V, Σ, R, S)
V = 变量集
Σ = 终结符集
R = 产生式规则
S = 起始符
L-系统特点:
- 并行重写(同时替换所有符号)
- 无终结符区分
- 确定性或随机性
字符串增长:
设规则将1个符号变为k个符号
迭代n次后字符串长度:
L(n) = L(0) × k^n
例子:F → FF
L(n) = 2^n
例子:F → F[+F]F
L(n) ≈ 4^n
指数增长是分形复杂度的来源
🌸 4.3 递归与迭代
递归关系:
分支长度递推:
L_n = r × L_{n-1}
解:
L_n = L_0 × r^n
分支数量递推:
N_n = 2 × N_{n-1}
解:
N_n = 2^n
总长度:
S_n = L_0 × (1 - r^{n+1}) / (1 - r)
当 n → ∞:
S_∞ = L_0 / (1 - r) (r < 1)
树高计算:
树的总高度:
H = L_0 + L_0×r + L_0×r² + ...
H = L_0 / (1 - r)
树的总宽度:
W = 2 × L_0 × r × sin(θ) / (1 - r)
其中 θ 为分支角度
🎯 4.4 随机分形
随机L-系统:
随机规则:
F → F[+F]F (概率 p1)
F → F[-F]F (概率 p2)
F → F[+F][-F] (概率 p3)
概率满足:p1 + p2 + p3 = 1
随机参数:
角度 = θ + random(-δ, δ)
长度 = L × (1 + random(-ε, ε))
统计特性:
随机分形的统计自相似性
期望维度:
E[D] = log(E[N]) / log(1/E[r])
方差分析:
Var[D] 随样本增加而减小
中心极限定理适用:
大量随机分形的平均趋向确定性
🔬 五、高级应用场景
🎨 5.1 植物建模
真实植物模拟:
dart
class PlantModel {
final double apicalDominance;
final double branchAngle;
final double phyllotaxis;
List<Branch> generate(int seasons) {
// 模拟多年生长
return [];
}
}
生长规则:
顶端优势:
主干生长优先于侧枝
向光性:
枝条向光方向弯曲
重力效应:
枝条受重力影响下垂
🌐 5.2 地形生成
分形地形:
dart
class FractalTerrain {
final double roughness;
final int gridSize;
List<List<double>> generate() {
// Diamond-Square算法
return [];
}
}
📱 5.3 鸿蒙多端适配
性能配置:
dart
class FractalConfig {
static int getMaxDepth(BuildContext context) {
final level = DevicePerformance.getLevel();
switch (level) {
case PerformanceLevel.high: return 12;
case PerformanceLevel.medium: return 9;
case PerformanceLevel.low: return 6;
}
}
}
📊 六、性能优化策略
⚡ 6.1 分支裁剪
视锥剔除:
dart
bool isVisible(Branch branch, Rect viewport) {
return viewport.contains(branch.start) || viewport.contains(branch.end);
}
💾 6.2 缓存优化
预计算缓存:
dart
class TreeCache {
static final Map<String, List<Branch>> _cache = {};
static List<Branch> get(String key, double angle, double ratio) {
return _cache.putIfAbsent(key, () => _compute(angle, ratio));
}
}
🎓 七、学习资源与拓展
📚 推荐阅读
| 主题 | 资源 | 难度 |
|---|---|---|
| 分形几何 | 《分形几何》Mandelbrot | ⭐⭐ |
| L-系统 | 《植物的算法美》 | ⭐⭐⭐ |
| 递归艺术 | 《递归与分形》 | ⭐⭐ |
| 计算机图形 | 《计算机图形学》 | ⭐⭐⭐ |
🔗 相关项目
- L-Studio:L-系统建模工具
- VLab:虚拟植物实验室
- Structure Synth:3D结构生成器
📝 八、总结
本篇文章深入探讨了递归分形树与L-系统的数学原理及其在 Flutter 中的实现。
✅ 核心知识点回顾
| 知识点 | 说明 |
|---|---|
| 🌳分形概念 | 自相似性与分数维度 |
| 📐L-系统 | 并行重写系统 |
| 🔄递归生成 | 简单规则产生复杂形态 |
| 🎨可视化 | 海龟绘图法 |
| ⚡随机性 | 自然形态的模拟 |
⭐ 最佳实践要点
- ✅ 合理设置递归深度
- ✅ 使用缓存避免重复计算
- ✅ 随机性增加自然感
- ✅ 根据设备性能调整参数
🚀 进阶方向
- 🔮 三维分形树
- ✨ 季节变化模拟
- 📊 植物生长建模
- 🎨 交互式编辑器
💡 提示:本文代码基于 Flutter for Harmony 开发,可在鸿蒙设备上流畅运行。