GithubHelp home page GithubHelp logo

fluttertesseract's Introduction

Flutter create

You navigate to next page by swiping to the left. There are 2 'stages' fist is after 2 page and second one is after 3 page.

For my flutter create project I decided to do something that no one has made in Flutter. First idea was making a 3D cube, it was shooting rainbow it was great but then I saw someone else already made game in Flutter using cubes, so I had to do something else.

I really liked my cube, so I just added one more dimension to it. I present you tesseract or also known as eight-cell, C8, octachoron, octahedroid, cubic prism, tetracube and hypercube done in Flutter! With 'w' rotation and all.

It's tesseract done in Flutter with CustomPainter and matrix multiplication

About

Start the app with dark theme and Scaffold

void main() => runApp(MaterialApp(theme: ThemeData.dark().copyWith(accentColor: Colors.white), home: Scaffold(body: MyApp())));

Tesseract class

Generates tesseract points and has update function to update vector after calling setValues that will assign new rotation x, rotation w and distance for stereographic projection and then we get offset of the vector by getting x and z values mapped to Offset

Class code with comments:
class Tesseract{
  /// Looping 16 times at constructor because tesseract has 16 points (2 cubes, connected to all available edges)
  /// 
  ///   final double x = (i + 1) % 4 > 1 ? _size : -_size;
  ///   final double y = i % 4 > 1 ? _size : -_size;
  ///   final double z = i % 8 > 3 ? _size : -_size;
  ///   final double w = i % 16 > 7 ? _size : -_size;
  /// 
  /// In the end of the loop we end up with this list of Vector4's (In this case we set size as 1):
  /// 
  ///   /// First 'cube'
  ///   Vector4(-1, -1, -1, 1),
  ///   Vector4(1, -1, -1, 1),
  ///   Vector4(1, 1, -1, 1),
  ///   Vector4(-1, 1, -1, 1),
  ///   Vector4(-1, -1, 1, 1),
  ///   Vector4(1, -1, 1, 1),
  ///   Vector4(1, 1, 1, 1),
  ///   Vector4(-1, 1, 1, 1),
  ///   /// Second 'cube'
  ///   Vector4(-1, -1, -1, -1),
  ///   Vector4(1, -1, -1, -1),
  ///   Vector4(1, 1, -1, -1),
  ///   Vector4(-1, 1, -1, -1),
  ///   Vector4(-1, -1, 1, -1),
  ///   Vector4(1, -1, 1, -1),
  ///   Vector4(1, 1, 1, -1),
  ///   Vector4(-1, 1, 1, -1),
  /// 
  /// Max distance has to correlate with size
  Tesseract(this._size) : _maxDistance = _size * 2 {
    for(int i = 0; i < 16; i++)
      _points.add(Vector4((i + 1) % 4 > 1 ? _size : -_size, i % 4 > 1 ? _size : -_size, i % 8 > 3 ? _size : -_size, i % 16 > 7 ? _size : -_size));
  }

  final double _size;
  final double _maxDistance;
  Matrix4 _cRot = Matrix4.rotationX(pi * .2) * Matrix4.rotationZ(pi * .2);
  final List<Vector4> _points = <Vector4>[];

  double _x = 0.0, _w = 0.0, _shadow = 0.0;
  Matrix4 _xwRot = Matrix4.identity();
  
  /// Setting new values for tesseract for rotation x, rotation y or distance for stereographic projection.
  /// This will generate new _xwRot matrix that is used to rotate the box
  void setValues(double x, double y, double page){
    _x = x;
    _w = y;
    _shadow = (page - 1).clamp(0.0, 1.0);
    _xwRot = Matrix4(cos(_x), -sin(_x), 0, 0, sin(_x), cos(_x), 0, 0, 0, 0, cos(_w), -sin(_w), 0, 0, sin(_w), cos(_w));
    _cRot = Matrix4.rotationX(pi * .1) * Matrix4.rotationY((page - 2).clamp(0.0, 1.0) * -pi * .3 + -pi * .3) * Matrix4.rotationZ(pi * .2);
    _projectAll();
  }

  /// This just gets Offset for canvas to draw for vector at index
  Offset getOffset(int index) => Offset(_points[index].x, _points[index].z);
  
  /// _projectAll() will be called to calculate new Vector positions.
  void _projectAll() => _points.forEach(_project);
  
  /// Project will map vector on new values that depend on rotation and shadow 'distance' 
  /// where _sgValue is value for distance of the object in stereographic projection.
  /// _sgProjection is matrix for stereographic projection that we will multiply with _rotated to get our projected vector
  /// _sgValue maps current _shadow value to range between 1 and _stereographic projection value determined by
  /// 
  ///     _size / (_maxShadow - _rotated.w) 
  ///     
  void _project(Vector4 vector){
    final Vector4 _rotated = _xwRot * v;
    final double _sgValue = (_size / (_maxShadow - _rotated.w) - 1) * _shadow + 1;
    final Matrix4 _sgProjection = Matrix4.diagonal3(Vector3.all(_sgValue));
    final Vector4 _pVector = _sgProjection * _rotated;
    _v[_v.indexOf(v)] = _cRot * _pVector;
  }
}

MyAppState class

This is main screen, everything gets drawn here. I used timer for rotations since it is less code than AnimationController. Strings are loaded from external json file.

Class code with comments:
class MyAppState extends State<MyApp>{
  double _x = 0, _w = 0, _shadow = 0, _scale = 1, _scaleDiff;
  List<String> _s = <String>[];
  PageController _page;

  @override
  void initState() {
    _page = PageController();
    _loadStrings();
    Timer.periodic(Duration(milliseconds: 32), _startTimer);
    super.initState();
  }

  /// Start timer. cancel it if view is not mounted anymore
  void _startTimer(Timer t){
    if(!mounted){
      t.cancel();
      return;
    }

    /// Get current page
    final double _index = _page.hasClients ? _page.page ?? 0 : 0;
    setState((){
      _x = (_x - .01) % (pi * 2);
      _shadow = max(.0, min(1.0, _index - 1));
      _w = _index > 2.5 ? (_w + .02) % (pi * 2) : max(.0, _w - .02);
    });
  }

  /// Load from external file, convert to JSON, get values from JSON array '_' and map values to the List<String>
  void _loadStrings() async => _s = List<dynamic>.of(json.decode(await DefaultAssetBundle.of(context).loadString('text.json'))['_']).map<String>((dynamic d) => d).toList();

  @override
  Widget build(BuildContext context){
    return GestureDetector(
      onScaleStart: (_) => _scaleDiff = _scale,
      onScaleUpdate: (ScaleUpdateDetails d) => _scale = d.scale * _scaleDiff,
      child: Stack(
        children: <Widget>[
          CustomPaint(painter: TesseractPainter(_x, _w, _shadow, _scale), child: const SizedBox.expand()),
          PageView(controller: _page, children: _s.map(_makeText).toList()),
          Container(
            alignment: const Alignment(.0, .9),
            child: Container(
              width: MediaQuery.of(context).size.width * .5,
              child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: _s.map(_makeDot).toList())
            )
          )
        ]
      )
    );
  }

  /// Make text widget that will display Strings loaded from external text.json file
  Widget _makeText(String s) => Container(
    padding: const EdgeInsets.symmetric(horizontal: 24),
    alignment: const Alignment(.0, -.8),
    child: Text(s, textAlign: TextAlign.center, style: TextStyle(fontSize: 22, fontWeight: FontWeight.w300))
  );

  /// Make navigation dot on bottom of the screen
  Widget _makeDot(String s){
    final double _i = (_s.indexOf(s) - (_page.page ?? .0)).clamp(.0, 1.0);
    final double _size = 12 - (2 * _i);

    return Container(width: _size, height: _size, decoration: BoxDecoration(
      color: Color.lerp(Colors.grey, Colors.black87, _i),
      shape: BoxShape.circle
    ));
  }
}

TesseractPainter class

Tesseract painter will paint tesseract object on canvas. Again we are using canvas.drawLine instead of Path because it is less code.

Class code with comments:
class TesseractPainter extends CustomPainter{
  TesseractPainter(this.x, this.w, this.shadow, this.scale);
  
  final double x, w, shadow, scale;
  final Paint p = Paint()..strokeWidth = .4..color = Colors.white..strokeCap = StrokeCap.round;

  Tesseract _tess;

  @override bool shouldRepaint(TesseractPainter oldDelegate) => x != oldDelegate.x || w != oldDelegate.w || shadow != oldDelegate.shadow;

  @override
  void paint(Canvas canvas, Size size) {
    if(size.shortestSide == 0)
      return;

    _tess ??= Tesseract(size.shortestSide * .2 * scale);
    _tess..setValues(x, w, shadow);

    canvas.translate(size.width / 2, size.height / 2);
    _cube(canvas, _tess, p, 8);
    
    /// Connect all cube ends to each other (Each one of those is like separate cube)
    /// Just like we needed just 4 lines (instead of 12) to make 4 planes to make cube out of 2 planes
    /// Now we need just 8 lines (instead of 48) to make 6 cubes out of 2 cubes
    for(int i = 0; i < 8; i++){
      canvas.drawLine(_tess.getOffset(i), _tess.getOffset(i + 8), p);
    }
    /// This cube will be little thicker so that one cube has better visibility in tesseract
    /// during double rotation
    p..color = Colors.white..strokeWidth = 2;
    _cube(canvas, _tess, p);
  }

  /// Connect 8 (4 on x, 4 on y and 4 on z) points to make cube wireframe
  void _cube(Canvas c, Tesseract _tess, Paint p, [int offset = 0]){
    for(int i = 0; i < 4; i++){
      c.drawLine(_tess.getOffset(offset + i), _tess.getOffset(offset + (i + 1) % 4), p);
      c.drawLine(_tess.getOffset(offset + i + 4), _tess.getOffset(offset + (i + 1) % 4 + 4), p);
      c.drawLine(_tess.getOffset(offset + i), _tess.getOffset(offset + i + 4), p);
    }
  }
}

fluttertesseract's People

Contributors

knezzz avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.