在这里插入图片描述

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


🌸 一、超形状:数学之美

📚 1.1 超椭圆的发现

超椭圆(Superellipse)是一种介于椭圆和矩形之间的曲线,由丹麦数学家皮特·海因(Piet Hein)在1959年发现。它的数学形式简洁优雅,在建筑和设计领域有广泛应用。

超椭圆方程

标准形式:
|x/a|^n + |y/b|^n = 1

当 n = 2 时:标准椭圆
当 n > 2 时:趋向矩形
当 n < 2 时:趋向星形
当 n → ∞:矩形
当 n → 0:十字形

参数方程:
x = a·cos^(2/n)(t)
y = b·sin^(2/n)(t)

其中 t ∈ [0, 2π]

不同 n 值的形态

n = 0.5:        n = 1:          n = 2:          n = 4:          n → ∞:
    ╲              ╲              ────            ┌──┐            ┌──┐
     ╲              ╲            ╱    ╲          │  │            │  │
      ╲              ╲          ╱      ╲         │  │            │  │
       ●              ●        ●        ●        │  │            │  │
      ╱              ╱          ╲      ╱         │  │            │  │
     ╱              ╱            ╲    ╱          │  │            │  │
    ╱              ╱              ────            └──┘            └──┘
  十字形          菱形            椭圆          超椭圆           矩形

📐 1.2 Gielis 超形状公式

2003年,比利时植物学家 Johan Gielis 提出了更通用的超形状公式,可以描述从星形到花瓣的各种自然形态。

Gielis 超形状公式

极坐标形式:
r(θ) = (|cos(mθ/4)/a|^(n₂) + |sin(mθ/4)/b|^(n₃))^(-1/n₁)

参数说明:
- m:对称轴数量
- a, b:缩放因子
- n₁, n₂, n₃:形状指数

特殊情形:
- m = 4, n₁ = n₂ = n₃ = 2:椭圆
- m = 4, n₁ = n₂ = n₃ → ∞:矩形
- m = 5, n₁ = n₂ = n₃ = 1:五角星
- m = 6, n₁ = n₂ = n₃ = 1:六角星

参数影响

参数 作用 效果
m 对称轴数 决定花瓣/尖角数量
n₁ 整体曲率 控制凹陷程度
n₂ x 方向影响 调整水平形态
n₃ y 方向影响 调整垂直形态
a, b 缩放比例 控制各向异性

🔬 1.3 自然界中的超形状

超形状公式可以精确描述许多自然形态:

植物形态

花朵:
- m = 5, n₁ = 1, n₂ = n₃ = 1:五瓣花
- m = 6, n₁ = 0.3, n₂ = n₃ = 1:六瓣花
- m = 8, n₁ = 0.5, n₂ = n₃ = 1:八瓣花

叶子:
- m = 1, n₁ = 0.5, n₂ = n₃ = 1:心形叶
- m = 3, n₁ = 1, n₂ = n₃ = 1:三叶草

果实:
- m = 4, n₁ = 4, n₂ = n₃ = 4:近似方形番茄

动物形态

海星:m = 5, n₁ ≈ 0.5
贝壳:m = 1, n₁ < 1(螺旋形态)
蝴蝶翅膀:m = 2, n₁ ≈ 0.3

超形状示意

m = 3 (三角):     m = 4 (四角):     m = 5 (五角):     m = 6 (六角):
    △                ◇                  ★                  ⬡
   ╱ ╲              ╱╲                ╱│╲                ╱ ╲
  ╱   ╲            ╱  ╲              ╱ │ ╲              ╱   ╲
 ●─────●          ●────●            ●───●───●          ●─────●
  ╲   ╱            ╲  ╱              ╲ │ ╱              ╲   ╱
   ╲ ╱              ╲╱                ╲│╱                ╲ ╱
    ▽                ◇                  ★                  ⬡

n₁ 越小,凹陷越深

🎯 1.4 超形状的数学性质

连续性与可微性

当 n₁, n₂, n₃ > 0 时,超形状是连续的

可微性:
- n₁, n₂, n₃ ≥ 1:曲线光滑
- n₁, n₂, n₃ < 1:存在尖点
- n₁, n₂, n₃ = 1:临界情况

曲率:
κ = (r² + 2r'² - rr'') / (r² + r'²)^(3/2)

在尖点处曲率趋于无穷

面积计算

超形状围成的面积:

A = ∫₀^(2π) ½r²(θ)dθ

对于简单情况(n₁ = n₂ = n₃):
A = 4ab · Γ(1 + 1/n)² / Γ(1 + 2/n)

其中 Γ 是伽马函数

特殊情况:
- n = 2(椭圆):A = πab
- n → ∞(矩形):A = 4ab

周长计算

周长公式:

L = ∫₀^(2π) √(r² + (dr/dθ)²)dθ

对于超椭圆,没有简单的解析解
通常使用数值积分计算

🔧 二、超形状的 Dart 实现

🧮 2.1 超形状生成器

import 'dart:math';

/// 超形状参数
class SuperShapeParams {
  final double m;
  final double n1;
  final double n2;
  final double n3;
  final double a;
  final double b;
  
  const SuperShapeParams({
    required this.m,
    required this.n1,
    required this.n2,
    required this.n3,
    this.a = 1.0,
    this.b = 1.0,
  });
  
  factory SuperShapeParams.ellipse({double a = 1, double b = 1}) =>
      SuperShapeParams(m: 4, n1: 2, n2: 2, n3: 2, a: a, b: b);
  
  factory SuperShapeParams.star(int points, {double depth = 0.5}) =>
      SuperShapeParams(m: points.toDouble(), n1: depth, n2: 1, n3: 1);
  
  factory SuperShapeParams.flower(int petals, {double curvature = 0.3}) =>
      SuperShapeParams(m: petals.toDouble(), n1: curvature, n2: 1, n3: 1);
  
  factory SuperShapeParams.rectangle() =>
      const SuperShapeParams(m: 4, n1: 100, n2: 100, n3: 100);
  
  SuperShapeParams copyWith({
    double? m,
    double? n1,
    double? n2,
    double? n3,
    double? a,
    double? b,
  }) {
    return SuperShapeParams(
      m: m ?? this.m,
      n1: n1 ?? this.n1,
      n2: n2 ?? this.n2,
      n3: n3 ?? this.n3,
      a: a ?? this.a,
      b: b ?? this.b,
    );
  }
}

/// 超形状生成器
class SuperShapeGenerator {
  final SuperShapeParams params;
  
  SuperShapeGenerator(this.params);
  
  double radius(double theta) {
    final m = params.m;
    final n1 = params.n1;
    final n2 = params.n2;
    final n3 = params.n3;
    final a = params.a;
    final b = params.b;
    
    final term1 = pow(_absCos(m * theta / 4) / a, n2);
    final term2 = pow(_absSin(m * theta / 4) / b, n3);
    
    if (term1 + term2 == 0) return 0;
    
    return pow(term1 + term2, -1 / n1);
  }
  
  double _absCos(double x) => cos(x).abs();
  double _absSin(double x) => sin(x).abs();
  
  List<Offset> generatePoints({
    required int pointCount,
    double scale = 1.0,
    Offset center = Offset.zero,
  }) {
    final points = <Offset>[];
    final dTheta = 2 * pi / pointCount;
    
    for (int i = 0; i < pointCount; i++) {
      final theta = i * dTheta;
      final r = radius(theta) * scale;
      final x = center.dx + r * cos(theta);
      final y = center.dy + r * sin(theta);
      points.add(Offset(x, y));
    }
    
    return points;
  }
  
  Path toPath({
    required int pointCount,
    double scale = 1.0,
    Offset center = Offset.zero,
  }) {
    final points = generatePoints(
      pointCount: pointCount,
      scale: scale,
      center: center,
    );
    
    final path = Path();
    if (points.isEmpty) return path;
    
    path.moveTo(points[0].dx, points[0].dy);
    for (int i = 1; i < points.length; i++) {
      path.lineTo(points[i].dx, points[i].dy);
    }
    path.close();
    
    return path;
  }
  
  double calculateArea(double scale) {
    final pointCount = 1000;
    final dTheta = 2 * pi / pointCount;
    double area = 0;
    
    for (int i = 0; i < pointCount; i++) {
      final theta = i * dTheta;
      final r = radius(theta) * scale;
      area += 0.5 * r * r * dTheta;
    }
    
    return area;
  }
  
  double calculatePerimeter(double scale) {
    final pointCount = 1000;
    final dTheta = 2 * pi / pointCount;
    double perimeter = 0;
    
    Offset? prevPoint;
    for (int i = 0; i <= pointCount; i++) {
      final theta = i * dTheta;
      final r = radius(theta) * scale;
      final x = r * cos(theta);
      final y = r * sin(theta);
      final point = Offset(x, y);
      
      if (prevPoint != null) {
        final dx = point.dx - prevPoint.dx;
        final dy = point.dy - prevPoint.dy;
        perimeter += sqrt(dx * dx + dy * dy);
      }
      prevPoint = point;
    }
    
    return perimeter;
  }
}

⚡ 2.2 超椭圆专用类

/// 超椭圆类
class SuperEllipse {
  final double a;
  final double b;
  final double n;
  
  SuperEllipse({required this.a, required this.b, required this.n});
  
  factory SuperEllipse.circle(double radius) =>
      SuperEllipse(a: radius, b: radius, n: 2);
  
  factory SuperEllipse.ellipse(double a, double b) =>
      SuperEllipse(a: a, b: b, n: 2);
  
  factory SuperEllipse.rectangle(double width, double height) =>
      SuperEllipse(a: width / 2, b: height / 2, n: 100);
  
  double x(double t) => a * _sign(cos(t)) * pow(cos(t).abs(), 2 / n);
  double y(double t) => b * _sign(sin(t)) * pow(sin(t).abs(), 2 / n);
  
  double _sign(double x) {
    if (x > 0) return 1;
    if (x < 0) return -1;
    return 0;
  }
  
  List<Offset> generatePoints(int pointCount) {
    return List.generate(pointCount, (i) {
      final t = 2 * pi * i / pointCount;
      return Offset(x(t), y(t));
    });
  }
  
  Path toPath(int pointCount) {
    final points = generatePoints(pointCount);
    final path = Path();
    
    if (points.isEmpty) return path;
    
    path.moveTo(points[0].dx, points[0].dy);
    for (int i = 1; i < points.length; i++) {
      path.lineTo(points[i].dx, points[i].dy);
    }
    path.close();
    
    return path;
  }
  
  double get area {
    if (n == 2) return pi * a * b;
    
    final gamma1 = _gamma(1 + 1 / n);
    final gamma2 = _gamma(1 + 2 / n);
    return 4 * a * b * gamma1 * gamma1 / gamma2;
  }
  
  double _gamma(double x) {
    if (x == 1) return 1;
    if (x == 0.5) return sqrt(pi);
    return (x - 1) * _gamma(x - 1);
  }
  
  double curvatureAt(double t) {
    if (n == 2) return a * b / pow(a * a * sin(t) * sin(t) + b * b * cos(t) * cos(t), 1.5);
    
    final dx = -2 * a / n * _sign(cos(t)) * pow(cos(t).abs(), 2 / n - 1) * sin(t);
    final dy = 2 * b / n * _sign(sin(t)) * pow(sin(t).abs(), 2 / n - 1) * cos(t);
    
    final ddx = -2 * a / n * (pow(cos(t).abs(), 2 / n - 1) * cos(t) +
                (2 / n - 1) * _sign(cos(t)) * pow(cos(t).abs(), 2 / n - 2) * (-sin(t)) * sin(t));
    final ddy = 2 * b / n * (-pow(sin(t).abs(), 2 / n - 1) * sin(t) +
                (2 / n - 1) * _sign(sin(t)) * pow(sin(t).abs(), 2 / n - 2) * cos(t) * cos(t));
    
    return (dx * ddy - dy * ddx).abs() / pow(dx * dx + dy * dy, 1.5);
  }
}

🎨 2.3 形态动画控制器

import 'package:flutter/material.dart';

/// 超形状动画控制器
class SuperShapeAnimationController extends ChangeNotifier {
  SuperShapeParams _params;
  double _rotation = 0;
  double _scale = 1.0;
  double _morphProgress = 0;
  SuperShapeParams? _targetParams;
  
  SuperShapeAnimationController({
    SuperShapeParams? params,
  }) : _params = params ?? SuperShapeParams.flower(5);
  
  SuperShapeParams get params => _params;
  double get rotation => _rotation;
  double get scale => _scale;
  double get morphProgress => _morphProgress;
  
  void setParams(SuperShapeParams newParams) {
    _params = newParams;
    notifyListeners();
  }
  
  void setM(double value) {
    _params = _params.copyWith(m: value);
    notifyListeners();
  }
  
  void setN1(double value) {
    _params = _params.copyWith(n1: value);
    notifyListeners();
  }
  
  void setN2(double value) {
    _params = _params.copyWith(n2: value);
    notifyListeners();
  }
  
  void setN3(double value) {
    _params = _params.copyWith(n3: value);
    notifyListeners();
  }
  
  void setRotation(double value) {
    _rotation = value;
    notifyListeners();
  }
  
  void setScale(double value) {
    _scale = value.clamp(0.1, 5.0);
    notifyListeners();
  }
  
  void startMorph(SuperShapeParams target) {
    _targetParams = target;
    _morphProgress = 0;
    notifyListeners();
  }
  
  void updateMorph(double dt) {
    if (_targetParams == null) return;
    
    _morphProgress += dt * 0.5;
    
    if (_morphProgress >= 1.0) {
      _params = _targetParams!;
      _targetParams = null;
      _morphProgress = 0;
    } else {
      _params = SuperShapeParams(
        m: _lerp(_params.m, _targetParams!.m, _morphProgress),
        n1: _lerp(_params.n1, _targetParams!.n1, _morphProgress),
        n2: _lerp(_params.n2, _targetParams!.n2, _morphProgress),
        n3: _lerp(_params.n3, _targetParams!.n3, _morphProgress),
        a: _lerp(_params.a, _targetParams!.a, _morphProgress),
        b: _lerp(_params.b, _targetParams!.b, _morphProgress),
      );
    }
    
    notifyListeners();
  }
  
  double _lerp(double a, double b, double t) => a + (b - a) * t;
  
  void reset() {
    _params = SuperShapeParams.flower(5);
    _rotation = 0;
    _scale = 1.0;
    _morphProgress = 0;
    _targetParams = null;
    notifyListeners();
  }
}

📦 三、完整示例代码

以下是完整的超形状可视化示例代码:

import 'package:flutter/material.dart';
import 'dart:math';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '超形状',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.pink, brightness: Brightness.dark),
        useMaterial3: true,
      ),
      home: const SuperShapeHomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

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

  
  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.tune, color: Colors.pink,
              onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ParamExplorerDemo()))),
          _buildCard(context, title: '预设形状', description: '常见超形状展示', icon: Icons.category, color: Colors.purple,
              onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const PresetShapesDemo()))),
          _buildCard(context, title: '形态变换', description: '形状之间的平滑过渡', icon: Icons.transform, color: Colors.teal,
              onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const MorphDemo()))),
          _buildCard(context, title: '自然形态', description: '模拟自然界的形状', icon: Icons.nature, color: Colors.green,
              onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const NaturalShapesDemo()))),
        ],
      ),
    );
  }

  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 SuperShape {
  double m, n1, n2, n3, a, b;
  SuperShape({required this.m, required this.n1, required this.n2, required this.n3, this.a = 1, this.b = 1});
  
  double radius(double theta) {
    final term1 = pow(cos(m * theta / 4).abs() / a, n2);
    final term2 = pow(sin(m * theta / 4).abs() / b, n3);
    if (term1 + term2 == 0) return 0;
    return pow(term1 + term2, -1 / n1).toDouble();
  }
  
  List<Offset> points(int count, double scale, Offset center) {
    return List.generate(count, (i) {
      final theta = 2 * pi * i / count;
      final r = radius(theta) * scale;
      return Offset(center.dx + r * cos(theta), center.dy + r * sin(theta));
    });
  }
  
  SuperShape copyWith({double? m, double? n1, double? n2, double? n3, double? a, double? b}) =>
      SuperShape(m: m ?? this.m, n1: n1 ?? this.n1, n2: n2 ?? this.n2, n3: n3 ?? this.n3, a: a ?? this.a, b: b ?? this.b);
}

/// 参数探索演示
class ParamExplorerDemo extends StatefulWidget {
  const ParamExplorerDemo({super.key});
  
  State<ParamExplorerDemo> createState() => _ParamExplorerDemoState();
}

class _ParamExplorerDemoState extends State<ParamExplorerDemo> {
  double _m = 5, _n1 = 1, _n2 = 1, _n3 = 1;
  double _rotation = 0;
  final List<Color> _colors = [Colors.pink, Colors.purple, Colors.blue, Colors.cyan, Colors.teal, Colors.green];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('参数探索')),
      body: Column(children: [
        Expanded(child: CustomPaint(painter: SuperShapePainter(SuperShape(m: _m, n1: _n1, n2: _n2, n3: _n3), _rotation, _colors), 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('m (对称轴): ', style: TextStyle(color: Colors.white70)),
          Expanded(child: Slider(value: _m, min: 1, max: 12, onChanged: (v) => setState(() => _m = v))),
          Text(_m.toStringAsFixed(1), style: const TextStyle(color: Colors.pink)),
        ]),
        Row(children: [
          const Text('n₁ (曲率): ', style: TextStyle(color: Colors.white70)),
          Expanded(child: Slider(value: _n1, min: 0.1, max: 5, onChanged: (v) => setState(() => _n1 = v))),
          Text(_n1.toStringAsFixed(2), style: const TextStyle(color: Colors.pink)),
        ]),
        Row(children: [
          const Text('n₂ (x影响): ', style: TextStyle(color: Colors.white70)),
          Expanded(child: Slider(value: _n2, min: 0.1, max: 5, onChanged: (v) => setState(() => _n2 = v))),
          Text(_n2.toStringAsFixed(2), style: const TextStyle(color: Colors.pink)),
        ]),
        Row(children: [
          const Text('n₃ (y影响): ', style: TextStyle(color: Colors.white70)),
          Expanded(child: Slider(value: _n3, min: 0.1, max: 5, onChanged: (v) => setState(() => _n3 = v))),
          Text(_n3.toStringAsFixed(2), style: const TextStyle(color: Colors.pink)),
        ]),
        Row(children: [
          const Text('旋转: ', style: TextStyle(color: Colors.white70)),
          Expanded(child: Slider(value: _rotation, min: 0, max: 2 * pi, onChanged: (v) => setState(() => _rotation = v))),
          Text('${(_rotation * 180 / pi).toStringAsFixed(0)}°', style: const TextStyle(color: Colors.pink)),
        ]),
      ]),
    );
  }
}

class SuperShapePainter extends CustomPainter {
  final SuperShape shape;
  final double rotation;
  final List<Color> colors;
  SuperShapePainter(this.shape, this.rotation, this.colors);

  
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final scale = min(size.width, size.height) * 0.4;
    
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
    
    for (int layer = 5; layer >= 0; layer--) {
      final layerScale = scale * (1 - layer * 0.1);
      final layerRotation = rotation + layer * 0.1;
      
      final points = shape.points(360, layerScale, center);
      final path = Path();
      
      for (int i = 0; i < points.length; i++) {
        final rotatedX = center.dx + (points[i].dx - center.dx) * cos(layerRotation) - (points[i].dy - center.dy) * sin(layerRotation);
        final rotatedY = center.dy + (points[i].dx - center.dx) * sin(layerRotation) + (points[i].dy - center.dy) * cos(layerRotation);
        
        if (i == 0) path.moveTo(rotatedX, rotatedY);
        else path.lineTo(rotatedX, rotatedY);
      }
      path.close();
      
      final paint = Paint()
        ..color = colors[layer % colors.length].withOpacity(0.3 + layer * 0.1)
        ..style = PaintingStyle.fill;
      canvas.drawPath(path, paint);
      
      final strokePaint = Paint()
        ..color = colors[layer % colors.length]
        ..style = PaintingStyle.stroke
        ..strokeWidth = 2;
      canvas.drawPath(path, strokePaint);
    }
  }

  
  bool shouldRepaint(covariant SuperShapePainter old) => true;
}

/// 预设形状演示
class PresetShapesDemo extends StatelessWidget {
  const PresetShapesDemo({super.key});

  
  Widget build(BuildContext context) {
    final presets = [
      ('圆形', SuperShape(m: 4, n1: 2, n2: 2, n3: 2)),
      ('椭圆', SuperShape(m: 4, n1: 2, n2: 2, n3: 2, a: 1.5, b: 1)),
      ('超椭圆', SuperShape(m: 4, n1: 4, n2: 4, n3: 4)),
      ('矩形', SuperShape(m: 4, n1: 100, n2: 100, n3: 100)),
      ('五角星', SuperShape(m: 5, n1: 0.5, n2: 1, n3: 1)),
      ('六角星', SuperShape(m: 6, n1: 0.5, n2: 1, n3: 1)),
      ('花瓣', SuperShape(m: 5, n1: 0.3, n2: 1, n3: 1)),
      ('十字', SuperShape(m: 4, n1: 0.3, n2: 1, n3: 1)),
    ];
    
    return Scaffold(
      appBar: AppBar(title: const Text('预设形状')),
      body: GridView.builder(
        padding: const EdgeInsets.all(8),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8),
        itemCount: presets.length,
        itemBuilder: (ctx, i) {
          final (name, shape) = presets[i];
          return Card(
            child: Column(children: [
              Expanded(child: CustomPaint(painter: PresetPainter(shape), size: Size.infinite)),
              Padding(padding: const EdgeInsets.all(8), child: Text(name, style: const TextStyle(fontWeight: FontWeight.bold))),
            ]),
          );
        },
      ),
    );
  }
}

class PresetPainter extends CustomPainter {
  final SuperShape shape;
  PresetPainter(this.shape);

  
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final scale = min(size.width, size.height) * 0.35;
    
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
    
    final points = shape.points(360, scale, center);
    final path = Path();
    
    for (int i = 0; i < points.length; i++) {
      if (i == 0) path.moveTo(points[i].dx, points[i].dy);
      else path.lineTo(points[i].dx, points[i].dy);
    }
    path.close();
    
    final hue = shape.m * 30;
    canvas.drawPath(path, Paint()..color = HSVColor.fromAHSV(0.3, hue, 0.7, 0.9).toColor()..style = PaintingStyle.fill);
    canvas.drawPath(path, Paint()..color = HSVColor.fromAHSV(1, hue, 0.8, 1).toColor()..style = PaintingStyle.stroke..strokeWidth = 2);
  }

  
  bool shouldRepaint(covariant PresetPainter old) => false;
}

/// 形态变换演示
class MorphDemo extends StatefulWidget {
  const MorphDemo({super.key});
  
  State<MorphDemo> createState() => _MorphDemoState();
}

class _MorphDemoState extends State<MorphDemo> with SingleTickerProviderStateMixin {
  late AnimationController _ctrl;
  double _time = 0;
  int _currentIndex = 0;
  
  final List<SuperShape> _shapes = [
    SuperShape(m: 4, n1: 2, n2: 2, n3: 2),
    SuperShape(m: 5, n1: 0.5, n2: 1, n3: 1),
    SuperShape(m: 6, n1: 0.3, n2: 1, n3: 1),
    SuperShape(m: 8, n1: 0.4, n2: 1, n3: 1),
    SuperShape(m: 4, n1: 4, n2: 4, n3: 4),
  ];

  
  void initState() {
    super.initState();
    _ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
    _ctrl.addListener(_update);
  }
  
  void _update() {
    _time += 0.016;
    if (_time > 3) { _time = 0; _currentIndex = (_currentIndex + 1) % _shapes.length; }
    setState(() {});
  }

  
  void dispose() { _ctrl.removeListener(_update); _ctrl.dispose(); super.dispose(); }

  
  Widget build(BuildContext context) {
    final current = _shapes[_currentIndex];
    final next = _shapes[(_currentIndex + 1) % _shapes.length];
    final t = _time / 3;
    
    final morphed = SuperShape(
      m: _lerp(current.m, next.m, t),
      n1: _lerp(current.n1, next.n1, t),
      n2: _lerp(current.n2, next.n2, t),
      n3: _lerp(current.n3, next.n3, t),
    );
    
    return Scaffold(
      appBar: AppBar(title: const Text('形态变换')),
      body: Column(children: [
        Expanded(child: CustomPaint(painter: MorphPainter(morphed, t), size: Size.infinite)),
        Padding(
          padding: const EdgeInsets.all(16),
          child: Text('变换进度: ${(t * 100).toStringAsFixed(0)}%', style: const TextStyle(color: Colors.white70)),
        ),
      ]),
    );
  }
  
  double _lerp(double a, double b, double t) => a + (b - a) * t;
}

class MorphPainter extends CustomPainter {
  final SuperShape shape;
  final double progress;
  MorphPainter(this.shape, this.progress);

  
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final scale = min(size.width, size.height) * 0.4;
    
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
    
    final points = shape.points(360, scale, center);
    final path = Path();
    
    for (int i = 0; i < points.length; i++) {
      if (i == 0) path.moveTo(points[i].dx, points[i].dy);
      else path.lineTo(points[i].dx, points[i].dy);
    }
    path.close();
    
    final hue = progress * 360;
    canvas.drawPath(path, Paint()..shader = RadialGradient(colors: [
      HSVColor.fromAHSV(1, hue, 0.8, 1).toColor(),
      HSVColor.fromAHSV(1, (hue + 60) % 360, 0.8, 1).toColor(),
    ]).createShader(Rect.fromCircle(center: center, radius: scale)));
    canvas.drawPath(path, Paint()..color = Colors.white..style = PaintingStyle.stroke..strokeWidth = 2);
  }

  
  bool shouldRepaint(covariant MorphPainter old) => true;
}

/// 自然形态演示
class NaturalShapesDemo extends StatelessWidget {
  const NaturalShapesDemo({super.key});

  
  Widget build(BuildContext context) {
    final natural = [
      ('玫瑰', SuperShape(m: 5, n1: 0.2, n2: 1, n3: 1), Colors.red),
      ('向日葵', SuperShape(m: 12, n1: 0.3, n2: 1, n3: 1), Colors.yellow),
      ('海星', SuperShape(m: 5, n1: 0.4, n2: 1, n3: 1), Colors.orange),
      ('雪花', SuperShape(m: 6, n1: 0.5, n2: 1, n3: 1), Colors.lightBlue),
      ('三叶草', SuperShape(m: 3, n1: 0.3, n2: 1, n3: 1), Colors.green),
      ('蝴蝶', SuperShape(m: 2, n1: 0.2, n2: 1.5, n3: 1), Colors.purple),
    ];
    
    return Scaffold(
      appBar: AppBar(title: const Text('自然形态')),
      body: ListView.builder(
        padding: const EdgeInsets.all(8),
        itemCount: natural.length,
        itemBuilder: (ctx, i) {
          final (name, shape, color) = natural[i];
          return Card(
            margin: const EdgeInsets.only(bottom: 8),
            child: ListTile(
              leading: SizedBox(width: 50, height: 50, child: CustomPaint(painter: NaturalPainter(shape, color), size: Size.infinite)),
              title: Text(name),
              trailing: const Icon(Icons.chevron_right),
              onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => NaturalDetailPage(name: name, shape: shape, color: color))),
            ),
          );
        },
      ),
    );
  }
}

class NaturalPainter extends CustomPainter {
  final SuperShape shape;
  final Color color;
  NaturalPainter(this.shape, this.color);

  
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final scale = min(size.width, size.height) * 0.4;
    
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
    
    final points = shape.points(360, scale, center);
    final path = Path();
    
    for (int i = 0; i < points.length; i++) {
      if (i == 0) path.moveTo(points[i].dx, points[i].dy);
      else path.lineTo(points[i].dx, points[i].dy);
    }
    path.close();
    
    canvas.drawPath(path, Paint()..color = color.withOpacity(0.5)..style = PaintingStyle.fill);
    canvas.drawPath(path, Paint()..color = color..style = PaintingStyle.stroke..strokeWidth = 2);
  }

  
  bool shouldRepaint(covariant NaturalPainter old) => false;
}

class NaturalDetailPage extends StatefulWidget {
  final String name;
  final SuperShape shape;
  final Color color;
  const NaturalDetailPage({required this.name, required this.shape, required this.color, super.key});
  
  State<NaturalDetailPage> createState() => _NaturalDetailPageState();
}

class _NaturalDetailPageState extends State<NaturalDetailPage> with SingleTickerProviderStateMixin {
  late AnimationController _ctrl;
  double _time = 0;

  
  void initState() {
    super.initState();
    _ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
    _ctrl.addListener(_update);
  }
  
  void _update() { _time += 0.016; setState(() {}); }

  
  void dispose() { _ctrl.removeListener(_update); _ctrl.dispose(); super.dispose(); }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.name)),
      body: CustomPaint(painter: NaturalDetailPainter(widget.shape, widget.color, _time), size: Size.infinite),
    );
  }
}

class NaturalDetailPainter extends CustomPainter {
  final SuperShape shape;
  final Color color;
  final double time;
  NaturalDetailPainter(this.shape, this.color, this.time);

  
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final baseScale = min(size.width, size.height) * 0.35;
    
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
    
    for (int layer = 4; layer >= 0; layer--) {
      final scale = baseScale * (1 + layer * 0.15);
      final rotation = time * 0.5 + layer * 0.2;
      final opacity = 0.2 + (4 - layer) * 0.15;
      
      final points = shape.points(360, scale, center);
      final path = Path();
      
      for (int i = 0; i < points.length; i++) {
        final rotatedX = center.dx + (points[i].dx - center.dx) * cos(rotation) - (points[i].dy - center.dy) * sin(rotation);
        final rotatedY = center.dy + (points[i].dx - center.dx) * sin(rotation) + (points[i].dy - center.dy) * cos(rotation);
        
        if (i == 0) path.moveTo(rotatedX, rotatedY);
        else path.lineTo(rotatedX, rotatedY);
      }
      path.close();
      
      canvas.drawPath(path, Paint()..color = color.withOpacity(opacity)..style = PaintingStyle.fill);
      canvas.drawPath(path, Paint()..color = color..style = PaintingStyle.stroke..strokeWidth = 1);
    }
  }

  
  bool shouldRepaint(covariant NaturalDetailPainter old) => true;
}

📝 四、数学原理深入解析

📐 4.1 极坐标变换

笛卡尔坐标与极坐标

极坐标 (r, θ) 到笛卡尔坐标 (x, y):
x = r·cos(θ)
y = r·sin(θ)

笛卡尔坐标到极坐标:
r = √(x² + y²)
θ = arctan(y/x)

超形状在极坐标中定义:
r(θ) = f(θ)

转换为笛卡尔:
x(θ) = f(θ)·cos(θ)
y(θ) = f(θ)·sin(θ)

切线与法线

切向量:
dx/dθ = dr/dθ·cos(θ) - r·sin(θ)
dy/dθ = dr/dθ·sin(θ) + r·cos(θ)

切线斜率:
dy/dx = (dy/dθ) / (dx/dθ)

法向量:
nx = -dy/dθ
ny = dx/dθ

单位法向量:
n̂ = (nx, ny) / √(nx² + ny²)

🔄 4.2 曲率分析

曲率公式

对于参数曲线 (x(θ), y(θ)):

κ = |x'y'' - y'x''| / (x'² + y'²)^(3/2)

对于超形状:
x(θ) = r(θ)·cos(θ)
y(θ) = r(θ)·sin(θ)

x' = r'·cos(θ) - r·sin(θ)
y' = r'·sin(θ) + r·cos(θ)

x'' = r''·cos(θ) - 2r'·sin(θ) - r·cos(θ)
y'' = r''·sin(θ) + 2r'·cos(θ) - r·sin(θ)

曲率半径:R = 1/|κ|

尖点分析

当 n₁ < 1 时,超形状出现尖点

在尖点处:
- 曲率趋于无穷
- 切向量方向突变
- 曲线不可微

尖点位置:
当 cos(mθ/4) = 0 或 sin(mθ/4) = 0 时
r(θ) 可能趋于无穷或零

🌸 4.3 面积与周长

面积积分

极坐标下的面积公式:

A = ½∫₀^(2π) r²(θ)dθ

对于超形状:
A = ½∫₀^(2π) [|cos(mθ/4)/a|^(n₂) + |sin(mθ/4)/b|^(n₃)]^(-2/n₁)dθ

特殊情况(n₁ = n₂ = n₃ = n):
A = 4ab·Γ(1 + 1/n)² / Γ(1 + 2/n)

验证:
- n = 2(椭圆):A = πab ✓
- n → ∞(矩形):A = 4ab ✓

周长积分

弧长公式:

L = ∫₀^(2π) √(r² + (dr/dθ)²)dθ

对于超形状,需要数值积分

近似公式(n₁ = n₂ = n₃ = n):
L ≈ 4(a + b)·Γ(1 + 1/n)² / Γ(1 + 2/n)·E(e)

其中 E(e) 是第二类完全椭圆积分

🎯 4.4 形态分类

按参数分类

根据 n₁ 分类:

n₁ > 1:凸形状
- 曲线向外凸
- 无凹陷
- 例:椭圆、超椭圆

n₁ = 1:临界形状
- 直边
- 例:菱形

n₁ < 1:凹形状
- 有凹陷或尖角
- 例:星形、花瓣

n₁ → 0:极端形状
- 深度凹陷
- 十字形或更复杂

按对称性分类

根据 m 分类:

m = 1:无对称轴
m = 2:二重对称(蝴蝶形)
m = 3:三重对称(三叶草)
m = 4:四重对称(菱形、方形)
m = 5:五重对称(五角星)
m = 6:六重对称(六角星、雪花)

对称性与自然形态:
- 花朵:m = 花瓣数
- 海星:m = 触手数
- 雪花:m = 6

🔬 五、高级应用场景

🎨 5.1 建筑设计

超椭圆在建筑中的应用

class ArchitecturalShape {
  static SuperShape sergiCalderonPillar() {
    return SuperShape(m: 4, n1: 2.5, n2: 2.5, n3: 2.5);
  }
  
  static SuperShape tableTop(double aspectRatio) {
    return SuperShape(m: 4, n1: 3, n2: 3, n3: 3, a: aspectRatio, b: 1);
  }
  
  static List<Offset> generateFloorPlan(SuperShape shape, double scale) {
    return shape.points(360, scale, Offset.zero);
  }
}

🌐 5.2 UI 设计

按钮和卡片形状

class SuperShapeBorder extends ShapeBorder {
  final SuperShape shape;
  final double scale;
  
  SuperShapeBorder({required this.shape, required this.scale});
  
  
  Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
    final center = Offset(rect.left + rect.width / 2, rect.top + rect.height / 2);
    return shape.toPath(360, scale, center);
  }
  
  
  EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
  
  
  Path getInnerPath(Rect rect, {TextDirection? textDirection}) => getOuterPath(rect);
  
  
  void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}
  
  
  ShapeBorder scale(double t) => SuperShapeBorder(shape: shape, scale: scale * t);
}

📱 5.3 鸿蒙多端适配

响应式形状生成

class AdaptiveSuperShape {
  static SuperShape forDevice(BuildContext context) {
    final size = MediaQuery.of(context).size;
    final aspectRatio = size.width / size.height;
    
    return SuperShape(
      m: 4,
      n1: 2.5,
      n2: 2.5,
      n3: 2.5,
      a: aspectRatio > 1 ? aspectRatio : 1,
      b: aspectRatio < 1 ? 1 / aspectRatio : 1,
    );
  }
  
  static double getOptimalScale(BuildContext context) {
    final size = MediaQuery.of(context).size;
    return min(size.width, size.height) * 0.4;
  }
}

📊 六、性能优化策略

⚡ 6.1 点缓存

预计算优化

class SuperShapeCache {
  static final Map<String, List<Offset>> _cache = {};
  
  static List<Offset> getPoints(SuperShape shape, int count, double scale, Offset center) {
    final key = '${shape.m}_${shape.n1}_${shape.n2}_${shape.n3}_${shape.a}_${shape.b}_$count';
    
    return _cache.putIfAbsent(key, () {
      final basePoints = shape.points(count, 1, Offset.zero);
      return basePoints.map((p) => Offset(p.dx * scale + center.dx, p.dy * scale + center.dy)).toList();
    });
  }
  
  static void clearCache() => _cache.clear();
}

💾 6.2 GPU 加速

Shader 优化

class SuperShapeShader {
  static String fragmentShader() => '''
    uniform float u_m;
    uniform float u_n1;
    uniform float u_n2;
    uniform float u_n3;
    uniform vec2 u_resolution;
    
    void main() {
      vec2 uv = gl_FragCoord.xy / u_resolution;
      vec2 center = vec2(0.5, 0.5);
      vec2 p = uv - center;
      
      float theta = atan(p.y, p.x);
      float r = length(p);
      
      float term1 = pow(abs(cos(u_m * theta / 4.0)), u_n2);
      float term2 = pow(abs(sin(u_m * theta / 4.0)), u_n3);
      float shapeR = pow(term1 + term2, -1.0 / u_n1);
      
      float inside = step(r, shapeR * 0.4);
      gl_FragColor = vec4(vec3(inside), 1.0);
    }
  ''';
}

🎓 七、学习资源与拓展

📚 推荐阅读

主题 资源 难度
超椭圆 Piet Hein 的设计哲学
Gielis 公式 Johan Gielis 的论文 ⭐⭐
极坐标几何 《极坐标与曲线》 ⭐⭐
自然形态 《On Growth and Form》 ⭐⭐⭐

🔗 相关项目

  • Superformula Explorer:在线超形状探索工具
  • Processing:创意编程平台
  • Generative Design:生成设计资源

📝 八、总结

本篇文章深入探讨了超形状与超椭圆的数学原理及其在 Flutter 中的可视化实现。

✅ 核心知识点回顾

知识点 说明
📐 超椭圆方程 介于椭圆和矩形之间
🌸 Gielis 公式 通用超形状描述
🔄 参数影响 m, n₁, n₂, n₃ 的作用
🎯 自然形态 植物、动物的形状模拟
形态变换 平滑过渡算法

⭐ 最佳实践要点

  • ✅ 使用缓存优化重复计算
  • ✅ 合理设置点数平衡精度与性能
  • ✅ 注意 n < 1 时的尖点处理
  • ✅ 利用对称性减少计算量

🚀 进阶方向

  • 🔮 三维超曲面
  • ✨ 动态形态动画
  • 📊 形状识别与分类
  • 🎨 生成艺术设计

💡 提示:本文代码基于 Flutter for Harmony 开发,可在鸿蒙设备上流畅运行。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐