estou lendo um artigo que fala sobre rolagem de uma bola em um ambiente 3D, enfim, o comentário diz:

"This approach works, as long as we're moving forward along the world Z axis. To make it work for any direction we have to derive the rotation axis from the movement direction and the contact normal. As the contact normal gets cleared to zero each physics step we'll have to keep track of the last one. Copy the normal to a field just before we clear it."

ccomo eu posso obter o último contato igualmente está dizendo?

meu código:
import 'package:flutter/material.dart';
import 'package:flutter_cube/flutter_cube.dart';
import 'package:flutter/scheduler.dart';
import 'dart:math';
import 'package:vector_math/vector_math_64.dart' as vec;

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Pool'),
        ),
        body: Stack(
          children: [Sinuca3DScene()],
        ),
        backgroundColor: const Color.fromARGB(255, 13, 104, 250),
      ),
    );
  }
}

class Sinuca3DScene extends StatefulWidget {
  @override
  _Sinuca3DSceneState createState() => _Sinuca3DSceneState();
}

class _Sinuca3DSceneState extends State<Sinuca3DScene>
    with TickerProviderStateMixin {
  Object? ballModel;
  vec.Vector3 ballPosition = vec.Vector3(0.0, 0.0, 0.0);
  vec.Vector3 ballVelocity = vec.Vector3(0, 0, 0);
  double force = 100.0;

  double rotationFactor = 2000;
  late Ticker _ticker;
  late Scene _scene;
  bool loaded = false;
  double friction = 0.98;
  final FocusNode _focusNode = FocusNode();

  @override
  void initState() {
    super.initState();
    _ticker = createTicker((_) => _update());
    _ticker.start();

    ballModel = Object(fileName: 'assets/ball.obj');
    ballModel?.scale.setValues(1, 1, 1);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _focusNode.requestFocus();
    });
  }

  void _update() {
    setState(() {
      _updateBallPosition(const Duration(milliseconds: 10));
    });
  }

  void _applyImpulse(vec.Vector3 impulse) {
    ballVelocity += impulse;
  }

  void rotateBall(Duration deltaTime) {
    if (ballModel != null && ballVelocity.length > 0.01) {
      ballModel!.updateTransform();
    }
  }

  double normalizeAngle(double angle) {
    angle = angle % 360;
    if (angle < 0) angle += 360;
    return angle;
  }

  void _updateBallPosition(Duration deltaTime) {
    ballPosition.y +=
        ballVelocity.y * deltaTime.inMilliseconds.toDouble() / 1000.0;
    ballModel?.position.setFrom(ballPosition);

    double wallMargin = 0.5;

    if (ballPosition.x >= (7 - wallMargin) ||
        ballPosition.x <= (-13 + wallMargin)) {
      ballVelocity.x = -ballVelocity.x;
    }
    if (ballPosition.y >= (10 - wallMargin) ||
        ballPosition.y <= (-10 + wallMargin)) {
      ballVelocity.y = -ballVelocity.y;
    }

    if (ballVelocity.length <= 1) {
      ballVelocity.setZero();
    }

    //rotateBall(deltaTime);

    ballVelocity *= friction;
  }

  @override
  void dispose() {
    _ticker.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Cube(
          onSceneCreated: (Scene scene) {
            _scene = scene;
            loaded = true;
            scene.world.add(ballModel!..position.setFrom(ballPosition));

            scene.camera.fov = 50;
            scene.world.scene!.camera.position.setValues(5, 5, 30);
          },
          interactive: false,
        ),
        Positioned(
          bottom: 20,
          left: 20,
          child: FloatingActionButton(
            onPressed: () {
              double impulseMagnitude = force;
              double degreesAngle = 10;

              vec.Vector3 impulse = vec.Vector3(
                  impulseMagnitude * cos(radians(degreesAngle)),
                  impulseMagnitude * sin(radians(degreesAngle)),
                  0);
              _applyImpulse(impulse);
            },
            child: Icon(Icons.arrow_forward),
          ),
        ),
        GestureDetector(
          onPanUpdate: (details) {
            print(details);
          },
        ),
        Focus(
            child: KeyboardListener(
          focusNode: _focusNode,
          child: Container(),
          onKeyEvent: (value) {
            print(value.logicalKey.keyLabel);
            if (value.logicalKey.keyLabel == 'W') {
              ballVelocity += Vector3(0, 1, 1);
            }
          },
        ))
      ],
    );
  }
}