diff --git a/CodeWalker.Core/CodeWalker.Core.csproj b/CodeWalker.Core/CodeWalker.Core.csproj index e1ee816..c0142de 100644 --- a/CodeWalker.Core/CodeWalker.Core.csproj +++ b/CodeWalker.Core/CodeWalker.Core.csproj @@ -60,6 +60,7 @@ + diff --git a/CodeWalker.Core/GameFiles/FileTypes/GtxdFile.cs b/CodeWalker.Core/GameFiles/FileTypes/GtxdFile.cs index 53dcaf3..4c47102 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/GtxdFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/GtxdFile.cs @@ -15,7 +15,7 @@ namespace CodeWalker.GameFiles public RbfFile Rbf { get; set; } - public Dictionary CMapParentTxds { get; set; } + public Dictionary TxdRelationships { get; set; } @@ -46,7 +46,7 @@ namespace CodeWalker.GameFiles if (rbfstruct.Name == "CMapParentTxds") { - LoadMapParentTxds(rbfstruct); + LoadTxdRelationships(rbfstruct); } Loaded = true; @@ -68,7 +68,7 @@ namespace CodeWalker.GameFiles xml = xml.Remove(0, bom.Length); } - LoadMapParentTxds(xml); + LoadTxdRelationships(xml); Loaded = true; } @@ -76,10 +76,10 @@ namespace CodeWalker.GameFiles } - private void LoadMapParentTxds(RbfStructure rbfstruct) + private void LoadTxdRelationships(RbfStructure rbfstruct) { - CMapParentTxds = new Dictionary(); + TxdRelationships = new Dictionary(); //StringBuilder sblist = new StringBuilder(); foreach (var child in rbfstruct.Children) { @@ -118,9 +118,9 @@ namespace CodeWalker.GameFiles } if ((!string.IsNullOrEmpty(parentstr)) && (!string.IsNullOrEmpty(childstr))) { - if (!CMapParentTxds.ContainsKey(childstr)) + if (!TxdRelationships.ContainsKey(childstr)) { - CMapParentTxds.Add(childstr, parentstr); + TxdRelationships.Add(childstr, parentstr); } else { @@ -140,13 +140,13 @@ namespace CodeWalker.GameFiles - private void LoadMapParentTxds(string xml) + private void LoadTxdRelationships(string xml) { XmlDocument xmldoc = new XmlDocument(); xmldoc.LoadXml(xml); //maybe better load xml.ToLower() and use "cmapparenttxds/txdrelationships/item" as xpath? XmlNodeList items = xmldoc.SelectNodes("CMapParentTxds/txdRelationships/Item | CMapParentTxds/txdRelationships/item"); - CMapParentTxds = new Dictionary(); + TxdRelationships = new Dictionary(); for (int i = 0; i < items.Count; i++) { string parentstr = Xml.GetChildInnerText(items[i], "parent"); @@ -154,9 +154,9 @@ namespace CodeWalker.GameFiles if ((!string.IsNullOrEmpty(parentstr)) && (!string.IsNullOrEmpty(childstr))) { - if (!CMapParentTxds.ContainsKey(childstr)) + if (!TxdRelationships.ContainsKey(childstr)) { - CMapParentTxds.Add(childstr, parentstr); + TxdRelationships.Add(childstr, parentstr); } } } diff --git a/CodeWalker.Core/GameFiles/FileTypes/VehiclesFile.cs b/CodeWalker.Core/GameFiles/FileTypes/VehiclesFile.cs new file mode 100644 index 0000000..8b3ad01 --- /dev/null +++ b/CodeWalker.Core/GameFiles/FileTypes/VehiclesFile.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; + +namespace CodeWalker.GameFiles +{ + public class VehiclesFile : GameFile, PackedFile + { + + public RbfFile Rbf { get; set; } + + + public Dictionary TxdRelationships { get; set; } + + + + + public VehiclesFile() : base(null, GameFileType.Vehicles) + { + } + public VehiclesFile(RpfFileEntry entry) : base(entry, GameFileType.Vehicles) + { + } + + + + public void Load(byte[] data, RpfFileEntry entry) + { + RpfFileEntry = entry; + Name = entry.Name; + FilePath = Name; + + + if (entry.NameLower.EndsWith(".meta")) + { + //required for update\x64\dlcpacks\mpheist\dlc.rpf\common\data\gtxd.meta and update\x64\dlcpacks\mpluxe\dlc.rpf\common\data\gtxd.meta + string bom = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); + string xml = Encoding.UTF8.GetString(data); + + if (xml.StartsWith(bom, StringComparison.Ordinal)) + { + xml = xml.Remove(0, bom.Length); + } + + XmlDocument xmldoc = new XmlDocument(); + xmldoc.LoadXml(xml); + + LoadVehicles(xmldoc); + + LoadTxdRelationships(xmldoc); + + Loaded = true; + } + } + + + private void LoadVehicles(XmlDocument xmldoc) + { + XmlNodeList items = xmldoc.SelectNodes("CVehicleModelInfo__InitDataList/InitDatas/Item | CVehicleModelInfo__InitDataList/InitDatas/item"); + for (int i = 0; i < items.Count; i++) + { + //TODO... + } + } + + private void LoadTxdRelationships(XmlDocument xmldoc) + { + XmlNodeList items = xmldoc.SelectNodes("CVehicleModelInfo__InitDataList/txdRelationships/Item | CVehicleModelInfo__InitDataList/txdRelationships/item"); + + TxdRelationships = new Dictionary(); + for (int i = 0; i < items.Count; i++) + { + string parentstr = Xml.GetChildInnerText(items[i], "parent"); + string childstr = Xml.GetChildInnerText(items[i], "child"); + + if ((!string.IsNullOrEmpty(parentstr)) && (!string.IsNullOrEmpty(childstr))) + { + if (!TxdRelationships.ContainsKey(childstr)) + { + TxdRelationships.Add(childstr, parentstr); + } + } + } + } + + + } +} diff --git a/CodeWalker.Core/GameFiles/GameFile.cs b/CodeWalker.Core/GameFiles/GameFile.cs index 0a82198..44806f1 100644 --- a/CodeWalker.Core/GameFiles/GameFile.cs +++ b/CodeWalker.Core/GameFiles/GameFile.cs @@ -70,6 +70,7 @@ namespace CodeWalker.GameFiles Ywr = 14, Yvr = 15, Gtxd = 16, + Vehicles = 17, } diff --git a/CodeWalker.Core/GameFiles/GameFileCache.cs b/CodeWalker.Core/GameFiles/GameFileCache.cs index 2e38f4e..95a87c8 100644 --- a/CodeWalker.Core/GameFiles/GameFileCache.cs +++ b/CodeWalker.Core/GameFiles/GameFileCache.cs @@ -27,7 +27,8 @@ namespace CodeWalker.GameFiles private volatile bool archetypesLoaded = false; private Dictionary archetypeDict = new Dictionary(); private Dictionary textureLookup = new Dictionary(); - private Dictionary textureParents; + private Dictionary textureParents; + private Dictionary hdtexturelookup; private object updateSyncRoot = new object(); private object requestSyncRoot = new object(); @@ -132,6 +133,7 @@ namespace CodeWalker.GameFiles mainCache.Clear(); textureLookup.Clear(); + GameFile queueclear; while (requestQueue.TryPop(out queueclear)) { } //empty the old queue out... @@ -958,6 +960,7 @@ namespace CodeWalker.GameFiles private void InitManifestDicts() { AllManifests = new List(); + hdtexturelookup = new Dictionary(); foreach (RpfFile file in ActiveMapRpfFiles.Values) //RpfMan.BaseRpfs) { if (file.AllEntries == null) continue; @@ -985,6 +988,19 @@ namespace CodeWalker.GameFiles { } else { } + + + if (ymffile.HDTxdAssetBindings != null) + { + for (int i = 0; i < ymffile.HDTxdAssetBindings.Length; i++) + { + var b = ymffile.HDTxdAssetBindings[i]; + var targetasset = JenkHash.GenHash(b.targetAsset.ToString().ToLowerInvariant()); + var hdtxd = JenkHash.GenHash(b.HDTxd.ToString().ToLowerInvariant()); + hdtexturelookup[targetasset] = hdtxd; + } + } + } } @@ -996,70 +1012,49 @@ namespace CodeWalker.GameFiles private void InitGtxds() { - Dictionary parentTxds = new Dictionary(); + var parentTxds = new Dictionary(); IEnumerable rpfs = PreloadedMode ? AllRpfs : (IEnumerable)ActiveMapRpfFiles.Values; - foreach (RpfFile file in rpfs) + var addTxdRelationships = new Action>((from) => { - if (file.AllEntries == null) continue; - foreach (RpfEntry entry in file.AllEntries) + foreach (var kvp in from) { - try + uint chash = JenkHash.GenHash(kvp.Key.ToLowerInvariant()); + uint phash = JenkHash.GenHash(kvp.Value.ToLowerInvariant()); + if (!parentTxds.ContainsKey(chash)) { - if ((entry.NameLower == "gtxd.ymt") || (entry.NameLower == "gtxd.meta")) - { - GtxdFile ymt = RpfMan.GetFile(entry); - if (ymt.CMapParentTxds != null) - { - foreach (var kvp in ymt.CMapParentTxds) - { - uint chash = JenkHash.GenHash(kvp.Key.ToLowerInvariant()); - uint phash = JenkHash.GenHash(kvp.Value.ToLowerInvariant()); - if (!parentTxds.ContainsKey(chash)) - { - parentTxds.Add(chash, phash); - } - else - { - } - } - } - } + parentTxds.Add(chash, phash); } - catch (Exception ex) + else { - string errstr = entry.Path + "\n" + ex.ToString(); - ErrorLog(errstr); } } - } + }); - if (EnableDlc) + var addRpfTxdRelationships = new Action>((from) => { - foreach (var dlcfile in DlcActiveRpfs) + foreach (RpfFile file in from) { - foreach (RpfEntry entry in dlcfile.AllEntries) + if (file.AllEntries == null) continue; + foreach (RpfEntry entry in file.AllEntries) { try { - if (entry.NameLower == "gtxd.meta") + if ((entry.NameLower == "gtxd.ymt") || (entry.NameLower == "gtxd.meta")) { GtxdFile ymt = RpfMan.GetFile(entry); - if (ymt.CMapParentTxds != null) + if (ymt.TxdRelationships != null) { - foreach (var kvp in ymt.CMapParentTxds) - { - uint chash = JenkHash.GenHash(kvp.Key.ToLowerInvariant()); - uint phash = JenkHash.GenHash(kvp.Value.ToLowerInvariant()); - if (!parentTxds.ContainsKey(chash)) - { - parentTxds.Add(chash, phash); - } - else - { - } - } + addTxdRelationships(ymt.TxdRelationships); + } + } + else if (entry.NameLower == "vehicles.meta") + { + VehiclesFile vf = RpfMan.GetFile(entry); + if (vf.TxdRelationships != null) + { + addTxdRelationships(vf.TxdRelationships); } } } @@ -1070,6 +1065,16 @@ namespace CodeWalker.GameFiles } } } + + }); + + + addRpfTxdRelationships(rpfs); + + + if (EnableDlc) + { + addRpfTxdRelationships(DlcActiveRpfs); } @@ -1078,7 +1083,7 @@ namespace CodeWalker.GameFiles - //ensure global texture dicts: + //ensure resident global texture dicts: YtdFile ytd1 = new YtdFile(GetYtdEntry(JenkHash.GenHash("mapdetail"))); LoadFile(ytd1); AddTextureLookups(ytd1); @@ -1087,17 +1092,6 @@ namespace CodeWalker.GameFiles LoadFile(ytd2); AddTextureLookups(ytd2); - YtdFile ytd3 = new YtdFile(GetYtdEntry(JenkHash.GenHash("vehshare_worn"))); - LoadFile(ytd3); - AddTextureLookups(ytd3); - - YtdFile ytd4 = new YtdFile(GetYtdEntry(JenkHash.GenHash("vehshare_army"))); - LoadFile(ytd4); - AddTextureLookups(ytd4); - - YtdFile ytd5 = new YtdFile(GetYtdEntry(JenkHash.GenHash("vehshare_truck"))); - LoadFile(ytd5); - AddTextureLookups(ytd5); } @@ -1435,7 +1429,7 @@ namespace CodeWalker.GameFiles { lock (updateSyncRoot) { - lock (textureSyncRoot) + //lock (textureSyncRoot) { SelectedDlc = dlc; EnableDlc = enable; @@ -1459,7 +1453,7 @@ namespace CodeWalker.GameFiles { lock (updateSyncRoot) { - lock (textureSyncRoot) + //lock (textureSyncRoot) { EnableMods = enable; RpfMan.EnableMods = enable; @@ -1901,7 +1895,7 @@ namespace CodeWalker.GameFiles break; case GameFileType.Ytd: req.Loaded = LoadFile(req as YtdFile); - if (req.Loaded) AddTextureLookups(req as YtdFile); + //if (req.Loaded) AddTextureLookups(req as YtdFile); break; case GameFileType.Ymap: YmapFile y = req as YmapFile; @@ -1983,7 +1977,7 @@ namespace CodeWalker.GameFiles } public YtdFile TryGetParentYtd(uint hash) { - uint phash; + MetaHash phash; if(textureParents.TryGetValue(hash, out phash)) { return GetYtd(phash); @@ -1992,19 +1986,31 @@ namespace CodeWalker.GameFiles } public uint TryGetParentYtdHash(uint hash) { - uint phash = 0; + MetaHash phash = 0; textureParents.TryGetValue(hash, out phash); return phash; } + public uint TryGetHDTextureHash(uint txdhash) + { + MetaHash hdhash = 0; + if (hdtexturelookup?.TryGetValue(txdhash, out hdhash) ?? false) + { + return hdhash; + } + return txdhash; + } public Texture TryFindTextureInParent(uint texhash, uint txdhash) { Texture tex = null; var ytd = TryGetParentYtd(txdhash); - while ((ytd != null) && (ytd.Loaded) && (ytd.TextureDict != null) && (tex == null)) + while ((ytd != null) && (tex == null)) { - tex = ytd.TextureDict.Lookup(texhash); + if (ytd.Loaded && (ytd.TextureDict != null)) + { + tex = ytd.TextureDict.Lookup(texhash); + } if (tex == null) { ytd = TryGetParentYtd(ytd.Key.Hash); diff --git a/CodeWalker.Core/GameFiles/Resources/Texture.cs b/CodeWalker.Core/GameFiles/Resources/Texture.cs index fba5a30..60b12aa 100644 --- a/CodeWalker.Core/GameFiles/Resources/Texture.cs +++ b/CodeWalker.Core/GameFiles/Resources/Texture.cs @@ -29,6 +29,7 @@ namespace CodeWalker.GameFiles public ResourcePointerList64 Textures { get; set; } public Dictionary Dict { get; set; } + public long MemoryUsage { get @@ -81,7 +82,7 @@ namespace CodeWalker.GameFiles dict[hash] = tex; } } - Dict = new Dictionary(dict); + Dict = dict;// new Dictionary(dict); } /// @@ -108,21 +109,6 @@ namespace CodeWalker.GameFiles }; } - public Dictionary GetDictionary() - { - Dictionary td = new Dictionary(); - if ((Textures != null) && (Textures.data_items != null)) - { - var texs = Textures.data_items; - var hashes = TextureNameHashes; - for (int i = 0; (i < texs.Length) && (i < hashes.Length); i++) - { - td.Add(hashes[i], texs[i]); - } - } - return td; - } - public Texture Lookup(uint hash) { Texture tex = null; @@ -132,6 +118,21 @@ namespace CodeWalker.GameFiles } return tex; } + + //public Dictionary GetDictionary() + //{ + // Dictionary td = new Dictionary(); + // if ((Textures != null) && (Textures.data_items != null)) + // { + // var texs = Textures.data_items; + // var hashes = TextureNameHashes; + // for (int i = 0; (i < texs.Length) && (i < hashes.Length); i++) + // { + // td.Add(hashes[i], texs[i]); + // } + // } + // return td; + //} } [TypeConverter(typeof(ExpandableObjectConverter))] public class TextureBase : ResourceSystemBlock diff --git a/Forms/ModelForm.cs b/Forms/ModelForm.cs index d95e9d4..8ee1146 100644 --- a/Forms/ModelForm.cs +++ b/Forms/ModelForm.cs @@ -519,7 +519,14 @@ namespace CodeWalker.Forms { var f = Yft.Fragment; - hash = Yft?.RpfFileEntry?.ShortNameHash ?? 0; + hash = Yft.RpfFileEntry?.ShortNameHash ?? 0; + + var namelower = Yft.RpfFileEntry?.GetShortNameLower(); + if (namelower?.EndsWith("_hi") ?? false) + { + hash = JenkHash.GenHash(namelower.Substring(0, namelower.Length - 3)); + } + arch = TryGetArchetype(hash); Renderer.RenderFragment(arch, null, f, hash); diff --git a/Rendering/Renderable.cs b/Rendering/Renderable.cs index 4112796..d23e69e 100644 --- a/Rendering/Renderable.cs +++ b/Rendering/Renderable.cs @@ -59,6 +59,9 @@ namespace CodeWalker.Rendering public bool AllTexturesLoaded = false; public RenderableModel[] HDModels; + public RenderableModel[] MedModels; + public RenderableModel[] LowModels; + public RenderableModel[] VlowModels; public RenderableModel[] AllModels; //public Dictionary TextureDict { get; private set; } //public long EmbeddedTextureSize { get; private set; } @@ -88,25 +91,31 @@ namespace CodeWalker.Rendering } if (med != null) { + MedModels = new RenderableModel[med.Length]; for (int i = 0; i < med.Length; i++) { - AllModels[curmodel + i] = InitModel(med[i]); + MedModels[i] = InitModel(med[i]); + AllModels[curmodel + i] = MedModels[i]; } curmodel += med.Length; } if (low != null) { + LowModels = new RenderableModel[low.Length]; for (int i = 0; i < low.Length; i++) { - AllModels[curmodel + i] = InitModel(low[i]); + LowModels[i] = InitModel(low[i]); + AllModels[curmodel + i] = LowModels[i]; } curmodel += low.Length; } if (vlow != null) { + VlowModels = new RenderableModel[vlow.Length]; for (int i = 0; i < vlow.Length; i++) { - AllModels[curmodel + i] = InitModel(vlow[i]); + VlowModels[i] = InitModel(vlow[i]); + AllModels[curmodel + i] = VlowModels[i]; } curmodel += vlow.Length; } @@ -364,7 +373,9 @@ namespace CodeWalker.Rendering public uint IndexDataSize { get; set; } public uint TotalDataSize { get; set; } public TextureBase[] Textures; + public Texture[] TexturesHD; public RenderableTexture[] RenderableTextures; + public RenderableTexture[] RenderableTexturesHD; public MetaName[] TextureParamHashes; public PrimitiveTopology Topology { get; set; } public bool IsFragment = false; @@ -392,6 +403,7 @@ namespace CodeWalker.Rendering public float WaterHeight { get; set; } = 0; //for terrainfoam public float WaveMovement { get; set; } = 0; //for terrainfoam public float HeightOpacity { get; set; } = 0; //for terrainfoam + public bool HDTextureEnable = true; public static MetaName[] GetTextureSamplerList() @@ -595,60 +607,9 @@ namespace CodeWalker.Rendering { TextureParamHashes = phashes.ToArray(); Textures = texs.ToArray(); + TexturesHD = new Texture[texs.Count]; RenderableTextures = new RenderableTexture[texs.Count]; //these will get populated at render time. - //Textures = new RenderableTexture[texs.Count]; - for (int i = 0; i < texs.Count; i++) - { - //RenderableTexture tex = new RenderableTexture(); - //tex.Init(texs[i]); - //Textures[i] = tex; - - - ////testing texture usage - //MetaName thash = phashes[i]; - //switch (thash) - //{ - // case MetaName.DiffuseSampler: //base diffuse - // case MetaName.SpecSampler: //base specular - // case MetaName.BumpSampler: //base normal - // case MetaName.TintPaletteSampler: // _pal - // case MetaName.DetailSampler: // ENV_ - // case MetaName.FlowSampler: //river _flow - // case MetaName.FogSampler: //river _fog , water slod - // case MetaName.TextureSampler_layer0: //CS_RSN_SL_Road_0007 - // case MetaName.BumpSampler_layer0: //CS_RSN_SL_Road_0007_n - // case MetaName.heightMapSamplerLayer0: //nxg_cs_rsn_sl_road_0007_h - // case MetaName.TextureSampler_layer1: //IM_Road_009b - // case MetaName.BumpSampler_layer1: //IM_Road_010b_N - // case MetaName.heightMapSamplerLayer1: //nxg_im_road_010b_h - // case MetaName.TextureSampler_layer2: //IM_Concrete10 - // case MetaName.BumpSampler_layer2: //IM_Concrete13_N - // case MetaName.heightMapSamplerLayer2: //nxg_im_concrete13_h - // case MetaName.TextureSampler_layer3: //SC1_RSN_NS_ground_0009 - // case MetaName.BumpSampler_layer3: //sc1_rsn_ns_ground_0010_n - // case MetaName.heightMapSamplerLayer3: //nxg_sc1_rsn_ns_ground_0010_b_h - // case MetaName.lookupSampler: //TF_RSN_Msk_CS1_DesHill1, bh1_43_golf_blendmap_04_LOD - // case MetaName.heightSampler: //nxg_prop_tree_palm2_displ_l - // case MetaName.FoamSampler: //bj_beachfoam01_lod, CS_RSN_SL_RiverFoam_01_A_lodCS_RSN_SL_RiverFoam_01_A - // case MetaName.textureSamp://cable... - // break; - // //not yet implemented: - // case MetaName.DiffuseSampler2: - // case MetaName.DiffuseHfSampler: - // case MetaName.ComboHeightSamplerFur01: - // case MetaName.ComboHeightSamplerFur23: - // case MetaName.ComboHeightSamplerFur45: - // case MetaName.ComboHeightSamplerFur67: - // case MetaName.StippleSampler: - // case MetaName.FurMaskSampler: - // case MetaName.EnvironmentSampler: - // case MetaName.distanceMapSampler: - // break; - // default: - // break; - //} - - } + RenderableTexturesHD = new RenderableTexture[texs.Count]; //these will get populated at render time. } } @@ -771,6 +732,14 @@ namespace CodeWalker.Rendering } RenderableTextures = null; } + if (RenderableTexturesHD != null) + { + for (int i = 0; i < RenderableTexturesHD.Length; i++) + { + RenderableTexturesHD[i] = null; + } + RenderableTexturesHD = null; + } } diff --git a/Rendering/Renderer.cs b/Rendering/Renderer.cs index 1bca21f..27717a6 100644 --- a/Rendering/Renderer.cs +++ b/Rendering/Renderer.cs @@ -111,6 +111,9 @@ namespace CodeWalker.Rendering private List renderskeletonlist = new List(); private List skeletonLineVerts = new List(); + public bool renderhdtextures = true; + + public MapSelectionMode SelectionMode = MapSelectionMode.Entity; //to assist in rendering embedded collisions properly... @@ -146,6 +149,7 @@ namespace CodeWalker.Rendering public bool RenderedBoundCompsListEnable = false; //whether or not to add rendered bound comps to the list + private List tryGetRenderableHDtxds = new List(); @@ -1578,38 +1582,31 @@ namespace CodeWalker.Rendering } - //go through the render list, and try ensure renderables and textures for all. - //if an entity is not fully loaded, set a flag for its parent, then traverse to root - //until found an entity that is fully loaded. - //on a second loop, build a final render list based on the flags. - - for (int i = 0; i < renderworldentities.Count; i++) + if (renderentities) { - var ent = renderworldentities[i]; - var arch = ent.Archetype; - var pent = ent.Parent; - var drawable = gameFileCache.TryGetDrawable(arch); - Renderable rndbl = TryGetRenderable(arch, drawable); - if ((rndbl != null) && rndbl.IsLoaded && (rndbl.AllTexturesLoaded || !waitforchildrentoload)) + //go through the render list, and try ensure renderables and textures for all. + //if an entity is not fully loaded, set a flag for its parent, then traverse to root + //until found an entity that is fully loaded. + //on a second loop, build a final render list based on the flags. + for (int i = 0; i < renderworldentities.Count; i++) { - RenderableEntity rent = new RenderableEntity(); - rent.Entity = ent; - rent.Renderable = rndbl; - renderworldrenderables.Add(rent); + var ent = renderworldentities[i]; + var arch = ent.Archetype; + var pent = ent.Parent; + var drawable = gameFileCache.TryGetDrawable(arch); + Renderable rndbl = TryGetRenderable(arch, drawable); + if ((rndbl != null) && rndbl.IsLoaded && (rndbl.AllTexturesLoaded || !waitforchildrentoload)) + { + RenderableEntity rent = new RenderableEntity(); + rent.Entity = ent; + rent.Renderable = rndbl; + renderworldrenderables.Add(rent); + } + else if (waitforchildrentoload) + { + //todo: render parent if children loading....... + } } - else if (waitforchildrentoload) - { - //todo: render parent if children loading....... - } - - if ((rendercollisionmeshes || (SelectionMode == MapSelectionMode.Collision)) && renderinteriors) - { - RenderInteriorCollisionMesh(ent); - } - } - - if(renderentities) - { for (int i = 0; i < renderworldrenderables.Count; i++) { var rent = renderworldrenderables[i]; @@ -1621,6 +1618,21 @@ namespace CodeWalker.Rendering } + + if (renderinteriors && (rendercollisionmeshes || (SelectionMode == MapSelectionMode.Collision))) + { + for (int i = 0; i < renderworldentities.Count; i++) + { + var ent = renderworldentities[i]; + if (ent.IsMlo) + { + RenderInteriorCollisionMesh(ent); + } + } + } + + + if (rendercars) { for (int y = 0; y < VisibleYmaps.Count; y++) @@ -1766,59 +1778,9 @@ namespace CodeWalker.Rendering renderworldentities.Add(ent); - if (renderinteriors && ent.IsMlo) //render Mlo child entities... + if (renderinteriors && ent.IsMlo && (ent.MloInstance != null)) //render Mlo child entities... { - if ((ent.MloInstance != null)) - { - if (ent.MloInstance.Entities != null) - { - for (int j = 0; j < ent.MloInstance.Entities.Length; j++) - { - var intent = ent.MloInstance.Entities[j]; - if (intent.Archetype == null) continue; //missing archetype... - if (!RenderIsEntityFinalRender(intent)) continue; //proxy or something.. - - intent.IsVisible = true; - - var iebscent = intent.Position + intent.BSCenter - camera.Position; - float iebsrad = intent.BSRadius; - if (!camera.ViewFrustum.ContainsSphereNoClipNoOpt(ref iebscent, iebsrad)) - { - continue; //frustum cull interior ents - } - - renderworldentities.Add(intent); - } - } - if (ent.MloInstance.EntitySets != null) - { - foreach (var entitysets in ent.MloInstance.EntitySets) - { - var entityset = entitysets.Value; - if (!entityset.Visible) continue; - - var entities = entityset.Entities; - for (int i = 0; i < entities.Count; i++) - { - var intent = entities[i]; - if (intent.Archetype == null) continue; //missing archetype... - if (!RenderIsEntityFinalRender(intent)) continue; //proxy or something.. - - intent.IsVisible = true; - - var iebscent = intent.Position + intent.BSCenter - camera.Position; - float iebsrad = intent.BSRadius; - if (!camera.ViewFrustum.ContainsSphereNoClipNoOpt(ref iebscent, iebsrad)) - { - continue; //frustum cull interior ents - } - - renderworldentities.Add(intent); - - } - } - } - } + RenderWorldAddInteriorEntities(ent); } } @@ -1835,7 +1797,57 @@ namespace CodeWalker.Rendering } } } + private void RenderWorldAddInteriorEntities(YmapEntityDef ent) + { + if (ent.MloInstance.Entities != null) + { + for (int j = 0; j < ent.MloInstance.Entities.Length; j++) + { + var intent = ent.MloInstance.Entities[j]; + if (intent.Archetype == null) continue; //missing archetype... + if (!RenderIsEntityFinalRender(intent)) continue; //proxy or something.. + intent.IsVisible = true; + + var iebscent = intent.Position + intent.BSCenter - camera.Position; + float iebsrad = intent.BSRadius; + if (!camera.ViewFrustum.ContainsSphereNoClipNoOpt(ref iebscent, iebsrad)) + { + continue; //frustum cull interior ents + } + + renderworldentities.Add(intent); + } + } + if (ent.MloInstance.EntitySets != null) + { + foreach (var entitysets in ent.MloInstance.EntitySets) + { + var entityset = entitysets.Value; + if (!entityset.Visible) continue; + + var entities = entityset.Entities; + for (int i = 0; i < entities.Count; i++) + { + var intent = entities[i]; + if (intent.Archetype == null) continue; //missing archetype... + if (!RenderIsEntityFinalRender(intent)) continue; //proxy or something.. + + intent.IsVisible = true; + + var iebscent = intent.Position + intent.BSCenter - camera.Position; + float iebsrad = intent.BSRadius; + if (!camera.ViewFrustum.ContainsSphereNoClipNoOpt(ref iebscent, iebsrad)) + { + continue; //frustum cull interior ents + } + + renderworldentities.Add(intent); + + } + } + } + } @@ -2381,6 +2393,16 @@ namespace CodeWalker.Rendering float distance = (camrel + bscen).Length(); + + //bool usehdtxd = renderhdtextures && ((dist - bsrad) <= arche._BaseArchetypeDef.hdTextureDist); + //var usehdtxd = false; + //if ((arch != null) && (renderhdtextures)) + //{ + // usehdtxd = ((ent.Distance - arch.BSRadius) <= arch._BaseArchetypeDef.hdTextureDist); + //} + + + if ((rendercollisionmeshes || (SelectionMode == MapSelectionMode.Collision)) && rendercollisionmeshlayerdrawable) { Drawable sdrawable = rndbl.Key as Drawable; @@ -2581,14 +2603,27 @@ namespace CodeWalker.Rendering - private Renderable TryGetRenderable(Archetype arche, DrawableBase drawable, uint txdHash = 0) { if (drawable == null) return null; //BUG: only last texdict used!! needs to cache textures per archetype........ //(but is it possible to have the same drawable with different archetypes?) - uint texDict = (arche != null) ? arche.TextureDict.Hash : txdHash; - uint clipDict = (arche != null) ? arche.ClipDict.Hash : 0; + MetaHash texDict = txdHash; + //uint texDictOrig = txdHash; + uint clipDict = 0; + + if (arche != null) + { + texDict = arche.TextureDict.Hash; + clipDict = arche.ClipDict.Hash; + + //texDictOrig = texDict; + //if (hdtxd) + //{ + // texDict = gameFileCache.TryGetHDTextureHash(texDict); + //} + } + Renderable rndbl = renderableCache.GetRenderable(drawable); if (rndbl == null) return null; @@ -2624,11 +2659,61 @@ namespace CodeWalker.Rendering var yptTexDict = (drawable.Owner as YptFile)?.PtfxList?.TextureDictionary; + + tryGetRenderableHDtxds.Clear(); + if (renderhdtextures) + { + if (arche != null) //try get HD txd for the asset + { + MetaHash hdtxd = gameFileCache.TryGetHDTextureHash(arche._BaseArchetypeDef.assetName); + if (hdtxd != arche._BaseArchetypeDef.assetName) + { + var asshdytd = gameFileCache.GetYtd(hdtxd); + if ((asshdytd != null) && (asshdytd.Loaded)) + { + tryGetRenderableHDtxds.Add(asshdytd); + } + } + } + if (texDict != 0) //try get HD txd for the current txd + { + MetaHash hdtxd = gameFileCache.TryGetHDTextureHash(texDict); + if (hdtxd != texDict) + { + var txdhdytd = gameFileCache.GetYtd(hdtxd); + if ((txdhdytd != null) && (txdhdytd.Loaded)) + { + tryGetRenderableHDtxds.Add(txdhdytd); + } + } + + MetaHash ptxdname = gameFileCache.TryGetParentYtdHash(texDict); + while (ptxdname != 0) //look for parent HD txds + { + MetaHash phdtxdname = gameFileCache.TryGetHDTextureHash(ptxdname); + if (phdtxdname != ptxdname) + { + var phdytd = gameFileCache.GetYtd(phdtxdname); + if ((phdytd != null) && (phdytd.Loaded)) + { + tryGetRenderableHDtxds.Add(phdytd); + } + } + ptxdname = gameFileCache.TryGetParentYtdHash(ptxdname); + } + } + if (tryGetRenderableHDtxds.Count > 0) + { } + } + + YtdFile ytd = (texDict != 0) ? gameFileCache.GetYtd(texDict) : null; + + bool alltexsloaded = true; int missingtexcount = 0; - for (int mi = 0; mi < rndbl.HDModels.Length; mi++) + for (int mi = 0; mi < rndbl.AllModels.Length; mi++) { - var model = rndbl.HDModels[mi]; + var model = rndbl.AllModels[mi]; //if (!RenderIsModelFinalRender(model) && !renderproxies) //{ @@ -2644,56 +2729,32 @@ namespace CodeWalker.Rendering { var tex = geom.Textures[i]; var ttex = tex as Texture; + Texture dtex = null; RenderableTexture rdtex = null; - if ((ttex == null) && (tex != null)) + if ((tex != null) && (ttex == null)) { //TextureRef means this RenderableTexture needs to be loaded from texture dict... if (yptTexDict != null) //for ypt files, first try the embedded tex dict.. { - var dtex = yptTexDict.Lookup(tex.NameHash); - rdtex = renderableCache.GetRenderableTexture(dtex); + dtex = yptTexDict.Lookup(tex.NameHash); } else if (texDict != 0) { - YtdFile ytd = gameFileCache.GetYtd(texDict); if ((ytd != null) && (ytd.Loaded) && (ytd.TextureDict != null)) { - var dtex = ytd.TextureDict.Lookup(tex.NameHash); + dtex = ytd.TextureDict.Lookup(tex.NameHash); if (dtex == null) { - //not present in dictionary... check already loaded texture dicts... - var ytd2 = gameFileCache.TryGetTextureDictForTexture(tex.NameHash); - if ((ytd2 != null) && (ytd2.Loaded) && (ytd2.TextureDict != null)) - { - dtex = ytd2.TextureDict.Lookup(tex.NameHash); - } - else - { - //couldn't find texture dict? - //first try going through ytd hierarchy... - dtex = gameFileCache.TryFindTextureInParent(tex.NameHash, texDict); - - - //if (dtex == null) - //{ //try for a texture dict with the same hash as the archetype? - // dtex = gameFileCache.TryFindTextureInParent(tex.TextureRef.NameHash, arche.Hash); - // if (dtex != null) - // { } - //} - } - } - if (dtex != null) - { - geom.Textures[i] = dtex; //cache it for next time to avoid the lookup... - rdtex = renderableCache.GetRenderableTexture(dtex); + //couldn't find texture in specified dict + //first try going through ytd hierarchy... + dtex = gameFileCache.TryFindTextureInParent(tex.NameHash, texDict); } if (rdtex == null) { } //nothing to see here :( } - else if ((ytd == null)) + else if ((ytd == null))//ytd not found { - Texture dtex = null; - if (drawable.ShaderGroup.TextureDictionary != null) + if (drawable.ShaderGroup.TextureDictionary != null)//check any embedded texdict { dtex = drawable.ShaderGroup.TextureDictionary.Lookup(tex.NameHash); if (dtex == null) @@ -2703,19 +2764,8 @@ namespace CodeWalker.Rendering } if (dtex == null) { - var ytd2 = gameFileCache.TryGetTextureDictForTexture(tex.NameHash); - if ((ytd2 != null) && (ytd2.Loaded) && (ytd2.TextureDict != null)) - { - dtex = ytd2.TextureDict.Lookup(tex.NameHash); - } - if (dtex == null) - { - dtex = gameFileCache.TryFindTextureInParent(tex.NameHash, texDict); - } + dtex = gameFileCache.TryFindTextureInParent(tex.NameHash, texDict); } - rdtex = renderableCache.GetRenderableTexture(dtex); - if (rdtex == null) - { missingtexcount -= 2; } //(give extra chance..) couldn't find the texture! :( } else if (ytd != null) { @@ -2724,13 +2774,26 @@ namespace CodeWalker.Rendering } } else //no texdict specified, nothing to see here.. + { } + if (dtex == null) { + //not present in dictionary... check already loaded texture dicts... (maybe resident?) var ytd2 = gameFileCache.TryGetTextureDictForTexture(tex.NameHash); if ((ytd2 != null) && (ytd2.Loaded) && (ytd2.TextureDict != null)) { - var dtex = ytd2.TextureDict.Lookup(tex.NameHash); - rdtex = renderableCache.GetRenderableTexture(dtex); + dtex = ytd2.TextureDict.Lookup(tex.NameHash); } + //else + //{ + // //couldn't find texture dict? + //} + } + + if (dtex != null) + { + geom.Textures[i] = dtex; //cache it for next time to avoid the lookup... + ttex = dtex; + rdtex = renderableCache.GetRenderableTexture(dtex); } } else if (ttex != null) //ensure embedded renderable texture @@ -2757,6 +2820,29 @@ namespace CodeWalker.Rendering } + RenderableTexture rhdtex = null; + if (renderhdtextures) + { + Texture hdtex = geom.TexturesHD[i]; + if (hdtex == null) + { + for (int j = 0; j < tryGetRenderableHDtxds.Count; j++) + { + hdtex = tryGetRenderableHDtxds[j].TextureDict?.Lookup(tex.NameHash); + if (hdtex != null) break; + } + if (hdtex != null) + { + geom.TexturesHD[i] = hdtex; + } + } + if (hdtex != null) + { + rhdtex = renderableCache.GetRenderableTexture(hdtex); + } + } + geom.RenderableTexturesHD[i] = rhdtex; + } } } diff --git a/Rendering/Shaders/BasicShader.cs b/Rendering/Shaders/BasicShader.cs index b586e77..9cc5f52 100644 --- a/Rendering/Shaders/BasicShader.cs +++ b/Rendering/Shaders/BasicShader.cs @@ -529,6 +529,14 @@ namespace CodeWalker.Rendering for (int i = 0; i < geom.RenderableTextures.Length; i++) { var itex = geom.RenderableTextures[i]; + if (geom.HDTextureEnable) + { + var hdtex = geom.RenderableTexturesHD[i]; + if ((hdtex != null) && (hdtex.IsLoaded)) + { + itex = hdtex; + } + } var ihash = geom.TextureParamHashes[i]; if (itex == null) continue; if (itex.Key?.NameHash == 1678728908 /*"blank"*/) continue; diff --git a/Rendering/Shaders/TerrainShader.cs b/Rendering/Shaders/TerrainShader.cs index 446015b..6f5c151 100644 --- a/Rendering/Shaders/TerrainShader.cs +++ b/Rendering/Shaders/TerrainShader.cs @@ -376,6 +376,14 @@ namespace CodeWalker.Rendering for (int i = 0; i < geom.RenderableTextures.Length; i++) { var itex = geom.RenderableTextures[i]; + if (geom.HDTextureEnable) + { + var hdtex = geom.RenderableTexturesHD[i]; + if ((hdtex != null) && (hdtex.IsLoaded)) + { + itex = hdtex; + } + } var ihash = geom.TextureParamHashes[i]; switch (ihash) { diff --git a/WorldForm.Designer.cs b/WorldForm.Designer.cs index 38132cf..23b5394 100644 --- a/WorldForm.Designer.cs +++ b/WorldForm.Designer.cs @@ -109,6 +109,7 @@ namespace CodeWalker this.tabPage4 = new System.Windows.Forms.TabPage(); this.OptionsTabControl = new System.Windows.Forms.TabControl(); this.tabPage8 = new System.Windows.Forms.TabPage(); + this.CarGeneratorsCheckBox = new System.Windows.Forms.CheckBox(); this.RenderEntitiesCheckBox = new System.Windows.Forms.CheckBox(); this.AdvancedSettingsButton = new System.Windows.Forms.Button(); this.ControlSettingsButton = new System.Windows.Forms.Button(); @@ -286,7 +287,7 @@ namespace CodeWalker this.ToolbarCameraMapViewButton = new System.Windows.Forms.ToolStripMenuItem(); this.ToolbarCameraOrthographicButton = new System.Windows.Forms.ToolStripMenuItem(); this.ToolbarPanel = new System.Windows.Forms.Panel(); - this.CarGeneratorsCheckBox = new System.Windows.Forms.CheckBox(); + this.HDTexturesCheckBox = new System.Windows.Forms.CheckBox(); this.StatusStrip.SuspendLayout(); this.ToolsPanel.SuspendLayout(); this.ToolsTabControl.SuspendLayout(); @@ -1335,6 +1336,17 @@ namespace CodeWalker this.tabPage8.Text = "General"; this.tabPage8.UseVisualStyleBackColor = true; // + // CarGeneratorsCheckBox + // + this.CarGeneratorsCheckBox.AutoSize = true; + this.CarGeneratorsCheckBox.Location = new System.Drawing.Point(10, 72); + this.CarGeneratorsCheckBox.Name = "CarGeneratorsCheckBox"; + this.CarGeneratorsCheckBox.Size = new System.Drawing.Size(124, 17); + this.CarGeneratorsCheckBox.TabIndex = 31; + this.CarGeneratorsCheckBox.Text = "Show car generators"; + this.CarGeneratorsCheckBox.UseVisualStyleBackColor = true; + this.CarGeneratorsCheckBox.CheckedChanged += new System.EventHandler(this.CarGeneratorsCheckBox_CheckedChanged); + // // RenderEntitiesCheckBox // this.RenderEntitiesCheckBox.AutoSize = true; @@ -1633,6 +1645,7 @@ namespace CodeWalker // // tabPage14 // + this.tabPage14.Controls.Add(this.HDTexturesCheckBox); this.tabPage14.Controls.Add(this.WireframeCheckBox); this.tabPage14.Controls.Add(this.RenderModeComboBox); this.tabPage14.Controls.Add(this.label11); @@ -3270,16 +3283,18 @@ namespace CodeWalker this.ToolbarPanel.TabIndex = 7; this.ToolbarPanel.Visible = false; // - // CarGeneratorsCheckBox + // HDTexturesCheckBox // - this.CarGeneratorsCheckBox.AutoSize = true; - this.CarGeneratorsCheckBox.Location = new System.Drawing.Point(10, 72); - this.CarGeneratorsCheckBox.Name = "CarGeneratorsCheckBox"; - this.CarGeneratorsCheckBox.Size = new System.Drawing.Size(124, 17); - this.CarGeneratorsCheckBox.TabIndex = 31; - this.CarGeneratorsCheckBox.Text = "Show car generators"; - this.CarGeneratorsCheckBox.UseVisualStyleBackColor = true; - this.CarGeneratorsCheckBox.CheckedChanged += new System.EventHandler(this.CarGeneratorsCheckBox_CheckedChanged); + this.HDTexturesCheckBox.AutoSize = true; + this.HDTexturesCheckBox.Checked = true; + this.HDTexturesCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; + this.HDTexturesCheckBox.Location = new System.Drawing.Point(10, 231); + this.HDTexturesCheckBox.Name = "HDTexturesCheckBox"; + this.HDTexturesCheckBox.Size = new System.Drawing.Size(82, 17); + this.HDTexturesCheckBox.TabIndex = 57; + this.HDTexturesCheckBox.Text = "HD textures"; + this.HDTexturesCheckBox.UseVisualStyleBackColor = true; + this.HDTexturesCheckBox.CheckedChanged += new System.EventHandler(this.HDTexturesCheckBox_CheckedChanged); // // WorldForm // @@ -3624,5 +3639,6 @@ namespace CodeWalker private System.Windows.Forms.CheckBox RenderEntitiesCheckBox; private System.Windows.Forms.ToolStripMenuItem ToolbarSelectOcclusionButton; private System.Windows.Forms.CheckBox CarGeneratorsCheckBox; + private System.Windows.Forms.CheckBox HDTexturesCheckBox; } } \ No newline at end of file diff --git a/WorldForm.cs b/WorldForm.cs index 6d28357..459e56d 100644 --- a/WorldForm.cs +++ b/WorldForm.cs @@ -6634,6 +6634,11 @@ namespace CodeWalker Renderer.renderproxies = ProxiesCheckBox.Checked; } + private void HDTexturesCheckBox_CheckedChanged(object sender, EventArgs e) + { + Renderer.renderhdtextures = HDTexturesCheckBox.Checked; + } + private void PathsCheckBox_CheckedChanged(object sender, EventArgs e) { renderpaths = PathsCheckBox.Checked; diff --git a/WorldForm.resx b/WorldForm.resx index 8689d72..fe93ed0 100644 --- a/WorldForm.resx +++ b/WorldForm.resx @@ -240,14 +240,6 @@ ufo YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAB4SURBVDhP3ZC7DcAgDEQZKTMwHOvSIFriS7BlEB+HMic9 QJbvFThLUkpXzjkSpaeuzMPlEELx3jdsBauyCHBY6UWYPQI93KEljQD3jL6EGzN6x0bASyNYwkKU8Udm gd6TMnIikDJyIqjVNz8T7FgKrAwFX6lVinM3aJ05lWDPRRcAAAAASUVORK5CYII= - - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAB0SURBVDhP7ZNBCoAgEEXnSJ3BqxmetNpaMLhVv5DNRJS2 - CxIeuvA9XSjtg5mHEILPxB6U7JyLxphmSkDK1o5x9dst87SUfTXwRsYsA+paT0BGDGsVOJ92hdz3Bz4f - wGPC48uu7w5IGd+gBlpRMgYCnRwyESUj3CsQkYNFDwAAAABJRU5ErkJggg== @@ -269,13 +261,12 @@ ufo WBXYx9R1nV75RuyHKrrnzCcGjE1u9ZyD4BugoZigQ9xrngAAAABJRU5ErkJggg== - + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACtSURBVDhPrZBBEsIgEAR5Gy/wFV55T/wHr+KgHuCKNsVY - ZI2JiU7VVIVlp7OL+1mllIr7cb8Ie++PQwQYITnnM24NWxoBgsQYm/l+gk699bMsRA4h1JTSPsg0Xert - em/mGwh3vW1Z7MvIABSWqXG3+iZHAEw1m4wD49oVANgVOL/VeSgeDAiX1mpWeKy9BIQiI+OxWQF77tG5 - 2Fc729BmeElf/3lNhORe+oecewDObEqX49RqCgAAAABJRU5ErkJggg== + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAB0SURBVDhP7ZNBCoAgEEXnSJ3BqxmetNpaMLhVv5DNRJS2 + CxIeuvA9XSjtg5mHEILPxB6U7JyLxphmSkDK1o5x9dst87SUfTXwRsYsA+paT0BGDGsVOJ92hdz3Bz4f + wGPC48uu7w5IGd+gBlpRMgYCnRwyESUj3CsQkYNFDwAAAABJRU5ErkJggg== @@ -304,6 +295,15 @@ ufo EcMw2DzPDMEke9AsYBrHs10vN4I1QqImwwDcFyMjQGaBHr5Bo8nEoYCnCQTGzVeI4oj6fIi+KHgoPBhC 4knCjTww9vxfbIUQNDEyiGIZ8t6tW/k0vC/AOpuiueNOLwVkUeylvju9FJCg8E1vM/2PlTv5UoervVTJ uQAAAABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACtSURBVDhPrZBBEsIgEAR5Gy/wFV55T/wHr+KgHuCKNsVY + ZI2JiU7VVIVlp7OL+1mllIr7cb8Ie++PQwQYITnnM24NWxoBgsQYm/l+gk699bMsRA4h1JTSPsg0Xert + em/mGwh3vW1Z7MvIABSWqXG3+iZHAEw1m4wD49oVANgVOL/VeSgeDAiX1mpWeKy9BIQiI+OxWQF77tG5 + 2Fc729BmeElf/3lNhORe+oecewDObEqX49RqCgAAAABJRU5ErkJggg== @@ -389,17 +389,6 @@ ufo 4BJN+IjGo5O8ZJndGVhKxpjWWts551aih0fre+0BLaVchRAezPB2NXSSV/gVwXGYPJiVUt6ns1ghCDjn UQG86w3FToVgDcWCWS9Fvi/Ao0RVAcwUjwpyhzmf4n8BFApS5HZRwRuONGMbrIJ1JIN8O2QAAAAASUVO RK5CYII= - - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEvSURBVDhP3dK/K0dRGMfxKxRJopCSEkLya/guUhQRmQwG - WfwIkYySgYUSKUKJlOK/MBoMFMofYLUIsfJ+f3NuF3+A8tRree5zP/fcc070f6oHT/jAPTqQj6WvXvCM - TZQgG3H58gFGcYVLtGIN15jBNDbwiGNUIg4pQx8GsQuHhrCDW8yjHyns4Q0DcCXpykM5bFzgHGPYxw1G - UIVMtMHfWUUj4nIg/KurGIYrSAZYOXDGlbhXcZlegUO8Yxzb+BlQAwNW0G0jVAYK0AwHtnCEOyQDZvGC - ObTbKIIvLMA9WIYDizhFMsDjfsAZptCA9JcdfoVBvryOSbgCe4HPTuCz+BQMKEUvJmCy96ET1ehCuAf2 - 5ZF+uwdZKEYtmuBGFSIXhtejBe5PHX7dxL+qKPoEppRHcXOtiDsAAAAASUVORK5CYII= @@ -435,6 +424,17 @@ ufo rp3fhGJScIRLzKMLFTC9cMIu3nCDVUyjB6WkYA93mEWbAyH9cMImPuA+rWMA31YwBU82kF6BS32Er/aO M8zAh+OEghpcwQ2bg3uwBW8ewFd7xQkm0IA4oaAS7bh2KHjBIZbhV/D6GJkFphrdcIP8lFrAGPwPOjCO QdQiTqrAWNICd7gPnUj+xBKaU9dxfhTkjwV/FxU+AbsiGnc46OYIAAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEvSURBVDhP3dK/K0dRGMfxKxRJopCSEkLya/guUhQRmQwG + WfwIkYySgYUSKUKJlOK/MBoMFMofYLUIsfJ+f3NuF3+A8tRree5zP/fcc070f6oHT/jAPTqQj6WvXvCM + TZQgG3H58gFGcYVLtGIN15jBNDbwiGNUIg4pQx8GsQuHhrCDW8yjHyns4Q0DcCXpykM5bFzgHGPYxw1G + UIVMtMHfWUUj4nIg/KurGIYrSAZYOXDGlbhXcZlegUO8Yxzb+BlQAwNW0G0jVAYK0AwHtnCEOyQDZvGC + ObTbKIIvLMA9WIYDizhFMsDjfsAZptCA9JcdfoVBvryOSbgCe4HPTuCz+BQMKEUvJmCy96ET1ehCuAf2 + 5ZF+uwdZKEYtmuBGFSIXhtejBe5PHX7dxL+qKPoEppRHcXOtiDsAAAAASUVORK5CYII=