Flutter for Harmony 跨平台开发实战:递归分形树——L-系统的生长逻辑

欢迎加入开源鸿蒙跨平台社区: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 开发,可在鸿蒙设备上流畅运行。

相关推荐
lqj_本人2 小时前
Flutter三方库适配OpenHarmony【apple_product_name】用户反馈系统集成设备信息
flutter
2601_949593652 小时前
Flutter for Harmony 跨平台开发实战:流场与矢量可视化——不可见力量的轨迹追踪
flutter
早點睡3902 小时前
Flutter for Harmony 跨平台开发实战:光线追踪——折射反射的光学模拟
flutter
早點睡3903 小时前
Flutter for Harmony 跨平台开发实战:鸿蒙与音乐律动艺术、FFT 频谱能量场:正弦函数的叠加艺术
flutter·华为·harmonyos
2601_949593653 小时前
Flutter for Harmony 跨平台开发实战:德劳内三角剖分——点集连接的几何美学
flutter
lili-felicity3 小时前
基础入门 Flutter for OpenHarmony:三方库实战 flutter_lifecycle_detector 生命周期检测详解
flutter
2601_949593653 小时前
Flutter for Harmony 跨平台开发实战:双曲几何与庞加莱圆盘——非欧空间的视觉映射
flutter
松叶似针3 小时前
Flutter三方库适配OpenHarmony【doc_text】— parseDocxXml:正则驱动的 XML 文本提取
xml·flutter
lili-felicity3 小时前
基础入门 Flutter for OpenHarmony:三方库实战 flutter_phone_direct_caller 电话拨号详解
flutter