【Flutter】 모방 변환을 통해 별 회전

38614 단어 FlutterDartmathtech

만든 물건


컨디션
기계: M1 맥북 에어
편집기:VScode
보관소: https://github.com/kenta-wakasa/flutter_playground

대충 모방해서 바꾸면


공식으로 간단하게 모방 변환이 이렇다는 것을 나타낸다.
y = Ax + t\tag1
이것이 무엇을 의미하는지 말하자면 x는 x에 어떤 변화를 가져오는 계수 A와 평행이동을 나타내는 t에 따라 y로 변한다.
컴퓨터 도형의 세계에서 이 얻기 어려운 공식은 확대, 회전, 평행 이동 도형을 할 수 있다.
예를 들어 (1)의 공식을 x 좌표, y 좌표에 적용하여 고려하면 다음과 같다.
\begin{cases}
x' = ax+by+x_0\\
y' = cx+dy+y_0
\end{cases}
이거 줄 서서 쓰면 이런 느낌이야.나는 내가 펼쳐 보면 매우 명백하다고 생각한다.
\begin{bmatrix}
x'\\
y'\\
1
\end{bmatrix}
=
\begin{bmatrix}
a & b & x_0\\
c & d & y_0\\
0 & 0 & 1
\end{bmatrix}
\begin{bmatrix}
x\\
y\\
1
\end{bmatrix}
이 a, b, c, d의 부분은 (1) 중의 A에 해당한다
A =
\begin{bmatrix}
a & b\\
c & d\\
\end{bmatrix}
그렇습니다.x, y만\theta 회전시키려면 다음과 같은 행렬을 나타낼 수 있습니다.
A =
\begin{bmatrix}
\cos\theta & -\sin\theta\\
\sin\theta & \cos\theta\\
\end{bmatrix}
이른바 회전 행렬이다.
여기에 상수를 더하면 x와 y 방향에서 같은 비율로 확대하고 축소할 수 있다.
즉, 상수가 a라면 이렇게 쓴다.
A = a
\begin{bmatrix}
\cos\theta & -\sin\theta\\
\sin\theta & \cos\theta\\
\end{bmatrix}
그다음에 이거만 코드에 넣으면 돼!

소스 코드 작성


모방 변환은 이렇다.
그렇긴 하지만 확대, 축소, 회전, 평행 이동만 할 뿐 잘라내지 않았다.
변환된 부분을 모방하다
  Offset _affinTranslate(
    Offset pos, {
    double radians = 0,
    double scale = 1.0,
    double tx = 0,
    double ty = 0,
  }) {
    final dx = scale * (pos.dx * cos(radians) - pos.dy * sin(radians)) + tx;
    final dy = scale * (pos.dx * sin(radians) + pos.dy * cos(radians)) + ty;
    return Offset(dx, dy);
  }

컨트롤러


별이 적혀있어서 이 부분도 참고할 수 있을 것 같아요.
전선으로 도형을 그리니 매우 즐겁다.
canvas_controller.dart
import 'dart:math';

import 'package:flutter/material.dart';

class CanvasController extends ChangeNotifier {
  CanvasController() {
    _radians = 0;
    _scale = 1;
    _tx = 0;
    _ty = 0;
    _initOffset = const Offset(0, -10); // 星型の最初の頂点を決める。
    _sourceOffsetList.add(_initOffset);
    // 星型は同一円の円周上を 4π/5 ずつ回転させた点を結ぶことで描ける。
    for (var radians = 0.0; radians <= 4 * pi; radians += 4 * pi / 5) {
      _sourceOffsetList.add(_affinTranslate(_initOffset, radians: radians));
    }
    _destinationOffsetList = _sourceOffsetList;
  }

  double _radians;
  double get radians => _radians;
  set radians(double radians) {
    _radians = radians;
    _update();
  }

  double _scale;
  double get scale => _scale;
  set scale(double scale) {
    _scale = scale;
    _update();
  }

  double _tx;
  double get tx => _tx;
  set tx(double tx) {
    _tx = tx;
    _update();
  }

  double _ty;
  double get ty => _ty;
  set ty(double ty) {
    _ty = ty;
    _update();
  }

  Offset _initOffset;
  final _sourceOffsetList = <Offset>[];
  List<Offset> _destinationOffsetList;
  List<Offset> get destinationOffsetList => _destinationOffsetList;

  /// 拡大・回転・平行移動のアフィン変換は次のようになっている
  /// ```
  /// dx =  scale * ( x*cosθ - y*sinθ ) + tx
  /// dy =  scale * ( x*sinθ + y*cosθ ) + ty
  /// ```
  Offset _affinTranslate(
    Offset pos, {
    double radians = 0,
    double scale = 1.0,
    double tx = 0,
    double ty = 0,
  }) {
    final dx = scale * (pos.dx * cos(radians) - pos.dy * sin(radians)) + tx;
    final dy = scale * (pos.dx * sin(radians) + pos.dy * cos(radians)) + ty;
    return Offset(dx, dy);
  }

  /// すべての offset に対してアフィン変換をかける
  void _update() {
    _destinationOffsetList = _sourceOffsetList
        .map((s) =>
            _affinTranslate(s, radians: radians, scale: scale, tx: tx, ty: ty))
        .toList();
    notifyListeners();
  }
}

페이지


슬라이더나 문자 상자에 를 입력합니다.
canvas_page.dart
import 'dart:math';

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

import 'canvas_controller.dart';
import 'painter.dart';

final canvasProvider = ChangeNotifierProvider.autoDispose<CanvasController>(
  (ref) => CanvasController(),
);

class CanvasPage extends ConsumerWidget {
  const CanvasPage({Key key}) : super(key: key);
  static const String title = 'アフィン変換';
  static const textStyle = TextStyle(fontSize: 24, fontWeight: FontWeight.bold);
  
  Widget build(BuildContext context, ScopedReader watch) {
    final _provider = watch(canvasProvider);
    return Scaffold(
        appBar: AppBar(
            title: const Text(title,
                style: TextStyle(fontWeight: FontWeight.bold))),
        body: SafeArea(
            child: Stack(children: [
          Center(
            child: CustomPaint(
                painter: Painter(offsetList: _provider.destinationOffsetList)),
          ),
          Column(
              mainAxisAlignment: MainAxisAlignment.end,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                SizedBox(
                    height: 80,
                    width: 300,
                    child: Column(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        children: [
                          Slider(
                              onChanged: (value) => _provider.radians = value,
                              value: _provider.radians,
                              min: 0,
                              max: 2 * pi),
                          const Text('Rotation', style: textStyle)
                        ])),
                SizedBox(
                    height: 80,
                    width: 300,
                    child: Column(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        children: [
                          Slider(
                              value: _provider.scale,
                              onChanged: (value) => _provider.scale = value,
                              min: 1,
                              max: 20),
                          const Text('Scale', style: textStyle)
                        ]))
              ]),
          Align(
              alignment: Alignment.bottomCenter,
              child:
                  Row(mainAxisAlignment: MainAxisAlignment.center, children: [
                SizedBox(
                  width: 160,
                  child: TextField(
                    style: const TextStyle(fontSize: 36),
                    decoration: const InputDecoration(
                        labelText: 'tx',
                        hintText: '0',
                        border: OutlineInputBorder()),
                    onChanged: (tx) {
                      _provider.tx = double.parse(tx);
                    },
                  ),
                ),
                const SizedBox(width: 16, height: 500),
                SizedBox(
                    width: 160,
                    child: TextField(
                        style: const TextStyle(fontSize: 36),
                        decoration: const InputDecoration(
                          labelText: 'ty',
                          hintText: '0',
                          border: OutlineInputBorder(),
                        ),
                        onChanged: (ty) {
                          _provider.ty = double.parse(ty);
                        }))
              ]))
        ])));
  }
}

페인트


그림 그리는 중입니다.
이번에 오프셋에서 점군 데이터를 준비했습니다.
그것들을 연결해서 선분으로 그려라.Path()..addPolygon 점을 연결해서 선을 그리면 됩니다.
painter.dart
import 'package:flutter/material.dart';

class Painter extends CustomPainter {
  Painter({ this.offsetList});
  List<Offset> offsetList;
  
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.orange
      ..strokeWidth = 2.0
      ..style = PaintingStyle.stroke;
    final path = Path()..addPolygon(offsetList, false);
    canvas.drawPath(path, paint);
  }

  
  bool shouldRepaint(Painter oldDelegate) {
    return true;
  }
}

최후

Offset 반에는 비례자와 평행으로 이동하는 방법이 있지만 원점을 중심으로 회전하는 방법은 없다.그냥 끼는 것도 좋을 것 같아요.

좋은 웹페이지 즐겨찾기