eu tenho dois códigos, um da library e outro meu main.dart, quero aplicar rotação na diagonal, porém apenas os eixos 90 e 180 se comportam de forma natural e correta, ou seja, uma rotação continua nesses ângulos, porém no eixo diagonal, por exemplo, um ângulo de rotação de 45 graus, isso não acontece de forma continua, e eu não entendo porque a esfera não gira corretamente em 45 ou em outros ângulos diagonais, mas em eixos "isolados" (ângulos de 180 ou 90), tem uma rotação continua, meu código da library:


library sphere;

import 'dart:async';
import 'dart:typed_data';
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:vector_math/vector_math.dart' as vc;

class Sphere extends StatefulWidget {
  const Sphere({
    Key? key,
    required this.surface,
    required this.radius,
    this.rotationX = 0, // Renomeado para indicar que é uma rotação no eixo X
    this.rotationY = 0, // Renomeado para indicar que é uma rotação no eixo Y
    this.alignment = Alignment.center,
    this.positionOffset = Offset.zero,
  }) : super(key: key);

  final String surface;
  final double radius;
  final double rotationX; // Ângulo de rotação contínuo em X
  final double rotationY; // Ângulo de rotação contínuo em Y
  final Alignment alignment;
  final Offset positionOffset;

  @override
  _SphereState createState() => _SphereState();
}

class _SphereState extends State<Sphere> {
  Uint32List? surface;
  double surfaceWidth = 0;
  double surfaceHeight = 0;

  @override
  void initState() {
    super.initState();
    _loadSurface();
  }

  void _loadSurface() {
    // Faça o carregamento da superfície apenas uma vez e armazene para todas as esferas
    // if (surface == null) {
    rootBundle.load(widget.surface).then((data) {
      ui.decodeImageFromList(data.buffer.asUint8List(), (image) {
        image.toByteData(format: ui.ImageByteFormat.rawRgba).then((pixels) {
          surface = pixels!.buffer.asUint32List();
          surfaceWidth = image.width.toDouble();
          surfaceHeight = image.height.toDouble();
          setState(() {}); // Redesenhar a esfera ao carregar a textura
        });
      });
    });
    // }
  }

  Future<SphereImage?> _buildSphere(double maxWidth, double maxHeight) async {
    if (surface == null) return null;

    final double r = widget.radius;

    final minX = math.max(-r, (-1 - widget.alignment.x) * maxWidth / 2);
    final minY = math.max(-r, (-1 + widget.alignment.y) * maxHeight / 2);
    final maxX = math.min(r, (1 - widget.alignment.x) * maxWidth / 2);
    final maxY = math.min(r, (1 + widget.alignment.y) * maxHeight / 2);
    final width = maxX - minX;
    final height = maxY - minY;
    if (width <= 0 || height <= 0) return null;
    final sphere = Uint32List(width.toInt() * height.toInt());

    // Ângulos de rotação incremental, usando radianos
    final rotationStepX = widget.rotationX * math.pi / 180;
    final rotationStepY = widget.rotationY * math.pi / 180;

    // Cria um quaternion com a rotação incremental
    final incrementalQuaternion =
        vc.Quaternion.euler(rotationStepX, -rotationStepY, 0);

    // Quaternion base para aplicar a rotação inicial alinhada ao centro
    var combinedRotation = vc.Quaternion.identity();
    combinedRotation *= incrementalQuaternion;

    // Ajustes para mapeamento da textura
    final surfaceXRate = (surfaceWidth - 1) / (2.0 * math.pi);
    final surfaceYRate = (surfaceHeight - 1) / (math.pi);

    for (var y = minY; y < maxY; y++) {
      final sphereY = (height - y + minY - 1).toInt() * width;
      for (var x = minX; x < maxX; x++) {
        var z = r * r - x * x - y * y;
        if (z > 0) {
          z = math.sqrt(z);

          // Aplica rotação incrementalmente
          final point = vc.Vector3(x, y, z);
          final rotatedPoint = combinedRotation.rotated(point);

          // Calcula latitude e longitude a partir do ponto rotacionado
          final lat = math.asin(rotatedPoint.y / r);
          final lon = math.atan2(rotatedPoint.x, rotatedPoint.z);

          // Calcula as coordenadas de textura
          final x0 = (lon + math.pi) * surfaceXRate;
          final y0 = (math.pi / 2 - lat) * surfaceYRate;

          // Busca a cor e a atribui na imagem da esfera
          final color = surface![(y0.toInt() * surfaceWidth + x0).toInt()];
          sphere[(sphereY + x - minX).toInt()] = color;
        }
      }
    }

    final completer = Completer<SphereImage>();
    ui.decodeImageFromPixels(
      sphere.buffer.asUint8List(),
      width.toInt(),
      height.toInt(),
      ui.PixelFormat.rgba8888,
      (image) {
        final sphereImage = SphereImage(
          image: image,
          radius: r,
          origin: Offset(-minX, -minY),
          offset: Offset((widget.alignment.x + 1) * maxWidth / 2,
                  (widget.alignment.y + 1) * maxHeight / 2) +
              widget.positionOffset,
        );
        completer.complete(sphereImage);
      },
    );
    return completer.future;
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        return FutureBuilder(
          future: _buildSphere(constraints.maxWidth, constraints.maxHeight),
          builder: (context, AsyncSnapshot<SphereImage?> snapshot) {
            return CustomPaint(
              painter: SpherePainter(snapshot.data),
              size: Size(constraints.maxWidth, constraints.maxHeight),
            );
          },
        );
      },
    );
  }
}

class SphereImage {
  SphereImage(
      {required this.image,
      required this.radius,
      required this.origin,
      required this.offset});
  final ui.Image image;
  final double radius;
  final Offset origin;
  final Offset offset;
}

class SpherePainter extends CustomPainter {
  SpherePainter(this.sphereImage);
  final SphereImage? sphereImage;

  @override
  void paint(Canvas canvas, Size size) {
    if (sphereImage == null) return;
    final paint = Paint();
    final offsetWithPosition = sphereImage!.offset;
    final rect = Rect.fromCircle(
        center: offsetWithPosition, radius: sphereImage!.radius - 1);

    final path = Path.combine(
      PathOperation.intersect,
      Path()..addOval(rect),
      Path()..addRect(Rect.fromLTWH(0, 0, size.width, size.height)),
    );

    canvas.clipPath(path);
    canvas.drawImage(
        sphereImage!.image, offsetWithPosition - sphereImage!.origin, paint);

    final gradient = RadialGradient(
      center: Alignment.center,
      colors: [
        Colors.transparent,
        Colors.black.withOpacity(0.35),
        Colors.black.withOpacity(0.5)
      ],
      stops: [0.1, 0.85, 1.0],
    );
    paint.shader = gradient.createShader(rect);
    canvas.drawRect(rect, paint);
  }

  @override
  bool shouldRepaint(covariant SpherePainter oldDelegate) {
    return true;
  }
}



Meu main.dart:

import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:lib_sphere/sphere_lib.dart';
import 'package:vector_math/vector_math.dart' as vc;

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: GameScreen(),
        ),
      ),
    );
  }
}

class GameScreen extends StatefulWidget {
  @override
  _GameScreenState createState() => _GameScreenState();
}

class _GameScreenState extends State<GameScreen> {
  double latitude = 0;
  double longitude = 0;

  final double rotationSpeed = 4; // Ajuste da velocidade de rotação
  double rotationAngle = 0; // Ângulo fixo de rotação (ex.: 90°)
  vc.Vector2 velocity = vc.Vector2.zero();
  vc.Vector2 position = vc.Vector2.zero();
  double rotationX = 0; // Ângulo de rotação acumulado em X
  double rotationY = 90; // Ângulo de rotação acumulado em Y
  double friction = 0.98;

  double radius = 100;

  @override
  void initState() {
    super.initState();
    gameLoop();
  }

  void gameLoop() {
    Timer.periodic(Duration(milliseconds: 16), (timer) {
      setState(() {
        // Atualiza a posição com base na velocidade
        position += velocity;

        if (velocity.length <= 0.33) {
          velocity = vc.Vector2.zero();
        }

        // Aplica o atrito para reduzir gradualmente a velocidade
        velocity *= friction;

        // Calcula o ângulo da direção de movimento
        double movementAngle = atan2(-velocity.y, velocity.x);

        // A rotação acumulativa é proporcional à velocidade
        double rotationSpeed =
            velocity.length; // ajuste o multiplicador se necessário

        // Atualiza os ângulos X e Y com base no movimento
        rotationX = (rotationX + rotationSpeed * cos(movementAngle)) % 360;
        rotationY = (rotationY + rotationSpeed * sin(movementAngle)) % 360;

/*  
        // Calcula a direção de rotação com base na velocidade
        rotationAngle = atan2(-velocity.y, velocity.x);

        

        // Calcula a velocidade de rotação com base na magnitude da velocidade atual
        double rotationSpeed =
            velocity.length * 2; // ajuste o multiplicador se necessário

        // Incrementos de latitude e longitude para a rotação
        latitude = (latitude + rotationSpeed * cos(rotationAngle)) % 360;
        longitude = (longitude + rotationSpeed * sin(rotationAngle)) % 360;*/

        // Verifica colisão com as bordas horizontais
        if (position.x <= -300 || position.x >= 300) {
          velocity.x = -velocity.x; // Inverte a velocidade no eixo X
        }

        // Verifica colisão com as bordas verticais
        if (position.y <= -300 || position.y >= 300) {
          velocity.y = -velocity.y; // Inverte a velocidade no eixo Y
        }
      });
    });
  }

  moveBall(double angle, double force) {
    velocity.x += force * cos(vc.radians(angle));
    velocity.y += force * sin(vc.radians(angle));
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Sphere(
          surface: 'assets/1.jpg',
          radius: 30,
          rotationX: -rotationY,
          rotationY: rotationX,
          positionOffset: Offset(position.x, position.y),
        ),
        Positioned(
            left: 100,
            top: 100,
            child: ElevatedButton(
                onPressed: () {
                  moveBall(45, 10);
                },
                child: Text('Here')))
      ],
    );
  }
}