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

示例效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

摘要

伴随全年龄段电子终端使用时长呈现指数级爆发,视觉调节痉挛(Accommodation Spasm)与睫状肌器质性僵化已成为不可逆近视发展的物理学诱因。本文跳出传统“低蓝光过滤”与“黑白滤镜”的被动防御误区,利用跨平台 UI 渲染框架 Flutter 逆向构建了一套“主动视功能物理干预系统”。本系统彻底贯穿了计算眼科学(Computational Ophthalmology)的生物反馈理论,通过微秒级 Ticker 滴答定时器在底层驱动晶状体的呼吸节律动画,以及带有高爆发物理运动方程的眼球扫视(Saccadic)追踪阵列。本文将从严谨的软硬件渲染边界出发,深度解构这套医疗级视觉防护数字疗法(Digital Therapeutics, DTx)终端的物理模型建模、眼部动力学数学方程推演,以及光栅着色层的架构核心代码。


一、 视觉睫状肌负荷的物理动力学痛点

1.1 被动式防护的局限性与肌肉代偿衰减

绝大多数所谓“护眼模式”仅停留于对 RGB 像素色温(Color Temperature)层面的改变(如削弱 450nm 短波蓝光)。然而,近视的核心发病物理机制并非单纯的光学灼伤,而是晶状体(Lens)悬韧带与睫状肌(Ciliary Muscle)长期处于近焦极度收缩(高张力状态)导致的肌肉僵硬。这种器质性疲劳无法通过颜色的改变来逆转,必须依赖高位错距的光学焦点移动(如 Brock String 训练)来强制拉伸并解痉。

1.2 主动式“屏幕反制”干预范式

我们提出的主动数字疗法,是将冰冷的二维屏幕转变为高频的光学引导器。系统利用屏幕发出的“动态强光目标”,强行劫持受试者的视觉中枢,使其视线被迫进行远近焦距的深呼吸式调节(调节功能训练),以及在屏幕极限对角线之间进行高初速度的肌肉拉扯(扫视追踪训练),从而加速房水循环,物理打断肌肉的痉挛稳态。


二、 视觉追踪与焦距调节的数学建模

在 Flutter 底层控制器的构建前,必须将生物学行为抽象为可执行的数学方程与物理学微分体系。

2.1 睫状肌舒张/收缩的正弦阻尼模型

设晶状体前表面屈光度为 P P P,其调节幅度可近似视作受到睫状肌拉力张量 F F F 的控制。在数字疗法的“呼吸舒缓模式”中,我们需要一个极致平滑的焦点距离变化模型。系统引入了基于正弦波的时间周期函数,并利用 CurvedAnimation(curve: Curves.easeInOutSine) 实现:

D ( t ) = D m i n + Δ D ⋅ 1 − cos ⁡ ( ω t ) 2 D(t) = D_{min} + \Delta D \cdot \frac{1 - \cos(\omega t)}{2} D(t)=Dmin+ΔD21cos(ωt)

其变量约束如下表所示:

参数/物理量 眼科学定义 UI层映射方程与含义
D ( t ) D(t) D(t) 瞬时调节屈光度 (Diopter) 对应 UI 圆环的实时物理半径 R ( t ) R(t) R(t)
D m i n D_{min} Dmin 基础远视点 (Far Point) R m a x R_{max} Rmax,此时睫状肌处于完全舒张休眠态
Δ D \Delta D ΔD 调节幅度 (Amplitude of Accommodation) 圆环的缩放位移差 Δ R \Delta R ΔR
ω \omega ω 呼吸角频率 ( 2 π / T 2\pi/T 2π/T) T = 4000 ms T=4000\text{ms} T=4000ms,控制单次远近拉伸的极限时长

2.2 扫视运动(Saccade)的爆炸型动力学映射

与平滑的晶状体调节不同,眼球的扫视运动(Saccadic Eye Movement)是由眼外直肌(Extraocular Muscles)主导的爆发式收缩。其速度方程在启动瞬间极大,随后迅速被对抗肌制动。

为了在代码中拟合这一高爆发制动,我们抛弃了常规的线性补间动画,引入了带有强阻尼指数衰减的测绘方程(对应 Flutter 中的 Curves.easeOutExpo):

v ( t ) = v m a x ⋅ exp ⁡ ( − t τ ) v(t) = v_{max} \cdot \exp\left( -\frac{t}{\tau} \right) v(t)=vmaxexp(τt)

借由该微积分渲染,屏幕上的目标点将以极高的初速度闪现至屏幕对角,引发眼球肌肉的强烈拉伸反应,随即稳稳停住,强迫黄斑区建立高敏锐注视(Fixation)。


三、 疗法系统架构与域模型解剖

为了确保极高频 60fps/120fps 光栅渲染不引发内存泄漏或脏读,系统通过清晰的领域驱动隔离机制(DDD)来管控生命周期。

3.1 核心状态观测机结构 (Mermaid UML)

驱动重绘

坐标注入

VisionTherapyDashboard

+TherapyMode currentMode

+bool isSessionActive

+double fatigueIndex

+toggleSession()

+switchMode()

CiliaryRelaxationEngine

+AnimationController breathingController

+Animation<double> lensFocusAnimation

+void ciliaryBenefitTick()

SaccadicTrackingEngine

+AnimationController saccadeController

+Offset currentTarget

+List<Offset> saccadeHistory

+scheduleNextSaccade()

CiliaryRelaxationPainter

SaccadicTargetPainter

3.2 疗法时序与视疲劳清算管线 (Flowchart)

模式A 晶状体舒缓

模式B 扫视追踪

系统初始化加载

受试者选择干预模式

启动4000ms Sine波振荡器

重算晶状体曲率半径 dynamicRadius

触发光栅着色

每Tick降低视疲劳积分

启动3000ms随机跳跃定时器

计算极大跨度张量矩阵 Offset

挂载easeOutExpo爆发制动动画

提取历史轨迹写入saccadeHistory

触发生成光晕与残影

清算眼外肌拉伸次数 骤降疲劳指数


四、 核心渲染矩阵全息极客代码解剖

本小节将摒弃一切表层结构,将探针直接扎入底层的 GPU 指令测绘区,为您拆解四大物理极客级代码单元。

4.1 核心一:防停滞的睫状肌解痉测绘引擎

为了将物理时间的推移转化为医学上的疲劳解离反馈,我们在 _breathingController 的底层心跳上硬挂载了一个监听器 _ciliaryBenefitTick

  void _ciliaryBenefitTick() {
    // 根据动画时间流逝,微幅降低疲劳指数
    if (mounted && _isSessionActive && _currentMode == TherapyMode.ciliaryRelaxation) {
      setState(() {
        _fatigueIndex = math.max(0, _fatigueIndex - 0.01);
        _ciliarySpasmReduction += 0.02;
      });
    }
  }

这段代码的无情之处在于:只要呼吸振荡器在跑,屏幕右上角的 VFI (视觉疲劳指数) 就会以每帧 0.01 的微积分颗粒度向下掉落,给予受试者极强的沉浸式生理治愈感反馈。

4.2 核心二:极限跨距的伪随机眼外肌牵扯算法

在触发下一次“扫视”点时,如果目标点离当前点过近,眼动拉伸就会失效。我们在生成器中引入了“极小距离拦截陷阱”的 do-while 暴力防御逻辑:

      Offset nextTarget;
      do {
        nextTarget = Offset(
          0.1 + rand.nextDouble() * 0.8, // 框定在安全屏显区域 10% - 90%
          0.1 + rand.nextDouble() * 0.8,
        );
      } while ((nextTarget - _currentTarget).distance < 0.4); // 拦截距离小于 0.4(归一化距离) 的无效生成

此处的 0.4 屏障强迫目标点发生剧烈的象限跨越,受试者的眼球会被迫进行超大角度(大扭矩)的机械偏转,瞬间撕裂眼外肌的僵死状态。

4.3 核心三:睫状肌晶状体的物理仿生折射渲染

CiliaryRelaxationPainter 的内核中,我们对晶状体随着焦距的变化形态进行了光学模拟。当焦点极近(focusPhase 趋向 1.0)时,晶状体变厚(半径缩小),反之则无限扩展至基准半径。

    final dynamicRadius = baseRadius * (1.0 - focusPhase * 0.7) + 20; // 最小收缩至 20
    
    // 绘制晶状体张力外晕 (睫状小带悬韧带拉伸感)
    final haloPaint = Paint()
      ..color = color.withValues(alpha: 0.15 + focusPhase * 0.2)
      ..maskFilter = MaskFilter.blur(BlurStyle.normal, 20 + focusPhase * 40);
    canvas.drawCircle(center, dynamicRadius + 10, haloPaint);

    // 核心视觉焦点
    final corePaint = Paint()
      ..color = color.withValues(alpha: 0.8)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 4.0 + focusPhase * 6.0; // 越近聚焦越实
    canvas.drawCircle(center, dynamicRadius, corePaint);

利用 MaskFilter.blur 控制晕染扩散半径,完美重现了悬韧带拉紧时那种“虚化”与“绷紧”共存的医疗物理感。

4.4 核心四:视觉皮层残影连通管线

在扫视模式 SaccadicTargetPainter 中,不仅绘制当前点,还要绘制带有衰减透明度的前置历史轨迹,用来模拟视觉皮层(Visual Cortex)对强光物体的正像驻留反应(Afterimage)。

        // 越早的轨迹越暗淡,利用循环索引进行反向梯度投射
        final alpha = (i + 1) / (history.length + 1);
        pathPaint.color = color.withValues(alpha: alpha * 0.3);
        pathPaint.strokeWidth = 2.0 + alpha * 2.0;
        
        canvas.drawLine(start, end, pathPaint);

随着眼球跳跃,这条带有荧光残影的赛博青色导线会在深空的黑色屏幕上网状交织,营造出极强的医疗极客矩阵压迫感。


五、 终端多态环境折叠防御与结语

为了保证该疗法系统既能生存在医院配备的 27 27 27 吋大屏干预机中,又能蜷缩在患者居家的手机环境中。系统部署了极为严密的 800 px 800\text{px} 800px 媒体查询探针:在宽屏下铺展为带有侧边全参数仪表的太空舱布局;在手机端则将仪表盘坍缩为底部的悬浮底座模式(Bottom Bar),主屏幕资源被极具贪婪地让渡给 CustomPaint 光栅画板,保证眼球活动的极限物理半径。

在这场代码与肌肉的跨界对话中,《近视防控数字疗法》终端证明了:防御近视不应只依赖死寂的纸质海报或被动的电子墨水屏。通过将强大的 GPU 渲染管线与严密的眼动生理学微积分结合,一行行冷峻的 Dart 代码足以化解人类在硅基时代所面临的视觉进化之殇。

完整代码

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

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
    statusBarColor: Colors.transparent,
    statusBarIconBrightness: Brightness.light,
  ));
  runApp(const MyopiaPreventionApp());
}

/// 全局主入口:近视防控数字疗法应用
class MyopiaPreventionApp extends StatelessWidget {
  const MyopiaPreventionApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '睫状肌舒缓与视觉追踪仪',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF00E676), // 医疗视力保护绿
          brightness: Brightness.dark,
          surface: const Color(0xFF0A1118), // 深空物理背景
          primary: const Color(0xFF00E676),
          secondary: const Color(0xFF00B0FF), // 轨迹追踪蓝
        ),
        scaffoldBackgroundColor: const Color(0xFF0A1118),
        cardColor: const Color(0xFF141E28),
      ),
      home: const VisionTherapyDashboard(),
    );
  }
}

/// 疗法模式枚举
enum TherapyMode {
  ciliaryRelaxation, // 睫状肌晶状体呼吸舒缓(远近焦距调节)
  saccadicTracking,  // 眼球扫视追踪(周边视野与跳跃注视)
}

/// 视觉状态测绘仪表盘
class VisionTherapyDashboard extends StatefulWidget {
  const VisionTherapyDashboard({super.key});

  @override
  State<VisionTherapyDashboard> createState() => _VisionTherapyDashboardState();
}

class _VisionTherapyDashboardState extends State<VisionTherapyDashboard> with TickerProviderStateMixin {
  TherapyMode _currentMode = TherapyMode.ciliaryRelaxation;
  bool _isSessionActive = false;

  // --- 晶状体舒缓引擎 (Ciliary Relaxation Engine) ---
  late AnimationController _breathingController;
  late Animation<double> _lensFocusAnimation; // 模拟焦点距离变化
  
  // --- 眼球扫视引擎 (Saccadic Engine) ---
  late AnimationController _saccadeController;
  late Animation<Offset> _saccadePositionAnimation;
  final List<Offset> _saccadeHistory = [];
  Offset _currentTarget = const Offset(0.5, 0.5); // 归一化坐标 [0..1]
  
  // 生物学统计面板数据
  double _fatigueIndex = 85.0; // 模拟视疲劳指数 (0-100)
  int _completedSaccades = 0;
  double _ciliarySpasmReduction = 0.0;

  @override
  void initState() {
    super.initState();
    
    // 初始化睫状肌呼吸动力学动画 (模拟看近与看远交替,正弦平滑)
    _breathingController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 4000), // 单次收缩/舒张周期 4);
    _lensFocusAnimation = CurvedAnimation(
      parent: _breathingController,
      curve: Curves.easeInOutSine,
    );
    
    // 初始化扫视跳跃动画 (模拟眼球极速转动到新注视点)
    _saccadeController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300), // 扫视通常在几百毫秒内完成
    );
    _saccadePositionAnimation = Tween<Offset>(
      begin: const Offset(0.5, 0.5),
      end: const Offset(0.5, 0.5),
    ).animate(CurvedAnimation(
      parent: _saccadeController,
      curve: Curves.easeOutExpo, // 模拟眼动肌肉的高爆发与迅速制动
    ));
    
    _saccadeController.addStatusListener((status) {
      if (status == AnimationStatus.completed && _isSessionActive && _currentMode == TherapyMode.saccadicTracking) {
        _scheduleNextSaccade();
      }
    });
  }

  @override
  void dispose() {
    _breathingController.dispose();
    _saccadeController.dispose();
    super.dispose();
  }

  /// 触发下一次眼球扫视跳跃
  void _scheduleNextSaccade() {
    Future.delayed(const Duration(milliseconds: 1200), () { // 注视停留 1.2 秒
      if (!mounted || !_isSessionActive || _currentMode != TherapyMode.saccadicTracking) return;
      
      final rand = math.Random();
      // 生成下一个归一化目标点,强制要求一定的跳跃跨度以拉伸眼外肌
      Offset nextTarget;
      do {
        nextTarget = Offset(
          0.1 + rand.nextDouble() * 0.8,
          0.1 + rand.nextDouble() * 0.8,
        );
      } while ((nextTarget - _currentTarget).distance < 0.4);
      
      _saccadeHistory.add(_currentTarget);
      if (_saccadeHistory.length > 5) {
        _saccadeHistory.removeAt(0); // 仅保留最近的轨迹用于残影测绘
      }
      
      _saccadePositionAnimation = Tween<Offset>(
        begin: _currentTarget,
        end: nextTarget,
      ).animate(CurvedAnimation(parent: _saccadeController, curve: Curves.easeOutExpo));
      
      _currentTarget = nextTarget;
      _saccadeController.forward(from: 0.0);
      
      setState(() {
        _completedSaccades++;
        _fatigueIndex = math.max(0, _fatigueIndex - 0.2); // 每次有效拉伸降低痉挛指数
      });
      HapticFeedback.lightImpact();
    });
  }

  void _toggleSession() {
    setState(() {
      _isSessionActive = !_isSessionActive;
      if (_isSessionActive) {
        if (_currentMode == TherapyMode.ciliaryRelaxation) {
          _breathingController.repeat(reverse: true);
          // 伴随呼吸降低视疲劳
          _breathingController.addListener(_ciliaryBenefitTick);
        } else {
          _scheduleNextSaccade();
        }
      } else {
        _breathingController.stop();
        _breathingController.removeListener(_ciliaryBenefitTick);
      }
    });
  }
  
  void _ciliaryBenefitTick() {
    // 根据动画时间流逝,微幅降低疲劳指数
    if (mounted && _isSessionActive && _currentMode == TherapyMode.ciliaryRelaxation) {
      setState(() {
        _fatigueIndex = math.max(0, _fatigueIndex - 0.01);
        _ciliarySpasmReduction += 0.02;
      });
    }
  }

  void _switchMode(TherapyMode mode) {
    if (_currentMode == mode) return;
    setState(() {
      _isSessionActive = false;
      _breathingController.stop();
      _breathingController.removeListener(_ciliaryBenefitTick);
      _currentMode = mode;
      _saccadeHistory.clear();
      _currentTarget = const Offset(0.5, 0.5);
    });
  }

  @override
  Widget build(BuildContext context) {
    final screenWidth = MediaQuery.of(context).size.width;
    final isWide = screenWidth > 800;

    return Scaffold(
      body: Row(
        children: [
          if (isWide) _buildControlPanel(320),
          Expanded(
            child: Column(
              children: [
                if (!isWide) _buildMobileHeader(),
                Expanded(
                  child: Stack(
                    children: [
                      // 底层视网膜基准网格
                      Positioned.fill(
                        child: CustomPaint(
                          painter: RetinalGridPainter(),
                        ),
                      ),
                      // 核心光学测绘渲染区
                      Positioned.fill(
                        child: LayoutBuilder(
                          builder: (context, constraints) {
                            if (_currentMode == TherapyMode.ciliaryRelaxation) {
                              return AnimatedBuilder(
                                animation: _lensFocusAnimation,
                                builder: (context, _) {
                                  return CustomPaint(
                                    painter: CiliaryRelaxationPainter(
                                      focusPhase: _lensFocusAnimation.value,
                                      isActive: _isSessionActive,
                                    ),
                                  );
                                },
                              );
                            } else {
                              return AnimatedBuilder(
                                animation: _saccadePositionAnimation,
                                builder: (context, _) {
                                  return CustomPaint(
                                    painter: SaccadicTargetPainter(
                                      currentPos: _saccadePositionAnimation.value,
                                      history: _saccadeHistory,
                                      isActive: _isSessionActive,
                                    ),
                                  );
                                },
                              );
                            }
                          },
                        ),
                      ),
                      // 浮动生物体征仪表框
                      Positioned(
                        top: 24,
                        right: 24,
                        child: _buildTelemetryHUD(),
                      ),
                    ],
                  ),
                ),
                if (!isWide) _buildMobileBottomBar(),
              ],
            ),
          ),
        ],
      ),
      floatingActionButton: isWide 
        ? FloatingActionButton.extended(
            onPressed: _toggleSession,
            backgroundColor: _isSessionActive ? Colors.redAccent : const Color(0xFF00E676),
            icon: Icon(_isSessionActive ? Icons.stop : Icons.play_arrow, color: Colors.black),
            label: Text(_isSessionActive ? '中止干预' : '启动视力重塑', style: const TextStyle(color: Colors.black, fontWeight: FontWeight.bold)),
          )
        : null,
    );
  }

  Widget _buildControlPanel(double width) {
    return Container(
      width: width,
      decoration: BoxDecoration(
        color: Theme.of(context).cardColor,
        border: Border(right: BorderSide(color: Colors.white.withValues(alpha: 0.05))),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Padding(
            padding: EdgeInsets.fromLTRB(24, 48, 24, 24),
            child: Text(
              '视觉物理疗法',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Color(0xFF00E676)),
            ),
          ),
          _buildModeSelector(),
          const Divider(color: Colors.white10, height: 48),
          _buildTherapyStats(),
        ],
      ),
    );
  }

  Widget _buildMobileHeader() {
    return Container(
      height: 90,
      padding: const EdgeInsets.only(top: 40, left: 20, right: 20),
      decoration: BoxDecoration(
        color: Theme.of(context).cardColor,
        border: Border(bottom: BorderSide(color: Colors.white.withValues(alpha: 0.1))),
      ),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          const Text(
            '视觉物理干预台',
            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, letterSpacing: 1.5),
          ),
          IconButton(
            icon: Icon(_isSessionActive ? Icons.stop_circle : Icons.play_circle_fill, 
                       color: _isSessionActive ? Colors.redAccent : const Color(0xFF00E676), size: 32),
            onPressed: _toggleSession,
          )
        ],
      ),
    );
  }

  Widget _buildMobileBottomBar() {
    return Container(
      padding: const EdgeInsets.symmetric(vertical: 16),
      decoration: BoxDecoration(
        color: Theme.of(context).cardColor,
        border: Border(top: BorderSide(color: Colors.white.withValues(alpha: 0.1))),
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          _buildModeSelector(),
        ],
      ),
    );
  }

  Widget _buildModeSelector() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 20),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('干预引擎阵列', style: TextStyle(color: Colors.white54, fontSize: 12)),
          const SizedBox(height: 12),
          Row(
            children: [
              Expanded(
                child: _buildModeButton(
                  title: '晶状体舒缓',
                  icon: Icons.lens_blur,
                  mode: TherapyMode.ciliaryRelaxation,
                  color: const Color(0xFF00E676),
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: _buildModeButton(
                  title: '扫视轨迹追踪',
                  icon: Icons.gps_fixed,
                  mode: TherapyMode.saccadicTracking,
                  color: const Color(0xFF00B0FF),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildModeButton({required String title, required IconData icon, required TherapyMode mode, required Color color}) {
    final isSelected = _currentMode == mode;
    return InkWell(
      onTap: () => _switchMode(mode),
      borderRadius: BorderRadius.circular(12),
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 200),
        padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
        decoration: BoxDecoration(
          color: isSelected ? color.withValues(alpha: 0.15) : Colors.transparent,
          borderRadius: BorderRadius.circular(12),
          border: Border.all(color: isSelected ? color : Colors.white.withValues(alpha: 0.1)),
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(icon, color: isSelected ? color : Colors.white54),
            const SizedBox(height: 8),
            Text(
              title,
              style: TextStyle(
                color: isSelected ? color : Colors.white54,
                fontSize: 12,
                fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
              ),
            )
          ],
        ),
      ),
    );
  }

  Widget _buildTherapyStats() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 20),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('生物学实时反馈矩阵', style: TextStyle(color: Colors.white54, fontSize: 12)),
          const SizedBox(height: 24),
          _buildStatRow('眼外肌牵拉位移', '${_completedSaccades * 12} mm', const Color(0xFF00B0FF)),
          const SizedBox(height: 20),
          _buildStatRow('睫状肌痉挛解离', '${_ciliarySpasmReduction.toStringAsFixed(1)} μT', const Color(0xFF00E676)),
        ],
      ),
    );
  }

  Widget _buildStatRow(String label, String value, Color color) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text(label, style: const TextStyle(color: Colors.white70)),
        Text(value, style: TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 18, fontFamily: 'monospace')),
      ],
    );
  }

  Widget _buildTelemetryHUD() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.black.withValues(alpha: 0.6),
        borderRadius: BorderRadius.circular(16),
        border: Border.all(color: Colors.white.withValues(alpha: 0.1)),
        boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.5), blurRadius: 10)],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisSize: MainAxisSize.min,
        children: [
          const Text('视觉疲劳指数 (VFI)', style: TextStyle(color: Colors.white54, fontSize: 10)),
          const SizedBox(height: 4),
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                _fatigueIndex.toStringAsFixed(1),
                style: TextStyle(
                  fontSize: 32,
                  fontWeight: FontWeight.bold,
                  color: Color.lerp(const Color(0xFF00E676), Colors.redAccent, _fatigueIndex / 100),
                  fontFamily: 'monospace',
                ),
              ),
              const Text('%', style: TextStyle(color: Colors.white54)),
            ],
          ),
          const SizedBox(height: 8),
          SizedBox(
            width: 100,
            child: LinearProgressIndicator(
              value: _fatigueIndex / 100,
              backgroundColor: Colors.white.withValues(alpha: 0.1),
              valueColor: AlwaysStoppedAnimation<Color>(
                Color.lerp(const Color(0xFF00E676), Colors.redAccent, _fatigueIndex / 100)!
              ),
            ),
          )
        ],
      ),
    );
  }
}

// ==========================================
// 物理级渲染器:视网膜基准网格映射
// ==========================================
class RetinalGridPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.white.withValues(alpha: 0.03)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1.0;

    // 绘制视野极坐标校准线
    final center = Offset(size.width / 2, size.height / 2);
    final maxRadius = math.sqrt(size.width * size.width + size.height * size.height) / 2;
    
    // 同心圆 (表示视野等距带)
    for (double r = 40; r <= maxRadius; r += 60) {
      canvas.drawCircle(center, r, paint);
    }
    
    // 放射线 (表示视神经辐射方向)
    for (int i = 0; i < 12; i++) {
      final angle = i * math.pi / 6;
      final dx = center.dx + maxRadius * math.cos(angle);
      final dy = center.dy + maxRadius * math.sin(angle);
      canvas.drawLine(center, Offset(dx, dy), paint);
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

// ==========================================
// 物理级渲染器:睫状肌舒缓动力学 (远近焦距模拟)
// ==========================================
class CiliaryRelaxationPainter extends CustomPainter {
  final double focusPhase; // 0.0 -> 1.0 -> 0.0 (收缩与舒张相位)
  final bool isActive;

  CiliaryRelaxationPainter({required this.focusPhase, required this.isActive});

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    // 模拟晶状体前表面曲率变化。focusPhase 越大,表示聚焦近处,晶状体变厚,圆缩小
    final baseRadius = math.min(size.width, size.height) * 0.4;
    final dynamicRadius = baseRadius * (1.0 - focusPhase * 0.7) + 20; // 最小收缩至 20
    
    final color = const Color(0xFF00E676);
    
    // 如果未启动,则绘制暗色待机状态
    if (!isActive) {
      final idlePaint = Paint()
        ..color = color.withValues(alpha: 0.2)
        ..style = PaintingStyle.stroke
        ..strokeWidth = 2.0;
      canvas.drawCircle(center, baseRadius, idlePaint);
      return;
    }

    // 绘制晶状体张力外晕 (睫状小带悬韧带拉伸感)
    final haloPaint = Paint()
      ..color = color.withValues(alpha: 0.15 + focusPhase * 0.2)
      ..maskFilter = MaskFilter.blur(BlurStyle.normal, 20 + focusPhase * 40);
    canvas.drawCircle(center, dynamicRadius + 10, haloPaint);

    // 绘制核心视觉焦点
    final corePaint = Paint()
      ..color = color.withValues(alpha: 0.8)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 4.0 + focusPhase * 6.0; // 越近聚焦越实
    canvas.drawCircle(center, dynamicRadius, corePaint);
    
    // 内部高频振荡波纹 (模拟睫状肌微颤)
    final ripplePaint = Paint()
      ..color = color.withValues(alpha: 0.5 * (1.0 - focusPhase))
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1.0;
    canvas.drawCircle(center, dynamicRadius * 0.6, ripplePaint);
    
    // 绘制注视准星
    final crossHairPaint = Paint()
      ..color = Colors.white.withValues(alpha: 0.8)
      ..strokeWidth = 1.5;
    canvas.drawLine(Offset(center.dx - 10, center.dy), Offset(center.dx + 10, center.dy), crossHairPaint);
    canvas.drawLine(Offset(center.dx, center.dy - 10), Offset(center.dx, center.dy + 10), crossHairPaint);
    
    // 距离提示文本
    final textPainter = TextPainter(
      text: TextSpan(
        text: focusPhase < 0.3 ? '望远 (睫状肌舒张)' : (focusPhase > 0.7 ? '近焦 (睫状肌收缩)' : '变焦过渡'),
        style: TextStyle(
          color: color.withValues(alpha: 0.8),
          fontSize: 14,
          fontWeight: FontWeight.bold,
          letterSpacing: 2.0,
        )
      ),
      textDirection: TextDirection.ltr,
    )..layout();
    textPainter.paint(canvas, Offset(center.dx - textPainter.width / 2, center.dy + dynamicRadius + 40));
  }

  @override
  bool shouldRepaint(covariant CiliaryRelaxationPainter oldDelegate) {
    return oldDelegate.focusPhase != focusPhase || oldDelegate.isActive != isActive;
  }
}

// ==========================================
// 物理级渲染器:扫视追踪坐标物理映射
// ==========================================
class SaccadicTargetPainter extends CustomPainter {
  final Offset currentPos; // 归一化坐标 [0..1, 0..1]
  final List<Offset> history;
  final bool isActive;

  SaccadicTargetPainter({required this.currentPos, required this.history, required this.isActive});

  @override
  void paint(Canvas canvas, Size size) {
    if (!isActive) return;

    final color = const Color(0xFF00B0FF);
    
    // 映射归一化坐标到屏幕物理坐标
    Offset mapToScreen(Offset norm) {
      // 预留边缘 padding
      final paddingX = size.width * 0.1;
      final paddingY = size.height * 0.1;
      final usableWidth = size.width * 0.8;
      final usableHeight = size.height * 0.8;
      return Offset(
        paddingX + norm.dx * usableWidth,
        paddingY + norm.dy * usableHeight,
      );
    }

    final screenCurrent = mapToScreen(currentPos);

    // 绘制视觉扫视残影轨迹连线 (模拟大脑皮层视觉驻留)
    if (history.isNotEmpty) {
      final pathPaint = Paint()
        ..style = PaintingStyle.stroke
        ..strokeCap = StrokeCap.round;
        
      for (int i = 0; i < history.length; i++) {
        final start = mapToScreen(history[i]);
        final end = i == history.length - 1 ? screenCurrent : mapToScreen(history[i + 1]);
        
        // 越早的轨迹越暗淡
        final alpha = (i + 1) / (history.length + 1);
        pathPaint.color = color.withValues(alpha: alpha * 0.3);
        pathPaint.strokeWidth = 2.0 + alpha * 2.0;
        
        canvas.drawLine(start, end, pathPaint);
      }
    }

    // 绘制追踪目标主体 (Saccadic Target)
    // 外部脉冲光晕
    final haloPaint = Paint()
      ..color = color.withValues(alpha: 0.4)
      ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 15);
    canvas.drawCircle(screenCurrent, 24, haloPaint);

    // 实体高频注视点
    final corePaint = Paint()
      ..color = color
      ..style = PaintingStyle.fill;
    canvas.drawCircle(screenCurrent, 8, corePaint);
    
    // 靶向环
    final ringPaint = Paint()
      ..color = Colors.white.withValues(alpha: 0.8)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2.0;
    canvas.drawCircle(screenCurrent, 16, ringPaint);
  }

  @override
  bool shouldRepaint(covariant SaccadicTargetPainter oldDelegate) {
    return oldDelegate.currentPos != currentPos || oldDelegate.history != history || oldDelegate.isActive != isActive;
  }
}

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐