O que eu fiz de errado, na esfera, texturização, ou transformação. Para que no eixo X, em determinado ponto da rotação, a esfera comece a girar para o lado oposto em que começou a rotação?

import 'dart:async';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:vector_math/vector_math_64.dart' hide Colors;

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Esfera 3D com Textura Fixa')),
        body: Center(child: SphereWidget()),
        backgroundColor: const Color.fromARGB(255, 52, 13, 245),
      ),
    );
  }
}

class SphereWidget extends StatefulWidget {
  @override
  _SphereWidgetState createState() => _SphereWidgetState();
}

class _SphereWidgetState extends State<SphereWidget> {
  double angle = 0;
  double rotation = 0;
  ui.Image? texture;

  @override
  void initState() {
    super.initState();
    loadImageFromAsset('assets/02.jpg');
    rotate();
  }

  void rotate() {
    Timer.periodic(Duration(milliseconds: 16), (timer) {
      setState(() {
        angle = (angle + 0.008) %
            (2 *
                math.pi); // Controla a velocidade e mantém o ângulo no intervalo de 0 a 2π
        rotation += 0.5;
      });
    });
  }

  Future<void> loadImageFromAsset(String path) async {
    final completer = Completer<ui.Image>();
    final image = AssetImage(path);
    image.resolve(ImageConfiguration()).addListener(
      ImageStreamListener((info, _) {
        completer.complete(info.image);
      }),
    );
    texture = await completer.future;
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    if (texture == null) {
      return Container();
    }

    /*
        return Stack(
      children: [
        for (int i = 0; i < 15; i++)
          Positioned(
            left: (i % 5) * 200.0, // Distância entre esferas horizontalmente
            top: (i ~/ 5) * 200.0, // Distância entre esferas verticalmente
            child: CustomPaint(
              size: Size(10, 10),
              painter: SpherePainter(texture!, angle, rotation),
            ),
          ),
      ],
    );
    */

    return CustomPaint(
      size: const Size(100, 100),
      painter: SpherePainter(texture!, angle, rotation),
    );
  }
}

class SpherePainter extends CustomPainter {
  final ui.Image texture;
  final double angle;
  final Matrix4 transform = Matrix4.identity();
  final Vector3 position = Vector3.zero();
  final Vector3 rotation = Vector3.zero();
  final Vector3 scale = Vector3(1, 1, 1);
  double rotacao = 0;

  SpherePainter(this.texture, this.angle, this.rotacao);

  void updateTransform() {
    final Matrix4 m = Matrix4.compose(
        position,
        Quaternion.euler(
            radians(rotation.y), radians(rotation.x), radians(rotation.z)),
        scale);
    transform.setFrom(m);
  }

  // Função para transformar os vértices com a rotação aplicada
  void transformVertices(
    List<Vector3> vertices,
    Matrix4 model,
    Matrix4 view,
    Matrix4 projection,
    Float32List positions,
    Float32List positionsZ,
  ) {
    final Matrix4 transform = model * view * projection; // Tente esta ordem

    for (int i = 0; i < vertices.length; i++) {
      final Vector4 v =
          Vector4(vertices[i].x, vertices[i].y, vertices[i].z, 1.0);
      v.applyMatrix4(transform);

      final double w = v.w;
      positions[i * 2] = v.x / w;
      positions[i * 2 + 1] = v.y / w;
      positionsZ[i] = v.z / w;
    }
  }

  // Função para configurar as coordenadas UV
  void setTextureCoordinates(
    List<Offset> texcoords,
    Float32List renderTexcoords,
    Rect textureRect,
    int vertexOffset,
  ) {
    final int imageWidth = textureRect.width.toInt();
    final int imageHeight = textureRect.height.toInt();
    final double imageLeft = textureRect.left;
    final double imageTop = textureRect.top;

    for (int i = 0; i < texcoords.length; i++) {
      final Offset t = texcoords[i];
      final double x = t.dx * imageWidth + imageLeft;
      final double y = (1.0 - t.dy) * imageHeight + imageTop;
      renderTexcoords[(vertexOffset + i) * 2] = x;
      renderTexcoords[(vertexOffset + i) * 2 + 1] = y;
    }
  }

  @override
  void paint(Canvas canvas, Size size) {
    canvas.translate(size.width / 2, size.height / 2);
    rotation.setValues(rotacao, 0, 0);
    updateTransform();

    final double radius = 100;
    final int latSegments = 50;
    final int lonSegments = 50;

    List<Vector3> vertices = List<Vector3>.filled(
        (latSegments + 1) * (lonSegments + 1), Vector3.zero());
    List<Offset> texcoords =
        List<Offset>.filled((latSegments + 1) * (lonSegments + 1), Offset.zero);
    List<List<int>> indices = [];

    // Gerando os vértices da esfera
    int i = 0;
    for (int y = 0; y <= latSegments; ++y) {
      final double v = y / latSegments;
      final double sv = math.sin(v * math.pi);
      final double cv = math.cos(v * math.pi);
      for (int x = 0; x <= lonSegments; ++x) {
        final double u = x / lonSegments;
        vertices[i] = Vector3(
          radius * math.cos(u * math.pi * 2.0) * sv,
          radius * cv,
          radius * math.sin(u * math.pi * 2.0) * sv,
        );
        texcoords[i] = Offset(u, v); // Normalized UV coordinates

        i++;
      }
    }

    // Construindo os índices para os triângulos
    for (int y = 0; y < latSegments; ++y) {
      final int base1 = (lonSegments + 1) * y;
      final int base2 = (lonSegments + 1) * (y + 1);
      for (int x = 0; x < lonSegments; ++x) {
        indices.add([base1 + x, base1 + x + 1, base2 + x]);
        indices.add([base1 + x + 1, base2 + x + 1, base2 + x]);
      }
    }

    // Transforma e renderiza os vértices da esfera
    final Float32List positions = Float32List(vertices.length * 2);
    final Float32List positionsZ = Float32List(vertices.length);

    //final Matrix4 model = Matrix4.identity()..rotateY(angle);

    //..rotateY(angle);
    //  ..rotateZ(angle);
    //   final Matrix4 view = Matrix4.identity();
    //   final Matrix4 projection =
    //Matrix4.identity(); // Simples perspectiva ortogonal para visualização

    //transform.translate(Vector3(0, 125, 10));
    //transform.rotateX(angle);

    //  final Matrix4 view =
    //     Matrix4.translationValues(-size.width / 2, -size.height / 2, 0);

    //  ..rotateX(angle); // Only rotating X-axis
    final Matrix4 view =
        Matrix4.identity(); // Adjust if needed for camera position
    final Matrix4 projection =
        Matrix4.identity(); // Adjust for perspective if needed

    transformVertices(
        vertices, transform, view, projection, positions, positionsZ);

    final Float32List renderTexcoords = Float32List(texcoords.length * 2);
    setTextureCoordinates(
        texcoords,
        renderTexcoords,
        Rect.fromLTWH(
            0, 0, texture.width.toDouble(), texture.height.toDouble()),
        0);

    // Converter índices para Uint16List
    final Uint16List indexBuffer = Uint16List(indices.length * 3);
    int indexCount = 0;
    for (var triangle in indices) {
      indexBuffer[indexCount++] = triangle[0];
      indexBuffer[indexCount++] = triangle[1];
      indexBuffer[indexCount++] = triangle[2];
    }

    // Renderizando com textura fixa
    final verticesData = Vertices.raw(
      VertexMode.triangles,
      positions,
      textureCoordinates: renderTexcoords,
      indices: indexBuffer,
    );

    final paint = Paint()
      ..shader = ImageShader(
        texture,
        TileMode.clamp,
        TileMode.clamp,
        Matrix4.identity().storage, // Matriz identidade mantém a textura fixa
      );

    canvas.drawVertices(verticesData, BlendMode.srcOver, paint);
  }

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