Basis.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. using System;
  2. using System.Runtime.InteropServices;
  3. #if REAL_T_IS_DOUBLE
  4. using real_t = System.Double;
  5. #else
  6. using real_t = System.Single;
  7. #endif
  8. namespace Godot
  9. {
  10. [StructLayout(LayoutKind.Sequential)]
  11. public struct Basis : IEquatable<Basis>
  12. {
  13. private static readonly Basis identity = new Basis
  14. (
  15. 1f, 0f, 0f,
  16. 0f, 1f, 0f,
  17. 0f, 0f, 1f
  18. );
  19. private static readonly Basis[] orthoBases = {
  20. new Basis(1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f),
  21. new Basis(0f, -1f, 0f, 1f, 0f, 0f, 0f, 0f, 1f),
  22. new Basis(-1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f),
  23. new Basis(0f, 1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f),
  24. new Basis(1f, 0f, 0f, 0f, 0f, -1f, 0f, 1f, 0f),
  25. new Basis(0f, 0f, 1f, 1f, 0f, 0f, 0f, 1f, 0f),
  26. new Basis(-1f, 0f, 0f, 0f, 0f, 1f, 0f, 1f, 0f),
  27. new Basis(0f, 0f, -1f, -1f, 0f, 0f, 0f, 1f, 0f),
  28. new Basis(1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, -1f),
  29. new Basis(0f, 1f, 0f, 1f, 0f, 0f, 0f, 0f, -1f),
  30. new Basis(-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, -1f),
  31. new Basis(0f, -1f, 0f, -1f, 0f, 0f, 0f, 0f, -1f),
  32. new Basis(1f, 0f, 0f, 0f, 0f, 1f, 0f, -1f, 0f),
  33. new Basis(0f, 0f, -1f, 1f, 0f, 0f, 0f, -1f, 0f),
  34. new Basis(-1f, 0f, 0f, 0f, 0f, -1f, 0f, -1f, 0f),
  35. new Basis(0f, 0f, 1f, -1f, 0f, 0f, 0f, -1f, 0f),
  36. new Basis(0f, 0f, 1f, 0f, 1f, 0f, -1f, 0f, 0f),
  37. new Basis(0f, -1f, 0f, 0f, 0f, 1f, -1f, 0f, 0f),
  38. new Basis(0f, 0f, -1f, 0f, -1f, 0f, -1f, 0f, 0f),
  39. new Basis(0f, 1f, 0f, 0f, 0f, -1f, -1f, 0f, 0f),
  40. new Basis(0f, 0f, 1f, 0f, -1f, 0f, 1f, 0f, 0f),
  41. new Basis(0f, 1f, 0f, 0f, 0f, 1f, 1f, 0f, 0f),
  42. new Basis(0f, 0f, -1f, 0f, 1f, 0f, 1f, 0f, 0f),
  43. new Basis(0f, -1f, 0f, 0f, 0f, -1f, 1f, 0f, 0f)
  44. };
  45. // NOTE: x, y and z are public-only. Use Column0, Column1 and Column2 internally.
  46. /// <summary>
  47. /// Returns the basis matrix’s x vector.
  48. /// This is equivalent to <see cref="Column0"/>.
  49. /// </summary>
  50. public Vector3 x
  51. {
  52. get => Column0;
  53. set => Column0 = value;
  54. }
  55. /// <summary>
  56. /// Returns the basis matrix’s y vector.
  57. /// This is equivalent to <see cref="Column1"/>.
  58. /// </summary>
  59. public Vector3 y
  60. {
  61. get => Column1;
  62. set => Column1 = value;
  63. }
  64. /// <summary>
  65. /// Returns the basis matrix’s z vector.
  66. /// This is equivalent to <see cref="Column2"/>.
  67. /// </summary>
  68. public Vector3 z
  69. {
  70. get => Column2;
  71. set => Column2 = value;
  72. }
  73. public Vector3 Row0;
  74. public Vector3 Row1;
  75. public Vector3 Row2;
  76. public Vector3 Column0
  77. {
  78. get => new Vector3(Row0.x, Row1.x, Row2.x);
  79. set
  80. {
  81. this.Row0.x = value.x;
  82. this.Row1.x = value.y;
  83. this.Row2.x = value.z;
  84. }
  85. }
  86. public Vector3 Column1
  87. {
  88. get => new Vector3(Row0.y, Row1.y, Row2.y);
  89. set
  90. {
  91. this.Row0.y = value.x;
  92. this.Row1.y = value.y;
  93. this.Row2.y = value.z;
  94. }
  95. }
  96. public Vector3 Column2
  97. {
  98. get => new Vector3(Row0.z, Row1.z, Row2.z);
  99. set
  100. {
  101. this.Row0.z = value.x;
  102. this.Row1.z = value.y;
  103. this.Row2.z = value.z;
  104. }
  105. }
  106. public static Basis Identity => identity;
  107. public Vector3 Scale
  108. {
  109. get
  110. {
  111. real_t detSign = Mathf.Sign(Determinant());
  112. return detSign * new Vector3
  113. (
  114. new Vector3(this.Row0[0], this.Row1[0], this.Row2[0]).Length(),
  115. new Vector3(this.Row0[1], this.Row1[1], this.Row2[1]).Length(),
  116. new Vector3(this.Row0[2], this.Row1[2], this.Row2[2]).Length()
  117. );
  118. }
  119. }
  120. public Vector3 this[int columnIndex]
  121. {
  122. get
  123. {
  124. switch (columnIndex)
  125. {
  126. case 0:
  127. return Column0;
  128. case 1:
  129. return Column1;
  130. case 2:
  131. return Column2;
  132. default:
  133. throw new IndexOutOfRangeException();
  134. }
  135. }
  136. set
  137. {
  138. switch (columnIndex)
  139. {
  140. case 0:
  141. Column0 = value;
  142. return;
  143. case 1:
  144. Column1 = value;
  145. return;
  146. case 2:
  147. Column2 = value;
  148. return;
  149. default:
  150. throw new IndexOutOfRangeException();
  151. }
  152. }
  153. }
  154. public real_t this[int columnIndex, int rowIndex]
  155. {
  156. get
  157. {
  158. switch (columnIndex)
  159. {
  160. case 0:
  161. return Column0[rowIndex];
  162. case 1:
  163. return Column1[rowIndex];
  164. case 2:
  165. return Column2[rowIndex];
  166. default:
  167. throw new IndexOutOfRangeException();
  168. }
  169. }
  170. set
  171. {
  172. switch (columnIndex)
  173. {
  174. case 0:
  175. {
  176. var column0 = Column0;
  177. column0[rowIndex] = value;
  178. Column0 = column0;
  179. return;
  180. }
  181. case 1:
  182. {
  183. var column1 = Column1;
  184. column1[rowIndex] = value;
  185. Column1 = column1;
  186. return;
  187. }
  188. case 2:
  189. {
  190. var column2 = Column2;
  191. column2[rowIndex] = value;
  192. Column2 = column2;
  193. return;
  194. }
  195. default:
  196. throw new IndexOutOfRangeException();
  197. }
  198. }
  199. }
  200. internal Quat RotationQuat()
  201. {
  202. Basis orthonormalizedBasis = Orthonormalized();
  203. real_t det = orthonormalizedBasis.Determinant();
  204. if (det < 0)
  205. {
  206. // Ensure that the determinant is 1, such that result is a proper rotation matrix which can be represented by Euler angles.
  207. orthonormalizedBasis = orthonormalizedBasis.Scaled(Vector3.NegOne);
  208. }
  209. return orthonormalizedBasis.Quat();
  210. }
  211. internal void SetQuantScale(Quat quat, Vector3 scale)
  212. {
  213. SetDiagonal(scale);
  214. Rotate(quat);
  215. }
  216. private void Rotate(Quat quat)
  217. {
  218. this *= new Basis(quat);
  219. }
  220. private void SetDiagonal(Vector3 diagonal)
  221. {
  222. Row0 = new Vector3(diagonal.x, 0, 0);
  223. Row1 = new Vector3(0, diagonal.y, 0);
  224. Row2 = new Vector3(0, 0, diagonal.z);
  225. }
  226. public real_t Determinant()
  227. {
  228. real_t cofac00 = Row1[1] * Row2[2] - Row1[2] * Row2[1];
  229. real_t cofac10 = Row1[2] * Row2[0] - Row1[0] * Row2[2];
  230. real_t cofac20 = Row1[0] * Row2[1] - Row1[1] * Row2[0];
  231. return Row0[0] * cofac00 + Row0[1] * cofac10 + Row0[2] * cofac20;
  232. }
  233. public Vector3 GetEuler()
  234. {
  235. Basis m = Orthonormalized();
  236. Vector3 euler;
  237. euler.z = 0.0f;
  238. real_t mxy = m.Row1[2];
  239. if (mxy < 1.0f)
  240. {
  241. if (mxy > -1.0f)
  242. {
  243. euler.x = Mathf.Asin(-mxy);
  244. euler.y = Mathf.Atan2(m.Row0[2], m.Row2[2]);
  245. euler.z = Mathf.Atan2(m.Row1[0], m.Row1[1]);
  246. }
  247. else
  248. {
  249. euler.x = Mathf.Pi * 0.5f;
  250. euler.y = -Mathf.Atan2(-m.Row0[1], m.Row0[0]);
  251. }
  252. }
  253. else
  254. {
  255. euler.x = -Mathf.Pi * 0.5f;
  256. euler.y = -Mathf.Atan2(-m.Row0[1], m.Row0[0]);
  257. }
  258. return euler;
  259. }
  260. public Vector3 GetRow(int index)
  261. {
  262. switch (index)
  263. {
  264. case 0:
  265. return Row0;
  266. case 1:
  267. return Row1;
  268. case 2:
  269. return Row2;
  270. default:
  271. throw new IndexOutOfRangeException();
  272. }
  273. }
  274. public void SetRow(int index, Vector3 value)
  275. {
  276. switch (index)
  277. {
  278. case 0:
  279. Row0 = value;
  280. return;
  281. case 1:
  282. Row1 = value;
  283. return;
  284. case 2:
  285. Row2 = value;
  286. return;
  287. default:
  288. throw new IndexOutOfRangeException();
  289. }
  290. }
  291. public Vector3 GetColumn(int index)
  292. {
  293. return this[index];
  294. }
  295. public void SetColumn(int index, Vector3 value)
  296. {
  297. this[index] = value;
  298. }
  299. [Obsolete("GetAxis is deprecated. Use GetColumn instead.")]
  300. public Vector3 GetAxis(int axis)
  301. {
  302. return new Vector3(this.Row0[axis], this.Row1[axis], this.Row2[axis]);
  303. }
  304. public int GetOrthogonalIndex()
  305. {
  306. var orth = this;
  307. for (int i = 0; i < 3; i++)
  308. {
  309. for (int j = 0; j < 3; j++)
  310. {
  311. var row = orth.GetRow(i);
  312. real_t v = row[j];
  313. if (v > 0.5f)
  314. v = 1.0f;
  315. else if (v < -0.5f)
  316. v = -1.0f;
  317. else
  318. v = 0f;
  319. row[j] = v;
  320. orth.SetRow(i, row);
  321. }
  322. }
  323. for (int i = 0; i < 24; i++)
  324. {
  325. if (orthoBases[i] == orth)
  326. return i;
  327. }
  328. return 0;
  329. }
  330. public Basis Inverse()
  331. {
  332. real_t cofac00 = Row1[1] * Row2[2] - Row1[2] * Row2[1];
  333. real_t cofac10 = Row1[2] * Row2[0] - Row1[0] * Row2[2];
  334. real_t cofac20 = Row1[0] * Row2[1] - Row1[1] * Row2[0];
  335. real_t det = Row0[0] * cofac00 + Row0[1] * cofac10 + Row0[2] * cofac20;
  336. if (det == 0)
  337. throw new InvalidOperationException("Matrix determinant is zero and cannot be inverted.");
  338. real_t detInv = 1.0f / det;
  339. real_t cofac01 = Row0[2] * Row2[1] - Row0[1] * Row2[2];
  340. real_t cofac02 = Row0[1] * Row1[2] - Row0[2] * Row1[1];
  341. real_t cofac11 = Row0[0] * Row2[2] - Row0[2] * Row2[0];
  342. real_t cofac12 = Row0[2] * Row1[0] - Row0[0] * Row1[2];
  343. real_t cofac21 = Row0[1] * Row2[0] - Row0[0] * Row2[1];
  344. real_t cofac22 = Row0[0] * Row1[1] - Row0[1] * Row1[0];
  345. return new Basis
  346. (
  347. cofac00 * detInv, cofac01 * detInv, cofac02 * detInv,
  348. cofac10 * detInv, cofac11 * detInv, cofac12 * detInv,
  349. cofac20 * detInv, cofac21 * detInv, cofac22 * detInv
  350. );
  351. }
  352. public Basis Orthonormalized()
  353. {
  354. Vector3 column0 = GetColumn(0);
  355. Vector3 column1 = GetColumn(1);
  356. Vector3 column2 = GetColumn(2);
  357. column0.Normalize();
  358. column1 = column1 - column0 * column0.Dot(column1);
  359. column1.Normalize();
  360. column2 = column2 - column0 * column0.Dot(column2) - column1 * column1.Dot(column2);
  361. column2.Normalize();
  362. return new Basis(column0, column1, column2);
  363. }
  364. public Basis Rotated(Vector3 axis, real_t phi)
  365. {
  366. return new Basis(axis, phi) * this;
  367. }
  368. public Basis Scaled(Vector3 scale)
  369. {
  370. var m = this;
  371. m.Row0[0] *= scale.x;
  372. m.Row0[1] *= scale.x;
  373. m.Row0[2] *= scale.x;
  374. m.Row1[0] *= scale.y;
  375. m.Row1[1] *= scale.y;
  376. m.Row1[2] *= scale.y;
  377. m.Row2[0] *= scale.z;
  378. m.Row2[1] *= scale.z;
  379. m.Row2[2] *= scale.z;
  380. return m;
  381. }
  382. public real_t Tdotx(Vector3 with)
  383. {
  384. return this.Row0[0] * with[0] + this.Row1[0] * with[1] + this.Row2[0] * with[2];
  385. }
  386. public real_t Tdoty(Vector3 with)
  387. {
  388. return this.Row0[1] * with[0] + this.Row1[1] * with[1] + this.Row2[1] * with[2];
  389. }
  390. public real_t Tdotz(Vector3 with)
  391. {
  392. return this.Row0[2] * with[0] + this.Row1[2] * with[1] + this.Row2[2] * with[2];
  393. }
  394. public Basis Transposed()
  395. {
  396. var tr = this;
  397. real_t temp = tr.Row0[1];
  398. tr.Row0[1] = tr.Row1[0];
  399. tr.Row1[0] = temp;
  400. temp = tr.Row0[2];
  401. tr.Row0[2] = tr.Row2[0];
  402. tr.Row2[0] = temp;
  403. temp = tr.Row1[2];
  404. tr.Row1[2] = tr.Row2[1];
  405. tr.Row2[1] = temp;
  406. return tr;
  407. }
  408. public Vector3 Xform(Vector3 v)
  409. {
  410. return new Vector3
  411. (
  412. this.Row0.Dot(v),
  413. this.Row1.Dot(v),
  414. this.Row2.Dot(v)
  415. );
  416. }
  417. public Vector3 XformInv(Vector3 v)
  418. {
  419. return new Vector3
  420. (
  421. this.Row0[0] * v.x + this.Row1[0] * v.y + this.Row2[0] * v.z,
  422. this.Row0[1] * v.x + this.Row1[1] * v.y + this.Row2[1] * v.z,
  423. this.Row0[2] * v.x + this.Row1[2] * v.y + this.Row2[2] * v.z
  424. );
  425. }
  426. public Quat Quat()
  427. {
  428. real_t trace = Row0[0] + Row1[1] + Row2[2];
  429. if (trace > 0.0f)
  430. {
  431. real_t s = Mathf.Sqrt(trace + 1.0f) * 2f;
  432. real_t inv_s = 1f / s;
  433. return new Quat(
  434. (Row2[1] - Row1[2]) * inv_s,
  435. (Row0[2] - Row2[0]) * inv_s,
  436. (Row1[0] - Row0[1]) * inv_s,
  437. s * 0.25f
  438. );
  439. }
  440. if (Row0[0] > Row1[1] && Row0[0] > Row2[2])
  441. {
  442. real_t s = Mathf.Sqrt(Row0[0] - Row1[1] - Row2[2] + 1.0f) * 2f;
  443. real_t inv_s = 1f / s;
  444. return new Quat(
  445. s * 0.25f,
  446. (Row0[1] + Row1[0]) * inv_s,
  447. (Row0[2] + Row2[0]) * inv_s,
  448. (Row2[1] - Row1[2]) * inv_s
  449. );
  450. }
  451. if (Row1[1] > Row2[2])
  452. {
  453. real_t s = Mathf.Sqrt(-Row0[0] + Row1[1] - Row2[2] + 1.0f) * 2f;
  454. real_t inv_s = 1f / s;
  455. return new Quat(
  456. (Row0[1] + Row1[0]) * inv_s,
  457. s * 0.25f,
  458. (Row1[2] + Row2[1]) * inv_s,
  459. (Row0[2] - Row2[0]) * inv_s
  460. );
  461. }
  462. else
  463. {
  464. real_t s = Mathf.Sqrt(-Row0[0] - Row1[1] + Row2[2] + 1.0f) * 2f;
  465. real_t inv_s = 1f / s;
  466. return new Quat(
  467. (Row0[2] + Row2[0]) * inv_s,
  468. (Row1[2] + Row2[1]) * inv_s,
  469. s * 0.25f,
  470. (Row1[0] - Row0[1]) * inv_s
  471. );
  472. }
  473. }
  474. public Basis(Quat quat)
  475. {
  476. real_t s = 2.0f / quat.LengthSquared;
  477. real_t xs = quat.x * s;
  478. real_t ys = quat.y * s;
  479. real_t zs = quat.z * s;
  480. real_t wx = quat.w * xs;
  481. real_t wy = quat.w * ys;
  482. real_t wz = quat.w * zs;
  483. real_t xx = quat.x * xs;
  484. real_t xy = quat.x * ys;
  485. real_t xz = quat.x * zs;
  486. real_t yy = quat.y * ys;
  487. real_t yz = quat.y * zs;
  488. real_t zz = quat.z * zs;
  489. Row0 = new Vector3(1.0f - (yy + zz), xy - wz, xz + wy);
  490. Row1 = new Vector3(xy + wz, 1.0f - (xx + zz), yz - wx);
  491. Row2 = new Vector3(xz - wy, yz + wx, 1.0f - (xx + yy));
  492. }
  493. public Basis(Vector3 euler)
  494. {
  495. real_t c;
  496. real_t s;
  497. c = Mathf.Cos(euler.x);
  498. s = Mathf.Sin(euler.x);
  499. var xmat = new Basis(1, 0, 0, 0, c, -s, 0, s, c);
  500. c = Mathf.Cos(euler.y);
  501. s = Mathf.Sin(euler.y);
  502. var ymat = new Basis(c, 0, s, 0, 1, 0, -s, 0, c);
  503. c = Mathf.Cos(euler.z);
  504. s = Mathf.Sin(euler.z);
  505. var zmat = new Basis(c, -s, 0, s, c, 0, 0, 0, 1);
  506. this = ymat * xmat * zmat;
  507. }
  508. public Basis(Vector3 axis, real_t phi)
  509. {
  510. var axis_sq = new Vector3(axis.x * axis.x, axis.y * axis.y, axis.z * axis.z);
  511. real_t cosine = Mathf.Cos(phi);
  512. real_t sine = Mathf.Sin(phi);
  513. Row0 = new Vector3
  514. (
  515. axis_sq.x + cosine * (1.0f - axis_sq.x),
  516. axis.x * axis.y * (1.0f - cosine) - axis.z * sine,
  517. axis.z * axis.x * (1.0f - cosine) + axis.y * sine
  518. );
  519. Row1 = new Vector3
  520. (
  521. axis.x * axis.y * (1.0f - cosine) + axis.z * sine,
  522. axis_sq.y + cosine * (1.0f - axis_sq.y),
  523. axis.y * axis.z * (1.0f - cosine) - axis.x * sine
  524. );
  525. Row2 = new Vector3
  526. (
  527. axis.z * axis.x * (1.0f - cosine) - axis.y * sine,
  528. axis.y * axis.z * (1.0f - cosine) + axis.x * sine,
  529. axis_sq.z + cosine * (1.0f - axis_sq.z)
  530. );
  531. }
  532. public Basis(Vector3 column0, Vector3 column1, Vector3 column2)
  533. {
  534. Row0 = new Vector3(column0.x, column1.x, column2.x);
  535. Row1 = new Vector3(column0.y, column1.y, column2.y);
  536. Row2 = new Vector3(column0.z, column1.z, column2.z);
  537. // Same as:
  538. // Column0 = column0;
  539. // Column1 = column1;
  540. // Column2 = column2;
  541. // We need to assign the struct fields here first so we can't do it that way...
  542. }
  543. internal Basis(real_t xx, real_t xy, real_t xz, real_t yx, real_t yy, real_t yz, real_t zx, real_t zy, real_t zz)
  544. {
  545. Row0 = new Vector3(xx, xy, xz);
  546. Row1 = new Vector3(yx, yy, yz);
  547. Row2 = new Vector3(zx, zy, zz);
  548. }
  549. public static Basis operator *(Basis left, Basis right)
  550. {
  551. return new Basis
  552. (
  553. right.Tdotx(left.Row0), right.Tdoty(left.Row0), right.Tdotz(left.Row0),
  554. right.Tdotx(left.Row1), right.Tdoty(left.Row1), right.Tdotz(left.Row1),
  555. right.Tdotx(left.Row2), right.Tdoty(left.Row2), right.Tdotz(left.Row2)
  556. );
  557. }
  558. public static bool operator ==(Basis left, Basis right)
  559. {
  560. return left.Equals(right);
  561. }
  562. public static bool operator !=(Basis left, Basis right)
  563. {
  564. return !left.Equals(right);
  565. }
  566. public override bool Equals(object obj)
  567. {
  568. if (obj is Basis)
  569. {
  570. return Equals((Basis)obj);
  571. }
  572. return false;
  573. }
  574. public bool Equals(Basis other)
  575. {
  576. return Row0.Equals(other.Row0) && Row1.Equals(other.Row1) && Row2.Equals(other.Row2);
  577. }
  578. public override int GetHashCode()
  579. {
  580. return Row0.GetHashCode() ^ Row1.GetHashCode() ^ Row2.GetHashCode();
  581. }
  582. public override string ToString()
  583. {
  584. return String.Format("({0}, {1}, {2})", new object[]
  585. {
  586. Row0.ToString(),
  587. Row1.ToString(),
  588. Row2.ToString()
  589. });
  590. }
  591. public string ToString(string format)
  592. {
  593. return String.Format("({0}, {1}, {2})", new object[]
  594. {
  595. Row0.ToString(format),
  596. Row1.ToString(format),
  597. Row2.ToString(format)
  598. });
  599. }
  600. }
  601. }