Magically import images and more into Gamecraft as blocks
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

339 lines
13KB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Reflection;
  5. using System.Runtime.CompilerServices;
  6. using RobocraftX.Common;
  7. using Newtonsoft.Json;
  8. using Unity.Mathematics;
  9. using UnityEngine;
  10. using GamecraftModdingAPI.Blocks;
  11. using GamecraftModdingAPI.Utility;
  12. using GamecraftModdingAPI;
  13. using Pixi.Common;
  14. namespace Pixi.Robots
  15. {
  16. public static class CubeUtility
  17. {
  18. private static Dictionary<uint, string> map = null;
  19. private static Dictionary<uint, BlockJsonInfo[]> blueprintMap = null;
  20. public static RobotStruct? ParseRobotInfo(string robotInfo)
  21. {
  22. try
  23. {
  24. return JsonConvert.DeserializeObject<RobotStruct>(robotInfo);
  25. }
  26. catch (Exception e)
  27. {
  28. Logging.MetaLog(e);
  29. return null;
  30. }
  31. }
  32. public static CubeInfo[] ParseCubes(RobotStruct robot)
  33. {
  34. return ParseCubes(robot.cubeData, robot.colourData);
  35. }
  36. public static CubeInfo[] ParseCubes(string cubeData, string colourData)
  37. {
  38. BinaryBufferReader cubes = new BinaryBufferReader(Convert.FromBase64String(cubeData), 0);
  39. BinaryBufferReader colours = new BinaryBufferReader(Convert.FromBase64String(colourData), 0);
  40. uint cubeCount = cubes.ReadUint();
  41. uint colourCount = colours.ReadUint();
  42. if (cubeCount != colourCount)
  43. {
  44. Logging.MetaLog("Something is fucking broken");
  45. return null;
  46. }
  47. Logging.MetaLog($"Detected {cubeCount} cubes");
  48. CubeInfo[] result = new CubeInfo[cubeCount];
  49. for (int cube = 0; cube < cubeCount; cube++)
  50. {
  51. result[cube] = TranslateSpacialEnumerations(
  52. cubes.ReadUint(),
  53. cubes.ReadByte(),
  54. cubes.ReadByte(),
  55. cubes.ReadByte(),
  56. cubes.ReadByte(),
  57. colours.ReadByte(),
  58. colours.ReadByte(),
  59. colours.ReadByte(),
  60. colours.ReadByte()
  61. );
  62. }
  63. return result;
  64. }
  65. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  66. public static CubeInfo TranslateSpacialEnumerations(uint cubeId, byte x, byte y, byte z, byte rotation, byte colour, byte colour_x, byte colour_y, byte colour_z)
  67. {
  68. if (x != colour_x || z != colour_z || y != colour_y) return default;
  69. CubeInfo result = new CubeInfo {visible = true, cubeId = cubeId, scale = new float3(1, 1, 1)};
  70. TranslateBlockColour(colour, ref result);
  71. TranslateBlockPosition(x, y, z, ref result);
  72. TranslateBlockRotation(rotation, ref result);
  73. TranslateBlockId(cubeId, ref result);
  74. #if DEBUG
  75. Logging.MetaLog($"Cube {cubeId} ({x}, {y}, {z}) rot:{rotation} decoded as {result.block} {result.position} rot: {result.rotation} color: {result.color} {result.darkness}");
  76. #endif
  77. return result;
  78. }
  79. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  80. private static void TranslateBlockRotation(byte rotation, ref CubeInfo result)
  81. {
  82. // face refers to the face of the block connected to the bottom of the current one
  83. // nvm, they're all incorrect
  84. switch (rotation)
  85. {
  86. case 0:
  87. result.rotation = new float3(0, 0, 0); // top face, forwards
  88. break;
  89. case 1:
  90. result.rotation = new float3(0, 0, 90); // left face, forwards
  91. break;
  92. case 2:
  93. result.rotation = new float3(0, 0, 180); // bottom face, forwards
  94. break;
  95. case 3:
  96. result.rotation = new float3(0, 0, -90); // front face, down
  97. break;
  98. case 4:
  99. result.rotation = new float3(0, 90, 0); // top face, right
  100. break;
  101. case 5:
  102. result.rotation = new float3(0, 90, 90); // front face, right
  103. break;
  104. case 6:
  105. result.rotation = new float3(-90, -90, 0); // right face, backwards
  106. break;
  107. case 7:
  108. result.rotation = new float3(0, 90, -90); // back face, right
  109. break;
  110. case 8:
  111. result.rotation = new float3(0, -90, 90); // back face, left
  112. break;
  113. case 9:
  114. result.rotation = new float3(0, -90, -90); // front face, left
  115. break;
  116. case 10:
  117. result.rotation = new float3(90, -90, 0); // left face, down
  118. break;
  119. case 11:
  120. result.rotation = new float3(90, 90, 0); // right face, forwards
  121. break;
  122. case 12:
  123. result.rotation = new float3(-90, 90, 0); // left face, up
  124. break;
  125. case 13:
  126. result.rotation = new float3(0, 90, 180); // bottom face, right
  127. break;
  128. case 14:
  129. result.rotation = new float3(0, 180, 0); // top face, backwards
  130. break;
  131. case 15:
  132. result.rotation = new float3(0, 180, 90); // right face, up
  133. break;
  134. case 16:
  135. result.rotation = new float3(0, 180, 180); // bottom face, backwards
  136. break;
  137. case 17:
  138. result.rotation = new float3(0, 180, -90); // left face, backwards
  139. break;
  140. case 18:
  141. result.rotation = new float3(0, -90, 0); // top face, left
  142. break;
  143. case 19:
  144. result.rotation = new float3(0, -90, 180); // bottom face, left
  145. break;
  146. case 20:
  147. result.rotation = new float3(90, 0, 0); // front face, down
  148. break;
  149. case 21:
  150. result.rotation = new float3(90, 180, 0); // back face, down
  151. break;
  152. case 22:
  153. result.rotation = new float3(-90, 0, 0); // back face, up
  154. break;
  155. case 23:
  156. result.rotation = new float3(-90, 180, 0); // front face, up
  157. break;
  158. default:
  159. #if DEBUG
  160. Logging.MetaLog($"Unknown rotation {rotation.ToString("X2")}");
  161. #endif
  162. result.rotation = float3.zero;
  163. break;
  164. }
  165. // my brain hurts after figuring out all of those rotations
  166. // I wouldn't recommend trying to redo this
  167. }
  168. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  169. private static void TranslateBlockPosition(byte x, byte y, byte z, ref CubeInfo result)
  170. {
  171. // for some reason, z is forwards in garage bays
  172. result.position = new float3(x, y, z);
  173. }
  174. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  175. private static void TranslateBlockColour(byte colour, ref CubeInfo result)
  176. {
  177. // I hope these colours are accurate, I just guessed
  178. // TODO colour accuracy (lol that won't ever happen)
  179. #if DEBUG
  180. Logging.MetaLog($"Cube colour {colour}");
  181. #endif
  182. BlockColor c = ColorSpaceUtility.QuantizeToBlockColor(colour);
  183. result.color = c.Color;
  184. result.darkness = c.Darkness;
  185. }
  186. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  187. public static string CubeIdDescription(uint cubeId)
  188. {
  189. if (map == null)
  190. {
  191. StreamReader cubemap = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Pixi.cubes-id.json"));
  192. map = JsonConvert.DeserializeObject<Dictionary<uint, string>>(cubemap.ReadToEnd());
  193. }
  194. if (!map.ContainsKey(cubeId))
  195. {
  196. return "Unknown cube #" + cubeId.ToString();
  197. //result.rotation = float3.zero;
  198. }
  199. return map[cubeId];
  200. }
  201. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  202. private static void TranslateBlockId(uint cubeId, ref CubeInfo result)
  203. {
  204. if (map == null)
  205. {
  206. StreamReader cubemap = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Pixi.cubes-id.json"));
  207. map = JsonConvert.DeserializeObject<Dictionary<uint, string>>(cubemap.ReadToEnd());
  208. }
  209. if (!map.ContainsKey(cubeId))
  210. {
  211. result.block = BlockIDs.TextBlock;
  212. result.name = "Unknown cube #" + cubeId.ToString();
  213. //result.rotation = float3.zero;
  214. #if DEBUG
  215. Logging.MetaLog($"Unknown cubeId {cubeId}");
  216. #endif
  217. }
  218. string cubeName = map[cubeId];
  219. string gcName = cubeName.Contains("glass") || cubeName.Contains("windshield")
  220. ? "Glass"
  221. : "Aluminium";
  222. if (cubeName.Contains("round"))
  223. gcName += "Rounded";
  224. if (cubeName.Contains("cube"))
  225. gcName += "Cube";
  226. else if (cubeName.Contains("prism") || cubeName.Contains("edge"))
  227. gcName += "Slope";
  228. else if (cubeName.Contains("inner"))
  229. gcName += "SlicedCube";
  230. else if (cubeName.Contains("tetra") || cubeName.Contains("corner"))
  231. gcName += "Corner";
  232. else if (cubeName.Contains("pyramid"))
  233. gcName += "PyramidSegment";
  234. else if (cubeName.Contains("cone"))
  235. gcName += "ConeSegment";
  236. else
  237. {
  238. result.block = BlockIDs.TextBlock;
  239. result.name = cubeName;
  240. return;
  241. }
  242. BlockIDs id = VoxelObjectNotationUtility.NameToEnum(gcName);
  243. result.block = id;
  244. }
  245. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  246. public static Block[] BuildBlueprintOrTextBlock(CubeInfo cube, float3 actualPosition, int scale = 3)
  247. {
  248. // actualPosition is the middle of the cube
  249. if (blueprintMap == null) LoadBlueprintMap();
  250. if (!blueprintMap.ContainsKey(cube.cubeId) || scale != 3)
  251. {
  252. #if DEBUG
  253. Logging.LogWarning($"Missing blueprint for {cube.name} (id:{cube.cubeId}), substituting {cube.block}");
  254. #endif
  255. return new Block[] { Block.PlaceNew(cube.block, actualPosition, cube.rotation, cube.color, cube.darkness, scale: cube.scale) };
  256. }
  257. #if DEBUG
  258. Logging.MetaLog($"Found blueprint for {cube.name} (id:{cube.cubeId})");
  259. #endif
  260. Quaternion cubeQuaternion = Quaternion.Euler(cube.rotation);
  261. BlockJsonInfo[] blueprint = blueprintMap[cube.cubeId];
  262. if (blueprint.Length == 0)
  263. {
  264. Logging.LogWarning($"Found empty blueprint for {cube.name} (id:{cube.cubeId}), is the blueprint correct?");
  265. return new Block[0];
  266. }
  267. float3 defaultCorrectionVec = new float3((float)(0), (float)(CommandRoot.BLOCK_SIZE), (float)(0));
  268. float3 baseRot = new float3(blueprint[0].rotation[0], blueprint[0].rotation[1], blueprint[0].rotation[2]);
  269. float3 baseScale = new float3(blueprint[0].scale[0], blueprint[0].scale[1], blueprint[0].scale[2]);
  270. Block[] placedBlocks = new Block[blueprint.Length];
  271. bool isBaseScaled = !(blueprint[0].scale[1] > 0f && blueprint[0].scale[1] < 2f);
  272. float3 correctionVec = isBaseScaled ? (float3)(Quaternion.Euler(baseRot) * baseScale / 2) * (float)-CommandRoot.BLOCK_SIZE : -defaultCorrectionVec;
  273. // FIXME scaled base blocks cause the blueprint to be placed in the wrong location (this also could be caused by a bug in DumpVON command)
  274. if (isBaseScaled)
  275. {
  276. Logging.LogWarning($"Found blueprint with scaled base block for {cube.name} (id:{cube.cubeId}), this is not currently supported");
  277. }
  278. for (int i = 0; i < blueprint.Length; i++)
  279. {
  280. BlockColor blueprintBlockColor = ColorSpaceUtility.QuantizeToBlockColor(blueprint[i].color);
  281. BlockColors blockColor = blueprintBlockColor.Color == BlockColors.White && blueprintBlockColor.Darkness == 0 ? cube.color : blueprintBlockColor.Color;
  282. byte blockDarkness = blueprintBlockColor.Color == BlockColors.White && blueprintBlockColor.Darkness == 0 ? cube.darkness : blueprintBlockColor.Darkness;
  283. float3 bluePos = new float3(blueprint[i].position[0], blueprint[i].position[1], blueprint[i].position[2]);
  284. float3 blueScale = new float3(blueprint[i].scale[0], blueprint[i].scale[1], blueprint[i].scale[2]);
  285. float3 blueRot = new float3(blueprint[i].rotation[0], blueprint[i].rotation[1], blueprint[i].rotation[2]);
  286. float3 physicalLocation = (float3)(cubeQuaternion * bluePos) + actualPosition;// + (blueprintSizeRotated / 2);
  287. //physicalLocation.x += blueprintSize.x / 2;
  288. physicalLocation += (float3)(cubeQuaternion * (correctionVec));
  289. //physicalLocation.y -= (float)(RobotCommands.blockSize * scale / 2);
  290. //float3 physicalScale = (float3)(cubeQuaternion * blueScale); // this actually over-rotates when combined with rotation
  291. float3 physicalScale = blueScale;
  292. float3 physicalRotation = (cubeQuaternion * Quaternion.Euler(blueRot)).eulerAngles;
  293. #if DEBUG
  294. Logging.MetaLog($"Placing blueprint block at {physicalLocation} rot{physicalRotation} scale{physicalScale}");
  295. Logging.MetaLog($"Location math check original:{bluePos} rotated: {(float3)(cubeQuaternion * bluePos)} actualPos: {actualPosition} result: {physicalLocation}");
  296. Logging.MetaLog($"Scale math check original:{blueScale} rotation: {(float3)cubeQuaternion.eulerAngles} result: {physicalScale}");
  297. Logging.MetaLog($"Rotation math check original:{blueRot} rotated: {(cubeQuaternion * Quaternion.Euler(blueRot))} result: {physicalRotation}");
  298. #endif
  299. placedBlocks[i] = Block.PlaceNew(VoxelObjectNotationUtility.NameToEnum(blueprint[i].name),
  300. physicalLocation,
  301. physicalRotation,
  302. blockColor,
  303. blockDarkness,
  304. scale: physicalScale);
  305. }
  306. #if DEBUG
  307. Logging.MetaLog($"Placed {placedBlocks.Length} blocks for blueprint {cube.name} (id:{cube.cubeId})");
  308. #endif
  309. return placedBlocks;
  310. }
  311. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  312. private static void LoadBlueprintMap()
  313. {
  314. StreamReader bluemap = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Pixi.blueprints.json"));
  315. blueprintMap = JsonConvert.DeserializeObject<Dictionary<uint, BlockJsonInfo[]>>(bluemap.ReadToEnd());
  316. }
  317. }
  318. }