SanAndreasUnity/Assets/Scripts/Importing/Conversion/TextureDictionary.cs

436 lines
No EOL
13 KiB
C#

using SanAndreasUnity.Importing.Archive;
using SanAndreasUnity.Importing.RenderWareStream;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace SanAndreasUnity.Importing.Conversion
{
public static class TextureDictionaryExtensions
{
public static LoadedTexture GetDiffuse(this TextureDictionary[] txds, string name)
{
foreach (var txd in txds)
{
var tex = txd.GetDiffuse(name, TextureLoadParams.Default);
if (tex != null) return tex;
}
return null;
}
public static LoadedTexture GetAlpha(this TextureDictionary[] txds, string name)
{
foreach (var txd in txds)
{
var tex = txd.GetAlpha(name, TextureLoadParams.Default);
if (tex != null) return tex;
}
return null;
}
}
public class LoadedTexture
{
public readonly Texture2D Texture;
public readonly bool HasAlpha;
public LoadedTexture(Texture2D tex, bool hasAlpha)
{
Texture = tex;
HasAlpha = hasAlpha;
}
}
public class TextureLoadParams
{
public bool makeNoLongerReadable = true;
private static TextureLoadParams _sDefault;
public static TextureLoadParams Default
{
get
{
if (null == _sDefault)
_sDefault = new TextureLoadParams();
return _sDefault;
}
}
}
public class TextureDictionary
{
private static byte[] ConvertDXT3ToDXT5(byte[] data)
{
for (var i = 0; i < data.Length; i += 16)
{
ulong packed = 0;
for (var j = 0; j < 16; ++j)
{
var s = 1 | ((j & 1) << 2);
var c = (data[i + (j >> 1)] >> s) & 0x7;
switch (c)
{
case 0:
c = 1;
break;
case 7:
c = 0;
break;
default:
c = 8 - c;
break;
}
packed |= ((ulong)c << (3 * j));
}
data[i + 0] = 0xff;
data[i + 1] = 0x00;
for (var j = 0; j < 6; ++j)
{
data[i + 2 + j] = (byte)((packed >> (j << 3)) & 0xff);
}
}
return data;
}
private static byte[] ConvertBGRToRGB(byte[] data)
{
for (var i = 0; i < data.Length; i += 4)
{
var t = data[i];
data[i] = data[i + 2];
data[i + 2] = t;
}
return data;
}
private static readonly byte[] _sR5G5B5Lookup = Enumerable.Range(0, 0x20)
.Select(x => (byte)Math.Round(((double)x / 0x1f) * 0xff))
.ToArray();
private static byte[] ConvertA1R5G5B5ToARGB32(byte[] data)
{
var dest = new byte[data.Length << 1];
for (var i = 0; i < data.Length - 1; i += 2)
{
var val = data[i] | (data[i + 1] << 8);
var d = i << 1;
dest[d + 0] = (byte)((val & 1) == 1 ? 0xff : 0);
dest[d + 1] = _sR5G5B5Lookup[(val >> 1) & 0x1f];
dest[d + 2] = _sR5G5B5Lookup[(val >> 6) & 0x1f];
dest[d + 3] = _sR5G5B5Lookup[(val >> 11) & 0x1f];
}
return dest;
}
private static LoadedTexture Convert(TextureNative src, TextureLoadParams textureLoadParams)
{
TextureFormat format;
var loadMips = (src.Format & RasterFormat.ExtMipMap) == RasterFormat.ExtMipMap;
var autoMips = (src.Format & RasterFormat.ExtAutoMipMap) == RasterFormat.ExtAutoMipMap;
switch (src.Format & RasterFormat.NoExt)
{
case RasterFormat.BGRA8:
case RasterFormat.BGR8:
format = TextureFormat.RGBA32;
break;
case RasterFormat.LUM8:
format = TextureFormat.Alpha8;
break;
case RasterFormat.R4G4B4A4:
format = TextureFormat.RGBA4444;
break;
case RasterFormat.A1R5G5B5:
format = TextureFormat.ARGB32;
break;
case RasterFormat.R5G6B5:
format = TextureFormat.RGB565;
break;
default:
throw new NotImplementedException(string.Format("RasterFormat.{0}", src.Format & RasterFormat.NoExt));
}
switch (src.Compression)
{
case CompressionMode.None:
break;
case CompressionMode.DXT1:
format = TextureFormat.DXT1;
break;
case CompressionMode.DXT3:
format = TextureFormat.DXT5;
break;
default:
throw new NotImplementedException(string.Format("CompressionMode.{0}", src.Compression));
}
var tex = new Texture2D(src.Width, src.Height, format, false /*loadMips*/);
switch (src.FilterFlags)
{
case Filter.None:
case Filter.Nearest:
case Filter.MipNearest:
tex.filterMode = FilterMode.Point;
break;
case Filter.Linear:
case Filter.MipLinear:
case Filter.LinearMipNearest:
tex.filterMode = FilterMode.Bilinear;
break;
case Filter.LinearMipLinear:
case Filter.Unknown:
tex.filterMode = FilterMode.Trilinear;
break;
}
var data = src.ImageData;
switch (src.Format & RasterFormat.NoExt)
{
case RasterFormat.BGR8:
case RasterFormat.BGRA8:
data = ConvertBGRToRGB(data);
break;
}
switch (src.Compression)
{
case CompressionMode.DXT3:
data = ConvertDXT3ToDXT5(data);
break;
}
tex.LoadRawTextureData(data);
tex.Apply(loadMips || autoMips, textureLoadParams.makeNoLongerReadable);
return new LoadedTexture(tex, src.Alpha);
}
private static readonly Dictionary<string, string> _sParents = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
private static readonly Utilities.AsyncLoader<string, TextureDictionary> s_asyncLoader =
new Utilities.AsyncLoader<string, TextureDictionary> (StringComparer.InvariantCultureIgnoreCase);
public static bool DontLoadTextures { get; set; } = false;
private static Texture2D s_dummyTexture;
private static Texture2D DummyTexture {
get {
if (null == s_dummyTexture)
s_dummyTexture = Utilities.F.CreateTexture(32, 32, Color.gray);
return s_dummyTexture;
}
}
public static TextureDictionary Load(string name)
{
name = name.ToLower();
if (s_asyncLoader.IsObjectLoaded (name))
return s_asyncLoader.GetLoadedObject (name);
UnityEngine.Profiling.Profiler.BeginSample ("TextureDictionary.Load");
var txd = new TextureDictionary(DontLoadTextures ? null : ArchiveManager.ReadFile<RenderWareStream.TextureDictionary>(name + ".txd"));
s_asyncLoader.AddToLoadedObjects(name, txd);
UnityEngine.Profiling.Profiler.EndSample ();
return txd;
}
public static void LoadAsync(string name, System.Action<TextureDictionary> onFinish)
{
name = name.ToLower();
if (!s_asyncLoader.TryLoadObject (name, onFinish))
return;
// read archive file asyncly
TextureDictionary loadedTxd = null;
bool bDontLoad = DontLoadTextures;
Behaviours.LoadingThread.RegisterJob (new Behaviours.LoadingThread.Job<RenderWareStream.TextureDictionary> () {
action = () => {
return bDontLoad ? null : ArchiveManager.ReadFile<RenderWareStream.TextureDictionary>(name + ".txd");
},
callbackSuccess = (RenderWareStream.TextureDictionary td) => {
UnityEngine.Profiling.Profiler.BeginSample ("TextureDictionary()");
loadedTxd = new TextureDictionary (td);
UnityEngine.Profiling.Profiler.EndSample ();
},
callbackFinish = (result) => {
s_asyncLoader.OnObjectFinishedLoading( name, loadedTxd, loadedTxd != null );
}
});
}
public static void AddParent(string dictName, string parentName)
{
dictName = dictName.ToLower();
parentName = parentName.ToLower();
if (_sParents.ContainsKey(dictName)) return;
_sParents.Add(dictName, parentName);
if (s_asyncLoader.IsObjectLoaded(dictName))
{
s_asyncLoader.GetLoadedObject(dictName).ParentName = parentName;
}
}
private class Texture
{
private LoadedTexture _converted;
private bool _attemptedConversion;
public string DiffuseName { get { return Native.DiffuseName; } }
public string AlphaName { get { return Native.AlphaName; } }
public bool IsDiffuse { get { return !string.IsNullOrEmpty(DiffuseName); } }
public bool IsAlpha { get { return !string.IsNullOrEmpty(AlphaName); } }
public LoadedTexture GetConverted(TextureLoadParams textureLoadParams)
{
if (_attemptedConversion) return _converted;
_attemptedConversion = true;
_converted = Convert(Native, textureLoadParams);
return _converted;
}
public readonly TextureNative Native;
public Texture(TextureNative native)
{
Native = native;
}
}
private readonly Dictionary<string, Texture> _diffuse;
private readonly Dictionary<string, Texture> _alpha;
private TextureDictionary _parent;
public string ParentName { get; set; }
public TextureDictionary Parent
{
get { return _parent ?? (_parent = Load(ParentName)); }
}
public IEnumerable<string> DiffuseNames
{
get { return _diffuse.Keys; }
}
public IEnumerable<string> AlphaNames
{
get { return _alpha.Keys; }
}
private TextureDictionary(RenderWareStream.TextureDictionary txd)
{
_diffuse = new Dictionary<string, Texture>(StringComparer.InvariantCultureIgnoreCase);
_alpha = new Dictionary<string, Texture>(StringComparer.InvariantCultureIgnoreCase);
if (null == txd)
return;
foreach (var native in txd.Textures)
{
var tex = new Texture(native);
if (tex.IsDiffuse)
{
_diffuse.Add(tex.DiffuseName, tex);
}
if (tex.IsAlpha)
{
if (_alpha.ContainsKey(tex.AlphaName))
{
Debug.LogWarningFormat("Tried to re-add {0} (diffuse {1} vs {2})",
tex.AlphaName, tex.DiffuseName, _alpha[tex.AlphaName].DiffuseName);
continue;
}
_alpha.Add(tex.AlphaName, tex);
}
}
}
public TextureNative GetDiffuseNative(string name)
{
if (!_diffuse.ContainsKey(name))
{
return ParentName != null ? Parent.GetDiffuseNative(name) : null;
}
return _diffuse[name].Native;
}
public LoadedTexture GetDiffuse(string name)
{
return GetDiffuse(name, TextureLoadParams.Default);
}
public LoadedTexture GetDiffuse(string name, TextureLoadParams textureLoadParams)
{
if (DontLoadTextures)
return new LoadedTexture(DummyTexture, false);
if (!_diffuse.ContainsKey(name))
{
return ParentName != null ? Parent.GetDiffuse(name, textureLoadParams) : null;
}
return _diffuse[name].GetConverted(textureLoadParams);
}
public LoadedTexture GetAlpha(string name, TextureLoadParams textureLoadParams)
{
if (DontLoadTextures)
return new LoadedTexture(DummyTexture, false);
if (!_alpha.ContainsKey(name))
{
return ParentName != null ? Parent.GetAlpha(name, textureLoadParams) : null;
}
return _alpha[name].GetConverted(textureLoadParams);
}
}
}