import 'dart:async'; import 'dart:math'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:trackoffical_app/logger.dart'; import 'package:trackoffical_app/model/game_map.dart'; import 'package:trackoffical_app/service/app.dart'; import 'package:trackoffical_app/service/game/game_model.dart'; import 'package:vibration/vibration.dart'; import '../../model/map_mode.dart'; import '../../widget/matrix_gesture_detector.dart'; import 'package:vector_math/vector_math_64.dart' as vec; class MapStatus { var canVibrate = false; var mapImageData = Uint8List(0).obs; var gameMapData = GameMap(); final GameModel _model = Get.find(); Rx get matrix => _model.mapTransformMatrix; Rx get mapMode => _model.mapMode; double get compassAngle => _model.compassRadiansFused.value; var lastCompassRadians = 0.0; var vibrateAccumulation = 0.0; var isTouching = false; double picFirstScale = 1; var isSetBeginMatrix = false; final isShowMapScale = false.obs; Timer? _showMapScaleTimer; double _mapLenMeter=1; // 比例尺 屏幕尺寸/米 实时 final mapScale = 0.0.obs; // 比例尺 屏幕尺寸/米 原始 var mapScaleSrc = 0.0; double get rotateRadian { final ogRm = matrix.value.clone(); double radian = MatrixGestureDetector.decomposeToValues(ogRm).rotation; return radian; } double get compassPlantRadian =>_model.compassPlantRadian; final _translationUpdater = _ValueUpdater( onUpdate: (oldVal, newVal) => newVal - (oldVal ?? Offset.zero), ); final _rotationUpdater = _ValueUpdater( onUpdate: (oldVal, newVal) => newVal - (oldVal ?? 0), ); final _scaleUpdater = _ValueUpdater( onUpdate: (oldVal, newVal) => newVal / (oldVal ?? 1), ); Future resetMatrix() async{ matrix.value = Matrix4.identity(); lastCompassRadians = 0.0; _rotationUpdater.value = 0; _translationUpdater.value = Offset.zero; _scaleUpdater.value = 1; isSetBeginMatrix=false; matrix.value = _doScale(4, matrix.value); const p0Src = Offset(0, 0); final p1Src = Offset(gameMapData.width, 0); final mapTopLeftLocation = await gameMapData.pixelToWorld(p0Src); final mapTopRightLocation = await gameMapData.pixelToWorld(p1Src); final disKm = mapTopLeftLocation.distance(mapTopRightLocation); _mapLenMeter = disKm.km; final screenLen = App.to.screenSize.width; mapScaleSrc = screenLen / _mapLenMeter; calculateMapScale(); } Offset picOffsetToScreen(Offset offset){ var thisOffset = Offset(offset.dx * picFirstScale, offset.dy * picFirstScale); final mr= matrix.value.applyToVector3Array([thisOffset.dx, thisOffset.dy, 0]); return Offset(mr[0], mr[1]); } void movePicPointTo(Offset src, Offset dst){ final pOnScreen = picOffsetToScreen(src); final dis = dst - pOnScreen; final tranM = Matrix4.translationValues(dis.dx, dis.dy, 0); matrix.value = tranM * matrix.value; } Matrix4? _getRotationMatrix( double radiansDelta, Offset focalPoint, {bool willVibrate=false}) { if (_rotationUpdater.value == null || _rotationUpdater.value!.isNaN) { _rotationUpdater.value = radiansDelta; } else { final rotationDelta = _rotationUpdater.update(radiansDelta); var c = cos(rotationDelta); var s = sin(rotationDelta); var dx = (1 - c) * focalPoint.dx + s * focalPoint.dy; var dy = (1 - c) * focalPoint.dy - s * focalPoint.dx; final rotationDeltaMatrix = Matrix4( c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1); if(willVibrate){ vibrateAccumulation += vec.degrees(rotationDelta); if (vibrateAccumulation.abs() >= 5) { vibrateAccumulation = 0; if (canVibrate) { Vibration.vibrate(duration: 20, repeat: 1); } } } return rotationDeltaMatrix; } return null; } void setRotate(double radians) { final focalPoint = _model.mapRotateCenter; if(focalPoint != null){ final rotate = _getRotationMatrix(radians, focalPoint); if(rotate!= null){ var matrix = this.matrix.value; this.matrix.value = rotate * matrix; } } } void mapModeSwitch() { resetMatrix(); switch (mapMode.value) { case MapMode.original: mapMode.value = MapMode.compass; break; case MapMode.compass: mapMode.value = MapMode.original; break; } } void onMapTouchScaleStart(ScaleStartDetails details) { isTouching=true; _translationUpdater.value = details.focalPoint; _rotationUpdater.value = double.nan; _scaleUpdater.value = 1.0; } Matrix4 _doScale(double scaleDelta, Matrix4 matrix){ final focalPoint = _model.mapRotateCenter!; var dx = (1 - scaleDelta) * focalPoint.dx; var dy = (1 - scaleDelta) * focalPoint.dy; final scaleDeltaMatrix = Matrix4( scaleDelta, 0, 0, 0, 0, scaleDelta, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1); return scaleDeltaMatrix * matrix; } void onMapTouchScaleUpdate(ScaleUpdateDetails details, Size screen) { final focalPoint = _model.mapRotateCenter!; var matrix = this.matrix.value; // 平移 if(!_model.isLockScreenCenterToMyPositionSystem){ final translationDelta = _translationUpdater.update(details.focalPoint); final translationDeltaMatrix = Matrix4.translationValues(translationDelta.dx, translationDelta.dy, 0); matrix = translationDeltaMatrix * matrix; } // 缩放 final scale = matrix[0].abs(); if (details.scale != 1.0) { final zoomOutLimit = scale > 0.5 || details.scale > 1; // final zoomInLimit = scale < 8 || details.scale < 1; final zoomInLimit = mapScale < 1.7 || details.scale < 1; if(zoomOutLimit && zoomInLimit){ final scaleDelta = _scaleUpdater.update(details.scale); matrix = _doScale(scaleDelta, matrix); } } // 旋转 var rotationDelta = 0.0; var willVibrate = false; switch (mapMode.value) { case MapMode.original: rotationDelta = details.rotation; willVibrate = true; break; case MapMode.compass: break; } if(rotationDelta != 0){ final rotationDeltaMatrix = _getRotationMatrix( rotationDelta, focalPoint, willVibrate: willVibrate); if(rotationDeltaMatrix!= null){ matrix = rotationDeltaMatrix * matrix; } } this.matrix.value = matrix; calculateMapScale(); } calculateMapScale(){ const p0Src = Offset(0, 0); final p1Src = Offset(gameMapData.width, 0); final p0Dst = picOffsetToScreen(p0Src); final p1Dst = picOffsetToScreen(p1Src); final disPx = sqrt((p1Dst.dx - p0Dst.dx)* (p1Dst.dx - p0Dst.dx) + (p1Dst.dy - p0Dst.dy) * (p1Dst.dy - p0Dst.dy)); final scale = disPx / _mapLenMeter; mapScale.value = scale; isShowMapScale.value = true; _showMapScaleTimer?.cancel(); _showMapScaleTimer = Timer(3.seconds, () { isShowMapScale.value = false; }); } } typedef _OnUpdate = T Function(T? oldValue, T newValue); class _ValueUpdater { final _OnUpdate onUpdate; T? value; _ValueUpdater({required this.onUpdate}); T update(T newValue) { T updated = onUpdate(value, newValue); value = newValue; return updated; } }