SteamVR_Utils.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. //======= Copyright (c) Valve Corporation, All rights reserved. ===============
  2. //
  3. // Purpose: Utilities for working with SteamVR
  4. //
  5. //=============================================================================
  6. using UnityEngine;
  7. using System.Collections;
  8. using System.Runtime.InteropServices;
  9. using Valve.VR;
  10. public static class SteamVR_Utils
  11. {
  12. // this version does not clamp [0..1]
  13. public static Quaternion Slerp(Quaternion A, Quaternion B, float t)
  14. {
  15. var cosom = Mathf.Clamp(A.x * B.x + A.y * B.y + A.z * B.z + A.w * B.w, -1.0f, 1.0f);
  16. if (cosom < 0.0f)
  17. {
  18. B = new Quaternion(-B.x, -B.y, -B.z, -B.w);
  19. cosom = -cosom;
  20. }
  21. float sclp, sclq;
  22. if ((1.0f - cosom) > 0.0001f)
  23. {
  24. var omega = Mathf.Acos(cosom);
  25. var sinom = Mathf.Sin(omega);
  26. sclp = Mathf.Sin((1.0f - t) * omega) / sinom;
  27. sclq = Mathf.Sin(t * omega) / sinom;
  28. }
  29. else
  30. {
  31. // "from" and "to" very close, so do linear interp
  32. sclp = 1.0f - t;
  33. sclq = t;
  34. }
  35. return new Quaternion(
  36. sclp * A.x + sclq * B.x,
  37. sclp * A.y + sclq * B.y,
  38. sclp * A.z + sclq * B.z,
  39. sclp * A.w + sclq * B.w);
  40. }
  41. public static Vector3 Lerp(Vector3 A, Vector3 B, float t)
  42. {
  43. return new Vector3(
  44. Lerp(A.x, B.x, t),
  45. Lerp(A.y, B.y, t),
  46. Lerp(A.z, B.z, t));
  47. }
  48. public static float Lerp(float A, float B, float t)
  49. {
  50. return A + (B - A) * t;
  51. }
  52. public static double Lerp(double A, double B, double t)
  53. {
  54. return A + (B - A) * t;
  55. }
  56. public static float InverseLerp(Vector3 A, Vector3 B, Vector3 result)
  57. {
  58. return Vector3.Dot(result - A, B - A);
  59. }
  60. public static float InverseLerp(float A, float B, float result)
  61. {
  62. return (result - A) / (B - A);
  63. }
  64. public static double InverseLerp(double A, double B, double result)
  65. {
  66. return (result - A) / (B - A);
  67. }
  68. public static float Saturate(float A)
  69. {
  70. return (A < 0) ? 0 : (A > 1) ? 1 : A;
  71. }
  72. public static Vector2 Saturate(Vector2 A)
  73. {
  74. return new Vector2(Saturate(A.x), Saturate(A.y));
  75. }
  76. public static float Abs(float A)
  77. {
  78. return (A < 0) ? -A : A;
  79. }
  80. public static Vector2 Abs(Vector2 A)
  81. {
  82. return new Vector2(Abs(A.x), Abs(A.y));
  83. }
  84. private static float _copysign(float sizeval, float signval)
  85. {
  86. return Mathf.Sign(signval) == 1 ? Mathf.Abs(sizeval) : -Mathf.Abs(sizeval);
  87. }
  88. public static Quaternion GetRotation(this Matrix4x4 matrix)
  89. {
  90. Quaternion q = new Quaternion();
  91. q.w = Mathf.Sqrt(Mathf.Max(0, 1 + matrix.m00 + matrix.m11 + matrix.m22)) / 2;
  92. q.x = Mathf.Sqrt(Mathf.Max(0, 1 + matrix.m00 - matrix.m11 - matrix.m22)) / 2;
  93. q.y = Mathf.Sqrt(Mathf.Max(0, 1 - matrix.m00 + matrix.m11 - matrix.m22)) / 2;
  94. q.z = Mathf.Sqrt(Mathf.Max(0, 1 - matrix.m00 - matrix.m11 + matrix.m22)) / 2;
  95. q.x = _copysign(q.x, matrix.m21 - matrix.m12);
  96. q.y = _copysign(q.y, matrix.m02 - matrix.m20);
  97. q.z = _copysign(q.z, matrix.m10 - matrix.m01);
  98. return q;
  99. }
  100. public static Vector3 GetPosition(this Matrix4x4 matrix)
  101. {
  102. var x = matrix.m03;
  103. var y = matrix.m13;
  104. var z = matrix.m23;
  105. return new Vector3(x, y, z);
  106. }
  107. public static Vector3 GetScale(this Matrix4x4 m)
  108. {
  109. var x = Mathf.Sqrt(m.m00 * m.m00 + m.m01 * m.m01 + m.m02 * m.m02);
  110. var y = Mathf.Sqrt(m.m10 * m.m10 + m.m11 * m.m11 + m.m12 * m.m12);
  111. var z = Mathf.Sqrt(m.m20 * m.m20 + m.m21 * m.m21 + m.m22 * m.m22);
  112. return new Vector3(x, y, z);
  113. }
  114. [System.Serializable]
  115. public struct RigidTransform
  116. {
  117. public Vector3 pos;
  118. public Quaternion rot;
  119. public static RigidTransform identity
  120. {
  121. get { return new RigidTransform(Vector3.zero, Quaternion.identity); }
  122. }
  123. public static RigidTransform FromLocal(Transform t)
  124. {
  125. return new RigidTransform(t.localPosition, t.localRotation);
  126. }
  127. public RigidTransform(Vector3 pos, Quaternion rot)
  128. {
  129. this.pos = pos;
  130. this.rot = rot;
  131. }
  132. public RigidTransform(Transform t)
  133. {
  134. this.pos = t.position;
  135. this.rot = t.rotation;
  136. }
  137. public RigidTransform(Transform from, Transform to)
  138. {
  139. var inv = Quaternion.Inverse(from.rotation);
  140. rot = inv * to.rotation;
  141. pos = inv * (to.position - from.position);
  142. }
  143. public RigidTransform(HmdMatrix34_t pose)
  144. {
  145. var m = Matrix4x4.identity;
  146. m[0, 0] = pose.m0;
  147. m[0, 1] = pose.m1;
  148. m[0, 2] = -pose.m2;
  149. m[0, 3] = pose.m3;
  150. m[1, 0] = pose.m4;
  151. m[1, 1] = pose.m5;
  152. m[1, 2] = -pose.m6;
  153. m[1, 3] = pose.m7;
  154. m[2, 0] = -pose.m8;
  155. m[2, 1] = -pose.m9;
  156. m[2, 2] = pose.m10;
  157. m[2, 3] = -pose.m11;
  158. this.pos = m.GetPosition();
  159. this.rot = m.GetRotation();
  160. }
  161. public RigidTransform(HmdMatrix44_t pose)
  162. {
  163. var m = Matrix4x4.identity;
  164. m[0, 0] = pose.m0;
  165. m[0, 1] = pose.m1;
  166. m[0, 2] = -pose.m2;
  167. m[0, 3] = pose.m3;
  168. m[1, 0] = pose.m4;
  169. m[1, 1] = pose.m5;
  170. m[1, 2] = -pose.m6;
  171. m[1, 3] = pose.m7;
  172. m[2, 0] = -pose.m8;
  173. m[2, 1] = -pose.m9;
  174. m[2, 2] = pose.m10;
  175. m[2, 3] = -pose.m11;
  176. m[3, 0] = pose.m12;
  177. m[3, 1] = pose.m13;
  178. m[3, 2] = -pose.m14;
  179. m[3, 3] = pose.m15;
  180. this.pos = m.GetPosition();
  181. this.rot = m.GetRotation();
  182. }
  183. public HmdMatrix44_t ToHmdMatrix44()
  184. {
  185. var m = Matrix4x4.TRS(pos, rot, Vector3.one);
  186. var pose = new HmdMatrix44_t();
  187. pose.m0 = m[0, 0];
  188. pose.m1 = m[0, 1];
  189. pose.m2 = -m[0, 2];
  190. pose.m3 = m[0, 3];
  191. pose.m4 = m[1, 0];
  192. pose.m5 = m[1, 1];
  193. pose.m6 = -m[1, 2];
  194. pose.m7 = m[1, 3];
  195. pose.m8 = -m[2, 0];
  196. pose.m9 = -m[2, 1];
  197. pose.m10 = m[2, 2];
  198. pose.m11 = -m[2, 3];
  199. pose.m12 = m[3, 0];
  200. pose.m13 = m[3, 1];
  201. pose.m14 = -m[3, 2];
  202. pose.m15 = m[3, 3];
  203. return pose;
  204. }
  205. public HmdMatrix34_t ToHmdMatrix34()
  206. {
  207. var m = Matrix4x4.TRS(pos, rot, Vector3.one);
  208. var pose = new HmdMatrix34_t();
  209. pose.m0 = m[0, 0];
  210. pose.m1 = m[0, 1];
  211. pose.m2 = -m[0, 2];
  212. pose.m3 = m[0, 3];
  213. pose.m4 = m[1, 0];
  214. pose.m5 = m[1, 1];
  215. pose.m6 = -m[1, 2];
  216. pose.m7 = m[1, 3];
  217. pose.m8 = -m[2, 0];
  218. pose.m9 = -m[2, 1];
  219. pose.m10 = m[2, 2];
  220. pose.m11 = -m[2, 3];
  221. return pose;
  222. }
  223. public override bool Equals(object o)
  224. {
  225. if (o is RigidTransform)
  226. {
  227. RigidTransform t = (RigidTransform)o;
  228. return pos == t.pos && rot == t.rot;
  229. }
  230. return false;
  231. }
  232. public override int GetHashCode()
  233. {
  234. return pos.GetHashCode() ^ rot.GetHashCode();
  235. }
  236. public static bool operator ==(RigidTransform a, RigidTransform b)
  237. {
  238. return a.pos == b.pos && a.rot == b.rot;
  239. }
  240. public static bool operator !=(RigidTransform a, RigidTransform b)
  241. {
  242. return a.pos != b.pos || a.rot != b.rot;
  243. }
  244. public static RigidTransform operator *(RigidTransform a, RigidTransform b)
  245. {
  246. return new RigidTransform
  247. {
  248. rot = a.rot * b.rot,
  249. pos = a.pos + a.rot * b.pos
  250. };
  251. }
  252. public void Inverse()
  253. {
  254. rot = Quaternion.Inverse(rot);
  255. pos = -(rot * pos);
  256. }
  257. public RigidTransform GetInverse()
  258. {
  259. var t = new RigidTransform(pos, rot);
  260. t.Inverse();
  261. return t;
  262. }
  263. public void Multiply(RigidTransform a, RigidTransform b)
  264. {
  265. rot = a.rot * b.rot;
  266. pos = a.pos + a.rot * b.pos;
  267. }
  268. public Vector3 InverseTransformPoint(Vector3 point)
  269. {
  270. return Quaternion.Inverse(rot) * (point - pos);
  271. }
  272. public Vector3 TransformPoint(Vector3 point)
  273. {
  274. return pos + (rot * point);
  275. }
  276. public static Vector3 operator *(RigidTransform t, Vector3 v)
  277. {
  278. return t.TransformPoint(v);
  279. }
  280. public static RigidTransform Interpolate(RigidTransform a, RigidTransform b, float t)
  281. {
  282. return new RigidTransform(Vector3.Lerp(a.pos, b.pos, t), Quaternion.Slerp(a.rot, b.rot, t));
  283. }
  284. public void Interpolate(RigidTransform to, float t)
  285. {
  286. pos = SteamVR_Utils.Lerp(pos, to.pos, t);
  287. rot = SteamVR_Utils.Slerp(rot, to.rot, t);
  288. }
  289. }
  290. public delegate object SystemFn(CVRSystem system, params object[] args);
  291. public static object CallSystemFn(SystemFn fn, params object[] args)
  292. {
  293. var initOpenVR = (!SteamVR.active && !SteamVR.usingNativeSupport);
  294. if (initOpenVR)
  295. {
  296. var error = EVRInitError.None;
  297. OpenVR.Init(ref error, EVRApplicationType.VRApplication_Utility);
  298. }
  299. var system = OpenVR.System;
  300. var result = (system != null) ? fn(system, args) : null;
  301. if (initOpenVR)
  302. OpenVR.Shutdown();
  303. return result;
  304. }
  305. public static void TakeStereoScreenshot(uint screenshotHandle, GameObject target, int cellSize, float ipd, ref string previewFilename, ref string VRFilename)
  306. {
  307. const int width = 4096;
  308. const int height = width / 2;
  309. const int halfHeight = height / 2;
  310. var texture = new Texture2D(width, height * 2, TextureFormat.ARGB32, false);
  311. var timer = new System.Diagnostics.Stopwatch();
  312. Camera tempCamera = null;
  313. timer.Start();
  314. var camera = target.GetComponent<Camera>();
  315. if (camera == null)
  316. {
  317. if (tempCamera == null)
  318. tempCamera = new GameObject().AddComponent<Camera>();
  319. camera = tempCamera;
  320. }
  321. // Render preview texture
  322. const int previewWidth = 2048;
  323. const int previewHeight = 2048;
  324. var previewTexture = new Texture2D(previewWidth, previewHeight, TextureFormat.ARGB32, false);
  325. var targetPreviewTexture = new RenderTexture(previewWidth, previewHeight, 24);
  326. var oldTargetTexture = camera.targetTexture;
  327. var oldOrthographic = camera.orthographic;
  328. var oldFieldOfView = camera.fieldOfView;
  329. var oldAspect = camera.aspect;
  330. var oldstereoTargetEye = camera.stereoTargetEye;
  331. camera.stereoTargetEye = StereoTargetEyeMask.None;
  332. camera.fieldOfView = 60.0f;
  333. camera.orthographic = false;
  334. camera.targetTexture = targetPreviewTexture;
  335. camera.aspect = 1.0f;
  336. camera.Render();
  337. // copy preview texture
  338. RenderTexture.active = targetPreviewTexture;
  339. previewTexture.ReadPixels(new Rect(0, 0, targetPreviewTexture.width, targetPreviewTexture.height), 0, 0);
  340. RenderTexture.active = null;
  341. camera.targetTexture = null;
  342. Object.DestroyImmediate(targetPreviewTexture);
  343. var fx = camera.gameObject.AddComponent<SteamVR_SphericalProjection>();
  344. var oldPosition = target.transform.localPosition;
  345. var oldRotation = target.transform.localRotation;
  346. var basePosition = target.transform.position;
  347. var baseRotation = Quaternion.Euler(0, target.transform.rotation.eulerAngles.y, 0);
  348. var transform = camera.transform;
  349. int vTotal = halfHeight / cellSize;
  350. float dv = 90.0f / vTotal; // vertical degrees per segment
  351. float dvHalf = dv / 2.0f;
  352. var targetTexture = new RenderTexture(cellSize, cellSize, 24);
  353. targetTexture.wrapMode = TextureWrapMode.Clamp;
  354. targetTexture.antiAliasing = 8;
  355. camera.fieldOfView = dv;
  356. camera.orthographic = false;
  357. camera.targetTexture = targetTexture;
  358. camera.aspect = oldAspect;
  359. camera.stereoTargetEye = StereoTargetEyeMask.None;
  360. // Render sections of a sphere using a rectilinear projection
  361. // and resample using a sphereical projection into a single panorama
  362. // texture per eye. We break into sections in order to keep the eye
  363. // separation similar around the sphere. Rendering alternates between
  364. // top and bottom sections, sweeping horizontally around the sphere,
  365. // alternating left and right eyes.
  366. for (int v = 0; v < vTotal; v++)
  367. {
  368. var pitch = 90.0f - (v * dv) - dvHalf;
  369. var uTotal = width / targetTexture.width;
  370. var du = 360.0f / uTotal; // horizontal degrees per segment
  371. var duHalf = du / 2.0f;
  372. var vTarget = v * halfHeight / vTotal;
  373. for (int i = 0; i < 2; i++) // top, bottom
  374. {
  375. if (i == 1)
  376. {
  377. pitch = -pitch;
  378. vTarget = height - vTarget - cellSize;
  379. }
  380. for (int u = 0; u < uTotal; u++)
  381. {
  382. var yaw = -180.0f + (u * du) + duHalf;
  383. var uTarget = u * width / uTotal;
  384. var vTargetOffset = 0;
  385. var xOffset = -ipd / 2 * Mathf.Cos(pitch * Mathf.Deg2Rad);
  386. for (int j = 0; j < 2; j++) // left, right
  387. {
  388. if (j == 1)
  389. {
  390. vTargetOffset = height;
  391. xOffset = -xOffset;
  392. }
  393. var offset = baseRotation * Quaternion.Euler(0, yaw, 0) * new Vector3(xOffset, 0, 0);
  394. transform.position = basePosition + offset;
  395. var direction = Quaternion.Euler(pitch, yaw, 0.0f);
  396. transform.rotation = baseRotation * direction;
  397. // vector pointing to center of this section
  398. var N = direction * Vector3.forward;
  399. // horizontal span of this section in degrees
  400. var phi0 = yaw - (du / 2);
  401. var phi1 = phi0 + du;
  402. // vertical span of this section in degrees
  403. var theta0 = pitch + (dv / 2);
  404. var theta1 = theta0 - dv;
  405. var midPhi = (phi0 + phi1) / 2;
  406. var baseTheta = Mathf.Abs(theta0) < Mathf.Abs(theta1) ? theta0 : theta1;
  407. // vectors pointing to corners of image closes to the equator
  408. var V00 = Quaternion.Euler(baseTheta, phi0, 0.0f) * Vector3.forward;
  409. var V01 = Quaternion.Euler(baseTheta, phi1, 0.0f) * Vector3.forward;
  410. // vectors pointing to top and bottom midsection of image
  411. var V0M = Quaternion.Euler(theta0, midPhi, 0.0f) * Vector3.forward;
  412. var V1M = Quaternion.Euler(theta1, midPhi, 0.0f) * Vector3.forward;
  413. // intersection points for each of the above
  414. var P00 = V00 / Vector3.Dot(V00, N);
  415. var P01 = V01 / Vector3.Dot(V01, N);
  416. var P0M = V0M / Vector3.Dot(V0M, N);
  417. var P1M = V1M / Vector3.Dot(V1M, N);
  418. // calculate basis vectors for plane
  419. var P00_P01 = P01 - P00;
  420. var P0M_P1M = P1M - P0M;
  421. var uMag = P00_P01.magnitude;
  422. var vMag = P0M_P1M.magnitude;
  423. var uScale = 1.0f / uMag;
  424. var vScale = 1.0f / vMag;
  425. var uAxis = P00_P01 * uScale;
  426. var vAxis = P0M_P1M * vScale;
  427. // update material constant buffer
  428. fx.Set(N, phi0, phi1, theta0, theta1,
  429. uAxis, P00, uScale,
  430. vAxis, P0M, vScale);
  431. camera.aspect = uMag / vMag;
  432. camera.Render();
  433. RenderTexture.active = targetTexture;
  434. texture.ReadPixels(new Rect(0, 0, targetTexture.width, targetTexture.height), uTarget, vTarget + vTargetOffset);
  435. RenderTexture.active = null;
  436. }
  437. // Update progress
  438. var progress = (float)( v * ( uTotal * 2.0f ) + u + i*uTotal) / (float)(vTotal * ( uTotal * 2.0f ) );
  439. OpenVR.Screenshots.UpdateScreenshotProgress(screenshotHandle, progress);
  440. }
  441. }
  442. }
  443. // 100% flush
  444. OpenVR.Screenshots.UpdateScreenshotProgress(screenshotHandle, 1.0f);
  445. // Save textures to disk.
  446. // Add extensions
  447. previewFilename += ".png";
  448. VRFilename += ".png";
  449. // Preview
  450. previewTexture.Apply();
  451. System.IO.File.WriteAllBytes(previewFilename, previewTexture.EncodeToPNG());
  452. // VR
  453. texture.Apply();
  454. System.IO.File.WriteAllBytes(VRFilename, texture.EncodeToPNG());
  455. // Cleanup.
  456. if (camera != tempCamera)
  457. {
  458. camera.targetTexture = oldTargetTexture;
  459. camera.orthographic = oldOrthographic;
  460. camera.fieldOfView = oldFieldOfView;
  461. camera.aspect = oldAspect;
  462. camera.stereoTargetEye = oldstereoTargetEye;
  463. target.transform.localPosition = oldPosition;
  464. target.transform.localRotation = oldRotation;
  465. }
  466. else
  467. {
  468. tempCamera.targetTexture = null;
  469. }
  470. Object.DestroyImmediate(targetTexture);
  471. Object.DestroyImmediate(fx);
  472. timer.Stop();
  473. Debug.Log(string.Format("Screenshot took {0} seconds.", timer.Elapsed));
  474. if (tempCamera != null)
  475. {
  476. Object.DestroyImmediate(tempCamera.gameObject);
  477. }
  478. Object.DestroyImmediate(previewTexture);
  479. Object.DestroyImmediate(texture);
  480. }
  481. }