Flutter 3D cube distorted after adding GridView.builder

157 Views Asked by At

I have this code that I have wrote from following a fellow youtuber to create a 3D cube with its animation, It works very well until I introduce a GridView or a ListView widgets inside any of the faces.

import 'dart:async';
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sizer/sizer.dart';
import 'package:tictactoe/Controllers/gameEngine.dart';
import 'package:tictactoe/UIUX/themesAndStyles.dart';
import 'UIUX/customWidgets.dart';
import 'gamePage.dart';



enum faces {top, bottom, right, left, front, back}

class CubeGame extends StatefulWidget {
  const CubeGame({super.key});

  @override
  State<CubeGame> createState() => _CubeGameState();
}

class _CubeGameState extends State<CubeGame> with SingleTickerProviderStateMixin{

  Offset offset = Offset.zero;

  double rx = 0.0;
  double ry = 0.0;
  double rz = 0.0;
  List<Widget> children = [];

  GameEngine frontGame = GameEngine();
  GameEngine backGame = GameEngine();
  GameEngine rightGame = GameEngine();
  GameEngine leftGame = GameEngine();

  late AnimationController _animationController;

  late Animation _animation;

  late Widget right;
  late Widget front;
  late Widget back ;
  late Widget left;
  late Widget top;
  late Widget bottom;

  double cubeSize = 70.w;

  updateFaces(double bX, double bY){
    rx += bX;
    ry += bY;
    rx %= pi*2;
    ry %= pi*2;
    if ((rx >= 0 && rx <= 1.70) || (rx >= 4.70)){
      if (ry < pi / 4) {
        children = [right, front];
      } else if (ry < pi / 2) {
        children = [front, right];
      } else if (ry < 3 * pi / 4) {
        children = [back, right];
      } else if (ry < pi) {
        children = [right, back];
      } else if (ry < 5 * pi / 4) {
        children = [left, back];
      } else if (ry < 3 * pi / 2) {
        children = [back, left];
      } else if (ry < 7 * pi / 4) {
        children = [front, left];
      } else {
        children = [left, front];
      }
    }else{
      if (ry < pi / 4) {
        children = [left, back];
      } else if (ry < pi / 2) {
        children = [back, left];
      } else if (ry < 3 * pi / 4) {
        children = [front, left];
      } else if (ry < pi) {
        children = [left, front];
      } else if (ry < 5 * pi / 4) {
        children = [right, front];
      } else if (ry < 3 * pi / 2) {
        children = [front, right];
      } else if (ry < 7 * pi / 4) {
        children = [back, right];
      } else {
        children = [right, back];
      }
    }
    if (rx  >= 0 && rx <= pi) {
      // TODO: not perfect - does not consider perspective:
      // When `rotateX` is positive but very small, like 0.1, when taking
      // account of perspective, the "top" face should be drawn *behind* the
      // front face, not *in front* of it. But this works reasonably well for
      // larger values (like > 0.1) of `rotateX`.
      if (rx >= 0 && rx <= 0.1 || rx > 3){
        children = [top,...children];
      }else{
        children = [...children, top];
      }

    } else if(rx >= pi && rx <= pi*2) {
      if (rx <= pi + 0.1 || (rx >= 6.18)){
        children = [bottom, ...children];
      }else{
        children = [...children, bottom];
      }
    }
    setState(() {});
  }

  generateFaces(){
    for (var face in faces.values){
      switch (face){

        case faces.top:
          top = buildBoardFace(face: faces.top, size: cubeSize);
          break;
        case faces.bottom:
          bottom = buildBoardFace(face: faces.bottom, size: cubeSize);
          break;
        case faces.right:
          right = buildBoardFace(face: faces.right, size: cubeSize, customEngine: frontGame);
          break;
        case faces.front:
          front = buildBoardFace(face: faces.front, size: cubeSize, customEngine: frontGame);
          break;
        case faces.back:
          back = buildBoardFace(face: faces.back, size: cubeSize, customEngine: frontGame);
          break;
        case faces.left:
          left = buildBoardFace(face: faces.left, size: cubeSize, customEngine: frontGame);
      }
    }
  }

  @override
  void initState() {
    _animationController = AnimationController(duration: Duration(milliseconds: 3000), vsync: this);

    _animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
    generateFaces();
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      updateFaces(0.65, 0.8);
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanUpdate: (det){
        double bX = 0.0, bY = 0.0;
      if ((rx >= 0 && rx <= 1.70) || (rx >= 4.70)){
        bX = det.delta.dy * 0.01;
        bY = -det.delta.dx * 0.01;
      }else{
        bX = det.delta.dy * 0.01;
        bY = det.delta.dx * 0.01;
      }

        updateFaces(bX, bY);
      },
      child: Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ChangeNotifierProvider(
                create: (context)=>frontGame,
                child: Transform(
                  transform: Matrix4.identity()
                  ..setEntry(3, 2, 0.001)
                  ..rotateX(rx)
                  ..rotateY(ry)
                  ..rotateZ(rz),
                  alignment: Alignment.center,
                  child: SizedBox(
                    height: cubeSize,
                    width: cubeSize,
                    child: Stack(
                      children: [
                        ...children
                      ],
                    ),
                  ),
                ),
              ),
              SizedBox(height: 20.h),
              Slider(value: rx,
                  min: 0,
                  max: pi * 2,
                  onChanged: (value){
                    rx = value;
                    setState(() {
                      updateFaces(rx, 0);
                    });
              }),
              Text(rx.toString()),
              Slider(value: ry,
                  min: 0,
                  max: pi * 2,
                  onChanged: (value){
                    ry = value;
                    setState(() {
                      updateFaces(0, ry);
                    });
                  }),
              Text(ry.toString()),
              Slider(value: rz,
                  min: 0,
                  max: pi * 2,
                  onChanged: (value){
                    rz = value;
                    setState(() {
                    });
                  }),
              Text(rz.toString())
            ],
          )
        ),
      ),
    );
  }

  buildBoardFace({required faces face, required double size, GameEngine? customEngine}){

    late double rotateX;
    late double rotateY;
    late double trX;
    late double trY;
    late double trZ;
    switch (face){

      case faces.top:
        trX = 0.0;
        trY = -cubeSize/2;
        trZ = 0.0;
        rotateX = -pi / 2;
        rotateY = 0.0;
      case faces.bottom:
        trX = 0.0;
        trY = cubeSize/2;
        trZ = 0.0;
        rotateX = pi / 2;
        rotateY = 0.0;
      case faces.right:
        trX = cubeSize/2;
        trY = 0.0;
        trZ = 0.0;
        rotateX = 0.0;
        rotateY = -pi / 2;
      case faces.left:
        trX = -cubeSize/2;
        trY = 0.0;
        trZ = 0.0;
        rotateX = 0.0;
        rotateY = pi / 2;
      case faces.front:
        trX = 0.0;
        trY = 0.0;
        trZ = -cubeSize/2;
        rotateX = 0.0;
        rotateY = 0.0;
      case faces.back:
        trX = 0.0;
        trY = 0.0;
        trZ = cubeSize/2;
        rotateX = 0.0;
        rotateY = pi;
    }
    List<Widget> lines = [];
    // Draw box lines
    init(size, size, lines, Colors.lightBlueAccent.withOpacity(0.5), _animationController);
    final linearGrid = <int>[];
    if (customEngine != null) {
      for (var i in customEngine.grid){
        linearGrid.addAll(i);
      }
    }
    return face != faces.front ? Transform(
        transform: Matrix4.identity()
    ..translate(trX, trY, trZ)
    ..rotateX(rotateX)
    ..rotateY(rotateY),
    alignment: Alignment.center,
    child: Container(
      height: size,
      width: size,
      color: colorLightYellow,
      child: Image.asset('assets/patternXO.png'),
    )
    ) :
    SizedBox(
      height: size,
      width: size,
      child: Container(
        alignment: Alignment.topLeft,
        child: Stack(
          children: lines..addAll([
            Column(
              mainAxisAlignment: MainAxisAlignment.start,
              children: [
                GridView.builder(
                    gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: 3,
                    ),
                    physics: const NeverScrollableScrollPhysics(),
                    padding: EdgeInsets.zero,
                    shrinkWrap: true,
                    itemCount: linearGrid.length,
                    itemBuilder: (context, index){
                      return InkWell(
                        onTap: () async{
                          gameWinner? winner;
                          if (linearGrid[index] == -1){
                            winner = customEngine?.setManualMove(isO: false, ((index ~/ 3),(index % 3)));
                            setState(() {});
                            await Future.delayed(const Duration(milliseconds: 1000));
                            winner = customEngine?.setAiMove(isO: true);
                          }
                          setState(() {});

                        },
                        child: Container(
                          padding: const EdgeInsets.all(5),
                          child: linearGrid[index] == -1 ? Container() :
                          Icon(linearGrid[index] == 0 ? CupertinoIcons.circle :  CupertinoIcons.xmark,
                              color: Colors.blue, size: 12.w),
                        ),
                      );
                    }),
              ],
            ),
          ]),
        ),
      ),
    );

  }
}

Below is a recording for when I run this top code which only give the front face (1/6 face) a gridView child:

a busy cat

Now I have tried multiple solutions but it appears that the issue is mainly because once I introduce these GridView or ListView widgets the calculations I made for rotating and fixing the side into its 3d position gets messed up and should be re-calculated into some other sort that I cannot figure.

for reference:

this piece of code I have tried and added to it the part of code with the gridview and it works fine, it has another way of calculating the position and as well only renders 3 sides that I don't want it to happen in my app:

https://stackoverflow.com/a/74942907/15008725

0

There are 0 best solutions below