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.

653 lines
28KB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Runtime.CompilerServices;
  6. using System.Threading;
  7. using UnityEngine;
  8. using Unity.Mathematics;
  9. using Svelto.ECS;
  10. using GamecraftModdingAPI;
  11. using GamecraftModdingAPI.Blocks;
  12. using GamecraftModdingAPI.Commands;
  13. using GamecraftModdingAPI.Utility;
  14. using Svelto.DataStructures;
  15. namespace Pixi.Common
  16. {
  17. /// <summary>
  18. /// Command implementation.
  19. /// CommandRoot.Pixi is the root of all Pixi calls from the CLI
  20. /// </summary>
  21. public class CommandRoot : ICustomCommandEngine
  22. {
  23. public void Ready()
  24. {
  25. CommandRegistrationHelper.Register<string>(Name, (name) => tryOrCommandLogError(() => this.Pixi(null, name)), Description);
  26. CommandRegistrationHelper.Register<string, string>(Name+"2", this.Pixi, "Import something into Gamecraft using magic. Usage: Pixi \"importer\" \"myfile.png\"");
  27. }
  28. public EntitiesDB entitiesDB { get; set; }
  29. public void Dispose()
  30. {
  31. CommandRegistrationHelper.Unregister(Name);
  32. CommandRegistrationHelper.Unregister(Name+"2");
  33. }
  34. public string Name { get; } = "Pixi";
  35. public bool isRemovable { get; } = false;
  36. public string Description { get; } = "Import something into Gamecraft using magic. Usage: Pixi \"myfile.png\"";
  37. public Dictionary<int, Importer[]> importers = new Dictionary<int, Importer[]>();
  38. public static ThreadSafeDictionary<int, bool> optimisableBlockCache = new ThreadSafeDictionary<int, bool>();
  39. public const float BLOCK_SIZE = 0.2f;
  40. public const float DELTA = BLOCK_SIZE / 2048;
  41. public static int OPTIMISATION_PASSES = 2;
  42. public static int GROUP_SIZE = 32;
  43. // optimisation algorithm constants
  44. private static float3[] cornerMultiplicands1 = new float3[8]
  45. {
  46. new float3(1, 1, 1),
  47. new float3(1, 1, -1),
  48. new float3(-1, 1, 1),
  49. new float3(-1, 1, -1),
  50. new float3(-1, -1, 1),
  51. new float3(-1, -1, -1),
  52. new float3(1, -1, 1),
  53. new float3(1, -1, -1),
  54. };
  55. private static float3[] cornerMultiplicands2 = new float3[8]
  56. {
  57. new float3(1, 1, 1),
  58. new float3(1, 1, -1),
  59. new float3(1, -1, 1),
  60. new float3(1, -1, -1),
  61. new float3(-1, 1, 1),
  62. new float3(-1, 1, -1),
  63. new float3(-1, -1, 1),
  64. new float3(-1, -1, -1),
  65. };
  66. private static int[][] cornerFaceMappings = new int[][]
  67. {
  68. new int[] {0, 1, 2, 3}, // top
  69. new int[] {2, 3, 4, 5}, // left
  70. new int[] {4, 5, 6, 7}, // bottom
  71. new int[] {6, 7, 0, 1}, // right
  72. new int[] {0, 2, 4, 6}, // back
  73. new int[] {1, 3, 5, 7}, // front
  74. };
  75. private static int[][] oppositeFaceMappings = new int[][]
  76. {
  77. new int[] {6, 7, 4, 5}, // bottom
  78. new int[] {0, 1, 6, 7}, // right
  79. new int[] {2, 3, 0, 1}, // top
  80. new int[] {4, 5, 2, 3}, // left
  81. new int[] {1, 3, 5, 7}, // front
  82. new int[] {0, 2, 4, 6}, // back
  83. };
  84. public CommandRoot()
  85. {
  86. CommandManager.AddCommand(this);
  87. }
  88. public void Inject(Importer imp)
  89. {
  90. if (importers.ContainsKey(imp.Priority))
  91. {
  92. // extend array by 1 and place imp at the end
  93. Importer[] oldArr = importers[imp.Priority];
  94. Importer[] newArr = new Importer[oldArr.Length + 1];
  95. for (int i = 0; i < oldArr.Length; i++)
  96. {
  97. newArr[i] = oldArr[i];
  98. }
  99. newArr[oldArr.Length] = imp;
  100. importers[imp.Priority] = newArr;
  101. }
  102. else
  103. {
  104. importers[imp.Priority] = new Importer[] {imp};
  105. }
  106. }
  107. private void Pixi(string importerName, string name)
  108. {
  109. // organise priorities
  110. int[] priorities = importers.Keys.ToArray();
  111. Array.Sort(priorities);
  112. Array.Reverse(priorities); // higher priorities go first
  113. // find relevant importer
  114. Importer magicImporter = null;
  115. foreach (int p in priorities)
  116. {
  117. Importer[] imps = importers[p];
  118. for (int i = 0; i < imps.Length; i++)
  119. {
  120. //Logging.MetaLog($"Now checking importer {imps[i].Name}");
  121. if ((importerName == null && imps[i].Qualifies(name))
  122. || (importerName != null && imps[i].Name.Contains(importerName)))
  123. {
  124. magicImporter = imps[i];
  125. break;
  126. }
  127. }
  128. if (magicImporter != null) break;
  129. }
  130. if (magicImporter == null)
  131. {
  132. Logging.CommandLogError("Unsupported file or string.");
  133. return;
  134. }
  135. #if DEBUG
  136. Logging.MetaLog($"Using '{magicImporter.Name}' to import '{name}'");
  137. #endif
  138. // import blocks
  139. BlockJsonInfo[] blocksInfo = magicImporter.Import(name);
  140. if (blocksInfo == null || blocksInfo.Length == 0)
  141. {
  142. #if DEBUG
  143. Logging.CommandLogError($"Importer {magicImporter.Name} didn't provide any blocks to import. Mission Aborted!");
  144. #endif
  145. return;
  146. }
  147. ProcessedVoxelObjectNotation[][] procVONs;
  148. BlueprintProvider blueprintProvider = magicImporter.BlueprintProvider;
  149. if (blueprintProvider == null)
  150. {
  151. // convert block info to API-compatible format
  152. procVONs = new ProcessedVoxelObjectNotation[][] {BlueprintUtility.ProcessBlocks(blocksInfo)};
  153. }
  154. else
  155. {
  156. // expand blueprints and convert block info
  157. procVONs = BlueprintUtility.ProcessAndExpandBlocks(name, blocksInfo, magicImporter.BlueprintProvider);
  158. }
  159. // reduce block placements by grouping neighbouring similar blocks
  160. // (after flattening block data representation)
  161. List<ProcessedVoxelObjectNotation> optVONs = new List<ProcessedVoxelObjectNotation>();
  162. for (int arr = 0; arr < procVONs.Length; arr++)
  163. {
  164. for (int elem = 0; elem < procVONs[arr].Length; elem++)
  165. {
  166. optVONs.Add(procVONs[arr][elem]);
  167. }
  168. }
  169. #if DEBUG
  170. Logging.MetaLog($"Imported {optVONs.Count} blocks for '{name}'");
  171. #endif
  172. int blockCountPreOptimisation = optVONs.Count;
  173. if (magicImporter.Optimisable)
  174. {
  175. for (int pass = 0; pass < OPTIMISATION_PASSES; pass++)
  176. {
  177. OptimiseBlocks(ref optVONs, (pass + 1) * GROUP_SIZE);
  178. #if DEBUG
  179. Logging.MetaLog($"Optimisation pass {pass} completed");
  180. #endif
  181. }
  182. #if DEBUG
  183. Logging.MetaLog($"Optimised down to {optVONs.Count} blocks for '{name}'");
  184. #endif
  185. }
  186. ProcessedVoxelObjectNotation[] optVONsArr = optVONs.ToArray();
  187. magicImporter.PreProcess(name, ref optVONsArr);
  188. // place blocks
  189. Block[] blocks = new Block[optVONsArr.Length];
  190. for (int i = 0; i < optVONsArr.Length; i++)
  191. {
  192. ProcessedVoxelObjectNotation desc = optVONsArr[i];
  193. if (desc.block != BlockIDs.Invalid)
  194. {
  195. Block b = Block.PlaceNew(desc.block, desc.position, desc.rotation, desc.color.Color,
  196. desc.color.Darkness, 1, desc.scale);
  197. blocks[i] = b;
  198. }
  199. #if DEBUG
  200. else
  201. {
  202. Logging.LogWarning($"Found invalid block at index {i}\n\t{optVONsArr[i].ToString()}");
  203. }
  204. #endif
  205. }
  206. // handle special block parameters
  207. PostProcessSpecialBlocks(ref optVONsArr, ref blocks);
  208. // post processing
  209. magicImporter.PostProcess(name, ref blocks);
  210. if (magicImporter.Optimisable && blockCountPreOptimisation > blocks.Length)
  211. {
  212. Logging.CommandLog($"Imported {blocks.Length} blocks using {magicImporter.Name} ({blockCountPreOptimisation/blocks.Length}x ratio)");
  213. }
  214. else
  215. {
  216. Logging.CommandLog($"Imported {blocks.Length} blocks using {magicImporter.Name}");
  217. }
  218. }
  219. private void OptimiseBlocks(ref List<ProcessedVoxelObjectNotation> optVONs, int chunkSize)
  220. {
  221. // Reduce blocks to place to reduce lag while placing and from excessive blocks in the world.
  222. // Blocks are reduced by grouping similar blocks that are touching (before they're placed)
  223. // multithreaded because this is an expensive (slow) operation
  224. int item = 0;
  225. ProcessedVoxelObjectNotation[][] groups = new ProcessedVoxelObjectNotation[optVONs.Count / chunkSize][];
  226. Thread[] tasks = new Thread[groups.Length];
  227. while (item < groups.Length)
  228. {
  229. groups[item] = new ProcessedVoxelObjectNotation[chunkSize];
  230. optVONs.CopyTo(item * chunkSize, groups[item], 0, chunkSize);
  231. int tmpItem = item; // scope is dumb
  232. tasks[item] = new Thread(() =>
  233. {
  234. groups[tmpItem] = groupBlocksBestEffort(groups[tmpItem], tmpItem);
  235. });
  236. tasks[item].Start();
  237. item++;
  238. }
  239. #if DEBUG
  240. Logging.MetaLog($"Created {groups.Length} + 1? groups");
  241. #endif
  242. // final group
  243. ProcessedVoxelObjectNotation[] finalGroup = null;
  244. Thread finalThread = null;
  245. if (optVONs.Count > item * chunkSize)
  246. {
  247. //finalGroup = optVONs.GetRange(item * GROUP_SIZE, optVONs.Count - (item * GROUP_SIZE)).ToArray();
  248. finalGroup = new ProcessedVoxelObjectNotation[optVONs.Count - (item * chunkSize)];
  249. optVONs.CopyTo(item * chunkSize, finalGroup, 0, optVONs.Count - (item * chunkSize));
  250. finalThread = new Thread(() =>
  251. {
  252. finalGroup = groupBlocksBestEffort(finalGroup, -1);
  253. });
  254. finalThread.Start();
  255. }
  256. // gather results
  257. List<ProcessedVoxelObjectNotation> result = new List<ProcessedVoxelObjectNotation>();
  258. for (int i = 0; i < groups.Length; i++)
  259. {
  260. #if DEBUG
  261. Logging.MetaLog($"Waiting for completion of task {i}");
  262. #endif
  263. tasks[i].Join();
  264. result.AddRange(groups[i]);
  265. }
  266. if (finalThread != null)
  267. {
  268. #if DEBUG
  269. Logging.MetaLog($"Waiting for completion of final task");
  270. #endif
  271. finalThread.Join();
  272. result.AddRange(finalGroup);
  273. }
  274. optVONs = result;
  275. }
  276. private static ProcessedVoxelObjectNotation[] groupBlocksBestEffort(ProcessedVoxelObjectNotation[] blocksToOptimise, int id)
  277. {
  278. // a really complicated algorithm to determine if two similar blocks are touching (before they're placed)
  279. // the general concept:
  280. // two blocks are touching when they have a common face (equal to 4 corners on the cube, where the 4 corners aren't completely opposite each other)
  281. // between the two blocks, the 8 corners that aren't in common are the corners for the merged block
  282. //
  283. // to merge the 2 blocks, switch out the 4 common corners of one block with the nearest non-common corners from the other block
  284. // i.e. swap the common face on block A with the face opposite the common face of block B
  285. // to prevent a nonsensical face (rotated compared to other faces), the corners of the face should be swapped out with the corresponding corner which shares an edge
  286. //
  287. // note: e.g. if common face on block A is its top, the common face of block B is not necessarily the bottom face because blocks can be rotated differently
  288. // this means it's not safe to assume that block A's common face (top) can be swapped with block B's non-common opposite face (top) to get the merged block
  289. //
  290. // note2: this does not work with blocks which aren't cubes (i.e. any block where rotation matters)
  291. try
  292. {
  293. #if DEBUG
  294. Stopwatch timer = Stopwatch.StartNew();
  295. #endif
  296. FasterList<ProcessedVoxelObjectNotation> optVONs = new FasterList<ProcessedVoxelObjectNotation>(blocksToOptimise);
  297. int item = 0;
  298. while (item < optVONs.count - 1)
  299. {
  300. #if DEBUG
  301. Logging.MetaLog($"({id}) Now grouping item {item}/{optVONs.count} ({100f * item/(float)optVONs.count}%)");
  302. #endif
  303. bool isItemUpdated = false;
  304. ProcessedVoxelObjectNotation itemVON = optVONs[item];
  305. if (isOptimisableBlock(itemVON.block))
  306. {
  307. float3[] itemCorners = calculateCorners(itemVON);
  308. int seeker = item + 1; // despite this, assume that seeker goes thru the entire list (not just blocks after item)
  309. while (seeker < optVONs.count)
  310. {
  311. if (seeker == item)
  312. {
  313. seeker++;
  314. }
  315. else
  316. {
  317. ProcessedVoxelObjectNotation seekerVON = optVONs[seeker];
  318. //Logging.MetaLog($"Comparing {itemVON} and {seekerVON}");
  319. float3[] seekerCorners = calculateCorners(seekerVON);
  320. int[][] mapping = findMatchingCorners(itemCorners, seekerCorners);
  321. if (mapping.Length != 0
  322. && itemVON.block == seekerVON.block
  323. && itemVON.color.Color == seekerVON.color.Color
  324. && itemVON.color.Darkness == seekerVON.color.Darkness
  325. && isOptimisableBlock(seekerVON.block)) // match found
  326. {
  327. // switch out corners based on mapping
  328. //Logging.MetaLog($"Corners {float3ArrToString(itemCorners)}\nand {float3ArrToString(seekerCorners)}");
  329. //Logging.MetaLog($"Mappings (len:{mapping[0].Length}) {mapping[0][0]} -> {mapping[1][0]}\n{mapping[0][1]} -> {mapping[1][1]}\n{mapping[0][2]} -> {mapping[1][2]}\n{mapping[0][3]} -> {mapping[1][3]}\n");
  330. for (byte i = 0; i < 4; i++)
  331. {
  332. itemCorners[mapping[0][i]] = seekerCorners[mapping[1][i]];
  333. }
  334. // remove 2nd block, since it's now part of the 1st block
  335. //Logging.MetaLog($"Removing {seekerVON}");
  336. optVONs.RemoveAt(seeker);
  337. if (seeker < item)
  338. {
  339. item--; // note: this will never become less than 0
  340. }
  341. isItemUpdated = true;
  342. // regenerate info
  343. //Logging.MetaLog($"Final corners {float3ArrToString(itemCorners)}");
  344. updateVonFromCorners(itemCorners, ref itemVON);
  345. itemCorners = calculateCorners(itemVON);
  346. //Logging.MetaLog($"Merged block is {itemVON}");
  347. }
  348. else
  349. {
  350. seeker++;
  351. }
  352. }
  353. }
  354. if (isItemUpdated)
  355. {
  356. optVONs[item] = itemVON;
  357. //Logging.MetaLog($"Optimised block is now {itemVON}");
  358. }
  359. item++;
  360. }
  361. else
  362. {
  363. item++;
  364. }
  365. }
  366. #if DEBUG
  367. timer.Stop();
  368. Logging.MetaLog($"({id}) Completed best effort grouping of range in {timer.ElapsedMilliseconds}ms");
  369. #endif
  370. return optVONs.ToArray();
  371. }
  372. catch (Exception e)
  373. {
  374. Logging.MetaLog($"({id}) Exception occured...\n{e.ToString()}");
  375. }
  376. return blocksToOptimise;
  377. }
  378. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  379. private static float3[] calculateCorners(ProcessedVoxelObjectNotation von)
  380. {
  381. float3[] corners = new float3[8];
  382. Quaternion rotation = Quaternion.Euler(von.rotation);
  383. float3 rotatedScale = rotation * von.scale;
  384. float3 trueCenter = von.position;
  385. // generate corners
  386. for (int i = 0; i < corners.Length; i++)
  387. {
  388. corners[i] = trueCenter + BLOCK_SIZE * (cornerMultiplicands1[i] * rotatedScale / 2);
  389. }
  390. return corners;
  391. }
  392. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  393. private static void updateVonFromCorners(float3[] corners, ref ProcessedVoxelObjectNotation von)
  394. {
  395. float3 newCenter = sumOfFloat3Arr(corners) / corners.Length;
  396. float3 newPosition = newCenter;
  397. Quaternion rot = Quaternion.Euler(von.rotation);
  398. float3 rotatedScale = 2 * (corners[0] - newCenter) / BLOCK_SIZE;
  399. von.scale = Quaternion.Inverse(rot) * rotatedScale;
  400. von.position = newPosition;
  401. //Logging.MetaLog($"Updated VON scale {von.scale} (absolute {rotatedScale})");
  402. }
  403. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  404. private static int[][] findMatchingCorners(float3[] corners1, float3[] corners2)
  405. {
  406. float3[][] faces1 = facesFromCorners(corners1);
  407. float3[][] faces2 = facesFromCorners(corners2);
  408. for (byte i = 0; i < faces1.Length; i++)
  409. {
  410. for (byte j = 0; j < faces2.Length; j++)
  411. {
  412. //Logging.MetaLog($"Checking faces {float3ArrToString(faces1[i])} and {float3ArrToString(faces2[j])}");
  413. int[] match = matchFace(faces1[i], faces2[j]);
  414. if (match.Length != 0)
  415. {
  416. //Logging.MetaLog($"Matched faces {float3ArrToString(faces1[i])} and {float3ArrToString(faces2[j])}");
  417. // translate from face mapping to corner mapping
  418. for (byte k = 0; k < match.Length; k++)
  419. {
  420. match[k] = oppositeFaceMappings[j][match[k]];
  421. }
  422. return new int[][] {cornerFaceMappings[i], match}; // {{itemCorners index}, {seekerCorners index}}
  423. }
  424. }
  425. }
  426. return new int[0][];
  427. }
  428. // this assumes the corners are in the order that calculateCorners outputs
  429. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  430. private static float3[][] facesFromCorners(float3[] corners)
  431. {
  432. return new float3[][]
  433. {
  434. new float3[] {corners[0], corners[1], corners[2], corners[3]}, // top
  435. new float3[] {corners[2], corners[3], corners[4], corners[5]}, // left
  436. new float3[] {corners[4], corners[5], corners[6], corners[7]}, // bottom
  437. new float3[] {corners[6], corners[7], corners[0], corners[1]}, // right
  438. new float3[] {corners[0], corners[2], corners[4], corners[6]}, // back
  439. new float3[] {corners[1], corners[3], corners[5], corners[7]}, // front
  440. };
  441. }
  442. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  443. private static int[] matchFace(float3[] face1, float3[] face2)
  444. {
  445. int[] result = new int[4];
  446. byte count = 0;
  447. for (int i = 0; i < 4; i++)
  448. {
  449. for (int j = 0; j < 4; j++)
  450. {
  451. //Logging.MetaLog($"Comparing {face1[i]} and {face1[i]} ({Mathf.Abs(face1[i].x - face2[j].x)} & {Mathf.Abs(face1[i].y - face2[j].y)} & {Mathf.Abs(face1[i].z - face2[j].z)} vs {DELTA})");
  452. // if (face1[i] == face2[j])
  453. if (Mathf.Abs(face1[i].x - face2[j].x) < DELTA
  454. && Mathf.Abs(face1[i].y - face2[j].y) < DELTA
  455. && Mathf.Abs(face1[i].z - face2[j].z) < DELTA)
  456. {
  457. count++;
  458. result[i] = j; // map corners to each other
  459. break;
  460. }
  461. }
  462. }
  463. //Logging.MetaLog($"matched {count}/4");
  464. if (count == 4)
  465. {
  466. return result;
  467. }
  468. return new int[0];
  469. }
  470. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  471. private static float3 sumOfFloat3Arr(float3[] arr)
  472. {
  473. float3 total = float3.zero;
  474. for (int i = 0; i < arr.Length; i++)
  475. {
  476. total += arr[i];
  477. }
  478. return total;
  479. }
  480. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  481. private static bool isOptimisableBlock(BlockIDs block)
  482. {
  483. if (optimisableBlockCache.ContainsKey((int) block))
  484. {
  485. return optimisableBlockCache[(int) block];
  486. }
  487. bool result = block.ToString().EndsWith("Cube", StringComparison.InvariantCultureIgnoreCase);
  488. optimisableBlockCache[(int) block] = result;
  489. return result;
  490. }
  491. private static void PostProcessSpecialBlocks(ref ProcessedVoxelObjectNotation[] pVONs, ref Block[] blocks)
  492. {
  493. // populate block attributes using metadata field from ProcessedVoxelObjectNotation
  494. for (int i = 0; i < pVONs.Length; i++)
  495. {
  496. switch (pVONs[i].block)
  497. {
  498. case BlockIDs.TextBlock:
  499. string[] textSplit = pVONs[i].metadata.Split('\t');
  500. if (textSplit.Length > 1)
  501. {
  502. TextBlock tb = blocks[i].Specialise<TextBlock>();
  503. tb.Text = textSplit[1];
  504. if (textSplit.Length > 2)
  505. {
  506. tb.TextBlockId = textSplit[2];
  507. }
  508. }
  509. break;
  510. case BlockIDs.ConsoleBlock:
  511. string[] cmdSplit = pVONs[i].metadata.Split('\t');
  512. if (cmdSplit.Length > 1)
  513. {
  514. ConsoleBlock cb = blocks[i].Specialise<ConsoleBlock>();
  515. cb.Command = cmdSplit[1];
  516. if (cmdSplit.Length > 2)
  517. {
  518. cb.Arg1 = cmdSplit[2];
  519. if (cmdSplit.Length > 3)
  520. {
  521. cb.Arg1 = cmdSplit[3];
  522. if (cmdSplit.Length > 4)
  523. {
  524. cb.Arg1 = cmdSplit[4];
  525. }
  526. }
  527. }
  528. }
  529. break;
  530. case BlockIDs.DampedSpring:
  531. string[] springSplit = pVONs[i].metadata.Split('\t');
  532. if (springSplit.Length > 1 && float.TryParse(springSplit[1], out float stiffness))
  533. {
  534. DampedSpring d = blocks[i].Specialise<DampedSpring>();
  535. d.Stiffness = stiffness;
  536. if (springSplit.Length > 2 && float.TryParse(springSplit[2], out float damping))
  537. {
  538. d.Damping = damping;
  539. }
  540. }
  541. break;
  542. case BlockIDs.ServoAxle:
  543. case BlockIDs.ServoHinge:
  544. case BlockIDs.PneumaticAxle:
  545. case BlockIDs.PneumaticHinge:
  546. string[] servoSplit = pVONs[i].metadata.Split('\t');
  547. if (servoSplit.Length > 1 && float.TryParse(servoSplit[1], out float minAngle))
  548. {
  549. Servo s = blocks[i].Specialise<Servo>();
  550. s.MinimumAngle = minAngle;
  551. if (servoSplit.Length > 2 && float.TryParse(servoSplit[2], out float maxAngle))
  552. {
  553. s.MaximumAngle = maxAngle;
  554. if (servoSplit.Length > 3 && float.TryParse(servoSplit[3], out float maxForce))
  555. {
  556. s.MaximumForce = maxForce;
  557. if (servoSplit.Length > 4 && bool.TryParse(servoSplit[4], out bool reverse))
  558. {
  559. s.Reverse = reverse;
  560. }
  561. }
  562. }
  563. }
  564. break;
  565. case BlockIDs.MotorM:
  566. case BlockIDs.MotorS:
  567. string[] motorSplit = pVONs[i].metadata.Split('\t');
  568. if (motorSplit.Length > 1 && float.TryParse(motorSplit[1], out float topSpeed))
  569. {
  570. Motor m = blocks[i].Specialise<Motor>();
  571. m.TopSpeed = topSpeed;
  572. if (motorSplit.Length > 2 && float.TryParse(motorSplit[2], out float torque))
  573. {
  574. m.Torque = torque;
  575. if (motorSplit.Length > 3 && bool.TryParse(motorSplit[3], out bool reverse))
  576. {
  577. m.Reverse = reverse;
  578. }
  579. }
  580. }
  581. break;
  582. default: break; // do nothing
  583. }
  584. }
  585. }
  586. private static string float3ArrToString(float3[] arr)
  587. {
  588. string result = "[";
  589. foreach (float3 f in arr)
  590. {
  591. result += f.ToString() + ", ";
  592. }
  593. return result.Substring(0, result.Length - 2) + "]";
  594. }
  595. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  596. private void tryOrCommandLogError(Action toTry)
  597. {
  598. try
  599. {
  600. toTry();
  601. }
  602. catch (Exception e)
  603. {
  604. #if DEBUG
  605. Logging.CommandLogError("RIP Pixi\n" + e);
  606. #else
  607. Logging.CommandLogError("Pixi failed (reason: " + e.Message + ")");
  608. #endif
  609. Logging.LogWarning("Pixi Error\n" + e);
  610. }
  611. }
  612. }
  613. }