public static ProcessedVoxelObjectNotation[][] ProcessAndExpandBlocks(string name, BlockJsonInfo[] blocks, BlueprintProvider blueprints) { List <ProcessedVoxelObjectNotation[]> expandedBlocks = new List <ProcessedVoxelObjectNotation[]>(); for (int i = 0; i < blocks.Length; i++) { ProcessedVoxelObjectNotation root = blocks[i].Process(); if (root.blueprint) { if (blueprints == null) { throw new NullReferenceException("Blueprint block info found but BlueprintProvider is null"); } BlockJsonInfo[] blueprint = blueprints.Blueprint(name, blocks[i]); ProcessedVoxelObjectNotation[] expanded = new ProcessedVoxelObjectNotation[blueprint.Length]; for (int j = 0; j < expanded.Length; j++) { expanded[j] = blueprint[j].Process(); } expandedBlocks.Add(expanded); } else { expandedBlocks.Add(new ProcessedVoxelObjectNotation[] { root }); } } return(expandedBlocks.ToArray()); }
private void OptimiseBlocks(ref List <ProcessedVoxelObjectNotation> optVONs, int chunkSize) { // Reduce blocks to place to reduce lag while placing and from excessive blocks in the world. // Blocks are reduced by grouping similar blocks that are touching (before they're placed) // multithreaded because this is an expensive (slow) operation int item = 0; ProcessedVoxelObjectNotation[][] groups = new ProcessedVoxelObjectNotation[optVONs.Count / chunkSize][]; Thread[] tasks = new Thread[groups.Length]; while (item < groups.Length) { groups[item] = new ProcessedVoxelObjectNotation[chunkSize]; optVONs.CopyTo(item * chunkSize, groups[item], 0, chunkSize); int tmpItem = item; // scope is dumb tasks[item] = new Thread(() => { groups[tmpItem] = groupBlocksBestEffort(groups[tmpItem], tmpItem); }); tasks[item].Start(); item++; } #if DEBUG Logging.MetaLog($"Created {groups.Length} + 1? groups"); #endif // final group ProcessedVoxelObjectNotation[] finalGroup = null; Thread finalThread = null; if (optVONs.Count > item * chunkSize) { //finalGroup = optVONs.GetRange(item * GROUP_SIZE, optVONs.Count - (item * GROUP_SIZE)).ToArray(); finalGroup = new ProcessedVoxelObjectNotation[optVONs.Count - (item * chunkSize)]; optVONs.CopyTo(item * chunkSize, finalGroup, 0, optVONs.Count - (item * chunkSize)); finalThread = new Thread(() => { finalGroup = groupBlocksBestEffort(finalGroup, -1); }); finalThread.Start(); } // gather results List <ProcessedVoxelObjectNotation> result = new List <ProcessedVoxelObjectNotation>(); for (int i = 0; i < groups.Length; i++) { #if DEBUG Logging.MetaLog($"Waiting for completion of task {i}"); #endif tasks[i].Join(); result.AddRange(groups[i]); } if (finalThread != null) { #if DEBUG Logging.MetaLog($"Waiting for completion of final task"); #endif finalThread.Join(); result.AddRange(finalGroup); } optVONs = result; }
public static ProcessedVoxelObjectNotation[] ProcessBlocks(BlockJsonInfo[] blocks) { ProcessedVoxelObjectNotation[] procBlocks = new ProcessedVoxelObjectNotation[blocks.Length]; for (int i = 0; i < blocks.Length; i++) { procBlocks[i] = blocks[i].Process(); } return(procBlocks); }
private static void updateVonFromCorners(float3[] corners, ref ProcessedVoxelObjectNotation von) { float3 newCenter = sumOfFloat3Arr(corners) / corners.Length; float3 newPosition = newCenter; Quaternion rot = Quaternion.Euler(von.rotation); float3 rotatedScale = 2 * (corners[0] - newCenter) / BLOCK_SIZE; von.scale = Quaternion.Inverse(rot) * rotatedScale; von.position = newPosition; //Logging.MetaLog($"Updated VON scale {von.scale} (absolute {rotatedScale})"); }
private static float3[] calculateCorners(ProcessedVoxelObjectNotation von) { float3[] corners = new float3[8]; Quaternion rotation = Quaternion.Euler(von.rotation); float3 rotatedScale = rotation * von.scale; float3 trueCenter = von.position; // generate corners for (int i = 0; i < corners.Length; i++) { corners[i] = trueCenter + BLOCK_SIZE * (cornerMultiplicands1[i] * rotatedScale / 2); } return(corners); }
private static ProcessedVoxelObjectNotation[] groupBlocksBestEffort(ProcessedVoxelObjectNotation[] blocksToOptimise, int id) { // a really complicated algorithm to determine if two similar blocks are touching (before they're placed) // the general concept: // 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) // between the two blocks, the 8 corners that aren't in common are the corners for the merged block // // to merge the 2 blocks, switch out the 4 common corners of one block with the nearest non-common corners from the other block // i.e. swap the common face on block A with the face opposite the common face of block B // 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 // // 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 // 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 // // note2: this does not work with blocks which aren't cubes (i.e. any block where rotation matters) try { #if DEBUG Stopwatch timer = Stopwatch.StartNew(); #endif FasterList <ProcessedVoxelObjectNotation> optVONs = new FasterList <ProcessedVoxelObjectNotation>(blocksToOptimise); int item = 0; while (item < optVONs.count - 1) { #if DEBUG Logging.MetaLog($"({id}) Now grouping item {item}/{optVONs.count} ({100f * item/(float)optVONs.count}%)"); #endif bool isItemUpdated = false; ProcessedVoxelObjectNotation itemVON = optVONs[item]; if (isOptimisableBlock(itemVON.block)) { float3[] itemCorners = calculateCorners(itemVON); int seeker = item + 1; // despite this, assume that seeker goes thru the entire list (not just blocks after item) while (seeker < optVONs.count) { if (seeker == item) { seeker++; } else { ProcessedVoxelObjectNotation seekerVON = optVONs[seeker]; //Logging.MetaLog($"Comparing {itemVON} and {seekerVON}"); float3[] seekerCorners = calculateCorners(seekerVON); int[][] mapping = findMatchingCorners(itemCorners, seekerCorners); if (mapping.Length != 0 && itemVON.block == seekerVON.block && itemVON.color.Color == seekerVON.color.Color && itemVON.color.Darkness == seekerVON.color.Darkness && isOptimisableBlock(seekerVON.block)) // match found { // switch out corners based on mapping //Logging.MetaLog($"Corners {float3ArrToString(itemCorners)}\nand {float3ArrToString(seekerCorners)}"); //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"); for (byte i = 0; i < 4; i++) { itemCorners[mapping[0][i]] = seekerCorners[mapping[1][i]]; } // remove 2nd block, since it's now part of the 1st block //Logging.MetaLog($"Removing {seekerVON}"); optVONs.RemoveAt(seeker); if (seeker < item) { item--; // note: this will never become less than 0 } isItemUpdated = true; // regenerate info //Logging.MetaLog($"Final corners {float3ArrToString(itemCorners)}"); updateVonFromCorners(itemCorners, ref itemVON); itemCorners = calculateCorners(itemVON); //Logging.MetaLog($"Merged block is {itemVON}"); } else { seeker++; } } } if (isItemUpdated) { optVONs[item] = itemVON; //Logging.MetaLog($"Optimised block is now {itemVON}"); } item++; } else { item++; } } #if DEBUG timer.Stop(); Logging.MetaLog($"({id}) Completed best effort grouping of range in {timer.ElapsedMilliseconds}ms"); #endif return(optVONs.ToArray()); } catch (Exception e) { Logging.MetaLog($"({id}) Exception occured...\n{e.ToString()}"); } return(blocksToOptimise); }
private void Pixi(string importerName, string name) { // organise priorities int[] priorities = importers.Keys.ToArray(); Array.Sort(priorities); Array.Reverse(priorities); // higher priorities go first // find relevant importer Importer magicImporter = null; foreach (int p in priorities) { Importer[] imps = importers[p]; for (int i = 0; i < imps.Length; i++) { //Logging.MetaLog($"Now checking importer {imps[i].Name}"); if ((importerName == null && imps[i].Qualifies(name)) || (importerName != null && imps[i].Name.Contains(importerName))) { magicImporter = imps[i]; break; } } if (magicImporter != null) { break; } } if (magicImporter == null) { Logging.CommandLogError("Unsupported file or string."); return; } #if DEBUG Logging.MetaLog($"Using '{magicImporter.Name}' to import '{name}'"); #endif // import blocks BlockJsonInfo[] blocksInfo = magicImporter.Import(name); if (blocksInfo == null || blocksInfo.Length == 0) { #if DEBUG Logging.CommandLogError($"Importer {magicImporter.Name} didn't provide any blocks to import. Mission Aborted!"); #endif return; } ProcessedVoxelObjectNotation[][] procVONs; BlueprintProvider blueprintProvider = magicImporter.BlueprintProvider; if (blueprintProvider == null) { // convert block info to API-compatible format procVONs = new ProcessedVoxelObjectNotation[][] { BlueprintUtility.ProcessBlocks(blocksInfo) }; } else { // expand blueprints and convert block info procVONs = BlueprintUtility.ProcessAndExpandBlocks(name, blocksInfo, magicImporter.BlueprintProvider); } // reduce block placements by grouping neighbouring similar blocks // (after flattening block data representation) List <ProcessedVoxelObjectNotation> optVONs = new List <ProcessedVoxelObjectNotation>(); for (int arr = 0; arr < procVONs.Length; arr++) { for (int elem = 0; elem < procVONs[arr].Length; elem++) { optVONs.Add(procVONs[arr][elem]); } } #if DEBUG Logging.MetaLog($"Imported {optVONs.Count} blocks for '{name}'"); #endif int blockCountPreOptimisation = optVONs.Count; if (magicImporter.Optimisable) { for (int pass = 0; pass < OPTIMISATION_PASSES; pass++) { OptimiseBlocks(ref optVONs, (pass + 1) * GROUP_SIZE); #if DEBUG Logging.MetaLog($"Optimisation pass {pass} completed"); #endif } #if DEBUG Logging.MetaLog($"Optimised down to {optVONs.Count} blocks for '{name}'"); #endif } ProcessedVoxelObjectNotation[] optVONsArr = optVONs.ToArray(); magicImporter.PreProcess(name, ref optVONsArr); // place blocks Block[] blocks = new Block[optVONsArr.Length]; for (int i = 0; i < optVONsArr.Length; i++) { ProcessedVoxelObjectNotation desc = optVONsArr[i]; if (desc.block != BlockIDs.Invalid) { Block b = Block.PlaceNew(desc.block, desc.position, desc.rotation, desc.color.Color, desc.color.Darkness, 1, desc.scale); blocks[i] = b; } #if DEBUG else { Logging.LogWarning($"Found invalid block at index {i}\n\t{optVONsArr[i].ToString()}"); } #endif } // handle special block parameters PostProcessSpecialBlocks(ref optVONsArr, ref blocks); // post processing magicImporter.PostProcess(name, ref blocks); if (magicImporter.Optimisable && blockCountPreOptimisation > blocks.Length) { Logging.CommandLog($"Imported {blocks.Length} blocks using {magicImporter.Name} ({blockCountPreOptimisation/blocks.Length}x ratio)"); } else { Logging.CommandLog($"Imported {blocks.Length} blocks using {magicImporter.Name}"); } }