Example #1
0
        /// <summary>
        /// Gets terrain albedo texture array containing each terrain tile in a seperate array slice.
        /// </summary>
        /// <param name="archive">Archive index.</param>
        /// <param name="stayReadable">Texture should stay readable.</param>
        /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param>
        /// <returns>Texture2DArray or null</returns>
        public Texture2DArray GetTerrainAlbedoTextureArray(
            int archive,
            bool stayReadable = false,
            SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24)
        {
            // Load texture file and check count matches terrain tiles
            TextureFile textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true);
            int         numSlices   = 0;

            if (textureFile.RecordCount == 56)
            {
                numSlices = textureFile.RecordCount;
            }
            else
            {
                return(null);
            }

            Texture2DArray textureArray;

            if (TextureReplacement.CustomTextureExist(archive, 0, 0))
            {
                GetTextureResults results = new GetTextureResults();
                TextureReplacement.LoadCustomTextureResults(archive, 0, 0, ref results, ref DaggerfallUnity.Instance.MaterialReader.GenerateNormals);
                textureArray = new Texture2DArray(results.albedoMap.width, results.albedoMap.height, numSlices, ParseTextureFormat(nonAlphaFormat), MipMaps);
            }
            else
            {
                textureArray = new Texture2DArray(textureFile.GetWidth(0), textureFile.GetWidth(1), numSlices, ParseTextureFormat(nonAlphaFormat), MipMaps);
            }

            // Rollout tiles into texture array
            for (int record = 0; record < textureFile.RecordCount; record++)
            {
                // Create base image with gutter
                DFSize    sz;
                Color32[] albedo = textureFile.GetColor32(record, 0, -1, 0, out sz);


                // Import custom texture(s)
                GetTextureResults resultsTile = new GetTextureResults();
                if (TextureReplacement.CustomTextureExist(archive, record, 0))
                {
                    TextureReplacement.LoadCustomTextureResults(archive, record, 0, ref resultsTile, ref DaggerfallUnity.Instance.MaterialReader.GenerateNormals);
                    albedo = resultsTile.albedoMap.GetPixels32();
                }

                // Insert into texture array
                textureArray.SetPixels32(albedo, record, 0);
            }
            textureArray.Apply(true, !stayReadable);

            // Change settings for these textures
            textureArray.wrapMode   = TextureWrapMode.Clamp;
            textureArray.anisoLevel = 8;

            return(textureArray);
        }
        /// <summary>
        /// Gets Unity albedo texture from Daggerfall texture with minimum options.
        /// </summary>
        /// <param name="archive">Archive index.</param>
        /// <param name="record">Record index.</param>
        /// <param name="frame">Frame index.</param>
        /// <param name="alphaIndex">Index to receive transparent alpha.</param>
        /// <returns>Texture2D or null.</returns>
        public Texture2D GetTexture2D(
            int archive,
            int record,
            int frame = 0,
            int alphaIndex = 0)
        {
            GetTextureSettings settings = new GetTextureSettings();
            settings.archive = archive;
            settings.record = record;
            settings.frame = frame;
            settings.alphaIndex = alphaIndex;
            GetTextureResults results = GetTexture2D(settings);

            return results.albedoMap;
        }
Example #3
0
        /// <summary>
        /// TEMP: Creates super-atlas populated with all archives in array.
        /// Currently does not support animated textures, normal map, or emission map.
        /// TODO: Integrate this feature fully with material system.
        /// </summary>
        /// <param name="archives">Archive array.</param>
        /// <param name="borderSize">Number of pixels border to add around image.</param>
        /// <param name="dilate">Blend texture into surrounding empty pixels. Requires border.</param>
        /// <param name="maxAtlasSize">Maximum atlas size.</param>
        /// <param name="alphaTextureFormat">Alpha TextureFormat.</param>
        /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param>
        /// <returns>TextureAtlasBuilder.</returns>
        public TextureAtlasBuilder CreateTextureAtlasBuilder(
            int[] archives,
            int borderSize   = 0,
            bool dilate      = false,
            int maxAtlasSize = 2048,
            SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.RGBA32,
            SupportedNonAlphaTextureFormats nonAlphaFormat  = SupportedNonAlphaTextureFormats.RGB24)
        {
            // Iterate archives
            TextureFile         textureFile = new TextureFile();
            TextureAtlasBuilder builder     = new TextureAtlasBuilder();
            GetTextureSettings  settings    = TextureReader.CreateTextureSettings(0, 0, 0, 0, borderSize, dilate, maxAtlasSize);

            settings.stayReadable = true;
            for (int i = 0; i < archives.Length; i++)
            {
                // Load texture file
                settings.archive = archives[i];
                textureFile.Load(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true);

                // Add all records for this archive - single frame only
                for (int record = 0; record < textureFile.RecordCount; record++)
                {
                    settings.record = record;
                    GetTextureResults results = GetTexture2D(settings, alphaTextureFormat, nonAlphaFormat);
                    DFSize            size    = textureFile.GetSize(record);
                    DFSize            scale   = textureFile.GetScale(record);
                    builder.AddTextureItem(
                        results.albedoMap,
                        settings.archive,
                        settings.record,
                        0, 1,
                        new Vector2(size.Width, size.Height),
                        new Vector2(scale.Width, scale.Height));
                }
            }

            // Apply the builder
            builder.Rebuild(borderSize);

            return(builder);
        }
Example #4
0
        ///// <summary>
        ///// TEMP: Creates super-atlas populated with all archives in array.
        ///// Currently does not support animated textures, normal map, or emission map.
        ///// TODO: Integrate this feature fully with material system.
        ///// </summary>
        ///// <param name="archives">Archive array.</param>
        ///// <param name="borderSize">Number of pixels border to add around image.</param>
        ///// <param name="dilate">Blend texture into surrounding empty pixels. Requires border.</param>
        ///// <param name="maxAtlasSize">Maximum atlas size.</param>
        ///// <param name="alphaTextureFormat">Alpha TextureFormat.</param>
        ///// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param>
        ///// <returns>TextureAtlasBuilder.</returns>
        //public TextureAtlasBuilder CreateTextureAtlasBuilder(
        //    int[] archives,
        //    int borderSize = 0,
        //    bool dilate = false,
        //    int maxAtlasSize = 2048,
        //    SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.RGBA32,
        //    SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24)
        //{
        //    // Iterate archives
        //    TextureFile textureFile = new TextureFile();
        //    TextureAtlasBuilder builder = new TextureAtlasBuilder();
        //    GetTextureSettings settings = TextureReader.CreateTextureSettings(0, 0, 0, 0, borderSize, dilate, maxAtlasSize);
        //    settings.stayReadable = true;
        //    for (int i = 0; i < archives.Length; i++)
        //    {
        //        // Load texture file
        //        settings.archive = archives[i];
        //        textureFile.Load(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true);

        //        // Add all records for this archive - single frame only
        //        for (int record = 0; record < textureFile.RecordCount; record++)
        //        {
        //            settings.record = record;
        //            GetTextureResults results = GetTexture2D(settings, alphaTextureFormat, nonAlphaFormat);
        //            DFSize size = textureFile.GetSize(record);
        //            DFSize scale = textureFile.GetScale(record);
        //            builder.AddTextureItem(
        //                results.albedoMap,
        //                settings.archive,
        //                settings.record,
        //                0, 1,
        //                new Vector2(size.Width, size.Height),
        //                new Vector2(scale.Width, scale.Height));
        //        }
        //    }

        //    // Apply the builder
        //    builder.Rebuild(borderSize);

        //    return builder;
        //}

        /// <summary>
        /// Gets specially packed tileset atlas for terrains.
        /// This needs to be improved to create each mip level manually to further reduce artifacts.
        /// </summary>
        /// <param name="archive">Archive index.</param>
        /// <param name="stayReadable">Texture should stay readable.</param>
        /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param>
        /// <returns></returns>
        public GetTextureResults GetTerrainTilesetTexture(
            int archive,
            bool stayReadable = false,
            SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24)
        {
            const int atlasDim   = 2048;
            const int gutterSize = 32;

            GetTextureResults results = new GetTextureResults();

            // Load texture file and check count matches terrain tiles
            TextureFile textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true);

            if (textureFile.RecordCount != 56)
            {
                return(results);
            }

            // Rollout tiles into atlas.
            // This is somewhat inefficient, but fortunately atlases only
            // need to be generated once and can be prepared offline where
            // startup time is critical.
            int x = 0, y = 0;

            Color32[] atlasColors = new Color32[atlasDim * atlasDim];
            for (int record = 0; record < textureFile.RecordCount; record++)
            {
                // Create base image with gutter
                DFSize    sz;
                Color32[] albedo = textureFile.GetColor32(record, 0, -1, gutterSize, out sz);

                // Wrap and clamp textures based on tile
                switch (record)
                {
                // Textures that do not tile in any direction
                case 5:
                case 7:
                case 10:
                case 12:
                case 15:
                case 17:
                case 20:
                case 22:
                case 25:
                case 27:
                case 30:
                case 32:
                case 34:
                case 35:
                case 36:
                case 37:
                case 38:
                case 39:
                case 40:
                case 41:
                case 42:
                case 43:
                case 44:
                case 45:
                case 47:
                case 48:
                case 49:
                case 50:
                case 51:
                case 52:
                case 53:
                case 55:
                    ImageProcessing.ClampBorder(ref albedo, sz, gutterSize);
                    break;

                // Textures that clamp horizontally and tile vertically
                case 6:
                case 11:
                case 16:
                case 21:
                case 26:
                case 31:
                    ImageProcessing.WrapBorder(ref albedo, sz, gutterSize, false);
                    ImageProcessing.ClampBorder(ref albedo, sz, gutterSize, true, false);
                    break;

                // Textures that tile in all directions
                default:
                    ImageProcessing.WrapBorder(ref albedo, sz, gutterSize);
                    break;
                }

                // Create variants
                Color32[] rotate     = ImageProcessing.RotateColors(ref albedo, sz.Width, sz.Height);
                Color32[] flip       = ImageProcessing.FlipColors(ref albedo, sz.Width, sz.Height);
                Color32[] rotateFlip = ImageProcessing.RotateColors(ref flip, sz.Width, sz.Height);

                // Insert into atlas
                ImageProcessing.InsertColors(ref albedo, ref atlasColors, x, y, sz.Width, sz.Height, atlasDim, atlasDim);
                ImageProcessing.InsertColors(ref rotate, ref atlasColors, x + sz.Width, y, sz.Width, sz.Height, atlasDim, atlasDim);
                ImageProcessing.InsertColors(ref flip, ref atlasColors, x + sz.Width * 2, y, sz.Width, sz.Height, atlasDim, atlasDim);
                ImageProcessing.InsertColors(ref rotateFlip, ref atlasColors, x + sz.Width * 3, y, sz.Width, sz.Height, atlasDim, atlasDim);

                // Increment position
                x += sz.Width * 4;
                if (x >= atlasDim)
                {
                    y += sz.Height;
                    x  = 0;
                }
            }

            // Create Texture2D
            Texture2D albedoAtlas = new Texture2D(atlasDim, atlasDim, ParseTextureFormat(nonAlphaFormat), MipMaps);

            albedoAtlas.SetPixels32(atlasColors);
            albedoAtlas.Apply(true, !stayReadable);

            // Change settings for these textures
            albedoAtlas.wrapMode   = TextureWrapMode.Clamp;
            albedoAtlas.anisoLevel = 8;

            // Store results
            results.albedoMap = albedoAtlas;

            return(results);
        }
Example #5
0
        /// <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.
        /// Currently supports one archive per atlas. Super-atlas packing (multiple archives) is in the works.
        /// </summary>
        /// <param name="settings">Get texture settings.</param>
        /// <param name="alphaTextureFormat">Alpha TextureFormat.</param>
        /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param>
        /// <returns>GetTextureResults.</returns>
        public GetTextureResults GetTexture2DAtlas(
            GetTextureSettings settings,
            SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.RGBA32,
            SupportedNonAlphaTextureFormats nonAlphaFormat  = SupportedNonAlphaTextureFormats.RGB24)
        {
            GetTextureResults results = new GetTextureResults();

            // Individual textures must remain readable to pack into atlas
            bool stayReadable = settings.stayReadable;

            settings.stayReadable = true;

            // Assign texture file
            TextureFile textureFile;

            if (settings.textureFile == null)
            {
                textureFile          = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true);
                settings.textureFile = textureFile;
            }
            else
            {
                textureFile = settings.textureFile;
            }

            // Create lists
            results.atlasSizes       = new List <Vector2>(textureFile.RecordCount);
            results.atlasScales      = new List <Vector2>(textureFile.RecordCount);
            results.atlasOffsets     = new List <Vector2>(textureFile.RecordCount);
            results.atlasFrameCounts = new List <int>(textureFile.RecordCount);

            // Read every texture in archive
            bool               hasNormalMaps    = false;
            bool               hasEmissionMaps  = false;
            bool               hasAnimation     = false;
            bool               allowImport      = (settings.atlasMaxSize == 4096);
            List <Texture2D>   albedoTextures   = new List <Texture2D>();
            List <Texture2D>   normalTextures   = new List <Texture2D>();
            List <Texture2D>   emissionTextures = new List <Texture2D>();
            List <RecordIndex> indices          = new List <RecordIndex>();

            for (int record = 0; record < textureFile.RecordCount; record++)
            {
                // Get record index and frame count
                settings.record = record;
                int frames = textureFile.GetFrameCount(record);
                if (frames > 1)
                {
                    hasAnimation = true;
                }

                // Get record information
                DFSize      size   = textureFile.GetSize(record);
                DFSize      scale  = textureFile.GetScale(record);
                DFPosition  offset = textureFile.GetOffset(record);
                RecordIndex ri     = new RecordIndex()
                {
                    startIndex = albedoTextures.Count,
                    frameCount = frames,
                    width      = size.Width,
                    height     = size.Height,
                };
                indices.Add(ri);
                for (int frame = 0; frame < frames; frame++)
                {
                    settings.frame = frame;
                    GetTextureResults nextTextureResults = GetTexture2D(settings, alphaTextureFormat, nonAlphaFormat, allowImport);

                    albedoTextures.Add(nextTextureResults.albedoMap);
                    if (nextTextureResults.normalMap != null)
                    {
                        normalTextures.Add(nextTextureResults.normalMap);
                        hasNormalMaps = true;
                    }
                    if (nextTextureResults.emissionMap != null)
                    {
                        emissionTextures.Add(nextTextureResults.emissionMap);
                        hasEmissionMaps = true;
                    }
                }

                results.atlasSizes.Add(new Vector2(size.Width, size.Height));
                results.atlasScales.Add(new Vector2(scale.Width, scale.Height));
                results.atlasOffsets.Add(new Vector2(offset.X, offset.Y));
                results.atlasFrameCounts.Add(frames);
                results.textureFile = textureFile;
            }

            // Pack albedo textures into atlas and get our rects
            Texture2D atlasAlbedoMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, ParseTextureFormat(alphaTextureFormat), MipMaps);

            Rect[] rects = atlasAlbedoMap.PackTextures(albedoTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable);

            // Pack normal textures into atlas
            Texture2D atlasNormalMap = null;

            if (hasNormalMaps)
            {
                // Normals must be ARGB32
                atlasNormalMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, TextureFormat.ARGB32, MipMaps);
                atlasNormalMap.PackTextures(normalTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable);
            }

            // Pack emission textures into atlas
            // TODO: Change this as packing not consistent
            Texture2D atlasEmissionMap = null;

            if (hasEmissionMaps)
            {
                // Repacking to ensure correct mix of lit and unlit
                atlasEmissionMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, ParseTextureFormat(alphaTextureFormat), MipMaps);
                atlasEmissionMap.PackTextures(emissionTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable);
            }

            // Add to results
            if (results.atlasRects == null)
            {
                results.atlasRects = new List <Rect>(rects.Length);
            }
            if (results.atlasIndices == null)
            {
                results.atlasIndices = new List <RecordIndex>(indices.Count);
            }
            results.atlasRects.AddRange(rects);
            results.atlasIndices.AddRange(indices);

            // Shrink UV rect to compensate for internal border
            float ru          = 1f / atlasAlbedoMap.width;
            float rv          = 1f / atlasAlbedoMap.height;
            int   finalBorder = settings.borderSize + settings.atlasShrinkUVs;

            for (int i = 0; i < results.atlasRects.Count; i++)
            {
                Rect rct = results.atlasRects[i];
                rct.xMin += finalBorder * ru;
                rct.xMax -= finalBorder * ru;
                rct.yMin += finalBorder * rv;
                rct.yMax -= finalBorder * rv;
                results.atlasRects[i] = rct;
            }

            // Store results
            results.albedoMap       = atlasAlbedoMap;
            results.normalMap       = atlasNormalMap;
            results.emissionMap     = atlasEmissionMap;
            results.isAtlasAnimated = hasAnimation;
            results.isEmissive      = hasEmissionMaps;

            return(results);
        }
Example #6
0
        /// <summary>
        /// Gets Unity textures from Daggerfall texture with all options.
        /// Returns all supported texture maps for Standard shader in one call.
        /// </summary>
        /// <param name="settings">Get texture settings.</param>
        /// <param name="alphaTextureFormat">Alpha TextureFormat.</param>
        /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param>
        /// <param name="allowImport">Import texture from disk if present.</param>
        /// <returns>GetTextureResults.</returns>
        public GetTextureResults GetTexture2D(
            GetTextureSettings settings,
            SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.RGBA32,
            SupportedNonAlphaTextureFormats nonAlphaFormat  = SupportedNonAlphaTextureFormats.RGB24,
            bool allowImport = true)
        {
            GetTextureResults results = new GetTextureResults();

            // Check if window or auto-emissive
            bool isWindow   = ClimateSwaps.IsExteriorWindow(settings.archive, settings.record);
            bool isEmissive = (settings.autoEmission) ? IsEmissive(settings.archive, settings.record) : false;

            // Override readable flag when user has set preference in material reader
            if (DaggerfallUnity.Instance.MaterialReader.ReadableTextures)
            {
                settings.stayReadable = true;
            }

            // Assign texture file
            TextureFile textureFile;

            if (settings.textureFile == null)
            {
                textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true);
            }
            else
            {
                textureFile = settings.textureFile;
            }

            // Get starting DFBitmap and albedo Color32 array
            DFSize   sz;
            DFBitmap srcBitmap = textureFile.GetDFBitmap(settings.record, settings.frame);

            Color32[] albedoColors = textureFile.GetColor32(srcBitmap, settings.alphaIndex, settings.borderSize, out sz);

            // Sharpen source image
            if (settings.sharpen)
            {
                albedoColors = ImageProcessing.Sharpen(ref albedoColors, sz.Width, sz.Height);
            }

            // Dilate edges
            if (settings.borderSize > 0 && settings.dilate && !settings.copyToOppositeBorder)
            {
                ImageProcessing.DilateColors(ref albedoColors, sz);
            }

            // Copy to opposite border
            if (settings.borderSize > 0 && settings.copyToOppositeBorder)
            {
                ImageProcessing.WrapBorder(ref albedoColors, sz, settings.borderSize);
            }

            // Set albedo texture
            Texture2D albedoMap = null;

            if (allowImport && TextureReplacement.CustomTextureExist(settings.archive, settings.record, settings.frame))
            {
                // Import albedo texture
                albedoMap = TextureReplacement.LoadCustomTexture(settings.archive, settings.record, settings.frame);
            }
            else
            {
                // Create albedo texture
                if (settings.alphaIndex < 0)
                {
                    albedoMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(nonAlphaFormat), MipMaps);
                }
                else
                {
                    albedoMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps);
                }
                albedoMap.SetPixels32(albedoColors);
                albedoMap.Apply(true, !settings.stayReadable);
            }

            // Set normal texture
            Texture2D normalMap = null;

            if (allowImport && TextureReplacement.CustomNormalExist(settings.archive, settings.record, settings.frame))
            {
                // Always import normal if present on disk
                normalMap = TextureReplacement.LoadCustomNormal(settings.archive, settings.record, settings.frame);
            }
            else if (settings.createNormalMap && textureFile.SolidType == TextureFile.SolidTypes.None)
            {
                // Create normal texture - must be ARGB32
                // Normal maps are bypassed for solid-colour textures
                Color32[] normalColors;
                normalColors = ImageProcessing.GetBumpMap(ref albedoColors, sz.Width, sz.Height);
                normalColors = ImageProcessing.ConvertBumpToNormals(ref normalColors, sz.Width, sz.Height, settings.normalStrength);
                normalMap    = new Texture2D(sz.Width, sz.Height, TextureFormat.ARGB32, MipMaps);
                normalMap.SetPixels32(normalColors);
                normalMap.Apply(true, !settings.stayReadable);
            }

            // Import emission map or create basic emissive texture
            Texture2D emissionMap    = null;
            bool      resultEmissive = false;

            if (allowImport && TextureReplacement.CustomEmissionExist(settings.archive, settings.record, settings.frame))
            {
                // Always import emission if present on disk
                emissionMap    = TextureReplacement.LoadCustomEmission(settings.archive, settings.record, settings.frame);
                resultEmissive = true;
            }
            else
            {
                if (settings.createEmissionMap || (settings.autoEmission && isEmissive) && !isWindow)
                {
                    // Just reuse albedo map for basic colour emission
                    emissionMap    = albedoMap;
                    resultEmissive = true;
                }

                // Windows need special handling as only glass parts are emissive
                if ((settings.createEmissionMap || settings.autoEmissionForWindows) && isWindow)
                {
                    // Create custom emission texture for glass area of windows
                    Color32[] emissionColors = textureFile.GetWindowColors32(srcBitmap);
                    emissionMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps);
                    emissionMap.SetPixels32(emissionColors);
                    emissionMap.Apply(true, !settings.stayReadable);
                    resultEmissive = true;
                }

                // Lights need special handling as this archive contains a mix of emissive and non-emissive flats
                // This can cause problems with atlas packing due to mismatch between albedo and emissive texture counts
                if ((settings.createEmissionMap || settings.autoEmission) && settings.archive == LightsTextureArchive)
                {
                    // For the unlit flats we create a null-emissive black texture
                    if (!isEmissive)
                    {
                        Color32[] emissionColors = new Color32[sz.Width * sz.Height];
                        emissionMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps);
                        emissionMap.SetPixels32(emissionColors);
                        emissionMap.Apply(true, !settings.stayReadable);
                        resultEmissive = true;
                    }
                }
            }

            // Shrink UV rect to compensate for internal border
            float ru = 1f / sz.Width;
            float rv = 1f / sz.Height;

            results.singleRect = new Rect(
                settings.borderSize * ru,
                settings.borderSize * rv,
                (sz.Width - settings.borderSize * 2) * ru,
                (sz.Height - settings.borderSize * 2) * rv);

            // Store results
            results.albedoMap   = albedoMap;
            results.normalMap   = normalMap;
            results.emissionMap = emissionMap;
            results.isWindow    = isWindow;
            results.isEmissive  = resultEmissive;
            results.textureFile = textureFile;

            return(results);
        }
        /// <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.
        /// Currently supports one archive per atlas. Super-atlas packing (multiple archives) is in the works.
        /// </summary>
        /// <param name="settings">Get texture settings.</param>
        /// <param name="alphaTextureFormat">Alpha TextureFormat.</param>
        /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param>
        /// <returns>GetTextureResults.</returns>
        public GetTextureResults GetTexture2DAtlas(
            GetTextureSettings settings,
            SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.RGBA32,
            SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24)
        {
            GetTextureResults results = new GetTextureResults();

            // Individual textures must remain readable to pack into atlas
            bool stayReadable = settings.stayReadable;
            settings.stayReadable = true;

            // Assign texture file
            TextureFile textureFile;
            if (settings.textureFile == null)
            {
                textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true);
                settings.textureFile = textureFile;
            }
            else
                textureFile = settings.textureFile;

            // Create lists
            results.atlasSizes = new List<Vector2>(textureFile.RecordCount);
            results.atlasScales = new List<Vector2>(textureFile.RecordCount);
            results.atlasOffsets = new List<Vector2>(textureFile.RecordCount);
            results.atlasFrameCounts = new List<int>(textureFile.RecordCount);

            // Read every texture in archive
            bool hasNormalMaps = false;
            bool hasEmissionMaps = false;
            bool hasAnimation = false;
            List<Texture2D> albedoTextures = new List<Texture2D>();
            List<Texture2D> normalTextures = new List<Texture2D>();
            List<Texture2D> emissionTextures = new List<Texture2D>();
            List<RecordIndex> indices = new List<RecordIndex>();
            for (int record = 0; record < textureFile.RecordCount; record++)
            {
                // Get record index and frame count
                settings.record = record;
                int frames = textureFile.GetFrameCount(record);
                if (frames > 1)
                    hasAnimation = true;

                // Get record information
                DFSize size = textureFile.GetSize(record);
                DFSize scale = textureFile.GetScale(record);
                DFPosition offset = textureFile.GetOffset(record);
                RecordIndex ri = new RecordIndex()
                {
                    startIndex = albedoTextures.Count,
                    frameCount = frames,
                    width = size.Width,
                    height = size.Height,
                };
                indices.Add(ri);
                for (int frame = 0; frame < frames; frame++)
                {
                    settings.frame = frame;
                    GetTextureResults nextTextureResults = GetTexture2D(settings, alphaTextureFormat, nonAlphaFormat);
                    albedoTextures.Add(nextTextureResults.albedoMap);
                    if (nextTextureResults.normalMap != null)
                    {
                        normalTextures.Add(nextTextureResults.normalMap);
                        hasNormalMaps = true;
                    }
                    if (nextTextureResults.emissionMap != null)
                    {
                        emissionTextures.Add(nextTextureResults.emissionMap);
                        hasEmissionMaps = true;
                    }
                }

                results.atlasSizes.Add(new Vector2(size.Width, size.Height));
                results.atlasScales.Add(new Vector2(scale.Width, scale.Height));
                results.atlasOffsets.Add(new Vector2(offset.X, offset.Y));
                results.atlasFrameCounts.Add(frames);
                results.textureFile = textureFile;
            }

            // Pack albedo textures into atlas and get our rects
            Texture2D atlasAlbedoMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, ParseTextureFormat(alphaTextureFormat), MipMaps);
            Rect[] rects = atlasAlbedoMap.PackTextures(albedoTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable);

            // Pack normal textures into atlas
            Texture2D atlasNormalMap = null;
            if (hasNormalMaps)
            {
                // Normals must be ARGB32
                atlasNormalMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, TextureFormat.ARGB32, MipMaps);
                atlasNormalMap.PackTextures(normalTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable);
            }

            // Pack emission textures into atlas
            // TODO: Change this as packing not consistent
            Texture2D atlasEmissionMap = null;
            if (hasEmissionMaps)
            {
                // Repacking to ensure correct mix of lit and unlit
                atlasEmissionMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, ParseTextureFormat(alphaTextureFormat), MipMaps);
                atlasEmissionMap.PackTextures(emissionTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable);
            }

            // Add to results
            if (results.atlasRects == null) results.atlasRects = new List<Rect>(rects.Length);
            if (results.atlasIndices == null) results.atlasIndices = new List<RecordIndex>(indices.Count);
            results.atlasRects.AddRange(rects);
            results.atlasIndices.AddRange(indices);

            // Shrink UV rect to compensate for internal border
            float ru = 1f / atlasAlbedoMap.width;
            float rv = 1f / atlasAlbedoMap.height;
            int finalBorder = settings.borderSize + settings.atlasShrinkUVs;
            for (int i = 0; i < results.atlasRects.Count; i++)
            {
                Rect rct = results.atlasRects[i];
                rct.xMin += finalBorder * ru;
                rct.xMax -= finalBorder * ru;
                rct.yMin += finalBorder * rv;
                rct.yMax -= finalBorder * rv;
                results.atlasRects[i] = rct;
            }

            // Store results
            results.albedoMap = atlasAlbedoMap;
            results.normalMap = atlasNormalMap;
            results.emissionMap = atlasEmissionMap;
            results.isAtlasAnimated = hasAnimation;
            results.isEmissive = hasEmissionMaps;

            return results;
        }
        /// <summary>
        /// Gets Unity textures from Daggerfall texture with all options.
        /// Returns all supported texture maps for Standard shader in one call.
        /// </summary>
        /// <param name="settings">Get texture settings.</param>
        /// <param name="alphaTextureFormat">Alpha TextureFormat.</param>
        /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param>
        /// <returns>GetTextureResults.</returns>
        public GetTextureResults GetTexture2D(
            GetTextureSettings settings,
            SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.RGBA32,
            SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24)
        {
            GetTextureResults results = new GetTextureResults();

            // Check if window or auto-emissive
            bool isWindow = ClimateSwaps.IsExteriorWindow(settings.archive, settings.record);
            bool isEmissive = (settings.autoEmission) ? IsEmissive(settings.archive, settings.record) : false;

            // Assign texture file
            TextureFile textureFile;
            if (settings.textureFile == null)
                textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true);
            else
                textureFile = settings.textureFile;

            // Get starting DFBitmap and albedo Color32 array
            DFSize sz;
            DFBitmap srcBitmap = textureFile.GetDFBitmap(settings.record, settings.frame);
            Color32[] albedoColors = textureFile.GetColor32(srcBitmap, settings.alphaIndex, settings.borderSize, out sz);

            // Sharpen source image
            if (settings.sharpen)
                albedoColors = ImageProcessing.Sharpen(ref albedoColors, sz.Width, sz.Height);

            // Dilate edges
            if (settings.borderSize > 0 && settings.dilate && !settings.copyToOppositeBorder)
                ImageProcessing.DilateColors(ref albedoColors, sz);

            // Copy to opposite border
            if (settings.borderSize > 0 && settings.copyToOppositeBorder)
                ImageProcessing.WrapBorder(ref albedoColors, sz, settings.borderSize);

            // Create albedo texture
            Texture2D albedoMap = null;
            if (settings.alphaIndex < 0)
                albedoMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(nonAlphaFormat), MipMaps);
            else
                albedoMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps);
            albedoMap.SetPixels32(albedoColors);
            albedoMap.Apply(true, !settings.stayReadable);

            // Create normal texture - must be ARGB32
            // Normal maps are bypassed for solid-colour textures
            Texture2D normalMap = null;
            if (settings.createNormalMap && textureFile.SolidType == TextureFile.SolidTypes.None)
            {
                Color32[] normalColors;
                normalColors = ImageProcessing.GetBumpMap(ref albedoColors, sz.Width, sz.Height);
                normalColors = ImageProcessing.ConvertBumpToNormals(ref normalColors, sz.Width, sz.Height, settings.normalStrength);
                normalMap = new Texture2D(sz.Width, sz.Height, TextureFormat.ARGB32, MipMaps);
                normalMap.SetPixels32(normalColors);
                normalMap.Apply(true, !settings.stayReadable);
            }

            // Create basic emissive texture
            Texture2D emissionMap = null;
            bool resultEmissive = false;
            if (settings.createEmissionMap || (settings.autoEmission && isEmissive) && !isWindow)
            {
                // Just reuse albedo map for basic colour emission
                emissionMap = albedoMap;
                resultEmissive = true;
            }

            // Windows need special handling as only glass parts are emissive
            if ((settings.createEmissionMap || settings.autoEmissionForWindows) && isWindow)
            {
                // Create custom emission texture for glass area of windows
                Color32[] emissionColors = textureFile.GetWindowColors32(srcBitmap);
                emissionMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps);
                emissionMap.SetPixels32(emissionColors);
                emissionMap.Apply(true, !settings.stayReadable);
                resultEmissive = true;
            }

            // Lights need special handling as this archive contains a mix of emissive and non-emissive flats
            // This can cause problems with atlas packing due to mismatch between albedo and emissive texture counts
            if ((settings.createEmissionMap || settings.autoEmission) && settings.archive == LightsTextureArchive)
            {
                // For the unlit flats we create a null-emissive black texture
                if (!isEmissive)
                {
                    Color32[] emissionColors = new Color32[sz.Width * sz.Height];
                    emissionMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps);
                    emissionMap.SetPixels32(emissionColors);
                    emissionMap.Apply(true, !settings.stayReadable);
                    resultEmissive = true;
                }
            }

            // Shrink UV rect to compensate for internal border
            float ru = 1f / sz.Width;
            float rv = 1f / sz.Height;
            results.singleRect = new Rect(
                settings.borderSize * ru,
                settings.borderSize * rv,
                (sz.Width - settings.borderSize * 2) * ru,
                (sz.Height - settings.borderSize * 2) * rv);

            // Store results
            results.albedoMap = albedoMap;
            results.normalMap = normalMap;
            results.emissionMap = emissionMap;
            results.isWindow = isWindow;
            results.isEmissive = resultEmissive;
            results.textureFile = textureFile;

            return results;
        }
        /// <summary>
        /// Gets specially packed tileset atlas for terrains.
        /// This needs to be improved to create each mip level manually to further reduce artifacts.
        /// </summary>
        /// <param name="archive">Archive index.</param>
        /// <param name="stayReadable">Texture should stay readable.</param>
        /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param>
        /// <returns></returns>
        public GetTextureResults GetTerrainTilesetTexture(
            int archive,
            bool stayReadable = false,
            SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24)
        {
            const int atlasDim = 2048;
            const int gutterSize = 32;

            GetTextureResults results = new GetTextureResults();

            // Load texture file and check count matches terrain tiles
            TextureFile textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true);
            if (textureFile.RecordCount != 56)
                return results;

            // Rollout tiles into atlas.
            // This is somewhat inefficient, but fortunately atlases only
            // need to be generated once and can be prepared offline where
            // startup time is critical.
            int x = 0, y = 0;
            Color32[] atlasColors = new Color32[atlasDim * atlasDim];
            for (int record = 0; record < textureFile.RecordCount; record++)
            {
                // Create base image with gutter
                DFSize sz;
                Color32[] albedo = textureFile.GetColor32(record, 0, -1, gutterSize, out sz);

                // Wrap and clamp textures based on tile
                switch (record)
                {
                    // Textures that do not tile in any direction
                    case 5:
                    case 7:
                    case 10:
                    case 12:
                    case 15:
                    case 17:
                    case 20:
                    case 22:
                    case 25:
                    case 27:
                    case 30:
                    case 32:
                    case 34:
                    case 35:
                    case 36:
                    case 37:
                    case 38:
                    case 39:
                    case 40:
                    case 41:
                    case 42:
                    case 43:
                    case 44:
                    case 45:
                    case 47:
                    case 48:
                    case 49:
                    case 50:
                    case 51:
                    case 52:
                    case 53:
                    case 55:
                        ImageProcessing.ClampBorder(ref albedo, sz, gutterSize);
                        break;

                    // Textures that clamp horizontally and tile vertically
                    case 6:
                    case 11:
                    case 16:
                    case 21:
                    case 26:
                    case 31:
                        ImageProcessing.WrapBorder(ref albedo, sz, gutterSize, false);
                        ImageProcessing.ClampBorder(ref albedo, sz, gutterSize, true, false);
                        break;

                    // Textures that tile in all directions
                    default:
                        ImageProcessing.WrapBorder(ref albedo, sz, gutterSize);
                        break;
                }

                // Create variants
                Color32[] rotate = ImageProcessing.RotateColors(ref albedo, sz.Width, sz.Height);
                Color32[] flip = ImageProcessing.FlipColors(ref albedo, sz.Width, sz.Height);
                Color32[] rotateFlip = ImageProcessing.RotateColors(ref flip, sz.Width, sz.Height);

                // Insert into atlas
                ImageProcessing.InsertColors(ref albedo, ref atlasColors, x, y, sz.Width, sz.Height, atlasDim, atlasDim);
                ImageProcessing.InsertColors(ref rotate, ref atlasColors, x + sz.Width, y, sz.Width, sz.Height, atlasDim, atlasDim);
                ImageProcessing.InsertColors(ref flip, ref atlasColors, x + sz.Width * 2, y, sz.Width, sz.Height, atlasDim, atlasDim);
                ImageProcessing.InsertColors(ref rotateFlip, ref atlasColors, x + sz.Width * 3, y, sz.Width, sz.Height, atlasDim, atlasDim);

                // Increment position
                x += sz.Width * 4;
                if (x >= atlasDim)
                {
                    y += sz.Height;
                    x = 0;
                }
            }

            // Create Texture2D
            Texture2D albedoAtlas = new Texture2D(atlasDim, atlasDim, ParseTextureFormat(nonAlphaFormat), MipMaps);
            albedoAtlas.SetPixels32(atlasColors);
            albedoAtlas.Apply(true, !stayReadable);

            // Change settings for these textures
            albedoAtlas.wrapMode = TextureWrapMode.Clamp;
            albedoAtlas.anisoLevel = 8;

            // Store results
            results.albedoMap = albedoAtlas;

            return results;
        }
Example #10
0
        /// <summary>
        /// Import texture(s) used on models.
        /// </summary>
        /// <param name="archive">Archive index</param>
        /// <param name="record">Record index</param>
        /// <param name="frame">Texture frame</param>
        /// <param name="results">Texture Results</param>
        /// <param name="GenerateNormals">Will create normal map</param>
        static public void LoadCustomTextureResults(int archive, int record, int frame, ref GetTextureResults results, ref bool GenerateNormals)
        {
            // Main texture
            if (CustomTextureExist(archive, record, frame))
            {
                results.albedoMap = LoadCustomTexture(archive, record, frame);
            }

            // Normal map
            if (CustomNormalExist(archive, record, frame))
            {
                results.normalMap = LoadCustomNormal(archive, record, frame);
                GenerateNormals   = true;
            }

            // Emission map
            // windowed walls use a custom emission map or stick with vanilla
            // non-window use the main texture as emission, unless a custom map is provided
            if (results.isEmissive)
            {
                if (CustomEmissionExist(archive, record, frame))
                {
                    // Import emission texture
                    results.emissionMap = LoadCustomEmission(archive, record, frame);
                }
                else if (!results.isWindow && CustomTextureExist(archive, record, frame))
                {
                    // Reuse albedo map for basic colour emission
                    results.emissionMap = results.albedoMap;
                }
            }
            else if (CustomEmissionExist(archive, record, frame))
            {
                // Force emission map
                results.emissionMap = LoadCustomEmission(archive, record, frame);
                results.isEmissive  = true;
                results.isWindow    = false;
            }
        }