///======================================================================== /// Method : ReindexImages /// /// <summary> /// Reindex images of the given type - remove duplicates and renumber /// to the top of the image array. /// </summary> /// <param name="xiImageType"></param> ///======================================================================== public void ReindexImages(eTexMetaDataEntries xiImageType) { Level lLevel = mMainForm.CurrentLevel; if (lLevel == null) { MessageBox.Show("Must have a level open for this action"); return; } Chunk[] lChunkArray; if (xiImageType == eTexMetaDataEntries.Bumpmap) { lChunkArray = lLevel.SHET.BumpImages.mChildren; } else if (xiImageType == eTexMetaDataEntries.Steering) { lChunkArray = lLevel.SHET.SteeringImages.mChildren; } else if (xiImageType == eTexMetaDataEntries.CameraPos) { lChunkArray = lLevel.SHET.CameraPositions.mChildren; } else { throw new Exception("Internal error: Cannot reindex images of type " + xiImageType.ToString()); } // count how many times each image is currently in use int[] lUseCount = new int[lChunkArray.Length]; int lTexMetaIndex = (int)xiImageType; foreach (FlatChunk flat in lLevel.SHET.Flats) { if (flat.TexMetaData != null) { foreach (byte[][] row in flat.TexMetaData) { foreach (byte[] entry in row) { lUseCount[entry[lTexMetaIndex]]++; } } } } //get a mapping from each equivalence class of chunks to the lowest index in that class SortedDictionary <IReindexableChunk, int> lImagesToCanonicalId = new SortedDictionary <IReindexableChunk, int>(new ReindexableChunkComparer()); for (int i = lChunkArray.Length - 1; i >= 0; i--) { if (lUseCount[i] == 0) { // Ignore any unused images - otherwise we might try to remap used images // to unused ones, which would cause errors when we remove all the unused // images later on. continue; } lImagesToCanonicalId[(IReindexableChunk)lChunkArray[i]] = i; } int[] lOldToNewIndexMap = new int[lUseCount.Length]; //determine where each image should map to: int lNextUnusedId = 0; for (int lOldIdx = 0; lOldIdx < lOldToNewIndexMap.Length; lOldIdx++) { if (lUseCount[lOldIdx] > 0) { //we need to map this to somewhere. //can it be coalesced with other, identical bumps? int lCanonicalId = (int)lImagesToCanonicalId[(IReindexableChunk)lChunkArray[lOldIdx]]; if (lCanonicalId < lOldIdx) { lOldToNewIndexMap[lOldIdx] = lOldToNewIndexMap[lCanonicalId]; } else if (lCanonicalId == lOldIdx) { lOldToNewIndexMap[lOldIdx] = lNextUnusedId; lNextUnusedId++; } else { throw new Exception("Internal error: lCanonicalId can't be greater than lOldIdx"); } } else { lOldToNewIndexMap[lOldIdx] = -1; } } //take a copy of each canonical image, so we don't lose any information Dictionary <int, byte[]> lCanonicalImages = new Dictionary <int, byte[]>(); foreach (KeyValuePair <IReindexableChunk, int> lEntry in lImagesToCanonicalId) { lCanonicalImages.Add(lEntry.Value, (byte[])lEntry.Key.Data.Clone()); } //fill in all the bump images with their new values: int lHighestUsed = -1; for (int lOldIdx = 0; lOldIdx < lOldToNewIndexMap.Length; lOldIdx++) { int lNew = lOldToNewIndexMap[lOldIdx]; if (lNew != -1) { if (lCanonicalImages.ContainsKey(lOldIdx)) { lHighestUsed = lNew; ((IReindexableChunk)lChunkArray[lNew]).Data = lCanonicalImages[lOldIdx]; } } } for (int i = lHighestUsed + 1; i < lChunkArray.Length; i++) { ((IReindexableChunk)lChunkArray[i]).Clear(); } //now update all the bumps in the Flats foreach (FlatChunk flat in lLevel.SHET.Flats) { if (flat.TexMetaData != null) { foreach (byte[][] row in flat.TexMetaData) { foreach (byte[] entry in row) { int lOldIdx = entry[lTexMetaIndex]; int lNewIdx = lOldToNewIndexMap[lOldIdx]; if (lNewIdx < 0) { throw new Exception("Internal error: this image should be unused"); } entry[lTexMetaIndex] = (byte)lNewIdx; } } } } MessageBox.Show(string.Format( "Successfully reindexed. There are {0} {2} tiles in use, and {1} free", lCanonicalImages.Count, lChunkArray.Length - lCanonicalImages.Count, xiImageType.ToString().ToLower())); }
///======================================================================== /// Method : CompactImages /// /// <summary> /// Compact images fo the given type - remove any unused images from the /// end of the array. /// </summary> /// <param name="xiImageType"></param> /// <remarks> /// Note that no attempt is made to deduplicate - it is assumed that /// duplicates will be removed via ReindexImages if required. /// </remarks> ///======================================================================== public void CompactImages(eTexMetaDataEntries xiImageType) { Level lLevel = mMainForm.CurrentLevel; if (lLevel == null) { MessageBox.Show("Must have a level open for this action"); return; } //======================================================================= // Work out which array of images to work with //======================================================================= Chunk[] lChunkArray; if (xiImageType == eTexMetaDataEntries.Bumpmap) { lChunkArray = lLevel.SHET.BumpImages.mChildren; } else if (xiImageType == eTexMetaDataEntries.Steering) { lChunkArray = lLevel.SHET.SteeringImages.mChildren; } else if (xiImageType == eTexMetaDataEntries.CameraPos) { lChunkArray = lLevel.SHET.CameraPositions.mChildren; } else { throw new Exception("Internal error: Cannot reindex images of type " + xiImageType.ToString()); } //======================================================================= // Find the highest numbered image that's in use - we'll remove all // higher-numbered images //======================================================================= int lMaxUsedIndex = -1; int lTexMetaIndex = (int)xiImageType; foreach (FlatChunk lFlat in lLevel.SHET.Flats) { if (lFlat.TexMetaData != null) { foreach (byte[][] lRow in lFlat.TexMetaData) { foreach (byte[] lEntry in lRow) { if (lMaxUsedIndex < lEntry[lTexMetaIndex]) { lMaxUsedIndex = lEntry[lTexMetaIndex]; } } } } } if (lMaxUsedIndex == -1) { MessageBox.Show(string.Format("No compaction required - there are no {0} tiles!", xiImageType.ToString().ToLower())); return; } //======================================================================= // Create a new image array and adjust zero padding //======================================================================= Chunk[] lNewChunkArray = new Chunk[lMaxUsedIndex + 1]; Array.Copy(lChunkArray, lNewChunkArray, lMaxUsedIndex + 1); lLevel.SHET.TrailingZeroByteCount += (lChunkArray.Length - (lMaxUsedIndex + 1)) * ((IReindexableChunk)lChunkArray[0]).Data.Length; if (xiImageType == eTexMetaDataEntries.Bumpmap) { lLevel.SHET.BumpImages.mChildren = lNewChunkArray; } else if (xiImageType == eTexMetaDataEntries.Steering) { lLevel.SHET.SteeringImages.mChildren = lNewChunkArray; } else if (xiImageType == eTexMetaDataEntries.CameraPos) { lLevel.SHET.CameraPositions.mChildren = lNewChunkArray; } MessageBox.Show(string.Format("Successfully compacted. There are {0} {1} tiles remaining", lMaxUsedIndex + 1, xiImageType.ToString().ToLower())); }