429 lines
13 KiB
C#
429 lines
13 KiB
C#
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using System.Collections;
|
||
using SK.Framework;
|
||
|
||
/// <summary>
|
||
/// 在RawImage上显示和控制3D模型的查看器
|
||
/// 支持以模型为中心的旋转、缩放、平移等基本操作
|
||
/// </summary>
|
||
public class RawImageModelViewer : MonoBehaviour
|
||
{
|
||
[Header("【模型显示设置】")]
|
||
[Tooltip("要显示的3D模型预制件")]
|
||
public GameObject modelPrefab;
|
||
|
||
[Header("【UI组件引用】")]
|
||
[Tooltip("用于显示模型的RawImage组件")]
|
||
public RawImage displayImage;
|
||
[Tooltip("渲染纹理,用于将3D模型渲染到UI上")]
|
||
public RenderTexture renderTexture;
|
||
[Tooltip("专门用于渲染模型到纹理的相机")]
|
||
public Camera renderCamera;
|
||
|
||
[Header("【旋转控制】")]
|
||
[Tooltip("旋转灵敏度")]
|
||
public float rotationSpeed = 5.0f;
|
||
[Tooltip("是否启用旋转功能")]
|
||
public bool enableRotation = true;
|
||
[Tooltip("垂直旋转角度限制")]
|
||
public Vector2 verticalAngleLimit = new Vector2(-80f, 80f);
|
||
|
||
[Header("【缩放控制】")]
|
||
[Tooltip("缩放灵敏度")]
|
||
public float zoomSpeed = 5.0f;
|
||
[Tooltip("最小缩放距离")]
|
||
public float minZoomDistance = 1.0f;
|
||
[Tooltip("最大缩放距离")]
|
||
public float maxZoomDistance = 20.0f;
|
||
[Tooltip("默认缩放距离")]
|
||
public float defaultZoomDistance = 5.0f;
|
||
[Tooltip("是否启用缩放功能")]
|
||
public bool enableZoom = true;
|
||
|
||
[Header("【平移控制】")]
|
||
[Tooltip("平移灵敏度")]
|
||
public float panSpeed = 0.5f;
|
||
[Tooltip("是否启用平移功能")]
|
||
public bool enablePan = true;
|
||
[Tooltip("X轴最大平移距离")]
|
||
public float maxPanDistanceX = 5.0f;
|
||
[Tooltip("Z轴最大平移距离")]
|
||
public float maxPanDistanceZ = 5.0f;
|
||
|
||
[Header("【相机设置】")]
|
||
[Tooltip("相机的初始位置偏移")]
|
||
public Vector3 cameraOffset = new Vector3(0, 0, -5);
|
||
[Tooltip("相机的视野角度")]
|
||
public float fieldOfView = 60f;
|
||
|
||
[Header("【模型设置】")]
|
||
[Tooltip("模型的初始旋转")]
|
||
public Vector3 modelInitialRotation = Vector3.zero;
|
||
[Tooltip("模型的初始缩放")]
|
||
public Vector3 modelInitialScale = Vector3.one;
|
||
|
||
[Header("【平滑设置】")]
|
||
[Tooltip("旋转平滑度")]
|
||
public float rotationSmoothness = 10.0f;
|
||
[Tooltip("缩放平滑度")]
|
||
public float zoomSmoothness = 10.0f;
|
||
[Tooltip("平移平滑度")]
|
||
public float panSmoothness = 10.0f;
|
||
|
||
// 内部变量
|
||
private GameObject displayedModel;
|
||
private Transform modelTransform;
|
||
private Vector3 modelCenter; // 模型中心点
|
||
private Vector3 currentRotation = Vector3.zero;
|
||
private Vector3 targetRotation = Vector3.zero;
|
||
private float currentDistance = 5.0f;
|
||
private float targetDistance = 5.0f;
|
||
private Vector3 targetPositionOffset = Vector3.zero;
|
||
private Vector3 currentTargetPositionOffset = Vector3.zero;
|
||
private Vector3 lastMousePosition = Vector3.zero;
|
||
private bool isDragging = false;
|
||
private bool isPanning = false;
|
||
|
||
private float childCurrentDistance = 3.0f;
|
||
private float childTargetDistance = 3.0f;
|
||
|
||
void Start()
|
||
{
|
||
//InitializeModelViewer();
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
if (displayedModel != null)
|
||
{
|
||
HandleInput();
|
||
UpdateModelPosition();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化模型
|
||
/// </summary>
|
||
private void InitializeModelViewer()
|
||
{
|
||
if (modelPrefab == null)
|
||
{
|
||
Debug.LogWarning("RawImageModelViewer: 未指定模型预制件", this);
|
||
return;
|
||
}
|
||
|
||
if (displayImage == null || renderTexture == null || renderCamera == null)
|
||
{
|
||
Debug.LogWarning("RawImageModelViewer: 请设置必要的UI组件", this);
|
||
return;
|
||
}
|
||
|
||
if (renderCamera == null)
|
||
{
|
||
Debug.LogWarning("RawImageModelViewer: 未指定相机", this);
|
||
return;
|
||
}
|
||
|
||
displayedModel = Instantiate(modelPrefab, modelCenter, Quaternion.Euler(modelPrefab.transform.eulerAngles));
|
||
Debug.Log(modelPrefab.transform.eulerAngles);
|
||
modelTransform = displayedModel.transform;
|
||
|
||
MonoBehaviour[] scripts = displayedModel.GetComponentsInChildren<MonoBehaviour>();
|
||
foreach (MonoBehaviour script in scripts)
|
||
{
|
||
Destroy(script);
|
||
}
|
||
MeshRenderer[] meshRenderers = displayedModel.GetComponentsInChildren<MeshRenderer>();
|
||
foreach (MeshRenderer meshRenderer in meshRenderers)
|
||
{
|
||
meshRenderer.SetLayer(6);
|
||
}
|
||
|
||
renderCamera.targetTexture = renderTexture;
|
||
renderCamera.fieldOfView = fieldOfView;
|
||
displayImage.texture = renderTexture;
|
||
|
||
Bounds bounds = CalculateBounds(displayedModel);
|
||
modelCenter = bounds.center;
|
||
|
||
/*float objectSize = Mathf.Max(modelCenter.x, modelCenter.y, modelCenter.z);
|
||
targetDistance = objectSize * 1.5f;
|
||
targetDistance = Mathf.Clamp(childTargetDistance, minZoomDistance, maxZoomDistance);
|
||
currentDistance = targetDistance;
|
||
|
||
Vector3 toCamera = (renderCamera.transform.position - modelCenter).normalized;
|
||
targetRotation.x = Mathf.Atan2(toCamera.x, toCamera.z) * Mathf.Rad2Deg;
|
||
targetRotation.y = Mathf.Asin(toCamera.y) * Mathf.Rad2Deg;*/
|
||
|
||
ResetView();
|
||
}
|
||
|
||
private void HandleInput()
|
||
{
|
||
if (IsMouseOverRawImage())
|
||
{
|
||
// 旋转控制
|
||
if (enableRotation)
|
||
{
|
||
if (Input.GetMouseButtonDown(0))
|
||
{
|
||
isDragging = true;
|
||
lastMousePosition = Input.mousePosition;
|
||
}
|
||
|
||
if (Input.GetMouseButton(0) && isDragging)
|
||
{
|
||
Vector3 delta = Input.mousePosition - lastMousePosition;
|
||
|
||
targetRotation.y += delta.x * rotationSpeed * 0.01f;
|
||
targetRotation.x -= delta.y * rotationSpeed * 0.01f;
|
||
|
||
// 限制垂直旋转角度
|
||
targetRotation.x = Mathf.Clamp(targetRotation.x, verticalAngleLimit.x, verticalAngleLimit.y);
|
||
|
||
lastMousePosition = Input.mousePosition;
|
||
}
|
||
|
||
if (Input.GetMouseButtonUp(0))
|
||
{
|
||
isDragging = false;
|
||
}
|
||
}
|
||
|
||
// 平移控制
|
||
if (enablePan)
|
||
{
|
||
if (Input.GetMouseButtonDown(2))
|
||
{
|
||
isPanning = true;
|
||
lastMousePosition = Input.mousePosition;
|
||
}
|
||
|
||
if (Input.GetMouseButton(2) && isPanning)
|
||
{
|
||
Vector3 delta = Input.mousePosition - lastMousePosition;
|
||
|
||
Vector3 right = renderCamera.transform.right;
|
||
Vector3 up = renderCamera.transform.up;
|
||
right.y = 0;
|
||
up.y = 0;
|
||
|
||
// 应用平移限制
|
||
Vector3 panDelta = (right * delta.x + up * delta.y) * panSpeed * -0.01f;
|
||
ApplyPanWithLimits(panDelta);
|
||
|
||
lastMousePosition = Input.mousePosition;
|
||
}
|
||
|
||
if (Input.GetMouseButtonUp(2))
|
||
{
|
||
isPanning = false;
|
||
}
|
||
}
|
||
|
||
// 缩放控制
|
||
if (enableZoom)
|
||
{
|
||
float scroll = Input.GetAxis("Mouse ScrollWheel");
|
||
if (Mathf.Abs(scroll) > 0.01f)
|
||
{
|
||
targetDistance -= scroll * zoomSpeed;
|
||
targetDistance = Mathf.Clamp(targetDistance, minZoomDistance, maxZoomDistance);
|
||
|
||
|
||
}
|
||
}
|
||
|
||
// 重置视图
|
||
if (Input.GetKeyDown(KeyCode.R))
|
||
{
|
||
ResetView();
|
||
}
|
||
|
||
// 聚焦到模型中心
|
||
if (Input.GetKeyDown(KeyCode.F))
|
||
{
|
||
FocusOnModel();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新相机位置
|
||
/// </summary>
|
||
private void UpdateModelPosition()
|
||
{
|
||
// 旋转
|
||
currentRotation = Vector3.Lerp(currentRotation, targetRotation, Time.deltaTime * rotationSmoothness);
|
||
// 缩放
|
||
currentDistance = Mathf.Lerp(currentDistance, targetDistance, Time.deltaTime * zoomSmoothness);
|
||
// 平移
|
||
currentTargetPositionOffset = Vector3.Lerp(currentTargetPositionOffset, targetPositionOffset, Time.deltaTime * panSmoothness);
|
||
// 计算旋转后的相机偏移
|
||
Vector3 rotatedOffset = Quaternion.Euler(currentRotation) * cameraOffset.normalized * currentDistance;
|
||
Vector3 cameraPosition = modelCenter + currentTargetPositionOffset + rotatedOffset;
|
||
//childCurrentDistance = childTargetDistance;
|
||
renderCamera.transform.position = cameraPosition;
|
||
renderCamera.transform.LookAt(modelCenter + currentTargetPositionOffset);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检测鼠标
|
||
/// </summary>
|
||
private bool IsMouseOverRawImage()
|
||
{
|
||
if (displayImage == null) return false;
|
||
|
||
RectTransform rectTransform = displayImage.rectTransform;
|
||
Vector2 localPoint;
|
||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||
rectTransform, Input.mousePosition, transform.root.GetComponent<Canvas>().worldCamera, out localPoint);
|
||
return rectTransform.rect.Contains(localPoint);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 限制平移
|
||
/// </summary>
|
||
private void ApplyPanWithLimits(Vector3 panDelta)
|
||
{
|
||
Vector3 newOffset = targetPositionOffset - panDelta;
|
||
|
||
// 限制平移范围
|
||
newOffset.x = Mathf.Clamp(newOffset.x, -maxPanDistanceX, maxPanDistanceX);
|
||
newOffset.z = Mathf.Clamp(newOffset.z, -maxPanDistanceZ, maxPanDistanceZ);
|
||
|
||
targetPositionOffset = newOffset;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置视图
|
||
/// </summary>
|
||
public void ResetView()
|
||
{
|
||
//targetRotation = modelInitialRotation;
|
||
targetRotation = new Vector3(20,130,0);
|
||
targetDistance = 1.0f;
|
||
targetPositionOffset = Vector3.zero;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 聚焦到模型中心
|
||
/// </summary>
|
||
public void FocusOnModel()
|
||
{
|
||
if (modelTransform != null)
|
||
{
|
||
targetPositionOffset = Vector3.zero;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更换模型
|
||
/// </summary>
|
||
public void ChangeModel(GameObject newModelPrefab)
|
||
{
|
||
if (newModelPrefab == null) return;
|
||
|
||
// 销毁当前模型
|
||
if (displayedModel != null)
|
||
{
|
||
DestroyImmediate(displayedModel);
|
||
}
|
||
|
||
// 创建新模型
|
||
modelPrefab = newModelPrefab;
|
||
InitializeModelViewer();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置模型的旋转
|
||
/// </summary>
|
||
public void SetModelRotation(Vector3 rotation)
|
||
{
|
||
if (modelTransform != null)
|
||
{
|
||
modelTransform.localRotation = Quaternion.Euler(rotation);
|
||
currentRotation = rotation;
|
||
targetRotation = rotation;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置模型的缩放
|
||
/// </summary>
|
||
public void SetModelScale(Vector3 scale)
|
||
{
|
||
if (modelTransform != null)
|
||
{
|
||
modelTransform.localScale = scale;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置相机的缩放距离
|
||
/// </summary>
|
||
public void SetZoomDistance(float distance)
|
||
{
|
||
targetDistance = Mathf.Clamp(distance, minZoomDistance, maxZoomDistance);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置是否启用旋转功能
|
||
/// </summary>
|
||
public void SetEnableRotation(bool enable)
|
||
{
|
||
enableRotation = enable;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置是否启用缩放功能
|
||
/// </summary>
|
||
public void SetEnableZoom(bool enable)
|
||
{
|
||
enableZoom = enable;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置是否启用平移功能
|
||
/// </summary>
|
||
public void SetEnablePan(bool enable)
|
||
{
|
||
enablePan = enable;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置平移限制
|
||
/// </summary>
|
||
public void SetPanLimits(float maxX, float maxZ)
|
||
{
|
||
maxPanDistanceX = Mathf.Max(0, maxX);
|
||
maxPanDistanceZ = Mathf.Max(0, maxZ);
|
||
}
|
||
|
||
private Bounds CalculateBounds(GameObject obj)
|
||
{
|
||
Renderer[] renderers = obj.GetComponentsInChildren<Renderer>();
|
||
if (renderers.Length == 0)
|
||
{
|
||
return new Bounds(obj.transform.position, Vector3.one);
|
||
}
|
||
|
||
Bounds bounds = renderers[0].bounds;
|
||
foreach (Renderer renderer in renderers)
|
||
{
|
||
bounds.Encapsulate(renderer.bounds);
|
||
}
|
||
|
||
return bounds;
|
||
}
|
||
|
||
void OnDestroy()
|
||
{
|
||
if (displayedModel != null)
|
||
{
|
||
DestroyImmediate(displayedModel);
|
||
}
|
||
}
|
||
}
|