/// <summary> /// Precalculate and cache billboard scale for every record. /// This will change based on animation state and orientation. /// Cache this to array so it only needs to be calculated once. /// Also store number of frames for state animations. /// </summary> /// <param name="dfUnity">DaggerfallUnity singleton. Required for content readers and settings.</param> /// <param name="archive">Texture archive index derived from type and gender.</param> private void CacheRecordSizesAndFrames(DaggerfallUnity dfUnity, int archive) { // Open texture file string path = Path.Combine(dfUnity.Arena2Path, TextureFile.IndexToFileName(archive)); TextureFile textureFile = new TextureFile(path, FileUsage.UseMemory, true); // Cache size and scale for each record summary.RecordSizes = new Vector2[textureFile.RecordCount]; summary.RecordFrames = new int[textureFile.RecordCount]; for (int i = 0; i < textureFile.RecordCount; i++) { // Get size and scale of this texture DFSize size = textureFile.GetSize(i); DFSize scale = textureFile.GetScale(i); // Set start size Vector2 startSize; startSize.x = size.Width; startSize.y = size.Height; // Apply scale Vector2 finalSize; int xChange = (int)(size.Width * (scale.Width / BlocksFile.ScaleDivisor)); int yChange = (int)(size.Height * (scale.Height / BlocksFile.ScaleDivisor)); finalSize.x = (size.Width + xChange); finalSize.y = (size.Height + yChange); // Set optional scale TextureReplacement.SetBillboardScale(archive, i, ref finalSize); // Store final size and frame count summary.RecordSizes[i] = finalSize * MeshReader.GlobalScale; summary.RecordFrames[i] = textureFile.GetFrameCount(i); } }
private void SetEnemy() { // Get random enemy int index = Random.Range(0, GameObjectHelper.EnemyDict.Count); MobileEnemy mobileEnemy = GameObjectHelper.EnemyDict.ElementAt(index).Value; // Get random texture int archive = Random.value < 0.5f ? mobileEnemy.MaleTexture : mobileEnemy.FemaleTexture; string fileName = string.Format("TEXTURE.{0}", archive); TextureFile textureFile = new TextureFile(Path.Combine(DaggerfallUnity.Instance.Arena2Path, fileName), FileUsage.UseMemory, true); int record = Random.Range(0, textureFile.RecordCount); int frame = Random.Range(0, textureFile.GetFrameCount(record)); // Set fields enemyTexture = ImageReader.GetTexture(fileName, record, frame, true); enemyName = TextManager.Instance.GetLocalizedEnemyName(mobileEnemy.ID); }
/// <summary> /// Import textures for all records and frames of this enemy. /// </summary> public static void SetEnemyImportedTextures(int archive, MeshFilter meshFilter, ref EnemyImportedTextures importedTextures) { if (!DaggerfallUnity.Settings.MeshAndTextureReplacement) { return; } // Check first texture. Texture2D tex; bool hasImportedTextures = LoadFromCacheOrImport(archive, 0, 0, out tex); if (importedTextures.HasImportedTextures = hasImportedTextures) { string fileName = TextureFile.IndexToFileName(archive); var textureFile = new TextureFile(Path.Combine(DaggerfallUnity.Instance.Arena2Path, fileName), FileUsage.UseMemory, true); // Import all textures in this archive var textures = new List <List <Texture2D> >(); for (int record = 0; record < textureFile.RecordCount; record++) { int frames = textureFile.GetFrameCount(record); var frameTextures = new List <Texture2D>(); for (int frame = 0; frame < frames; frame++) { if ((record != 0 || frame != 0) && !LoadFromCacheOrImport(archive, record, frame, out tex)) { Debug.LogErrorFormat("Imported archive {0} does not contain texture for record {1}, frame {2}!", archive, record, frame); tex = ImageReader.GetTexture(fileName, record, frame, true); } frameTextures.Add(tex); } textures.Add(frameTextures); } // Update UV map SetUv(meshFilter); // Save results importedTextures.Textures = textures; } }
/// <summary> /// Precalculate and cache billboard scale for every record. /// This will change based on animation state and orientation. /// Cache this to array so it only needs to be calculated once. /// Also store number of frames for state animations. /// </summary> /// <param name="dfUnity">DaggerfallUnity singleton. Required for content readers and settings.</param> /// <param name="archive">Texture archive index derived from type and gender.</param> private void CacheRecordSizesAndFrames(DaggerfallUnity dfUnity, int archive) { // Open texture file string path = Path.Combine(dfUnity.Arena2Path, TextureFile.IndexToFileName(archive)); TextureFile textureFile = new TextureFile(path, FileUsage.UseMemory, true); // Cache size and scale for each record summary.RecordSizes = new Vector2[textureFile.RecordCount]; summary.RecordFrames = new int[textureFile.RecordCount]; for (int i = 0; i < textureFile.RecordCount; i++) { // Get size and scale of this texture DFSize size = textureFile.GetSize(i); DFSize scale = textureFile.GetScale(i); // Set start size Vector2 startSize; startSize.x = size.Width; startSize.y = size.Height; // Apply scale Vector2 finalSize; int xChange = (int)(size.Width * (scale.Width / BlocksFile.ScaleDivisor)); int yChange = (int)(size.Height * (scale.Height / BlocksFile.ScaleDivisor)); finalSize.x = (size.Width + xChange); finalSize.y = (size.Height + yChange); // Get eventual custom scale if ((summary.CustomMaterial.isCustom) && (XMLManager.XmlFileExist(archive, i))) { Vector2 enemyScale = XMLManager.GetScale(TextureReplacement.GetName(archive, i), TextureReplacement.TexturesPath); finalSize.x *= enemyScale.x; finalSize.y *= enemyScale.y; } // Store final size and frame count summary.RecordSizes[i] = finalSize * MeshReader.GlobalScale; summary.RecordFrames[i] = textureFile.GetFrameCount(i); } }
/// <summary> /// Reads any Daggerfall image file to ImageData package. /// </summary> /// <param name="filename">Name of standalone file as it appears in arena2 folder.</param> /// <param name="record">Which image record to read for multi-image files.</param> /// <param name="frame">Which frame to read for multi-frame images.</param> /// <param name="hasAlpha">Enable this for image cutouts.</param> /// <param name="createTexture">Create a Texture2D.</param> /// <param name="createAllFrameTextures">Creates a Texture2D for every frame in a TEXTURE file (if greater than 1 frames).</param> /// <param name="alphaIndex">Set palette index for alpha checks (default is 0).</param> /// <returns>ImageData. If result.type == ImageTypes.None then read failed.</returns> public static ImageData GetImageData(string filename, int record = 0, int frame = 0, bool hasAlpha = false, bool createTexture = true, bool createAllFrameTextures = false, int alphaIndex = 0) { // Check API ready DaggerfallUnity dfUnity = DaggerfallUnity.Instance; if (!dfUnity.IsReady) { return(new ImageData()); } // Parse image file type ImageTypes fileType; try { fileType = ParseFileType(filename); } catch { return(new ImageData()); } // Create base image data ImageData imageData = new ImageData(); imageData.type = fileType; imageData.filename = filename; imageData.record = record; imageData.frame = frame; imageData.hasAlpha = hasAlpha; imageData.alphaIndex = alphaIndex; // Read supported image files DFBitmap dfBitmap = null; DFBitmap[] dfBitmapAllFrames = null; switch (fileType) { case ImageTypes.TEXTURE: TextureFile textureFile = new TextureFile(Path.Combine(dfUnity.Arena2Path, filename), FileUsage.UseMemory, true); textureFile.LoadPalette(Path.Combine(dfUnity.Arena2Path, textureFile.PaletteName)); dfBitmap = textureFile.GetDFBitmap(record, frame); int frameCount = textureFile.GetFrameCount(record); if (createAllFrameTextures && frameCount > 1) { dfBitmapAllFrames = new DFBitmap[frameCount]; for (int i = 0; i < frameCount; i++) { dfBitmapAllFrames[i] = textureFile.GetDFBitmap(record, i); } } imageData.offset = textureFile.GetOffset(record); imageData.scale = textureFile.GetScale(record); imageData.size = textureFile.GetSize(record); // Texture pack support int archive = AssetInjection.TextureReplacement.FileNameToArchive(filename); if (createTexture && AssetInjection.TextureReplacement.TryImportTexture(archive, record, frame, out imageData.texture)) { createTexture = false; } if (createAllFrameTextures && frameCount > 1 && AssetInjection.TextureReplacement.TryImportTexture(archive, record, out imageData.animatedTextures)) { createAllFrameTextures = false; } break; case ImageTypes.IMG: ImgFile imgFile = new ImgFile(Path.Combine(dfUnity.Arena2Path, filename), FileUsage.UseMemory, true); imgFile.LoadPalette(Path.Combine(dfUnity.Arena2Path, imgFile.PaletteName)); dfBitmap = imgFile.GetDFBitmap(); imageData.offset = imgFile.ImageOffset; imageData.scale = new DFSize(); imageData.size = imgFile.GetSize(0); // Texture pack support if (createTexture && AssetInjection.TextureReplacement.TryImportImage(filename, false, out imageData.texture)) { createTexture = false; } break; case ImageTypes.CIF: case ImageTypes.RCI: CifRciFile cifFile = new CifRciFile(Path.Combine(dfUnity.Arena2Path, filename), FileUsage.UseMemory, true); cifFile.LoadPalette(Path.Combine(dfUnity.Arena2Path, cifFile.PaletteName)); dfBitmap = cifFile.GetDFBitmap(record, frame); imageData.offset = cifFile.GetOffset(record); imageData.scale = new DFSize(); imageData.size = cifFile.GetSize(record); // Texture pack support if (createTexture && AssetInjection.TextureReplacement.TryImportCifRci(filename, record, frame, false, out imageData.texture)) { createTexture = false; } break; case ImageTypes.CFA: CfaFile cfaFile = new CfaFile(Path.Combine(dfUnity.Arena2Path, filename), FileUsage.UseMemory, true); cfaFile.LoadPalette(Path.Combine(dfUnity.Arena2Path, cfaFile.PaletteName)); dfBitmap = cfaFile.GetDFBitmap(record, frame); imageData.offset = new DFPosition(0, 0); imageData.scale = new DFSize(); imageData.size = cfaFile.GetSize(record); // Texture pack support if (createTexture && AssetInjection.TextureReplacement.TryImportCifRci(filename, record, frame, false, out imageData.texture)) { createTexture = false; } break; case ImageTypes.BSS: BssFile bssFile = new BssFile(Path.Combine(dfUnity.Arena2Path, filename), FileUsage.UseMemory, true); bssFile.LoadPalette(Path.Combine(dfUnity.Arena2Path, bssFile.PaletteName)); dfBitmap = bssFile.GetDFBitmap(record, frame); imageData.offset = new DFPosition(0, 0); imageData.scale = new DFSize(); imageData.size = bssFile.GetSize(record); // Texture pack support if (createTexture && AssetInjection.TextureReplacement.TryImportCifRci(filename, record, frame, false, out imageData.texture)) { createTexture = false; } break; case ImageTypes.GFX: GfxFile gfxFile = new GfxFile(Path.Combine(dfUnity.Arena2Path, filename), FileUsage.UseMemory, true); gfxFile.LoadPalette(Path.Combine(dfUnity.Arena2Path, gfxFile.PaletteName)); dfBitmap = gfxFile.GetDFBitmap(record, frame); imageData.offset = new DFPosition(0, 0); imageData.scale = new DFSize(); imageData.size = gfxFile.GetSize(record); // Texture pack support if (createTexture && AssetInjection.TextureReplacement.TryImportCifRci(filename, record, frame, false, out imageData.texture)) { createTexture = false; } break; default: return(new ImageData()); } // Store bitmap imageData.dfBitmap = dfBitmap; imageData.width = dfBitmap.Width; imageData.height = dfBitmap.Height; // Create Texture2D if (createTexture) { // Get colors array Color32[] colors = GetColors(imageData); if (colors == null) { return(new ImageData()); } // Create new Texture2D imageData.texture = GetTexture(colors, imageData.width, imageData.height); } // Create animated Texture2D frames if (createAllFrameTextures && dfBitmapAllFrames != null) { imageData.animatedTextures = new Texture2D[dfBitmapAllFrames.Length]; for (int i = 0; i < dfBitmapAllFrames.Length; i++) { ImageData curFrame = imageData; curFrame.dfBitmap = dfBitmapAllFrames[i]; Color32[] colors = GetColors(curFrame); imageData.animatedTextures[i] = GetTexture(colors, imageData.width, imageData.height); } } return(imageData); }
/// <summary> /// Gets a custom material for a mobile billboard with textures and configuration imported from mods. /// </summary> /// <param name="archive">Archive index.</param> /// <param name="meshFilter">The MeshFilter of the billboard object.</param> /// <param name="importedTextures">All the imported textures for the archive.</param> /// <remarks> /// Seek the texture for the first frame of the first record. If found, it imports the entire archive. /// If this texture has an emission map the material is considered emissive and all emission maps are imported. /// </remarks> /// <returns>A material or null.</returns> public static Material GetMobileBillboardMaterial(int archive, MeshFilter meshFilter, ref MobileBillboardImportedTextures importedTextures) { if (!DaggerfallUnity.Settings.AssetInjection) { return(null); } Texture2D tex, emission; if (importedTextures.HasImportedTextures = LoadFromCacheOrImport(archive, 0, 0, true, true, out tex, out emission)) { string renderMode = null; // Read xml configuration XMLManager xml; if (XMLManager.TryReadXml(ImagesPath, string.Format("{0:000}", archive), out xml)) { xml.TryGetString("renderMode", out renderMode); importedTextures.IsEmissive = xml.GetBool("emission"); } // Make material Material material = MakeBillboardMaterial(renderMode); // Enable emission ToggleEmission(material, importedTextures.IsEmissive |= emission != null); // Load texture file to get record and frame count string fileName = TextureFile.IndexToFileName(archive); var textureFile = new TextureFile(Path.Combine(DaggerfallUnity.Instance.Arena2Path, fileName), FileUsage.UseMemory, true); // Import all textures in this archive importedTextures.Albedo = new Texture2D[textureFile.RecordCount][]; importedTextures.EmissionMaps = importedTextures.IsEmissive ? new Texture2D[textureFile.RecordCount][] : null; for (int record = 0; record < textureFile.RecordCount; record++) { int frames = textureFile.GetFrameCount(record); var frameTextures = new Texture2D[frames]; var frameEmissionMaps = importedTextures.IsEmissive ? new Texture2D[frames] : null; for (int frame = 0; frame < frames; frame++) { if (record != 0 || frame != 0) { LoadFromCacheOrImport(archive, record, frame, importedTextures.IsEmissive, true, out tex, out emission); } frameTextures[frame] = tex ?? ImageReader.GetTexture(fileName, record, frame, true); if (frameEmissionMaps != null) { frameEmissionMaps[frame] = emission ?? frameTextures[frame]; } } importedTextures.Albedo[record] = frameTextures; if (importedTextures.EmissionMaps != null) { importedTextures.EmissionMaps[record] = frameEmissionMaps; } } // Update UV map SetUv(meshFilter); return(material); } return(null); }
/// <summary> /// Gets Texture2D atlas from Daggerfall texture archive. /// Every record and frame in the archive will be added to atlas. /// An array of rects will be returned with sub-texture rect for each record index and frame. /// Used for non-tiling textures like flats and ground tiles. /// </summary> /// <param name="archive">Archive index to create atlas from.</param> /// <param name="alphaIndex">Index to receive transparent alpha.</param> /// <param name="padding">Number of pixels padding around each sub-texture.</param> /// <param name="maxAtlasSize">Max size of atlas.</param> /// <param name="rectsOut">Array of rects, one for each record sub-texture and frame.</param> /// <param name="indicesOut">Array of record indices into rect array, accounting for animation frames.</param> /// <param name="border">Number of pixels internal border around each texture.</param> /// <param name="dilate">Blend texture into surrounding empty pixels.</param> /// <param name="shrinkUVs">Number of extra pixels to shrink UV rect.</param> /// <param name="copyToOppositeBorder">Copy texture edges to opposite border. Requires border, will overwrite dilate.</param> /// <returns>Texture2D atlas or null.</returns> public Texture2D GetTexture2DAtlas( int archive, int alphaIndex, int padding, int maxAtlasSize, out Rect[] rectsOut, out RecordIndex[] indicesOut, out Vector2[] sizesOut, out Vector2[] scalesOut, out Vector2[] offsetsOut, int border, bool dilate, int shrinkUVs = 0, bool copyToOppositeBorder = false, bool makeNoLongerReadable = true) { // Ready check if (!ReadyCheck()) { rectsOut = null; indicesOut = null; sizesOut = null; scalesOut = null; offsetsOut = null; return(null); } // Load texture file textureFile.Load(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true); // Read every texture in archive Rect rect; List <Texture2D> textures = new List <Texture2D>(); List <RecordIndex> indices = new List <RecordIndex>(); sizesOut = new Vector2[textureFile.RecordCount]; scalesOut = new Vector2[textureFile.RecordCount]; offsetsOut = new Vector2[textureFile.RecordCount]; for (int record = 0; record < textureFile.RecordCount; record++) { int frames = textureFile.GetFrameCount(record); DFSize size = textureFile.GetSize(record); DFSize scale = textureFile.GetScale(record); DFSize offset = textureFile.GetOffset(record); RecordIndex ri = new RecordIndex() { startIndex = textures.Count, frameCount = frames, width = size.Width, height = size.Height, }; indices.Add(ri); for (int frame = 0; frame < frames; frame++) { textures.Add(GetTexture2D(archive, record, frame, alphaIndex, out rect, border, dilate, copyToOppositeBorder, false)); } sizesOut[record] = new Vector2(size.Width, size.Height); scalesOut[record] = new Vector2(scale.Width, scale.Height); offsetsOut[record] = new Vector2(offset.Width, offset.Height); } // Pack textures into atlas Texture2D atlas = new Texture2D(maxAtlasSize, maxAtlasSize, TextureFormat.RGBA32, MipMaps); rectsOut = atlas.PackTextures(textures.ToArray(), padding, maxAtlasSize, makeNoLongerReadable); indicesOut = indices.ToArray(); // Shrink UV rect to compensate for internal border float ru = 1f / atlas.width; float rv = 1f / atlas.height; border += shrinkUVs; for (int i = 0; i < rectsOut.Length; i++) { Rect rct = rectsOut[i]; rct.xMin += border * ru; rct.xMax -= border * ru; rct.yMin += border * rv; rct.yMax -= border * rv; rectsOut[i] = rct; } return(atlas); }