282 lines
9.1 KiB
C#
282 lines
9.1 KiB
C#
/*
|
|
* ModelController
|
|
*
|
|
* 旋转、平移和缩放对象
|
|
*
|
|
* 作者: Kirurobo http://twitter.com/kirurobo
|
|
* 许可证: MIT
|
|
*/
|
|
|
|
using System;
|
|
using UnityEngine;
|
|
|
|
namespace Kirurobo
|
|
{
|
|
public class ModelController : MonoBehaviour
|
|
{
|
|
[Flags]
|
|
public enum RotationAxes : int
|
|
{
|
|
None = 0,
|
|
Pitch = 1,
|
|
Yaw = 2,
|
|
PitchAndYaw = 3
|
|
}
|
|
|
|
[Flags]
|
|
public enum DragState
|
|
{
|
|
None,
|
|
Rotating,
|
|
Translating,
|
|
}
|
|
|
|
public RotationAxes axes = RotationAxes.PitchAndYaw;
|
|
public float yawSensitivity = 1f;
|
|
public float pitchSensitvity = 1f;
|
|
public float scaleSensitivity = 0.5f;
|
|
|
|
public Vector2 minimumAngles = new Vector2(-90f, -360f);
|
|
public Vector2 maximumAngles = new Vector2(90f, 360f);
|
|
|
|
[Tooltip("Restrict to move out from screen")]
|
|
public bool confineTranslation = true; // 是否将位移限制在窗口(屏幕)范围内
|
|
|
|
[Tooltip("Default is the parent transform")]
|
|
public Transform centerTransform; // 回転中心
|
|
|
|
[Tooltip("Default is the main camera")]
|
|
public Camera currentCamera;
|
|
|
|
internal GameObject centerObject = null; // 当未指定旋转中心Transform时创建的对象
|
|
|
|
internal Vector3 rotation;
|
|
internal Vector3 translation;
|
|
internal Vector3 lastMousePosition; // 上一帧的鼠标坐标
|
|
internal DragState dragState; // 拖动时根据开始时的按钮设置对应状态
|
|
|
|
internal Vector3 relativePosition;
|
|
internal Quaternion relativeRotation;
|
|
internal Vector3 originalLocalScale;
|
|
internal float zoom;
|
|
|
|
|
|
void Start()
|
|
{
|
|
Initialize();
|
|
SetupTransform();
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
// 如果旋转中心是独立创建的,则删除
|
|
if (centerObject) GameObject.Destroy(centerObject);
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (!currentCamera.isActiveAndEnabled) return;
|
|
{
|
|
HandleMouse();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取并准备所需对象
|
|
/// </summary>
|
|
internal void Initialize()
|
|
{
|
|
if (!centerTransform)
|
|
{
|
|
centerTransform = this.transform.parent;
|
|
if (!centerTransform || centerTransform == this.transform)
|
|
{
|
|
centerObject = new GameObject();
|
|
centerTransform = centerObject.transform;
|
|
centerTransform.position = Vector3.zero;
|
|
}
|
|
}
|
|
|
|
if (!currentCamera)
|
|
{
|
|
currentCamera = Camera.main;
|
|
}
|
|
|
|
lastMousePosition = InputProxy.mousePosition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置初始位置和姿态
|
|
/// 在目标对象就绪后执行
|
|
/// </summary>
|
|
internal void SetupTransform()
|
|
{
|
|
relativePosition = transform.position- centerTransform.position; // 从对象到中心坐标的向量
|
|
relativeRotation = transform.rotation * Quaternion.Inverse(centerTransform.rotation);
|
|
originalLocalScale = transform.localScale;
|
|
|
|
ResetTransform();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset rotation and translation.
|
|
/// </summary>
|
|
public void ResetTransform()
|
|
{
|
|
rotation = relativeRotation.eulerAngles;
|
|
translation = relativePosition;
|
|
zoom = 0f;
|
|
|
|
UpdateTransform();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 应用旋转和平移
|
|
/// </summary>
|
|
internal void UpdateTransform()
|
|
{
|
|
Quaternion rot = Quaternion.Euler(rotation);
|
|
transform.rotation = rot;
|
|
transform.position = centerTransform.position + translation;
|
|
|
|
transform.localScale = originalLocalScale * Mathf.Pow(10f, zoom);
|
|
}
|
|
|
|
internal virtual void HandleMouse()
|
|
{
|
|
Vector3 mousePos = InputProxy.mousePosition;
|
|
|
|
if (InputProxy.GetMouseButtonDown(0))
|
|
{
|
|
// 左键(0)拖动时进行平移
|
|
if (dragState == DragState.None && IsHit(mousePos))
|
|
{
|
|
dragState = DragState.Translating;
|
|
|
|
// 限制在屏幕范围内
|
|
if (confineTranslation)
|
|
{
|
|
Vector3 screenMax = new Vector3(Screen.width, Screen.height);
|
|
mousePos = Vector3.Max(Vector3.Min(mousePos, screenMax), Vector3.zero);
|
|
}
|
|
|
|
lastMousePosition = mousePos; // 拖动开始时重置
|
|
}
|
|
}
|
|
else if (InputProxy.GetMouseButtonDown(1))
|
|
{
|
|
// 右键(1)拖动时进行旋转
|
|
if (dragState == DragState.None && IsHit(mousePos))
|
|
{
|
|
dragState = DragState.Rotating;
|
|
lastMousePosition = mousePos; // 拖动开始时重置
|
|
}
|
|
}
|
|
|
|
// 通过拖动旋转
|
|
if (dragState == DragState.Rotating)
|
|
{
|
|
// 仅在按下按钮时操作
|
|
if (InputProxy.GetMouseButton(1))
|
|
{
|
|
// 通过拖动旋转
|
|
if ((axes & RotationAxes.Yaw) > RotationAxes.None)
|
|
{
|
|
rotation.y -= (mousePos.x - lastMousePosition.x) * 360f / Screen.width * yawSensitivity;
|
|
rotation.y = ClampAngle(rotation.y, minimumAngles.y, maximumAngles.y);
|
|
}
|
|
|
|
if ((axes & RotationAxes.Pitch) > RotationAxes.None)
|
|
{
|
|
rotation.x += (mousePos.y - lastMousePosition.y) * 360f / Screen.height * pitchSensitvity;
|
|
rotation.x = ClampAngle(rotation.x, minimumAngles.x, maximumAngles.x);
|
|
}
|
|
|
|
UpdateTransform();
|
|
}
|
|
else
|
|
{
|
|
// 如果右键已松开则结束旋转
|
|
dragState = DragState.None;
|
|
}
|
|
}
|
|
|
|
// 通过拖动平移
|
|
if (dragState == DragState.Translating)
|
|
{
|
|
// 仅在按下按钮时操作
|
|
if (InputProxy.GetMouseButton(0))
|
|
{
|
|
// 限制在屏幕范围内
|
|
if (confineTranslation)
|
|
{
|
|
Vector3 screenMax = new Vector3(Screen.width, Screen.height);
|
|
mousePos = Vector3.Max(Vector3.Min(mousePos, screenMax), Vector3.zero);
|
|
}
|
|
|
|
Vector3 screenPos = currentCamera.WorldToScreenPoint(transform.position);
|
|
Vector3 deltaPos = mousePos - lastMousePosition;
|
|
deltaPos.z = 0f;
|
|
Vector3 worldPos = currentCamera.ScreenToWorldPoint(screenPos + deltaPos);
|
|
translation = worldPos - centerTransform.position;
|
|
|
|
UpdateTransform();
|
|
}
|
|
else
|
|
{
|
|
// 如果按钮已松开则结束位移
|
|
dragState = DragState.None;
|
|
}
|
|
}
|
|
|
|
// 暂时仅在使用 Legacy Input Manager 时接受滚轮操作
|
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
|
// 如果滚轮转动,则进行缩放
|
|
if (!Mathf.Approximately(Input.GetAxis("Mouse ScrollWheel"), 0f) && IsHit(mousePos))
|
|
{
|
|
// 滚轮操作量
|
|
float wheelDelta = Input.GetAxis("Mouse ScrollWheel") * scaleSensitivity;
|
|
|
|
// 更改缩放倍率
|
|
zoom -= wheelDelta;
|
|
zoom = Mathf.Clamp(zoom, -1f, 2f); // 视场角的对数范围 [度]
|
|
|
|
UpdateTransform();
|
|
}
|
|
#endif
|
|
|
|
lastMousePosition = mousePos;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 判断鼠标操作时是否点击到对象
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
internal bool IsHit(Vector3 screenPosition)
|
|
{
|
|
RaycastHit hit;
|
|
Ray ray = currentCamera.ScreenPointToRay(screenPosition);
|
|
|
|
if (Physics.Raycast(ray, out hit))
|
|
{
|
|
if (hit.transform.IsChildOf(transform)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 若角度超出指定范围,则进行修正
|
|
/// </summary>
|
|
/// <param name="angle"></param>
|
|
/// <param name="min"></param>
|
|
/// <param name="max"></param>
|
|
/// <returns></returns>
|
|
public static float ClampAngle(float angle, float min, float max)
|
|
{
|
|
if (angle < -min) angle = -((-angle) % 360f);
|
|
if (angle > max) angle = angle % 360f;
|
|
return Mathf.Clamp(angle, min, max);
|
|
}
|
|
}
|
|
} |