using UnityEngine; using UnityEngine.UI; using LightGlue.Unity.Game; using LightGlue.Unity.Networking; using LightGlue.Unity.Config; namespace LightGlue.Unity.UI { /// /// 相机位置准心渲染器 /// - 将Python算法返回的相机坐标映射到Unity屏幕/Canvas坐标 /// - 在屏幕上渲染准心指示器 /// public class CameraPositionCrosshair : MonoBehaviour { [Header("结果来源")] [Tooltip("必须指定 LightGlueManager,由其统一分发算法结果。禁止直接从 Bridge 获取结果。")] public LightGlueManager manager; [Header("参考图像尺寸")] [Tooltip("参考图像的宽度(像素),用于坐标映射")] public int referenceImageWidth = 640; [Tooltip("参考图像的高度(像素),用于坐标映射")] public int referenceImageHeight = 480; private void OnEnable() { ReferenceImageResizeService.EnsureInitialized(); SetReferenceImageSize(ReferenceImageResizeService.Width, ReferenceImageResizeService.Height); ReferenceImageResizeService.OnResizeChanged += SetReferenceImageSize; } private void OnDisable() { ReferenceImageResizeService.OnResizeChanged -= SetReferenceImageSize; } [Header("准心渲染方式")] [Tooltip("渲染模式:ScreenSpace(屏幕空间)或 Canvas(Canvas空间)")] public RenderMode renderMode = RenderMode.Canvas; [Header("ScreenSpace模式设置")] [Tooltip("ScreenSpace模式:是否使用Camera(如果为false,使用屏幕坐标)")] public bool useCamera = false; [Tooltip("ScreenSpace模式:使用的Camera(如果useCamera=true)")] public Camera targetCamera; [Header("Canvas模式设置")] [Tooltip("Canvas模式:目标Canvas(如果为null,自动查找)")] public Canvas targetCanvas; [Header("准心外观")] [Tooltip("准心颜色")] public Color crosshairColor = Color.red; [Tooltip("准心大小(像素)")] public float crosshairSize = 20f; [Tooltip("准心线宽度(像素)")] public float lineWidth = 2f; [Tooltip("是否显示准心(可以通过代码控制)")] public bool showCrosshair = true; [Header("坐标映射设置")] [Tooltip("是否自动适配Canvas尺寸(如果为true,会根据Canvas尺寸自动计算映射)")] public bool autoFitCanvas = true; [Tooltip("手动设置的目标显示区域宽度(像素,如果autoFitCanvas=false)")] public float targetDisplayWidth = 640f; [Tooltip("手动设置的目标显示区域高度(像素,如果autoFitCanvas=false)")] public float targetDisplayHeight = 480f; [Tooltip("显示区域偏移X(像素,用于调整准心位置)")] public float offsetX = 0f; [Tooltip("显示区域偏移Y(像素,用于调整准心位置)")] public float offsetY = 0f; [Header("更新设置")] [Tooltip("更新频率(每秒更新次数,默认30次/秒)")] [Range(1, 60)] public int updateRate = 30; private RectTransform _canvasRect; private Canvas _canvas; private GameObject _crosshairObject; private RectTransform _crosshairRect; private Image _crosshairImageH; // 水平线 private Image _crosshairImageV; // 垂直线 private float _updateInterval; private float _lastUpdateTime; private Vector2 _currentScreenPosition; private bool _hasValidPosition = false; // 由 LightGlueManager 统一分发的最新结果缓存 private LightGlueResult _latestResult; private bool _hasLatestResult; public enum RenderMode { ScreenSpace, // 屏幕空间(使用Screen坐标或Camera.WorldToScreenPoint) Canvas // Canvas空间(使用RectTransform) } private void Start() { _updateInterval = 1.0f / updateRate; _lastUpdateTime = Time.time; // 结果来源:强制通过全局 LightGlueManager 获取 if (manager == null) { manager = LightGlueManager.Instance; } if (manager == null) { Debug.LogError("[CameraPositionCrosshair] 未找到 LightGlueManager,无法获取算法结果。请在场景中放置 LightGlueSystem 预制并确保 LightGlueManager 存在。"); } else { // 订阅 Manager 的结果事件(由 Manager 统一从 Bridge 拉取一次,再分发给所有订阅者) manager.OnResultUpdated += OnResultUpdated; // 如果 Manager 已经有缓存的最新结果,立即用一次,避免等下一帧 if (manager.HasLatestResult) { OnResultUpdated(manager.LatestResult); } } // 初始化渲染方式 InitializeRenderMode(); } private void InitializeRenderMode() { if (renderMode == RenderMode.Canvas) { // Canvas模式:创建准心UI元素 if (targetCanvas == null) { targetCanvas = FindObjectOfType(); if (targetCanvas == null) { Debug.LogError("[CameraPositionCrosshair] Canvas模式需要Canvas组件,请确保场景中存在Canvas"); return; } } _canvas = targetCanvas; _canvasRect = targetCanvas.GetComponent(); // 创建准心GameObject CreateCrosshairUI(); } else { // ScreenSpace模式:使用OnGUI或LineRenderer if (useCamera && targetCamera == null) { targetCamera = Camera.main; if (targetCamera == null) { Debug.LogWarning("[CameraPositionCrosshair] ScreenSpace模式需要Camera,将使用屏幕坐标"); useCamera = false; } } } } private void CreateCrosshairUI() { // 创建准心容器 _crosshairObject = new GameObject("CameraCrosshair"); _crosshairObject.transform.SetParent(_canvas.transform, false); _crosshairRect = _crosshairObject.AddComponent(); _crosshairRect.anchorMin = Vector2.zero; _crosshairRect.anchorMax = Vector2.zero; _crosshairRect.pivot = new Vector2(0.5f, 0.5f); _crosshairRect.sizeDelta = new Vector2(crosshairSize * 2, crosshairSize * 2); // 创建水平线 GameObject lineH = new GameObject("LineH"); lineH.transform.SetParent(_crosshairObject.transform, false); RectTransform rectH = lineH.AddComponent(); rectH.anchorMin = new Vector2(0.5f, 0.5f); rectH.anchorMax = new Vector2(0.5f, 0.5f); rectH.pivot = new Vector2(0.5f, 0.5f); rectH.sizeDelta = new Vector2(crosshairSize * 2, lineWidth); rectH.anchoredPosition = Vector2.zero; _crosshairImageH = lineH.AddComponent(); _crosshairImageH.color = crosshairColor; // 创建垂直线 GameObject lineV = new GameObject("LineV"); lineV.transform.SetParent(_crosshairObject.transform, false); RectTransform rectV = lineV.AddComponent(); rectV.anchorMin = new Vector2(0.5f, 0.5f); rectV.anchorMax = new Vector2(0.5f, 0.5f); rectV.pivot = new Vector2(0.5f, 0.5f); rectV.sizeDelta = new Vector2(lineWidth, crosshairSize * 2); rectV.anchoredPosition = Vector2.zero; _crosshairImageV = lineV.AddComponent(); _crosshairImageV.color = crosshairColor; // 初始隐藏 _crosshairObject.SetActive(false); } private void Update() { // 按指定频率刷新准心位置(事件可能一帧触发多次,这里按频率消费最新结果) if (Time.time - _lastUpdateTime < _updateInterval) return; _lastUpdateTime = Time.time; // 强制使用 Manager 分发的结果 if (manager == null) { return; } if (!_hasLatestResult) { _hasValidPosition = false; HideCrosshair(); return; } var result = _latestResult; if (result.IsValid) { // 将参考图像坐标转换为屏幕坐标 Vector2 screenPos = MapReferenceToScreen(result.CameraPosition); _currentScreenPosition = screenPos; _hasValidPosition = true; // 更新准心位置 UpdateCrosshairPosition(screenPos); } else { _hasValidPosition = false; HideCrosshair(); } } /// /// 将参考图像坐标映射到屏幕/Canvas坐标 /// private Vector2 MapReferenceToScreen(Vector2 referencePos) { // 计算缩放比例 float scaleX, scaleY; float targetWidth, targetHeight; if (renderMode == RenderMode.Canvas && autoFitCanvas && _canvasRect != null) { // 自动适配Canvas尺寸 targetWidth = _canvasRect.rect.width; targetHeight = _canvasRect.rect.height; } else { // 使用手动设置的尺寸 targetWidth = targetDisplayWidth; targetHeight = targetDisplayHeight; } scaleX = targetWidth / referenceImageWidth; scaleY = targetHeight / referenceImageHeight; // 坐标转换 // 参考图像坐标系:原点在左上角,X向右,Y向下 // Unity UI坐标系:原点在左下角,X向右,Y向上 float screenX = referencePos.x * scaleX + offsetX; float screenY = targetHeight - (referencePos.y * scaleY) + offsetY; // Y轴翻转 return new Vector2(screenX, screenY); } /// /// 更新准心位置 /// private void UpdateCrosshairPosition(Vector2 screenPos) { if (!showCrosshair) return; if (renderMode == RenderMode.Canvas) { // Canvas模式:直接设置RectTransform位置 if (_crosshairRect != null) { _crosshairRect.anchoredPosition = screenPos; if (_crosshairObject != null && !_crosshairObject.activeSelf) { _crosshairObject.SetActive(true); } } } else { // ScreenSpace模式:使用OnGUI或LineRenderer // 这里需要在OnGUI中绘制,或者使用3D LineRenderer // 暂时使用OnGUI方式 } } /// /// 隐藏准心 /// private void HideCrosshair() { if (_crosshairObject != null) { _crosshairObject.SetActive(false); } } /// /// 由 LightGlueManager 触发的结果更新回调。 /// 这里只做简单缓存,真正的坐标映射和 UI 更新仍在 Update 中按频率执行。 /// /// 最新算法结果(已经过 Manager 的丢包平滑处理)。 private void OnResultUpdated(LightGlueResult result) { _latestResult = result; _hasLatestResult = true; } private void OnGUI() { // ScreenSpace模式:使用OnGUI绘制准心 if (renderMode == RenderMode.ScreenSpace && showCrosshair && _hasValidPosition) { Vector2 screenPos = _currentScreenPosition; if (useCamera && targetCamera != null) { // 如果使用Camera,需要将世界坐标转换为屏幕坐标 // 这里假设screenPos已经是屏幕坐标 } // 绘制准心(十字线) float halfSize = crosshairSize * 0.5f; Color oldColor = GUI.color; GUI.color = crosshairColor; // 水平线 GUI.DrawTexture( new Rect(screenPos.x - halfSize, screenPos.y - lineWidth * 0.5f, crosshairSize, lineWidth), Texture2D.whiteTexture ); // 垂直线 GUI.DrawTexture( new Rect(screenPos.x - lineWidth * 0.5f, screenPos.y - halfSize, lineWidth, crosshairSize), Texture2D.whiteTexture ); GUI.color = oldColor; } } /// /// 设置参考图像尺寸(供外部调用,例如从实际图像获取) /// public void SetReferenceImageSize(int width, int height) { referenceImageWidth = width; referenceImageHeight = height; } /// /// 设置准心显示状态 /// public void SetCrosshairVisible(bool visible) { showCrosshair = visible; if (!visible) { HideCrosshair(); } } /// /// 设置准心颜色 /// public void SetCrosshairColor(Color color) { crosshairColor = color; if (_crosshairImageH != null) _crosshairImageH.color = color; if (_crosshairImageV != null) _crosshairImageV.color = color; } private void OnDestroy() { ReferenceImageResizeService.OnResizeChanged -= SetReferenceImageSize; if (manager != null) { manager.OnResultUpdated -= OnResultUpdated; } if (_crosshairObject != null) { Destroy(_crosshairObject); } } } }