pode me ajudar a corrigir esse erro da rotação da bola?


import 'package:flame/game.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cube/flutter_cube.dart';
import 'dart:math';
import 'package:vector_math/vector_math.dart' as vector_math;

const double TABLE_WIDTH = 330;
const double TABLE_HEIGHT = 603;

const double limitX = 10000;
const double limitY = 10000;

const double IMAGE_SCALE = 0.7;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Pool'),
        ),
        body: Center(
          child: Stack(
            alignment: Alignment.center,
            clipBehavior: Clip.none,
            children: [
              Container(
                width: TABLE_WIDTH * IMAGE_SCALE,
                height: TABLE_HEIGHT * IMAGE_SCALE,
                clipBehavior: Clip.none,
                child: Image.asset(
                  'assets/table.png',
                  scale: 3,
                  fit: BoxFit.cover,
                ),
              ),
              Positioned(
                left: 0,
                right: 0,
                child: SizedBox(
                  width: TABLE_WIDTH,
                  height: TABLE_HEIGHT,
                  child: BallScene(),
                ),
              )
            ],
          ),
        ),
        backgroundColor: Color.fromARGB(106, 17, 52, 231),
        resizeToAvoidBottomInset: false,
      ),
    );
  }
}

class Ball {
  late Object ball;
  double radius = 6.5;
  double velocityX = 0;
  double velocityY = 0;
  double CUSHION_RESTITUTION = 0.8;
  bool apply = false;

  Ball(String fileName, double initialX, double initialY) {
    ball = Object(
      fileName: fileName,
    );

    // Define a rotação inicial da bola
    ball.rotation.setValues(-180, -90, -59);
    ball.position.setValues(initialX, initialY, 0);

    ball.scale.setValues(radius, radius, radius);
  }

  Object get object => ball;

  double angularVelocity = 0.0;

  double angle = 0; // Inicializa o ângulo

  double rotationSpeed = 10;

  Quaternion orientation = Quaternion.identity();
  Vector3 angularVelocityVec = Vector3.zero();
  double rotationalFriction = 0.99;

  void update(double dt, double friction, List<Rectangle> rectangles) {
    ball.position.x += velocityX * dt;
    ball.position.y += velocityY * dt;

    velocityX *= friction;
    velocityY *= friction;

    // Calcula o eixo de rotação baseado na direção do movimento
    Vector3 velocity = Vector3(velocityX, velocityY, 0);
    Vector3 rotationAxis = velocity.cross(Vector3(0, 0, 1)).normalized();

    if (rotationAxis.length > 0) {
      double speed = velocity.length;

      // Calculamos a quantidade de rotação
      double angle = speed * 10 * dt;

      // Criamos o quaternion de rotação
      Quaternion rotation = Quaternion.axisAngle(rotationAxis, angle);

      // Atualizamos a orientação
      orientation = rotation * orientation;
      orientation.normalize();

      // Aplicamos a rotação à bola usando o quaternion
      ball.rotation.setFrom(orientation.axis);
    }

    // Aplicar fricção rotacional
    angularVelocityVec.scale(rotationalFriction);

    for (var rect in rectangles) {
      // Verificar colisão com o retângulo
      if (ball.position.x + radius > rect.x &&
          ball.position.x - radius < rect.x + rect.width &&
          ball.position.y + radius > rect.y &&
          ball.position.y - radius < rect.y + rect.height) {
        // Calcular o ponto mais próximo no retângulo
        double nearestX =
            max(rect.x, min(ball.position.x, rect.x + rect.width));
        double nearestY =
            max(rect.y, min(ball.position.y, rect.y + rect.height));

        // Calcular a distância do ponto mais próximo até o centro da bola
        double deltaX = ball.position.x - nearestX;
        double deltaY = ball.position.y - nearestY;
        double distance = sqrt(deltaX * deltaX + deltaY * deltaY);

        if (distance < radius) {
          // Ajustar a posição da bola para fora do retângulo
          double overlap = radius - distance;
          ball.position.x += (deltaX / distance) * overlap;
          ball.position.y += (deltaY / distance) * overlap;

          // Inverter a velocidade para simular o rebote
          if (deltaX.abs() > deltaY.abs()) {
            velocityX = -velocityX * CUSHION_RESTITUTION;
          } else {
            velocityY = -velocityY * CUSHION_RESTITUTION;
          }
        }
      }
    }

    ball.updateTransform();
  }
}

class Rectangle {
  double x, y, width, height;

  Rectangle(this.x, this.y, this.width, this.height);

  bool contains(double px, double py) {
    // Verifica se o ponto (px, py) está dentro do retângulo
    return px > x && px < x + width && py > y && py < y + height;
  }
}

class BallScene extends StatefulWidget {
  @override
  _BallSceneState createState() => _BallSceneState();
}

class _BallSceneState extends State<BallScene> {
  late List<Ball> _balls;
  late List<Rectangle> _rectangles;
  double friction = 0.99;
  double _lastTime = 0.0;
  Offset cuePosition = Offset(0, 0);
  double cueAngle = radians(90);
  Offset projection = Offset.zero;
  late List<Offset> projections;

  late List<Vector3> rectProject = [Vector3(-45, 89, 0)];
  late List<Rectangle> convertedRect = [];

  late Scene _scene;
  bool isLoaded = false;

  @override
  void initState() {
    super.initState();
    _initializeBalls();
    _initializeRectangles();
    projections = List.filled(_balls.length, Offset.zero);
    _scene = Scene();
    WidgetsBinding.instance.addPostFrameCallback((_) => _startAnimation());
  }

  void _initializeBalls() {
    _balls = [
      Ball('assets/boll.obj', 0, 81),
    ];
    // moveBalls(100, 100 * 2);
  }

  void _initializeRectangles() {
    _rectangles = [
      Rectangle(-33, 85, 70, 3),
      Rectangle(-35, -89, 70, 3),
      Rectangle(-45, 4, 1, 70)
    ];
  }

  void moveBalls(double angle, double force) {
    //print(angle);
    _balls[0].velocityX -= force * cos(angle);
    _balls[0].velocityY += force * sin(angle);
  }

  void _startAnimation() {
    _lastTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
    _update();
  }

  void _update() {
    double currentTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
    double dt = currentTime - _lastTime;
    _lastTime = currentTime;

    setState(() {
      for (var ballIndex = 0; ballIndex < _balls.length; ballIndex++) {
        _balls[ballIndex].update(dt, friction, _rectangles);

        projections[ballIndex] =
            project(_balls[ballIndex].ball.position, _scene);
      }
    });
    if (isLoaded) {
      // projection = project(_balls[0].ball.position, _scene);
    }

    WidgetsBinding.instance.scheduleFrameCallback((_) => _update());
  }

  void updateCue(Offset details, Offset ball) {
    // Posição da bola
    double ballX = ball.dx;
    double ballY = ball.dy;

    // Cálculo da diferença de coordenadas
    double dx = details.dx - ballX;
    double dy = details.dy - ballY;

    // Cálculo do ângulo
    double angle = atan2(dy, dx);

    // Criação da matriz de rotação
    Matrix4 rotationMatrix = Matrix4.rotationZ(angle);

    // Vetor de offset
    Vector3 offsetVector = Vector3(10, 0, 0); // Distância do taco à bola

    // Nova posição do taco
    Vector3 newPos = rotationMatrix.transform3(offsetVector);

    // Ajustar a posição do taco em relação à bola
    double cueX = ballX + newPos.x;
    double cueY = ballY + newPos.y;

    setState(() {
      cuePosition = Offset(cueX, cueY);
      cueAngle = angle; // Guardar o ângulo para a rotação do taco
    });
  }

  Offset project(Vector3 position, Scene scene) {
    // Matriz de projeção da câmera
    Matrix4 projectionMatrix = scene.camera.projectionMatrix;

    // Matriz de visão da câmera
    Matrix4 viewMatrix = scene.camera.lookAtMatrix;

    // Matriz de transformação completa (projeção * visão)
    Matrix4 transformMatrix = projectionMatrix * viewMatrix;

    // Posição em espaço de câmera
    Vector4 posCameraSpace = transformMatrix
        .transform(Vector4(position.x, position.y, position.z, 1.0));

    // Normalização do ponto (perspective divide)
    if (posCameraSpace.w != 0.0) {
      posCameraSpace.x /= posCameraSpace.w;
      posCameraSpace.y /= posCameraSpace.w;
      posCameraSpace.z /= posCameraSpace.w;
    }

    // Coordenadas da tela
    double screenX =
        (posCameraSpace.x + 1.0) * 0.5 * scene.camera.viewportWidth;
    double screenY =
        (1.0 - posCameraSpace.y) * 0.5 * scene.camera.viewportHeight;

    return Offset(screenX, screenY);
  }

  Vector3 unproject2(Offset screenPosition, Scene scene, double depth) {
    double screenX = 2.0 * screenPosition.dx / scene.camera.viewportWidth - 1.0;
    double screenY =
        1.0 - 2.0 * screenPosition.dy / scene.camera.viewportHeight;

    Vector4 screenPos = Vector4(screenX, screenY, depth, 1.0);

    Matrix4 inverseProjectionMatrix =
        Matrix4.copy(scene.camera.projectionMatrix);
    inverseProjectionMatrix.invert();

    Matrix4 inverseViewMatrix = Matrix4.copy(scene.camera.lookAtMatrix);
    inverseViewMatrix.invert();

    Vector4 viewPos = inverseProjectionMatrix.transform(screenPos);
    if (viewPos.w != 0.0) {
      viewPos.x /= viewPos.w;
      viewPos.y /= viewPos.w;
      viewPos.z /= viewPos.w;
    }

    Vector4 worldPos = inverseViewMatrix.transform(viewPos);
    return Vector3(worldPos.x, worldPos.y, worldPos.z);
  }

  Offset rectProj(Scene scene, Vector3 positions) {
    Offset position = Offset.zero;

    position = project(positions, scene);

    return position;
  }

  @override
  Widget build(BuildContext context) {
    // Definindo a posição inicial do trapézio
    // Definindo a posição inicial do trapézio

    return GestureDetector(
      onPanUpdate: (details) {
        setState(() {
          Size screenSize = MediaQuery.of(context).size;
          Offset off = project(_balls[0].ball.position, _scene);

          updateCue(details.globalPosition, off);
          //print(off);

          Offset convertedPos = rectProj(_scene, rectProject[0]);

          convertedRect.add(Rectangle(
              convertedPos.dx, convertedPos.dy, 70 * 2, _rectangles[0].height));

          for (var ball in _balls) {}
        });
      },
      onPanEnd: (details) {
        moveBalls(cueAngle, 100);
      },
      child: Stack(
        clipBehavior: Clip.none,
        children: [
          for (var ballIndex = 0; ballIndex < _balls.length; ballIndex++)
            Positioned(
                left: projections[ballIndex].dx,
                top: projections[ballIndex].dy,
                child: Container(
                  width: _balls[0].radius * 2,
                  height: _balls[0].radius * 2,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    boxShadow: [
                      BoxShadow(
                        color:
                            const Color.fromARGB(255, 0, 0, 0).withOpacity(0.3),
                        blurRadius: 1,
                        spreadRadius: 3,
                        offset: const Offset(-5, -5),
                      ),
                    ],
                  ),
                )),
          Cube(
            onSceneCreated: (Scene scene) {
              _scene = scene;
              for (var ball in _balls) {
                scene.world.add(ball.object);
              }

              scene.camera.viewportHeight = 800;
              scene.camera.viewportWidth = 600;
              // Definindo a posição da câmera

              scene.camera.position.setValues(0, 0, 1000);
              scene.camera.fov = 15;

              //scene.world.backfaceCulling = true;
              isLoaded = true;
            },
            interactive: false,
          ),
          for (var ballIndex = 0; ballIndex < _balls.length; ballIndex++)
            Positioned(
                left: projections[ballIndex].dx,
                top: projections[ballIndex].dy,
                child: Container(
                  width: _balls[0].radius,
                  height: _balls[0].radius,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    boxShadow: [
                      BoxShadow(
                        color:
                            Color.fromARGB(255, 245, 245, 245).withOpacity(0.5),
                        blurRadius: 2,
                        spreadRadius: 1,
                        offset: const Offset(-5, -5),
                      ),
                    ],
                  ),
                )),
          for (var ballIndex = 0; ballIndex < _balls.length; ballIndex++)
            Positioned(
                left: projections[ballIndex].dx,
                top: projections[ballIndex].dy,
                child: Container(
                  width: _balls[0].radius - 1,
                  height: _balls[0].radius - 1,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    boxShadow: [
                      BoxShadow(
                        color:
                            Color.fromARGB(255, 255, 255, 255).withOpacity(0.6),
                        blurRadius: 1,
                        spreadRadius: 0.01,
                        blurStyle: BlurStyle.solid,
                        offset: const Offset(-5, -5),
                      ),
                    ],
                  ),
                )),
          CustomPaint(
            painter: RectanglePainter(convertedRect),
            child: Container(),
          ),
          Positioned(
            left: cuePosition.dx,
            top: cuePosition.dy,
            child: Transform.rotate(
              angle: cueAngle,
              origin: Offset(-122, 0),
              child: Container(
                width: 250,
                height: 15,
                decoration: BoxDecoration(
                  image: DecorationImage(
                    image: AssetImage('assets/cue.png'),
                    //fit: BoxFit.cover,
                  ),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.3),
                      blurRadius: 10,
                      spreadRadius: 3,
                      offset: Offset(5, 5),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class RectanglePainter extends CustomPainter {
  final List<Rectangle> rectangles;

  RectanglePainter(this.rectangles);

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Color.fromARGB(255, 0, 96, 12)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1;

    for (var rect in rectangles) {
      canvas.drawRect(
        Rect.fromLTWH(rect.x, rect.y, rect.width, rect.height),
        paint,
      );
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}