SteamVR(HTC Vive) Unity插件深度分析(十四)
10.22. SteamVR_Status.cs
这是一个根据事件进行渐变显示的基类,实际使用的是其派生类SteamVR_StatusText,也就是文字渐变显示和隐藏(渐入和渐出),在[Status]预制体中有好几个用到了它
public abstract class SteamVR_Status : MonoBehaviour
{
这个是自定义事件的名字
public string message; // name of event torespond to
这个是(文字)持续显示的时间及渐显/渐隐的时间。整个过程其实是一个渐显和渐隐 的过程,先是渐显出来,然后再渐隐,渐变的时间都由fade指定,而(duration-fade) 的时间就是完全显示的时间
public float duration, fade;
timer是当前的时间
protected float timer;
status是事件的状态,比如out_of_range事件,如果出边界,那么status就是true
protected bool status;
(状态)事件的4种模式
public enum Mode
{
(文字)在状态变为true的时候显示
OnTrue,
(文字)在状态变为false的时候显示
OnFalse,
(文字)在状态持续为true(比如calibrating事件,测量是一个持续的过程) 时显示
WhileTrue,
(文字)在状态持续为false时显示
WhileFalse
}
public Mode mode;
设置透明度,这个是虚函数,由派生类实现,可以根据要显示的内容做特殊处理
protected abstract void SetAlpha(float a);
void OnEnable()
{
根据Inspector中配置的要监听的事件建立监听
SteamVR_Utils.Event.Listen(message, OnEvent);
}
void OnDisable()
{
取消监听
SteamVR_Utils.Event.Remove(message, OnEvent);
}
void OnEvent(params object[] args)
{
获取事件状态
status= (bool)args[0];
只有对于OnTrue和OnFalse的触发类型事件持续时间duration才有用。将 timer初始设为持续时间,后面会逐渐递减
if (status)
{
if (mode == Mode.OnTrue)
timer= duration;
}
else
{
if (mode == Mode.OnFalse)
timer= duration;
}
}
void Update()
{
if (mode == Mode.OnTrue || mode == Mode.OnFalse)
{
对于OnTrue和OnFalse类型事件,将要显示的内容先渐显,然后再渐隐
timer逐帧递减
timer-= Time.deltaTime;
if (timer < 0.0f)
{
timer小于0,表示设定的持续时间已经到了,彻底隐藏
这里最好重置timer值,不然最终浮点数还是会溢出的。也最好设置个 状态,不然,这些浮点数的操作和比较总是会耗费资源的,特别是浮点数 操作对资源消耗还比较大,特别是对于手机
SetAlpha(0.0f);
}
else
{
var a = 1.0f;
if (timer < fade)
当timer小于fade时,表示现在要开始做渐隐了。这里a的值从1.0 变成0.0
a= timer / fade;
if (timer > duration -fade)
满足这个条件表示现在处于渐显阶段
Mathf.InverseLerp(a,b,c)的作用是返回c在[a,b]区间的位置 比例,即|c-a|/|b-a|。c=a时为0,c=b时为1。
这里a的值从0.0到1.0
a= Mathf.InverseLerp(duration,duration - fade, timer);
SetAlpha(a);
}
}
else
{
WhileTrue和WhileFalse持续状态情况下,show表示是否显示
var show = (mode == Mode.WhileTrue &&status == true) || (mode == Mode.WhileFalse &&status == false);
要显示的情况下,通过timer将alpha在fade时间内从0.0升到1.0
要隐藏的情况下,通过timer将alpha在fade时间内从1.0降到0.0
timer= show ? Mathf.Min(fade, timer + Time.deltaTime) : Mathf.Max(0.0f, timer - Time.deltaTime);
SetAlpha(timer /fade);
}
}
}
现在可以看几个例子:
这是[Status]预制体下面的Calibration子物体中的情况,它这里的意思就是在calibrating事件(即测量过程中)持续显示信息,渐显/渐隐时间为0.5秒
这个是[Status]预制体下面的TrackingLost子物体的情况,它的意思是在out_of_range为true事件到来时,即失去跟踪时,显示相关信息,信息的总持续时间为2秒,信息的渐显和渐隐时间为0.1秒
这个是[Status]预制体下面的TrackingRestore子物体的情况,它的意思是在out_of_range为false事件到来时,即恢复跟踪时,显示相关信息,信息的总持续时间为2秒,信息的渐显和渐隐时间为0.1秒
这个是[Status]预制体下面的SteamInitFailure子物体的情况。它的意思是在steam_init_failure事件为true时,即初始化Steam失败时显示相关信息,信息的总持续时间为90秒,信息渐显和渐隐时间为0.1秒
10.23. SteamVR_StatusText.cs
这个就是上面提到的渐显/渐隐的一个实现,它是对文本的一个实现。它的实现很简单,依赖于GUIText组件来显示文字,从SteamVR_Status派生,实现渐隐和渐现
要求有GUIText组件
[RequireComponent(typeof(GUIText))]
public class SteamVR_StatusText : SteamVR_Status
{
GUIText text;
void Awake()
{
text = GetComponent<GUIText>();
这里感觉有点问题,timer被初始化成了fade(假设text.color alpha值为 1.0),根据上面的计算,当要显示的时候,就没有渐显效果了,而是直接显示出 来了。
if (mode == Mode.WhileTrue || mode == Mode.WhileFalse)
timer = fade *text.color.a;
}
Override设置alpha值
protected override void SetAlpha(float a)
{
if (a > 0.0f)
{
text.enabled = true;
text.color = new Color(text.color.r, text.color.g,text.color.b, a);
}
else
{
text.enabled = false;
}
}
}
10.24. SteamVR_TestController.cs
这个是做测试用的,打印一些控制器相关的log
public class SteamVR_TestController : MonoBehaviour
{
记录当前连接的控制器索引
List<int> controllerIndices = new List<int>();
private void OnDeviceConnected(params object[] args)
{
var index = (int)args[0];
var system = OpenVR.System;
只针对控制器,头显就不管了
if (system == null ||system.GetTrackedDeviceClass((uint)index) != ETrackedDeviceClass.Controller)
return;
设备连接和断开连接的时候打印控制器的状态
var connected = (bool)args[1];
if (connected)
{
Debug.Log(string.Format("Controller { 0} connected.", index));
PrintControllerStatus(index);
controllerIndices.Add(index);
}
else
{
Debug.Log(string.Format("Controller { 0} disconnected.", index));
PrintControllerStatus(index);
controllerIndices.Remove(index);
}
}
void OnEnable()
{
监听跟踪设备连接
SteamVR_Utils.Event.Listen("device_connected", OnDeviceConnected);
}
void OnDisable()
{
SteamVR_Utils.Event.Remove("device_connected", OnDeviceConnected);
}
void PrintControllerStatus(int index)
{
打印的是SteamVR_Controller.Device中的字段
var device = SteamVR_Controller.Input(index);
Debug.Log("index: " + device.index);
Debug.Log("connected: " + device.connected);
Debug.Log("hasTracking: " + device.hasTracking);
Debug.Log("outOfRange: " + device.outOfRange);
Debug.Log("calibrating: " + device.calibrating);
Debug.Log("uninitialized: " + device.uninitialized);
Debug.Log("pos: " + device.transform.pos);
Debug.Log("rot: " + device.transform.rot.eulerAngles);
Debug.Log("velocity: " + device.velocity);
Debug.Log("angularVelocity: " + device.angularVelocity);
var l = SteamVR_Controller.GetDeviceIndex(SteamVR_Controller.DeviceRelation.Leftmost);
var r = SteamVR_Controller.GetDeviceIndex(SteamVR_Controller.DeviceRelation.Rightmost);
Debug.Log((l == r) ? "first" : (l == index) ? "left" : "right");
}
EVRButtonId[] buttonIds = new EVRButtonId[] {
EVRButtonId.k_EButton_ApplicationMenu,
EVRButtonId.k_EButton_Grip,
EVRButtonId.k_EButton_SteamVR_Touchpad,
EVRButtonId.k_EButton_SteamVR_Trigger
} ;
EVRButtonId[] axisIds = new EVRButtonId[] {
EVRButtonId.k_EButton_SteamVR_Touchpad,
EVRButtonId.k_EButton_SteamVR_Trigger
} ;
point用于表示控制器指向的overlay的位置,pointer则表示控制器的位置。所以 可以用两个物体(比如两个球)表示控制器的位置以及控制器指向的overlay的位置
public Transform point, pointer;
void Update()
{
foreach (var index in controllerIndices)
{
var overlay = SteamVR_Overlay.instance;
if (overlay &&point && pointer)
{
获取控制器的位置
var t = SteamVR_Controller.Input(index).transform;
pointer.transform.localPosition= t.pos;
pointer.transform.localRotation= t.rot;
var results = new SteamVR_Overlay.IntersectionResults();
计算控制器的前向与overlay的交点
var hit =overlay.ComputeIntersection(t.pos, t.rot * Vector3.forward, ref results);
if (hit)
{
point.transform.localPosition= results.point;
注意这里的将一个向量(应该是旋转轴吧)转换成旋转的方法
point.transform.localRotation= Quaternion.LookRotation(results.normal);
}
continue;
}
foreach (var buttonId in buttonIds)
{
打印几个常用按键的按下或弹起状态
if (SteamVR_Controller.Input(index).GetPressDown(buttonId))
Debug.Log(buttonId + " press down");
if (SteamVR_Controller.Input(index).GetPressUp(buttonId))
{
Debug.Log(buttonId + " press up");
if (buttonId == EVRButtonId.k_EButton_SteamVR_Trigger)
{
扳机松开时还触发一个震动
SteamVR_Controller.Input(index).TriggerHapticPulse();
打印当前控制器的状态
PrintControllerStatus(index);
}
}
GetPressDown是持续按下,这里是首次按下
if (SteamVR_Controller.Input(index).GetPress(buttonId))
Debug.Log(buttonId);
}
foreach (var buttonId in axisIds)
{
获取轴输入设备的状态
if (SteamVR_Controller.Input(index).GetTouchDown(buttonId))
Debug.Log(buttonId + " touch down");
if (SteamVR_Controller.Input(index).GetTouchUp(buttonId))
Debug.Log(buttonId + " touch up");
if (SteamVR_Controller.Input(index).GetTouch(buttonId))
{
首次按下时还获取对应的轴数据(0-1之间)
var axis = SteamVR_Controller.Input(index).GetAxis(buttonId);
Debug.Log("axis: " + axis);
}
}
}
}
}
10.25. SteamVR_TrackedObject.cs
这个脚本可以放在所有的跟踪设备(头显、手柄)上,用于从硬件中获取位置然后更新到对应的Unity对象上,从而可以实时更新跟踪对象的位置
public class SteamVR_TrackedObject : MonoBehaviour
{
总共支持16个跟踪设备,其它头显索引固定为0,手柄的索引并不一定就是1和2, 即使是1和2,也有左右手之分
public enum EIndex
{
None = -1,
Hmd = (int)OpenVR.k_unTrackedDeviceIndex_Hmd,
Device1,
Device2,
Device3,
Device4,
Device5,
Device6,
Device7,
Device8,
Device9,
Device10,
Device11,
Device12,
Device13,
Device14,
Device15
}
public EIndex index;
这里的origin不知道代表什么,如果指定了,会在它的基础上再对通知过来的姿态进 行一定的运算。如果没有指定,则是直接把传进来的姿态设给了当前的transform。 通常没有指定,而通常在CameraRig中三个跟踪设备(头显,两个手柄)正是都放在 一个origin节点下面的
public Transform origin; // if not set, relative to parent
public bool isValid = false;
下面就是根据通知过来的跟踪设备的姿态来更新当前跟踪设备物体的位置
private void OnNewPoses(params object[] args)
{
if (index == EIndex.None)
return;
var i = (int)index;
isValid = false;
var poses = (Valve.VR.TrackedDevicePose_t[])args[0];
if (poses.Length <= i)
return;
if (!poses[i].bDeviceIsConnected)
return;
if (!poses[i].bPoseIsValid)
return;
isValid = true;
var pose = new SteamVR_Utils.RigidTransform(poses[i].mDeviceToAbsoluteTracking);
if (origin != null)
{
TODO 这里涉及到RigidTransform的一些运算,还需要再看
pose= new SteamVR_Utils.RigidTransform(origin) * pose;
pose.pos.x *=origin.localScale.x;
pose.pos.y *=origin.localScale.y;
pose.pos.z *=origin.localScale.z;
transform.position= pose.pos;
transform.rotation= pose.rot;
}
else
{
transform.localPosition= pose.pos;
transform.localRotation= pose.rot;
}
}
void OnEnable()
{
var render = SteamVR_Render.instance;
if (render == null)
{
enabled = false;
return;
}
SteamVR_Utils.Event.Listen("new_poses", OnNewPoses);
}
void OnDisable()
{
SteamVR_Utils.Event.Remove("new_poses", OnNewPoses);
isValid = false;
}
这个会被顶层的SteamVR_ControllerManager调用,以设置下面所有子节点上 SteamVR_TrackedObject的索引。
public void SetDeviceIndex(int index)
{
if (System.Enum.IsDefined(typeof(EIndex), index))
this.index = (EIndex)index;
}
}
10.26. SteamVR_UpdatePoses.cs
这个脚本用于对非5.x版本更新位置,在SteamVR_Render中使用。对于5.x版本,是直接在SteamVR_Render中的RenderLoop里直接GetLastPoses获取位置,然后通过自定义的“new_poses”通知通知所有监听者。
这个脚本要求绑定在Camera上(不是绑定到已有的Camera上,而是会自动添加一个Camera组件),它利用Camera的OnPreCull来获取并通知跟踪设备的位置。TODO 那么为什么非5.x版本不能在SteamVR_Render.RenderLoop里面更新位置呢?大概就是因为不同版本整个流程不一致,可以参看Unity的流程图:
5.x以前版本放到了OnPreCull,5.x以后版本放到了yield WaitForEndOfFrame(协程)里面
[RequireComponent(typeof(Camera))]
public class SteamVR_UpdatePoses : MonoBehaviour
{
void Awake()
{
修改相机参数
var camera =GetComponent<Camera>();
#if !(UNITY_5_3 || UNITY_5_2|| UNITY_5_1 || UNITY_5_0)
camera.stereoTargetEye= StereoTargetEyeMask.None;
#endif
camera.clearFlags= CameraClearFlags.Nothing;
camera.useOcclusionCulling = false;
cullingMask为Nothing,什么也不渲染,这里只是利用其OnPreCull而已
camera.cullingMask= 0;
depth取了一个极小值,会最先被渲染,以尽早更新设备位置
camera.depth= -9999;
}
OnPreCull是在相机进行场景剔除前调用的
void OnPreCull()
{
var compositor = OpenVR.Compositor;
if (compositor != null)
{
var render = SteamVR_Render.instance;
compositor.GetLastPoses(render.poses,render.gamePoses);
SteamVR_Utils.Event.Send("new_poses", render.poses);
SteamVR_Utils.Event.Send("new_poses_applied");
}
}
}
10.27. SteamVR_Utils.cs
工具类
public static class SteamVR_Utils
{
一种简单的事件通知机制。使用的委托链
public class Event
{
public delegate void Handler(params object[] args);
Listen的时候把handler添加到委托链中
public static void Listen(string message, Handler action)
{
var actions =listeners[message] as Handler;
if (actions != null)
{
listeners[message]= actions + action;
}
else
{
listeners[message]= action;
}
}
Remove的时候从委托链中删除
public static void Remove(string message, Handler action)
{
var actions =listeners[message] as Handler;
if (actions != null)
{
listeners[message]= actions - action;
}
}
Send就是直接调用委托链
public static void Send(string message, params object[] args)
{
var actions =listeners[message] as Handler;
if (actions != null)
{
actions(args);
}
}
用一个hashtable保存事件名称与委托链的对应关系
private static Hashtable listeners = new Hashtable();
}
这个是类似于Quaternion.Slerp,是做球形插值的。那么它们有什么区别呢?目前也 搞不清楚,首先得弄明白下面的代码做了什么。从注释看是Quaternion.Slerp会把 值限定在[0-1]?而这个不会?TODO
// this version does notclamp [0..1]
public static Quaternion Slerp(Quaternion A, Quaternion B, float t)
{
var cosom = Mathf.Clamp(A.x * B.x + A.y * B.y + A.z * B.z + A.w * B.w, -1.0f, 1.0f);
if (cosom < 0.0f)
{
B = new Quaternion(-B.x, -B.y, -B.z, -B.w);
cosom = -cosom;
}
float sclp, sclq;
if ((1.0f - cosom) > 0.0001f)
{
var omega = Mathf.Acos(cosom);
var sinom = Mathf.Sin(omega);
sclp = Mathf.Sin((1.0f - t) * omega) / sinom;
sclq = Mathf.Sin(t * omega) / sinom;
}
else
{
// "from" and"to" very close, so do linear interp
sclp= 1.0f - t;
sclq = t;
}
return new Quaternion(
sclp * A.x + sclq* B.x,
sclp * A.y + sclq* B.y,
sclp * A.z + sclq* B.z,
sclp * A.w + sclq* B.w);
}
这个应该和Vector3.Lerp是类似的,区别又在哪?仍然是clamp的区别吗?TODO
public static Vector3 Lerp(Vector3 A, Vector3 B, float t)
{
return new Vector3(
Lerp(A.x, B.x, t),
Lerp(A.y, B.y, t),
Lerp(A.z, B.z,t));
}
这个很明显和Mathf.Lerp一模一样啊,就是求[A,B]之间的插值点
public static float Lerp(float A, float B, float t)
{
return A + (B - A) * t;
}
public static double Lerp(double A, double B, double t)
{
return A + (B - A) * t;
}
反向插值
public static float InverseLerp(Vector3 A, Vector3 B, Vector3 result)
{
return Vector3.Dot(result - A, B - A);
}
反向插值,意思是求值的比例,即求result在[A,B]区间的比例,在[0-1]之间
public static float InverseLerp(float A, float B, float result)
{
return (result - A) / (B - A);
}
public static double InverseLerp(double A, double B, double result)
{
return (result - A) / (B - A);
}
这个显然就是将A的值限制在[0,1]之间,也就是和Mathf.Clamp(A, 0, 1)是等价的
public static float Saturate(float A)
{
return (A < 0)? 0 : (A > 1) ? 1 : A;
}
public static Vector2 Saturate(Vector2 A)
{
return new Vector2(Saturate(A.x), Saturate(A.y));
}
取绝对值,这些显然在Unity的Matchf类里面都有的
public static float Abs(float A)
{
return (A < 0)? -A : A;
}
public static Vector2 Abs(Vector2 A)
{
return new Vector2(Abs(A.x), Abs(A.y));
}
将signval的正负值赋给sizeval,这有什么意义呢?
private static float _copysign(float sizeval, float signval)
{
return Mathf.Sign(signval) == 1 ? Mathf.Abs(sizeval) : -Mathf.Abs(sizeval);
}
TODO
public static Quaternion GetRotation(this Matrix4x4 matrix)
{
Quaternion q = new Quaternion();
q.w = Mathf.Sqrt(Mathf.Max(0, 1 + matrix.m00 + matrix.m11 +matrix.m22)) / 2;
q.x = Mathf.Sqrt(Mathf.Max(0, 1 + matrix.m00 - matrix.m11 - matrix.m22))/ 2;
q.y = Mathf.Sqrt(Mathf.Max(0, 1 - matrix.m00 + matrix.m11 -matrix.m22)) / 2;
q.z = Mathf.Sqrt(Mathf.Max(0, 1 - matrix.m00 - matrix.m11 +matrix.m22)) / 2;
q.x = _copysign(q.x, matrix.m21 -matrix.m12);
q.y = _copysign(q.y, matrix.m02 - matrix.m20);
q.z = _copysign(q.z, matrix.m10 -matrix.m01);
return q;
}
TODO
public static Vector3 GetPosition(this Matrix4x4 matrix)
{
var x = matrix.m03;
var y = matrix.m13;
var z = matrix.m23;
return new Vector3(x, y, z);
}
TODO
public static Vector3 GetScale(this Matrix4x4 m)
{
var x = Mathf.Sqrt(m.m00 * m.m00 + m.m01 * m.m01 + m.m02 * m.m02);
var y = Mathf.Sqrt(m.m10 * m.m10 + m.m11 * m.m11 + m.m12 * m.m12);
var z = Mathf.Sqrt(m.m20 * m.m20 + m.m21 * m.m21 + m.m22 * m.m22);
return new Vector3(x, y, z);
}
名为刚体变换类,但实际上和Transform类功能其实是差不多的
[System.Serializable]
public struct RigidTransform
{
位置
public Vector3 pos;
旋转
public Quaternion rot;
这个应该怎么叫,单位变换?矩阵可以叫单位矩阵,这个不好叫。反正就是位置为 原点,没有旋转
public static RigidTransform identity
{
get { return new RigidTransform(Vector3.zero, Quaternion.identity); }
}
从Transform的局部坐标生成RigidTransform
public static RigidTransform FromLocal(Transform t)
{
return new RigidTransform(t.localPosition,t.localRotation);
}
从Vector3和Quaternion创建RigidTransform
public RigidTransform(Vector3 pos, Quaternion rot)
{
this.pos = pos;
this.rot = rot;
}
从Transform(全局坐标系)创建RigidTransform
public RigidTransform(Transform t)
{
this.pos = t.position;
this.rot = t.rotation;
}
从一个起始位置(变换)到一个结束位置(变换)创建RigidTransform
public RigidTransform(Transform from, Transform to)
{
TODO 按照想法,应该是先把from移到原点,重置旋转角度,to也相应的移 动和旋转就能得到结果。看样子,Quaternion.Inverse就可以做到重置旋 转角度,这相当于逆矩阵吗?
var inv = Quaternion.Inverse(from.rotation);
rot = inv *to.rotation;
pos = inv *(to.position - from.position);
}
从一个3x4矩阵创建RigidTransform,HmdMatrix34_t常用于表示跟踪设备的 位置
public RigidTransform(HmdMatrix34_t pose)
{
先创建4x4矩阵
var m = Matrix4x4.identity;
TODO 为什么下面有几个值取的是负值?
m[0, 0] = pose.m0;
m[0, 1] = pose.m1;
m[0, 2] = -pose.m2;
m[0, 3] = pose.m3;
m[1, 0] = pose.m4;
m[1, 1] = pose.m5;
m[1, 2] = -pose.m6;
m[1, 3] = pose.m7;
m[2, 0] = -pose.m8;
m[2, 1] = -pose.m9;
m[2, 2] = pose.m10;
m[2, 3] = -pose.m11;
然后取位置和旋转
this.pos = m.GetPosition();
看样子Matrx4x4.GetRotation可以将矩阵转换成四元数
this.rot = m.GetRotation();
}
从一个4x4矩阵创建RigidTransform,HmdMatrix44_t貌似主要用于投影矩阵
public RigidTransform(HmdMatrix44_t pose)
{
var m = Matrix4x4.identity;
m[0, 0] = pose.m0;
m[0, 1] = pose.m1;
m[0, 2] = -pose.m2;
m[0, 3] = pose.m3;
m[1, 0] = pose.m4;
m[1, 1] = pose.m5;
m[1, 2] = -pose.m6;
m[1, 3] = pose.m7;
m[2, 0] = -pose.m8;
m[2, 1] = -pose.m9;
m[2, 2] = pose.m10;
m[2, 3] = -pose.m11;
m[3, 0] = pose.m12;
m[3, 1] = pose.m13;
m[3, 2] = -pose.m14;
m[3, 3] = pose.m15;
this.pos = m.GetPosition();
this.rot = m.GetRotation();
}
转换成4x4矩阵
public HmdMatrix44_t ToHmdMatrix44()
{
从平移、旋转、缩放数据结构(Vector3或四元数)创建Unity Matrix4x4
var m = Matrix4x4.TRS(pos, rot, Vector3.one);
var pose = new HmdMatrix44_t();
pose.m0 = m[0, 0];
pose.m1 = m[0, 1];
pose.m2 =-m[0, 2];
pose.m3 = m[0, 3];
pose.m4 = m[1, 0];
pose.m5 = m[1, 1];
pose.m6 =-m[1, 2];
pose.m7 = m[1, 3];
pose.m8 =-m[2, 0];
pose.m9 =-m[2, 1];
pose.m10 = m[2, 2];
pose.m11 = -m[2, 3];
pose.m12 = m[3, 0];
pose.m13 = m[3, 1];
pose.m14 = -m[3, 2];
pose.m15 = m[3, 3];
return pose;
}
转换成3x4矩阵
public HmdMatrix34_t ToHmdMatrix34()
{
var m = Matrix4x4.TRS(pos, rot, Vector3.one);
var pose = new HmdMatrix34_t();
pose.m0 = m[0, 0];
pose.m1 = m[0, 1];
pose.m2 =-m[0, 2];
pose.m3 = m[0, 3];
pose.m4 = m[1, 0];
pose.m5 = m[1, 1];
pose.m6 =-m[1, 2];
pose.m7 = m[1, 3];
pose.m8 =-m[2, 0];
pose.m9 =-m[2, 1];
pose.m10 = m[2, 2];
pose.m11 = -m[2, 3];
return pose;
}
判断两个RigidTransform对象是否相等
public override bool Equals(object o)
{
if (o is RigidTransform)
{
RigidTransform t = (RigidTransform)o;
return pos == t.pos &&rot == t.rot;
}
return false;
}
生成hashcode,这样它可以作为hashtable的键值
public override int GetHashCode()
{
return pos.GetHashCode() ^rot.GetHashCode();
}
重载“==”运算符
public static bool operator ==(RigidTransform a, RigidTransform b)
{
return a.pos == b.pos&& a.rot == b.rot;
}
重载“!=”运算符
public static bool operator !=(RigidTransform a, RigidTransform b)
{
return a.pos != b.pos || a.rot!= b.rot;
}
重载“*”,可以认为是叉乘不?结果是旋转四元数相乘,位置Vector乘后再相加, 所以这个操作的意义是?TODO
public static RigidTransform operator *(RigidTransform a, RigidTransform b)
{
return new RigidTransform
{
rot= a.rot * b.rot,
pos= a.pos + a.rot * b.pos
} ;
}
逆变换,TODO 实际意义是什么?Quaternion.Inverse的意义应该是反向旋转。 比如,原来是绕x轴旋转90度,那四元数的逆就是绕x轴旋转-90度?
public void Inverse()
{
rot = Quaternion.Inverse(rot);
pos = -(rot *pos);
}
获取当前变换的逆变换,不改变当前变换
public RigidTransform GetInverse()
{
var t = new RigidTransform(pos, rot);
t.Inverse();
return t;
}
和“*”操作符是一样的
public void Multiply(RigidTransform a, RigidTransform b)
{
rot = a.rot *b.rot;
pos = a.pos +a.rot * b.pos;
}
逆变换一个点。TODO 它的意义是什么呢?
public Vector3 InverseTransformPoint(Vector3 point)
{
return Quaternion.Inverse(rot) * (point - pos);
}
变换一个点(旋转+平移)
public Vector3 TransformPoint(Vector3 point)
{
return pos + (rot * point);
}
重载“*”,将RigidTransform乘以Vector3,实际上就是变换一个点
public static Vector3 operator *(RigidTransform t, Vector3 v)
{
return t.TransformPoint(v);
}
插值。分别对Vector3和Quaternion进行插值
public static RigidTransform Interpolate(RigidTransform a, RigidTransform b, float t)
{
return new RigidTransform(Vector3.Lerp(a.pos, b.pos, t), Quaternion.Slerp(a.rot, b.rot, t));
}
对当前变换进行插值
public void Interpolate(RigidTransform to, float t)
{
pos = SteamVR_Utils.Lerp(pos, to.pos, t);
rot = SteamVR_Utils.Slerp(rot, to.rot, t);
}
}
这个是供SteamVR_CameraMask调用的用于创建相机过滤网格的,主要是将OpenVR 的参数格式转换为Unity参数格式。其中src是从IVRSystem.GetHiddenAreaMesh 获取的原始网格数据,bounds是根据左右眼的视觉参数创建的纹理映射参数
public static Mesh CreateHiddenAreaMesh(HiddenAreaMesh_t src, VRTextureBounds_t bounds)
{
if (src.unTriangleCount == 0)
return null;
var data = new float[src.unTriangleCount * 3 * 2]; //HmdVector2_t
Marshal.Copy(src.pVertexData,data, 0, data.Length);
var vertices = new Vector3[src.unTriangleCount * 3 + 12];
var indices = new int[src.unTriangleCount * 3 + 24];
var x0 = 2.0f * bounds.uMin - 1.0f;
var x1 = 2.0f * bounds.uMax - 1.0f;
var y0 = 2.0f * bounds.vMin - 1.0f;
var y1 = 2.0f * bounds.vMax - 1.0f;
for (int i = 0,j = 0; i 3;i++)
{
var x = Lerp(x0, x1,data[j++]);
var y = Lerp(y0, y1,data[j++]);
vertices[i] = new Vector3(x, y, 0.0f);
indices[i] = i;
}
// Add border
var offset = (int)src.unTriangleCount * 3;
var iVert = offset;
vertices[iVert++] = new Vector3(-1, -1, 0);
vertices[iVert++] = new Vector3(x0, -1, 0);
vertices[iVert++] = new Vector3(-1, 1, 0);
vertices[iVert++] = new Vector3(x0, 1, 0);
vertices[iVert++] = new Vector3(x1, -1, 0);
vertices[iVert++] = new Vector3( 1, -1, 0);
vertices[iVert++] = new Vector3(x1, 1, 0);
vertices[iVert++] = new Vector3( 1, 1, 0);
vertices[iVert++] = new Vector3(x0, y0, 0);
vertices[iVert++] = new Vector3(x1, y0, 0);
vertices[iVert++] = new Vector3(x0, y1, 0);
vertices[iVert++] = new Vector3(x1, y1, 0);
var iTri = offset;
indices[iTri++] = offset + 0;
indices[iTri++] = offset + 1;
indices[iTri++] = offset + 2;
indices[iTri++] = offset + 2;
indices[iTri++] = offset + 1;
indices[iTri++] = offset + 3;
indices[iTri++] = offset + 4;
indices[iTri++] = offset + 5;
indices[iTri++] = offset + 6;
indices[iTri++] = offset + 6;
indices[iTri++] = offset + 5;
indices[iTri++] = offset + 7;
indices[iTri++] = offset + 1;
indices[iTri++] = offset + 4;
indices[iTri++] = offset + 8;
indices[iTri++] = offset + 8;
indices[iTri++] = offset + 4;
indices[iTri++] = offset + 9;
indices[iTri++] = offset + 10;
indices[iTri++] = offset + 11;
indices[iTri++] = offset + 3;
indices[iTri++] = offset + 3;
indices[iTri++] = offset + 11;
indices[iTri++] = offset + 6;
var mesh = new Mesh();
mesh.vertices = vertices;
mesh.triangles = indices;
mesh.bounds = new Bounds( Vector3.zero, new Vector3( float.MaxValue, float.MaxValue, float.MaxValue ) ); // Prevent frustumculling from culling this mesh
return mesh;
}
提供对IVRSystem接口调用的一种封装,只要调用SteamVR_Utils.CallSystemFn 就可以了
public delegate object SystemFn(CVRSystem system, params object[] args);
public static object CallSystemFn(SystemFn fn, params object[] args)
{
var initOpenVR = (!SteamVR.active && !SteamVR.usingNativeSupport);
if (initOpenVR)
{
var error = EVRInitError.None;
OpenVR.Init(ref error, EVRApplicationType.VRApplication_Other);
}
var system = OpenVR.System;
var result = (system != null) ? fn(system, args) : null;
if (initOpenVR)
OpenVR.Shutdown();
return result;
}
提供对Unity插件事件机制的一种封装,这里是专门提供给渲染线程调用的
public static void QueueEventOnRenderThread(int eventID)
{
#if (UNITY_5_0 || UNITY_5_1)
GL.IssuePluginEvent(eventID);
#elif (UNITY_5_2 || UNITY_5_3)
GL.IssuePluginEvent(SteamVR.Unity.GetRenderEventFunc(),eventID);
#endif
}
}