| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- // tools/vector_normal.dart
- //
- // 计算部分走 Symja(符号计算库),不再用自研的 rationalsqrt2.dart。
- // 那个自研的 √n 代数只能表示"有理数 × 一层根号 + 若干项之和",遇到
- // 坐标里出现两层根号、分数套根号、或者三角函数就得放弃;交给 CAS 之后
- // 输入什么就能精确算什么。
- import 'package:flutter/material.dart';
- import 'package:flutter_math_fork/flutter_math.dart';
- import 'package:provider/provider.dart';
- import '../expr_syntax.dart';
- import '../symja.dart';
- class VectorNormalPage extends StatefulWidget {
- @override
- _VectorNormalPageState createState() => _VectorNormalPageState();
- }
- class _VectorNormalPageState extends State<VectorNormalPage> {
- final List<TextEditingController> _controllers = List.generate(9, (_) => TextEditingController());
- String _result = '';
- String? _resultTex;
- String? _error;
- bool _isCalculating = false;
- @override
- void dispose() {
- for (var controller in _controllers) {
- controller.dispose();
- }
- super.dispose();
- }
- // 计算法向量:AB × AC,全部交给 Symja 做符号运算
- Future<void> _calculateNormal() async {
- final session = context.read<SymjaSession>();
- setState(() {
- _isCalculating = true;
- _result = '';
- _resultTex = null;
- _error = null;
- });
- // 每个坐标先过一遍语法转换,这样用户沿用旧的 `2√3` 写法也能用
- String coord(int point, int axis) {
- final raw = _controllers[point * 3 + axis].text.trim();
- return raw.isEmpty ? '0' : toSymjaSyntax(raw);
- }
- // 边向量:to - from
- String vec(int from, int to) => '{'
- '(${coord(to, 0)} - ${coord(from, 0)}), '
- '(${coord(to, 1)} - ${coord(from, 1)}), '
- '(${coord(to, 2)} - ${coord(from, 2)})}';
- final expr = 'Cross[${vec(0, 1)}, ${vec(0, 2)}]';
- try {
- final r = await session.compute(expr);
- if (!mounted) return;
- setState(() {
- _result = r.text;
- _resultTex = r.tex;
- });
- } on SymjaException catch (e) {
- if (!mounted) return;
- setState(() => _error = '$e');
- } finally {
- if (mounted) setState(() => _isCalculating = false);
- }
- }
- // 清除所有输入
- void _clearAll() {
- for (var controller in _controllers) {
- controller.clear();
- }
- setState(() {
- _result = '';
- _resultTex = null;
- _error = null;
- });
- }
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: Text('三点法向量计算'),
- backgroundColor: Colors.white,
- elevation: 1,
- ),
- body: SingleChildScrollView(
- padding: EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- _buildInputSection('点A', 0),
- SizedBox(height: 16),
- _buildInputSection('点B', 3),
- SizedBox(height: 16),
- _buildInputSection('点C', 6),
- SizedBox(height: 24),
- _buildControlButtons(),
- SizedBox(height: 24),
- _buildResultSection(),
- SizedBox(height: 24),
- _buildInstructions(),
- ],
- ),
- ),
- );
- }
- Widget _buildInputSection(String title, int startIndex) {
- return Card(
- elevation: 2,
- child: Padding(
- padding: EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- title,
- style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
- ),
- SizedBox(height: 12),
- Row(
- children: [
- _buildCoordinateInput('x', _controllers[startIndex]),
- SizedBox(width: 12),
- _buildCoordinateInput('y', _controllers[startIndex + 1]),
- SizedBox(width: 12),
- _buildCoordinateInput('z', _controllers[startIndex + 2]),
- ],
- ),
- ],
- ),
- ),
- );
- }
- Widget _buildCoordinateInput(String label, TextEditingController controller) {
- return Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- label,
- style: TextStyle(fontSize: 14, color: Colors.grey[600]),
- ),
- SizedBox(height: 4),
- TextField(
- controller: controller,
- decoration: InputDecoration(
- hintText: '0',
- border: OutlineInputBorder(),
- contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
- ),
- keyboardType: TextInputType.number,
- ),
- ],
- ),
- );
- }
- Widget _buildControlButtons() {
- return Row(
- children: [
- Expanded(
- flex: 3,
- child: ElevatedButton(
- onPressed: _isCalculating ? null : _calculateNormal,
- style: ElevatedButton.styleFrom(
- backgroundColor: Colors.blue,
- padding: EdgeInsets.symmetric(vertical: 12),
- ),
- child: _isCalculating
- ? SizedBox(
- width: 20,
- height: 20,
- child: CircularProgressIndicator(
- strokeWidth: 2,
- valueColor: AlwaysStoppedAnimation(Colors.white),
- ),
- )
- : Text(
- '计算法向量',
- style: TextStyle(fontSize: 16, color: Colors.white),
- ),
- ),
- ),
- SizedBox(width: 12),
- Expanded(
- flex: 2,
- child: OutlinedButton(
- onPressed: _clearAll,
- style: OutlinedButton.styleFrom(
- padding: EdgeInsets.symmetric(vertical: 12),
- ),
- child: Text('清除', style: TextStyle(fontSize: 16)),
- ),
- ),
- ],
- );
- }
- Widget _buildResultSection() {
- if (_error != null) {
- return Card(
- color: Colors.red[50],
- elevation: 2,
- child: Padding(
- padding: EdgeInsets.all(16),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Icon(Icons.error_outline, color: Colors.red[700], size: 20),
- SizedBox(width: 8),
- Expanded(
- child: SelectableText(
- '计算错误:$_error',
- style: TextStyle(color: Colors.red[700], fontSize: 14),
- ),
- ),
- ],
- ),
- ),
- );
- }
- if (_result.isEmpty) return SizedBox();
- return Card(
- color: Colors.grey[50],
- elevation: 2,
- child: Padding(
- padding: EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- '计算结果',
- style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
- ),
- SizedBox(height: 12),
- // 法向量是精确的符号结果(可能带根号、分数),用 LaTeX 排出来
- // 比纯文本好读得多。
- if (_resultTex != null)
- Math.tex(
- _resultTex!,
- mathStyle: MathStyle.display,
- textStyle: TextStyle(fontSize: 20, color: Colors.blue[800]),
- onErrorFallback: (_) => Text(
- _result,
- style: TextStyle(fontSize: 16, color: Colors.blue[700]),
- ),
- )
- else
- Text(
- _result,
- style: TextStyle(fontSize: 16, color: Colors.blue[700]),
- ),
- SizedBox(height: 8),
- // 纯文本那份留着,方便复制到别处
- SelectableText(
- _result,
- style: TextStyle(fontSize: 12, color: Colors.grey[700]),
- ),
- SizedBox(height: 8),
- Text(
- '说明:法向量是通过向量AB和向量AC的叉积计算得到',
- style: TextStyle(fontSize: 12, color: Colors.grey[600]),
- ),
- ],
- ),
- ),
- );
- }
- Widget _buildInstructions() {
- return Card(
- color: Colors.grey[50],
- elevation: 2,
- child: Padding(
- padding: EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- '使用说明',
- style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
- ),
- SizedBox(height: 12),
- Text(
- '坐标按 Symja 语法输入,例如:\n'
- ' 2/3 分数\n'
- ' Sqrt[2] 根号(也可写 √2)\n'
- ' Sqrt[2]/2、1 + Sqrt[3]、Pi\n'
- '\n'
- '结果是精确值:带根号、分数会原样保留,不会转成小数。\n'
- '如果已知两向量坐标,将一个点设为原点即可。',
- style: TextStyle(fontSize: 14, color: Colors.grey[700]),
- ),
- ],
- ),
- ),
- );
- }
- }
|