Extended.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. using Microsoft.Xna.Framework;
  2. using Microsoft.Xna.Framework.Graphics;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Runtime.InteropServices;
  8. using System.Text;
  9. using System.Threading.Tasks;
  10. namespace OpenVIII
  11. {
  12. //Class that provides language extensions made by Maki
  13. public static class Extended
  14. {
  15. public enum Languages
  16. {
  17. en,
  18. fr,
  19. de,
  20. es,
  21. it,
  22. jp
  23. }
  24. public static bool Save_As_PNG(Texture2D texture,string path, int width, int height)
  25. {
  26. var return_value = false;
  27. try
  28. {
  29. using var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
  30. texture.SaveAsPng(fs, width, height);
  31. return_value = true;
  32. }
  33. catch (Exception e)
  34. {
  35. Memory.Log.WriteLine(e.Message);
  36. }
  37. return return_value;
  38. }
  39. //WORLD MAP COORDINATES HELPER
  40. /// <summary>
  41. /// Indicates vanilla game X axis coordinate for minimum possible X - visually the nearest to left on worldmap
  42. /// </summary>
  43. public const int WORLD_COORDS_MINLEFT = unchecked((int)0xFFFE0000);
  44. /// <summary>
  45. /// Indicates vanilla game X axis coordinate for maximum possible X - visually the furthest to right on worldmap
  46. /// </summary>
  47. public const int WORLD_COORDS_MAXRIGHT = unchecked((int)0x0001FFFF);
  48. /// <summary>
  49. /// zero based range of maximum left-right for worldmap coordinates
  50. /// </summary>
  51. public static readonly int WORLD_COORDS_XRANGE = Math.Abs(WORLD_COORDS_MINLEFT) + WORLD_COORDS_MAXRIGHT;
  52. /// <summary>
  53. /// Indicates vanilla game Y axis coordinate for minimum possible Y - visually at the semi top of worldmap
  54. /// </summary>
  55. public const int WORLD_COORDS_MINTOP = unchecked((int)0xFFFE8000);
  56. /// <summary>
  57. /// Indicates vanilla game Y axis coordinate for maximum possible Y - visually at the very bottom of worldmap
  58. /// </summary>
  59. public const int WORLD_COORDS_MAXBOTTOM = unchecked((int)0x00017FFF);
  60. /// <summary>
  61. /// zero based range of maximum left-right for worldmap coordinates
  62. /// </summary>
  63. public static readonly int WORLD_COORDS_ZRANGE = Math.Abs(WORLD_COORDS_MINTOP) + WORLD_COORDS_MAXBOTTOM;
  64. /// <summary>
  65. /// Indicates the OpenVIII coordinate system which shows maximum X axis. Minimum is always zero (it's the nearest to right side of worldmap)
  66. /// </summary>
  67. public const int WORLD_OPENVIII_MAXRIGHT = -(32 * 512);
  68. /// <summary>
  69. /// Indicates the OpenVIII coordinate system which shows maximum Y axis. Minimum is always zero (it's the furthest to bottom of worldmap)
  70. /// </summary>
  71. public const int WORLD_OPENVIII_MAXBOTTOM = -(24 * 512);
  72. /// <summary>
  73. /// Result of Vanilla.Y * x = openviii.Y equation
  74. /// </summary>
  75. public const float WORLD_COORD_YHELPER = -0.06f;
  76. /// <summary>
  77. /// This method converts vanilla world map coordinates to openVIII equivalent.
  78. /// </summary>
  79. /// <param name="x"></param>
  80. /// <returns></returns>
  81. public static float ConvertVanillaWorldXAxisToOpenVIII(float x)
  82. {
  83. if(x>WORLD_COORDS_MINLEFT)
  84. {
  85. var leftSide = x - WORLD_COORDS_MINLEFT; //this is the distance from left to middle of map
  86. //0x1FFFF is one part of map
  87. var percentUsage = leftSide / 0x1FFFF; //we now know the left side map percentage use
  88. return (float)(percentUsage * (WORLD_OPENVIII_MAXRIGHT / 2.0));
  89. }
  90. else
  91. {
  92. var percentUsage = x / WORLD_COORDS_MAXRIGHT;
  93. var rightSide = WORLD_OPENVIII_MAXRIGHT / 2.0;
  94. return (float)(percentUsage * rightSide + rightSide);
  95. }
  96. }
  97. /// <summary>
  98. /// This method converts vanilla world map coordinates to openVIII equivalent.
  99. /// </summary>
  100. /// <param name="x"></param>
  101. /// <returns></returns>
  102. public static float ConvertVanillaWorldZAxisToOpenVIII(float z)
  103. {
  104. if (z > WORLD_COORDS_MINTOP)
  105. {
  106. var topSide = z - WORLD_COORDS_MINTOP; //this is the distance from left to middle of map
  107. //0x1FFFF is one part of map
  108. var percentUsage = topSide / 0x17FFF; //we now know the left side map percentage use
  109. return (float)(percentUsage * (WORLD_OPENVIII_MAXBOTTOM / 2.0));
  110. }
  111. else
  112. {
  113. var percentUsage = z / WORLD_COORDS_MAXBOTTOM;
  114. var rightSide = WORLD_OPENVIII_MAXBOTTOM / 2.0;
  115. return (float)(percentUsage * rightSide + rightSide);
  116. }
  117. }
  118. /// <summary>
  119. /// This method converts vanilla world map coordinates to openVIII equivalent
  120. /// </summary>
  121. /// <param name="y"></param>
  122. /// <returns></returns>
  123. public static float ConvertVanillaWorldYAxisToOpenVIII(float y)
  124. => y * WORLD_COORD_YHELPER;
  125. //https://stackoverflow.com/a/2887/4509036
  126. public static T ByteArrayToClass<T>(byte[] bytes) where T : class
  127. {
  128. var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
  129. try
  130. {
  131. return (T)Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
  132. }
  133. finally
  134. {
  135. handle.Free();
  136. }
  137. }
  138. //https://stackoverflow.com/a/2887/4509036
  139. public static T ByteArrayToStructure<T>(byte[] bytes) where T : struct
  140. {
  141. var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
  142. try
  143. {
  144. return (T)Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
  145. }
  146. finally
  147. {
  148. handle.Free();
  149. }
  150. }
  151. #if DEBUG || _WINDOWS
  152. public static void DumpBuffer(byte[] buffer, string path)
  153. => System.IO.File.WriteAllBytes(path, buffer);
  154. public static void DumpBuffer(System.IO.MemoryStream ms, string path)
  155. => System.IO.File.WriteAllBytes(path, ms.GetBuffer());
  156. public static void DumpBuffer(System.IO.MemoryStream ms)
  157. => System.IO.File.WriteAllBytes(GetUnixFullPath(System.IO.Path.Combine(Memory.FF8Dir, "debugUnpack.debug")), ms.GetBuffer());
  158. public static void DumpTexture(Texture2D tex, string s)
  159. {
  160. if (Directory.Exists(Path.GetDirectoryName(s)))
  161. Extended.Save_As_PNG(tex, s, tex.Width, tex.Height);
  162. }
  163. #endif
  164. ///<summary>Detect if an object is a number</summary>
  165. ///<see cref="https://stackoverflow.com/questions/1130698/checking-if-an-object-is-a-number-in-c-sharp"/>
  166. public static bool IsNumber(object value) => value is sbyte
  167. || value is byte
  168. || value is short
  169. || value is ushort
  170. || value is int
  171. || value is uint
  172. || value is long
  173. || value is ulong
  174. || value is float
  175. || value is double
  176. || value is decimal;
  177. public static double Distance3D(Vector3 xo, Vector3 xa) => Vector3.Distance(xo, xa);
  178. /// <summary>
  179. /// Real-time collision 3D Raycast on plane + barycentric calculation
  180. /// </summary>
  181. /// <param name="R">ray with origin + given direction</param>
  182. /// <param name="a">vertex A</param>
  183. /// <param name="b">vertex B</param>
  184. /// <param name="c">vertex C</param>
  185. /// <param name="barycentric">out param for barycentric vector</param>
  186. /// <returns>0 - not applicable, 1- intersects;</returns>
  187. public static int RayIntersection3D(Ray R, Vector3 a, Vector3 b, Vector3 c, out Vector3 barycentric)
  188. {
  189. barycentric = Vector3.Zero;
  190. var p1 = b - a;
  191. var p2 = c - a;
  192. var lhs = Vector3.Cross(p1, p2);
  193. if (lhs == Vector3.Zero)
  194. return -1;
  195. var direction = R.Direction;
  196. var rhs = R.Position - a;
  197. var dot00 = -Vector3.Dot(lhs, rhs);
  198. var dot01 = Vector3.Dot(lhs, direction);
  199. if (Math.Abs(dot01) < 1E-08f)
  200. if (dot00 == 0f)
  201. return 2;
  202. else return 0;
  203. else
  204. {
  205. var dot02 = dot00 / dot01;
  206. if (dot02 < 0.0)
  207. return 0;
  208. barycentric = R.Position + dot02 * direction;
  209. var dot10 = Vector3.Dot(p1, p1);
  210. var dot11 = Vector3.Dot(p1, p2);
  211. var dot12 = Vector3.Dot(p2, p2);
  212. var lhs2 = barycentric - a;
  213. var dot21 = Vector3.Dot(lhs2, p1);
  214. var dot22 = Vector3.Dot(lhs2, p2);
  215. var dot30 = dot11 * dot11 - dot10 * dot12;
  216. var dot31 = (dot11 * dot22 - dot12 * dot21) / dot30;
  217. if (dot31 < 0.0 || dot31 > 1.0)
  218. return 0;
  219. var dot32 = (dot11 * dot21 - dot10 * dot22) / dot30;
  220. if (dot32 < 0.0 || (dot31 + dot32) > 1.0)
  221. return 0;
  222. return 1;
  223. }
  224. }
  225. public static BoundingBox GetBoundingBox(Vector3 a, Vector3 b, Vector3 c)
  226. {
  227. var Minx = (new float[] { a.X, b.X, c.X }).Min();
  228. var Maxx = (new float[] { a.X, b.X, c.X }).Max();
  229. var Miny = (new float[] { a.Y, b.Y, c.Y }).Min();
  230. var Maxy = (new float[] { a.Y, b.Y, c.Y }).Max();
  231. var Minz = (new float[] { a.Z, b.Z, c.Z }).Min();
  232. var Maxz = (new float[] { a.Z, b.Z, c.Z }).Max();
  233. var min = new Vector3(Minx, Miny, Minz);
  234. var max = new Vector3(Maxx, Maxy, Maxz);
  235. return new BoundingBox(min, max);
  236. }
  237. /// <summary>
  238. /// Provides VertexPositionTexture[] based on translatePosition and the scale. Result is plane geometry - 4 verts, 2 tris with UV of 0.0-1.0f
  239. /// </summary>
  240. /// <param name="translatePosition"></param>
  241. /// <param name="scale"></param>
  242. /// <returns></returns>
  243. public static VertexPositionTexture[] GetShadowPlane(Vector3 translatePosition, float scale=1f)
  244. {
  245. /*
  246. * THREE----ZERO
  247. * | |
  248. * | |
  249. * | |
  250. * | |
  251. * TWO------ONE*/
  252. var zero = new Vector3(1f * scale +translatePosition.X , translatePosition.Y, 1f * scale + translatePosition.Z);
  253. var one = new Vector3(1f * scale + translatePosition.X, translatePosition.Y, translatePosition.Z);
  254. var two = translatePosition;
  255. var three = new Vector3(translatePosition.X, translatePosition.Y, 1f * scale + translatePosition.Z);
  256. var vpt = new VertexPositionTexture[]
  257. {
  258. new VertexPositionTexture(zero, new Vector2(1f,1f)),
  259. new VertexPositionTexture(one, new Vector2(1f,0f)),
  260. new VertexPositionTexture(two, Vector2.Zero),
  261. new VertexPositionTexture(two, Vector2.Zero),
  262. new VertexPositionTexture(three, new Vector2(0f,1f)),
  263. new VertexPositionTexture(zero, new Vector2(1f,1f)),
  264. };
  265. return vpt;
  266. }
  267. /// <summary>
  268. /// Some debug text is crashing due to brackets not appearing in chartable. This function removes brackets inside string
  269. /// </summary>
  270. /// <param name="s"></param>
  271. /// <returns></returns>
  272. public static string RemoveBrackets(string s) => s.Replace('{', ' ').Replace('}', ' ');
  273. public static bool GetBit(byte @object, int positionFromRight) => ((@object >> positionFromRight) & 1) > 0;
  274. public static bool GetBit(int @object, int positionFromRight) => ((@object >> positionFromRight) & 1) > 0;
  275. /// <summary>
  276. /// Reads given char[] until null terminator, but returns as byte[]- to be used with FF8String
  277. /// </summary>
  278. /// <param name=""></param>
  279. /// <returns></returns>
  280. public static byte[] GetBinaryString(BinaryReader br)
  281. {
  282. var bb = new List<byte>();
  283. byte b;
  284. while ((b = br.ReadByte()) != 0x00)
  285. bb.Add(b);
  286. return bb.ToArray();
  287. }
  288. public static bool IsLinux
  289. {
  290. get
  291. {
  292. var p = (int)Environment.OSVersion.Platform;
  293. return (p == 4) || (p == 6) || (p == 128);
  294. }
  295. }
  296. public static string GetUnixFullPath(string pt)
  297. {
  298. #if _WINDOWS
  299. return System.IO.Path.GetFullPath(pt.Replace('/', '\\'));
  300. #else
  301. return System.IO.Path.GetFullPath(pt.Replace("\\", "/"));
  302. #endif
  303. }
  304. public static bool In(int _in, Vector2 range) =>
  305. _in >= range.X && _in <= range.Y;
  306. public static bool In(float _in, Vector2 range) => _in >= range.X && _in <= range.Y;
  307. //: false;
  308. public static bool In(int _in, int min, int max) => In(_in, new Vector2(min, max));
  309. public static bool In(float _in, float min, float max) => In(_in, new Vector2(min, max));
  310. public static Matrix GetRotationMatrixX(float angle)
  311. => new Matrix(
  312. 1, 0, 0, 0,
  313. 0, (float)Math.Cos(Radians(angle)), -(float)Math.Sin(Radians(angle)), 0,
  314. 0, (float)Math.Sin(Radians(angle)), (float)Math.Cos(Radians(angle)), 0,
  315. 0, 0, 0, 0);
  316. public static Matrix GetRotationMatrixY(double angle)
  317. => new Matrix(
  318. (float)Math.Cos(Radians(angle)), 0, (float)Math.Sin(Radians(angle)), 0,
  319. 0, 1, 0, 0,
  320. -(float)Math.Sin(Radians(angle)), 0, (float)Math.Cos(Radians(angle)), 0,
  321. 0, 0, 0, 0);
  322. public static Matrix GetRotationMatrixZ(float angle)
  323. => new Matrix(
  324. (float)Math.Cos(Radians(angle)), -(float)Math.Sin(Radians(angle)), 0, 0,
  325. (float)Math.Sin(Radians(angle)), (float)Math.Cos(Radians(angle)), 0, 0,
  326. 0, 0, 1, 0,
  327. 0, 0, 0, 0);
  328. /// <summary>
  329. /// This Matrix operation performs Matrix multiplication and transposing in-place
  330. /// </summary>
  331. /// <param name="a"></param>
  332. /// <param name="b"></param>
  333. /// <returns></returns>
  334. public static Matrix MatrixMultiply_transpose(Matrix a, Matrix b)
  335. => new Matrix(
  336. b.M11 * a.M11 + b.M21 * a.M12 + b.M31 * a.M13, b.M11 * a.M21 + b.M21 * a.M22 + b.M31 * a.M23, b.M11 * a.M31 + b.M21 * a.M32 + b.M31 * a.M33, 0,
  337. b.M12 * a.M11 + b.M22 * a.M12 + b.M32 * a.M13, b.M12 * a.M21 + b.M22 * a.M22 + b.M32 * a.M23, b.M12 * a.M31 + b.M22 * a.M32 + b.M32 * a.M33, 0,
  338. b.M13 * a.M11 + b.M23 * a.M12 + b.M33 * a.M13, b.M13 * a.M21 + b.M23 * a.M22 + b.M33 * a.M23, b.M13 * a.M31 + b.M23 * a.M32 + b.M33 * a.M33, 0,
  339. 0, 0, 0, 0);
  340. //This is the first time I had issue with precision. Cosinus from 270o was different for float and double. MathHelper is broken...
  341. public static double Radians(double angle) => angle * Math.PI / 180;
  342. public static double Cos(double angle) => Math.Cos(Radians(angle));
  343. public static double Sin(double angle) => Math.Sin(Radians(angle));
  344. /// <summary>
  345. /// Converts short to float via x/4096f
  346. /// </summary>
  347. /// <param name="x"></param>
  348. /// <returns></returns>
  349. public static float S16ToFloat(short x) => x / 4096f;
  350. public static string GetLanguageShort(bool bUseAlternative = false)
  351. {
  352. var languageIndicator = Memory.Languages.ToString();
  353. return bUseAlternative ? languageIndicator == "en" ? "us" : languageIndicator : languageIndicator;
  354. }
  355. public static Vector3 ShrinkVector4ToVector3(Vector4 reference, bool bMirrorY = false)
  356. {
  357. float x, y, z;
  358. reference.Deconstruct(out x, out y, out z, out _);
  359. if (bMirrorY)
  360. y = -y;
  361. return new Vector3(x, y, z);
  362. }
  363. /// <summary>
  364. /// Converts short to float via x/4096f
  365. /// </summary>
  366. /// <param name="x">mockup of short. I.e. short(50) -> float(50) -> 50.0f / 4096f</param>
  367. /// <returns></returns>
  368. public static float S16ToFloat(float x) => x / 4096f;
  369. /// <summary>
  370. /// Converts Vector3 containing direct short>float to Vector3 that XYZ are treated by S16ToFloat
  371. /// </summary>
  372. /// <param name="vec"></param>
  373. /// <returns></returns>
  374. public static Vector3 S16VectorToFloat(Vector3 vec) => new Vector3(
  375. S16ToFloat(vec.X),
  376. S16ToFloat(vec.Y),
  377. S16ToFloat(vec.Z));
  378. public static ushort UshortLittleEndian(ushort ushort_)
  379. => (ushort)((ushort_ << 8) | (ushort_ >> 8));
  380. public static short ShortLittleEndian(short ushort_)
  381. => (short)((ushort_ << 8) | (ushort_ >> 8));
  382. public static uint UintLittleEndian(uint uint_)
  383. => (uint_ << 24) | ((uint_ << 8) & 0x00FF0000) |
  384. ((uint_ >> 8) & 0x0000FF00) | (uint_ >> 24);
  385. public static int UintLittleEndian(int uint_)
  386. => (uint_ << 24) | ((uint_ << 8) & 0x00FF0000) |
  387. ((uint_ >> 8) & 0x0000FF00) | (uint_ >> 24);
  388. public static int ClampOverload(int a, int min, int max)
  389. => a < min ? max - Math.Abs(a) : a > max ? a - max : a;
  390. public static float ClampOverload(float a, float min, float max)
  391. => a < min ? max - Math.Abs(a) : a > max ? a - max : a;
  392. public static bool bRequestedBackBuffer = false;
  393. public static bool bBackBufferAvailable = false;
  394. private static Texture2D backBufferTexture;
  395. public static Texture2D BackBufferTexture
  396. {
  397. get
  398. {
  399. if (bBackBufferAvailable)
  400. {
  401. bBackBufferAvailable = false;
  402. return backBufferTexture;
  403. }
  404. else
  405. {
  406. return null;
  407. }
  408. }
  409. set
  410. {
  411. backBufferTexture = value;
  412. }
  413. }
  414. /// <summary>
  415. /// Makes a request AFTER base.draw() to dump the backBuffer- after that the callback happens to
  416. /// postBackBufferDelegate, so make sure to set this parameter to method you want
  417. /// </summary>
  418. /// <param name="callbackMethod"></param>
  419. public static void RequestBackBuffer()
  420. {
  421. bBackBufferAvailable = false;
  422. bRequestedBackBuffer = true;
  423. }
  424. public delegate void PostBackBufferDelegate();
  425. public static PostBackBufferDelegate postBackBufferDelegate = EmptyPostBackBufferDelegate;
  426. public static void EmptyPostBackBufferDelegate()
  427. {
  428. ;
  429. }
  430. /// <summary>
  431. /// This void converts vertexPositionTexture[] so it's texture coordinates
  432. /// are changing based on requested sprite (atlas)
  433. /// </summary>
  434. /// <param name="vpt"></param>
  435. /// <param name="maxSprite"></param>
  436. /// <param name="current"></param>
  437. public static void ConvertToSprite(ref VertexPositionTexture[] vpt, int maxSprite, int current)
  438. {
  439. float spriteLength = vpt.Max(x => x.TextureCoordinate.X) / maxSprite;
  440. float startAt = current * spriteLength;
  441. for(int i = 0; i<vpt.Length; i++)
  442. {
  443. if (vpt[i].TextureCoordinate.X == 0)
  444. vpt[i].TextureCoordinate.X = startAt;
  445. if (vpt[i].TextureCoordinate.X == 1)
  446. vpt[i].TextureCoordinate.X = startAt + spriteLength;
  447. }
  448. }
  449. }
  450. }