diff --git a/DSMapStudio/DSMapStudio.csproj b/DSMapStudio/DSMapStudio.csproj index fe9235eaa..297ce75fd 100644 --- a/DSMapStudio/DSMapStudio.csproj +++ b/DSMapStudio/DSMapStudio.csproj @@ -46,6 +46,10 @@ + + + + diff --git a/StudioCore/Editor/EditorDecorations.cs b/StudioCore/Editor/EditorDecorations.cs index 9af5216e3..ee6da883a 100644 --- a/StudioCore/Editor/EditorDecorations.cs +++ b/StudioCore/Editor/EditorDecorations.cs @@ -12,7 +12,6 @@ using System.Text.RegularExpressions; using FSParam; using StudioCore; -using StudioCore.Editor; using StudioCore.ParamEditor; using StudioCore.TextEditor; diff --git a/StudioCore/HelpMenu/HelpMenu.cs b/StudioCore/HelpMenu/HelpMenu.cs index ff03a846c..6117f09d7 100644 --- a/StudioCore/HelpMenu/HelpMenu.cs +++ b/StudioCore/HelpMenu/HelpMenu.cs @@ -120,10 +120,10 @@ private void DisplayCore() // Content Segments List coreSegments = new List(); - foreach(string segment in contents) + foreach (string segment in contents) { List segmentParts = segment.Split(" ").ToList(); - foreach(string part in segmentParts) + foreach (string part in segmentParts) { coreSegments.Add(part.ToLower()); } @@ -138,7 +138,7 @@ private void DisplayCore() } // Only show if input matches any title or content segments, or if it is blank - if (inputStr.ToLower() == "" || sectionNameSegments.Contains(inputStr) || coreSegments.Contains(inputStr) || tagList.Contains(inputStr) ) + if (inputStr.ToLower() == "" || sectionNameSegments.Contains(inputStr) || coreSegments.Contains(inputStr) || tagList.Contains(inputStr)) { if (ImGui.Selectable(sectionName)) { @@ -486,4 +486,4 @@ private List GetDisplayTextSections(List stringList) return displayTextSegments; } } -} +} \ No newline at end of file diff --git a/StudioCore/MapStudioNew.cs b/StudioCore/MapStudioNew.cs index 64dc20dd0..185151bf1 100644 --- a/StudioCore/MapStudioNew.cs +++ b/StudioCore/MapStudioNew.cs @@ -48,7 +48,7 @@ public class MapStudioNew private static bool _initialLoadComplete = false; private static bool _firstframe = true; public static bool FirstFrame = true; - + // ImGui Debug windows private bool _showImGuiDemoWindow = false; private bool _showImGuiMetricsWindow = false; @@ -59,13 +59,13 @@ public unsafe MapStudioNew(IGraphicsContext context, string version) { _version = version; _programTitle = $"Dark Souls Map Studio version {_version}"; - + // Hack to make sure dialogs work before the main window is created PlatformUtils.InitializeWindows(null); CFG.AttemptLoadOrDefault(); - + Environment.SetEnvironmentVariable("PATH", Environment.GetEnvironmentVariable("PATH") + Path.PathSeparator + "bin"); - + _context = context; _context.Initialize(); _context.Window.Title = _programTitle; @@ -555,7 +555,7 @@ private bool AttemptLoadProject(Editor.ProjectSettings settings, string filename private bool StealGameDllIfMissing(ProjectSettings settings, string dllName) { - dllName = dllName+".dll"; + dllName = dllName + ".dll"; if (File.Exists(Path.Join(Path.GetFullPath("."), dllName))) return true; if (!File.Exists(Path.Join(settings.GameRoot, dllName))) @@ -594,7 +594,7 @@ public void AttemptSaveOnCrash() CFG.Current.LastProjectFile = ""; CFG.Save(); } - catch(Exception e) + catch (Exception e) { PlatformUtils.Instance.MessageBox($"Unable to save config during crash recovery.\n" + $"If you continue to crash on startup, delete config in AppData\\Local\\DSMapStudio\n\n" + @@ -832,12 +832,12 @@ private unsafe void Update(float deltaseconds) { SaveAll(); } - + if (ImGui.MenuItem("Editor Settings")) { _settingsMenu.MenuOpenState = true; } - + if (Resource.FlverResource.CaptureMaterialLayouts && ImGui.MenuItem("Dump Flver Layouts (Debug)", "")) { DumpFlverLayouts(); @@ -1011,7 +1011,7 @@ private unsafe void Update(float deltaseconds) { _standardProjectUIOpened = false; } - + if (ImGui.BeginTabItem("Advanced")) { NewProject_NameGUI(); @@ -1188,7 +1188,7 @@ private unsafe void Update(float deltaseconds) commands = commandsplit.Skip(1).ToArray(); ImGui.SetNextWindowFocus(); } - + if (_context.Device == null) ImGui.PushStyleColor(ImGuiCol.WindowBg, *ImGui.GetStyleColorVec4(ImGuiCol.WindowBg)); else @@ -1273,4 +1273,4 @@ public static float GetUIScale() return CFG.Current.UIScale; } } -} +} \ No newline at end of file diff --git a/StudioCore/MsbEditor/Entity.cs b/StudioCore/MsbEditor/Entity.cs index 55996300b..e5fd052f7 100644 --- a/StudioCore/MsbEditor/Entity.cs +++ b/StudioCore/MsbEditor/Entity.cs @@ -1056,14 +1056,35 @@ public override bool HasTransform } } + public class ModelInfoExport + { + public string Name { get; set; } + public string Type { get; set; } + public string SibPath { get; set; } + public string RealPath { get; set; } + public string DCXPath { get; set; } + + } + + public class MapInfo + { + public string Name { get; set; } + public MapSerializationEntity SerializationEntity { get; set; } = null; + public Dictionary LoadedModels { get; set; } = null; + public Vector3 MapTransform { get; set; } + } + public class MapSerializationEntity { public string Name { get; set; } = null; public int Msbidx { get; set; } = -1; public MapEntity.MapEntityType Type { get; set; } + public string TypeName { get; set; } public Transform Transform { get; set; } + public Vector3 EularRotation { get; set; } public List Children { get; set; } = null; + public bool ShouldSerializeChildren() { return (Children != null && Children.Count > 0); @@ -1399,8 +1420,18 @@ public MapSerializationEntity Serialize(Dictionary idmap) if (HasTransform) { e.Transform = GetLocalTransform(); + + var eularrot = e.Transform.EulerRotation; + + // radians to angles + eularrot.X = eularrot.X * 180 / (float)Math.PI; + eularrot.Y = eularrot.Y * 180 / (float)Math.PI; + eularrot.Z = eularrot.Z * 180 / (float)Math.PI; + + e.EularRotation = eularrot; } e.Type = Type; + e.TypeName = Type.ToString(); e.Children = new List(); if (idmap.ContainsKey(this)) { diff --git a/StudioCore/MsbEditor/ObjectContainer.cs b/StudioCore/MsbEditor/ObjectContainer.cs index f2c8813d7..de7e7678d 100644 --- a/StudioCore/MsbEditor/ObjectContainer.cs +++ b/StudioCore/MsbEditor/ObjectContainer.cs @@ -16,6 +16,8 @@ using SoulsFormats.KF4; using static SoulsFormats.MCP; using System.ComponentModel; +using StudioCore.Resource; +using Google.Protobuf.WellKnownTypes; using StudioCore.Platform; namespace StudioCore.MsbEditor @@ -134,6 +136,10 @@ public void LoadFlver(FLVER2 flver, MeshRenderableProxy proxy) for (int i = 0; i < flver.Materials.Count; i++) { var matnode = new Entity(this, flver.Materials[i]); + foreach (var tex in flver.Materials[i].Textures) + { + tex.Path = FlverResource.FindTexturePath(tex.Type, "", flver.Materials[i].MTD); + } Objects.Add(matnode); materialsNode.AddChild(matnode); } @@ -928,14 +934,63 @@ public bool SerializeDS2ObjInstances(Param objs) return true; } - public MapSerializationEntity SerializeHierarchy() + public MapInfo SerializeHierarchy() { + MapInfo mapinfo = new MapInfo(); Dictionary idmap = new Dictionary(); for (int i = 0; i < Objects.Count; i++) { idmap.Add(Objects[i], i); } - return ((MapEntity)RootObject).Serialize(idmap); + mapinfo.Name = Name; + mapinfo.LoadedModels = new Dictionary(); + foreach (var lm in LoadedModels) + { + if(lm.Value.GetType().IsSubclassOf(typeof(MSBE.Model))) + { + // handle ER model + ModelInfoExport modelinfo = new ModelInfoExport(); + modelinfo.Name = lm.Key; + string typeandname = ((MSBE.Model)lm.Value).ToString(); + modelinfo.Type = typeandname.Substring(0, typeandname.IndexOf(" ")); + modelinfo.SibPath = ((MSBE.Model)lm.Value).SibPath; + if(modelinfo.Type == "MapPiece") + { + modelinfo.RealPath = ResourceManager.GetModelPath(Name+"_"+ modelinfo.Name.Substring(1)); + } + else + { + modelinfo.RealPath = ResourceManager.GetModelPath(modelinfo.Name); + } + + if(modelinfo.Type == "MapPiece") + { + string mapid_head = Name.Substring(0, 3); + modelinfo.DCXPath = $@"map\{mapid_head}\{Name}\{Name}_{modelinfo.Name.Substring(1)}.mapbnd.dcx"; + } + else if(modelinfo.Type == "Asset") + { + string asset_head = modelinfo.Name.Substring(0, modelinfo.Name.IndexOf('_')); + modelinfo.DCXPath = $@"asset\aeg\{asset_head.ToLower()}\{modelinfo.Name.ToLower()}.geombnd.dcx"; + } + else if (modelinfo.Type == "Enemy" || modelinfo.Type == "Player") + { + modelinfo.DCXPath = $@"chr\{modelinfo.Name.ToLower()}.chrbnd.dcx"; + } + else + { + modelinfo.DCXPath = ""; + } + + + mapinfo.LoadedModels.Add(lm.Key, modelinfo); + } + } + + mapinfo.MapTransform = SpecialMapConnections.GetEldenMapGlobalTransform(Name); + + mapinfo.SerializationEntity = ((MapEntity)RootObject).Serialize(idmap); + return mapinfo; } } } diff --git a/StudioCore/MsbEditor/SceneTree.cs b/StudioCore/MsbEditor/SceneTree.cs index 911f53e51..1fa2a3ecb 100644 --- a/StudioCore/MsbEditor/SceneTree.cs +++ b/StudioCore/MsbEditor/SceneTree.cs @@ -192,10 +192,10 @@ private void ChaliceDungeonImportButton() } private ulong _mapEnt_ImGuiID = 0; // Needed to avoid issue with identical IDs during keyboard navigation. May be unecessary when ImGUI is updated. - unsafe private void MapObjectSelectable(Entity e, bool visicon, bool hierarchial=false) + unsafe private void MapObjectSelectable(Entity e, bool visicon, bool hierarchial = false) { float scale = MapStudioNew.GetUIScale(); - + // Main selectable if (e is MapEntity me) { @@ -217,7 +217,7 @@ unsafe private void MapObjectSelectable(Entity e, bool visicon, bool hierarchial if (hierarchial && e.Children.Count > 0) { var treeflags = ImGuiTreeNodeFlags.OpenOnArrow | ImGuiTreeNodeFlags.SpanAvailWidth; - if ( _selection.GetSelection().Contains(e)) + if (_selection.GetSelection().Contains(e)) { treeflags |= ImGuiTreeNodeFlags.Selected; } @@ -233,7 +233,7 @@ unsafe private void MapObjectSelectable(Entity e, bool visicon, bool hierarchial else { _mapEnt_ImGuiID++; - if (ImGui.Selectable(padding + e.PrettyName+"##"+ _mapEnt_ImGuiID, _selection.GetSelection().Contains(e), ImGuiSelectableFlags.AllowDoubleClick | ImGuiSelectableFlags.AllowItemOverlap)) + if (ImGui.Selectable(padding + e.PrettyName + "##" + _mapEnt_ImGuiID, _selection.GetSelection().Contains(e), ImGuiSelectableFlags.AllowDoubleClick | ImGuiSelectableFlags.AllowItemOverlap)) { // If double clicked frame the selection in the viewport if (ImGui.IsMouseDoubleClicked(0)) @@ -376,7 +376,7 @@ unsafe private void MapObjectSelectable(Entity e, bool visicon, bool hierarchial _selection.AddSelection(e); } } - else if (_selection.GetSelection().Count > 0 + else if (_selection.GetSelection().Count > 0 && (InputTracker.GetKey(Key.ShiftLeft) || InputTracker.GetKey(Key.ShiftRight))) { // Select Range @@ -727,7 +727,7 @@ public void OnGui() ImGui.SameLine(); ImGui.PushTextWrapPos(); if (metaName.StartsWith("--")) // Marked as normally unused (use red text) - ImGui.TextColored(new Vector4(1.0f, 0.0f, 0.0f, 1.0f), @$"<{metaName.Replace("--","")}>"); + ImGui.TextColored(new Vector4(1.0f, 0.0f, 0.0f, 1.0f), @$"<{metaName.Replace("--", "")}>"); else ImGui.TextColored(new Vector4(1.0f, 1.0f, 0.0f, 1.0f), @$"<{metaName}>"); ImGui.PopTextWrapPos(); @@ -796,6 +796,19 @@ public void OnGui() GC.WaitForPendingFinalizers(); GC.Collect(); } + if (ImGui.Selectable("Export Map")) + { + try + { + _universe.ExportMap(m); + } + catch (SavingFailedException e) + { + PlatformUtils.Instance.MessageBox(e.Wrapped.Message, e.Message, + MessageBoxButtons.OK, + MessageBoxIcon.None); + } + } } if (_universe.GetLoadedMapCount() > 1) { @@ -947,4 +960,4 @@ public void OnActionEvent(ActionEvent evt) } } } -} +} \ No newline at end of file diff --git a/StudioCore/MsbEditor/SpecialMapConnections.cs b/StudioCore/MsbEditor/SpecialMapConnections.cs index 4435c4739..efd3c3e4e 100644 --- a/StudioCore/MsbEditor/SpecialMapConnections.cs +++ b/StudioCore/MsbEditor/SpecialMapConnections.cs @@ -14,6 +14,18 @@ namespace StudioCore.MsbEditor /// internal class SpecialMapConnections { + public static Vector3 GetEldenMapGlobalTransform(string mapid) + { + if (!TryInitializeEldenOffsets()) + { + return new Vector3(0, 0, 0); + } + if (!TryParseMap(mapid, out byte[] target) || !ToEldenGlobalCoords(target, Vector3.Zero, 0, 0, out Vector3 targetGlobal)) + { + return new Vector3(0, 0, 0); + } + return targetGlobal; + } public static Transform? GetEldenMapTransform( string mapid, IReadOnlyDictionary loadedMaps) @@ -26,9 +38,9 @@ internal class SpecialMapConnections { return null; } - (int originX, int originZ) = GetClosestTile(targetGlobal, 0, 0); + (int originTileX, int originTileZ) = GetClosestTile(targetGlobal, 0, 0); // Recenter target in terms of closest tile center, for maximum precision - if (!ToEldenGlobalCoords(target, Vector3.Zero, originX, originZ, out targetGlobal)) + if (!ToEldenGlobalCoords(target, Vector3.Zero, originTileX, originTileZ, out targetGlobal)) { return null; } @@ -41,7 +53,7 @@ internal class SpecialMapConnections if (entry.Value == null || !entry.Value.RootObject.HasTransform || !TryParseMap(entry.Key, out byte[] origin) - || !ToEldenGlobalCoords(origin, Vector3.Zero, originX, originZ, out Vector3 originGlobal)) + || !ToEldenGlobalCoords(origin, Vector3.Zero, originTileX, originTileZ, out Vector3 originGlobal)) { continue; } diff --git a/StudioCore/MsbEditor/Universe.cs b/StudioCore/MsbEditor/Universe.cs index 813062e37..a5fc85791 100644 --- a/StudioCore/MsbEditor/Universe.cs +++ b/StudioCore/MsbEditor/Universe.cs @@ -14,16 +14,18 @@ using SoulsFormats; using StudioCore.Scene; using StudioCore.Editor; +using Newtonsoft.Json.Linq; +using StudioCore.Platform; namespace StudioCore.MsbEditor { - [JsonSourceGenerationOptions(WriteIndented = true, + [JsonSourceGenerationOptions(WriteIndented = true, GenerationMode = JsonSourceGenerationMode.Metadata, IncludeFields = true)] [JsonSerializable(typeof(List))] internal partial class BtlLightSerializerContext : JsonSerializerContext { } - + /// /// A universe is a collection of loaded maps with methods to load, serialize, /// and unload individual maps. @@ -65,7 +67,7 @@ public int GetLoadedMapCount() { if (map.Value != null) { - i++; + i++; } } return i; @@ -149,7 +151,7 @@ private static ModelMarkerType GetModelMarkerType(string type) public RenderableProxy GetRegionDrawable(Map map, Entity obj) { - if (obj.WrappedObject is IMsbRegion r && r.Shape is MSB.Shape.Box ) + if (obj.WrappedObject is IMsbRegion r && r.Shape is MSB.Shape.Box) { var mesh = DebugPrimitiveRenderableProxy.GetBoxRegionProxy(_renderScene); mesh.World = obj.GetWorldMatrix(); @@ -445,7 +447,7 @@ public void LoadDS2Generators(string mapid, Map map) row.Name = "generator_" + row.ID.ToString(); } - + var mergedRow = new MergedParamRow(); mergedRow.AddRow("generator-loc", row); generatorParams.Add(row.ID, mergedRow); @@ -519,7 +521,7 @@ public void LoadDS2Generators(string mapid, Map map) row.Name = "eventloc_" + row.ID.ToString(); } eventLocationParams.Add(row.ID, row); - + var obj = new MapEntity(map, row, MapEntity.MapEntityType.DS2EventLocation); map.AddObject(obj); map.MapOffsetNode.AddChild(obj); @@ -669,7 +671,7 @@ public async void LoadMapAsync(string mapid, bool selectOnLoad = false) _dispGroupCount = 8; //? break; case GameType.ArmoredCoreVI: - //TODO AC6 + //TODO AC6 default: throw new Exception($"Error: Did not expect Gametype {_assetLocator.Type}"); //break; @@ -1285,7 +1287,7 @@ public void SaveBTL(Map map) var newLights = map.SerializeBtlLights(BTLs_w[i].AssetName); // Only save BTL if it has been modified - if (JsonSerializer.Serialize(btl.Lights, BtlLightSerializerContext.Default.ListLight) != + if (JsonSerializer.Serialize(btl.Lights, BtlLightSerializerContext.Default.ListLight) != JsonSerializer.Serialize(newLights, BtlLightSerializerContext.Default.ListLight)) { btl.Lights = newLights; @@ -1323,7 +1325,7 @@ public void SaveBTL(Map map) var newLights = map.SerializeBtlLights(BTLs_w[i].AssetName); // Only save BTL if it has been modified - if (JsonSerializer.Serialize(btl.Lights, BtlLightSerializerContext.Default.ListLight) != + if (JsonSerializer.Serialize(btl.Lights, BtlLightSerializerContext.Default.ListLight) != JsonSerializer.Serialize(newLights, BtlLightSerializerContext.Default.ListLight)) { btl.Lights = newLights; @@ -1559,5 +1561,17 @@ public Type GetPropertyType(string name) } return null; } + + public void ExportMap(Map map) + { + if (PlatformUtils.Instance.SaveFileDialog("Export Map To Json", new[] { "json" }, out string path)) + { + using (var file = new StreamWriter(path)) + { + var json = Newtonsoft.Json.JsonConvert.SerializeObject(map.SerializeHierarchy()); + file.Write(json); + } + } + } } -} +} \ No newline at end of file diff --git a/StudioCore/ParamEditor/MassParamEdit.cs b/StudioCore/ParamEditor/MassParamEdit.cs index 85f574396..0327093e4 100644 --- a/StudioCore/ParamEditor/MassParamEdit.cs +++ b/StudioCore/ParamEditor/MassParamEdit.cs @@ -8,7 +8,6 @@ using ImGuiNET; using SoulsFormats; using StudioCore.Editor; -using StudioCore.ParamEditor; namespace StudioCore.ParamEditor { diff --git a/StudioCore/ParamEditor/SearchEngine.cs b/StudioCore/ParamEditor/SearchEngine.cs index 05824284c..49d0b10c6 100644 --- a/StudioCore/ParamEditor/SearchEngine.cs +++ b/StudioCore/ParamEditor/SearchEngine.cs @@ -6,37 +6,38 @@ using FSParam; using ImGuiNET; using SoulsFormats; -using StudioCore.ParamEditor; using StudioCore.TextEditor; -namespace StudioCore.Editor +namespace StudioCore.ParamEditor { /* Restricted characters: colon, space, forward slash, ampersand, exclamation mark * */ - class SearchEngine + class SearchEngine { public SearchEngine() { Setup(); } - internal Dictionary> filterList = new Dictionary>(); - internal SearchEngineCommand defaultFilter = null; + internal Dictionary> filterList = new Dictionary>(); + internal SearchEngineCommand defaultFilter = null; internal Func> unpacker; - protected void addExistsFilter() { - filterList.Add("exists", newCmd(new string[0], "Selects all elements", noArgs(noContext((B)=>true)))); + protected void addExistsFilter() + { + filterList.Add("exists", newCmd(new string[0], "Selects all elements", noArgs(noContext((B) => true)))); } protected Func>> noArgs(Func> func) { - return (args, lenient)=>func; + return (args, lenient) => func; } protected Func> noContext(Func func) { - return (context)=>func; + return (context) => func; } - internal virtual void Setup(){ + internal virtual void Setup() + { } internal SearchEngineCommand newCmd(string[] args, string wiki, Func>> func, Func shouldShow = null) { @@ -54,12 +55,12 @@ public List AvailableCommandsForHelpText() List options = new List(); foreach (string op in filterList.Keys) { - SearchEngineCommand cmd = filterList[op]; + SearchEngineCommand cmd = filterList[op]; if (cmd.shouldShow == null || cmd.shouldShow()) - options.Add(op+"("+(filterList[op].args.Length)+" args)"); + options.Add(op + "(" + filterList[op].args.Length + " args)"); } if (defaultFilter != null && (defaultFilter.shouldShow == null || defaultFilter.shouldShow())) - options.Add("or omit specifying and use default ("+defaultFilter.args.Length+"args)"); + options.Add("or omit specifying and use default (" + defaultFilter.args.Length + "args)"); return options; } public List<(string, string[], string)> VisibleCommands() @@ -67,7 +68,7 @@ public List AvailableCommandsForHelpText() List<(string, string[], string)> options = new List<(string, string[], string)>(); foreach (string op in filterList.Keys) { - SearchEngineCommand cmd = filterList[op]; + SearchEngineCommand cmd = filterList[op]; if (cmd.shouldShow == null || cmd.shouldShow()) options.Add((op, cmd.args, cmd.wiki)); } @@ -96,7 +97,8 @@ public virtual List Search(A context, List sourceSet, string command, bool string[] conditions = command.Split("&&", StringSplitOptions.TrimEntries); List liveSet = sourceSet; - try { + try + { foreach (string condition in conditions) { //temp @@ -104,7 +106,7 @@ public virtual List Search(A context, List sourceSet, string command, bool break; string[] cmd = condition.Split(' ', 2); - SearchEngineCommand selectedCommand; + SearchEngineCommand selectedCommand; int argC; string[] args; bool not = false; @@ -117,7 +119,7 @@ public virtual List Search(A context, List sourceSet, string command, bool { selectedCommand = filterList[cmd[0]]; argC = selectedCommand.args.Length; - args = cmd.Length==1?new string[0] : cmd[1].Split(' ', argC, StringSplitOptions.TrimEntries); + args = cmd.Length == 1 ? new string[0] : cmd[1].Split(' ', argC, StringSplitOptions.TrimEntries); } else { @@ -125,7 +127,7 @@ public virtual List Search(A context, List sourceSet, string command, bool argC = selectedCommand.args.Length; args = condition.Split(" ", argC, StringSplitOptions.TrimEntries); } - for (int i=0; i : SearchEngine + class MultiStageSearchEngine : SearchEngine { internal Func contextGetterForMultiStage = null; internal Func sourceListGetterForMultiStage = null; @@ -191,18 +193,19 @@ class ParamAndRowSearchEngine : MultiStageSearchEngine parse = new ParamAndRowSearchEngine(); internal override void Setup() { - unpacker = (selection)=>{ + unpacker = (selection) => + { List<(MassEditRowSource, Param.Row)> list = new List<(MassEditRowSource, Param.Row)>(); list.AddRange(selection.getSelectedRows().Select((x, i) => (MassEditRowSource.Selection, x))); list.AddRange(ParamBank.ClipboardRows.Select((x, i) => (MassEditRowSource.Clipboard, x))); return list; }; - filterList.Add("selection", newCmd(new string[0], "Selects the current param selection and selected rows in that param", noArgs(noContext((row)=>row.Item1 == MassEditRowSource.Selection)))); - filterList.Add("clipboard", newCmd(new string[0], "Selects the param of the clipboard and the rows in the clipboard", noArgs(noContext((row)=>row.Item1 == MassEditRowSource.Clipboard)), ()=>ParamBank.ClipboardRows?.Count > 0)); - contextGetterForMultiStage = (ParamEditorSelectionState state, (MassEditRowSource, Param.Row) exampleItem) => (ParamBank.PrimaryBank, ParamBank.PrimaryBank.Params[exampleItem.Item1 == MassEditRowSource.Selection ? state.getActiveParam() : ParamBank.ClipboardParam]); - sourceListGetterForMultiStage = ((MassEditRowSource, Param.Row) row) => row.Item2; + filterList.Add("selection", newCmd(new string[0], "Selects the current param selection and selected rows in that param", noArgs(noContext((row) => row.Item1 == MassEditRowSource.Selection)))); + filterList.Add("clipboard", newCmd(new string[0], "Selects the param of the clipboard and the rows in the clipboard", noArgs(noContext((row) => row.Item1 == MassEditRowSource.Clipboard)), () => ParamBank.ClipboardRows?.Count > 0)); + contextGetterForMultiStage = (state, exampleItem) => (ParamBank.PrimaryBank, ParamBank.PrimaryBank.Params[exampleItem.Item1 == MassEditRowSource.Selection ? state.getActiveParam() : ParamBank.ClipboardParam]); + sourceListGetterForMultiStage = (row) => row.Item2; searchEngineForMultiStage = RowSearchEngine.rse; - resultRetrieverForMultiStage = (Param.Row row, (MassEditRowSource, Param.Row) exampleItem) => (exampleItem.Item1, row); + resultRetrieverForMultiStage = (row, exampleItem) => (exampleItem.Item1, row); } } enum MassEditRowSource @@ -221,25 +224,29 @@ private ParamSearchEngine(ParamBank bank) ParamBank bank; internal override void Setup() { - unpacker = (dummy)=>ParamBank.AuxBanks.Select((aux, i) => aux.Value.Params.Select((x, i) => (aux.Value, x.Value))).Aggregate(bank.Params.Values.Select((x, i) => (bank, x)), (o, n) => o.Concat(n)).ToList(); - filterList.Add("modified", newCmd(new string[0], "Selects params where any rows do not match the vanilla version, or where any are added. Ignores row names", noArgs(noContext((param)=>{ + unpacker = (dummy) => ParamBank.AuxBanks.Select((aux, i) => aux.Value.Params.Select((x, i) => (aux.Value, x.Value))).Aggregate(bank.Params.Values.Select((x, i) => (bank, x)), (o, n) => o.Concat(n)).ToList(); + filterList.Add("modified", newCmd(new string[0], "Selects params where any rows do not match the vanilla version, or where any are added. Ignores row names", noArgs(noContext((param) => + { if (param.Item1 != bank) return false; HashSet cache = bank.GetVanillaDiffRows(bank.GetKeyForParam(param.Item2)); return cache.Count > 0; })))); - filterList.Add("param", newCmd(new string[]{"param name (regex)"}, "Selects all params whose name matches the given regex", (args, lenient)=>{ + filterList.Add("param", newCmd(new string[] { "param name (regex)" }, "Selects all params whose name matches the given regex", (args, lenient) => + { Regex rx = lenient ? new Regex(args[0], RegexOptions.IgnoreCase) : new Regex($@"^{args[0]}$"); - return noContext((param)=>param.Item1 != bank ? false : rx.IsMatch(bank.GetKeyForParam(param.Item2) == null ? "" : bank.GetKeyForParam(param.Item2))); + return noContext((param) => param.Item1 != bank ? false : rx.IsMatch(bank.GetKeyForParam(param.Item2) == null ? "" : bank.GetKeyForParam(param.Item2))); })); - filterList.Add("auxparam", newCmd(new string[]{"parambank name", "param name (regex)"}, "Selects params from the specified regulation or parambnd where the param name matches the given regex", (args, lenient)=>{ + filterList.Add("auxparam", newCmd(new string[] { "parambank name", "param name (regex)" }, "Selects params from the specified regulation or parambnd where the param name matches the given regex", (args, lenient) => + { ParamBank auxBank = ParamBank.AuxBanks[args[0]]; Regex rx = lenient ? new Regex(args[1], RegexOptions.IgnoreCase) : new Regex($@"^{args[1]}$"); - return noContext((param)=>param.Item1 != auxBank ? false : rx.IsMatch(auxBank.GetKeyForParam(param.Item2) == null ? "" : auxBank.GetKeyForParam(param.Item2))); - }, ()=>ParamBank.AuxBanks.Count > 0 && CFG.Current.Param_AdvancedMassedit)); - defaultFilter = newCmd(new string[]{"param name (regex)"}, "Selects all params whose name matches the given regex", (args, lenient)=>{ + return noContext((param) => param.Item1 != auxBank ? false : rx.IsMatch(auxBank.GetKeyForParam(param.Item2) == null ? "" : auxBank.GetKeyForParam(param.Item2))); + }, () => ParamBank.AuxBanks.Count > 0 && CFG.Current.Param_AdvancedMassedit)); + defaultFilter = newCmd(new string[] { "param name (regex)" }, "Selects all params whose name matches the given regex", (args, lenient) => + { Regex rx = lenient ? new Regex(args[0], RegexOptions.IgnoreCase) : new Regex($@"^{args[0]}$"); - return noContext((param)=>param.Item1 != bank ? false : rx.IsMatch(bank.GetKeyForParam(param.Item2) == null ? "" : bank.GetKeyForParam(param.Item2))); + return noContext((param) => param.Item1 != bank ? false : rx.IsMatch(bank.GetKeyForParam(param.Item2) == null ? "" : bank.GetKeyForParam(param.Item2))); }); } } @@ -254,81 +261,93 @@ private RowSearchEngine(ParamBank bank) internal override void Setup() { unpacker = (param) => new List(param.Item2.Rows); - filterList.Add("modified", newCmd(new string[0], "Selects rows which do not match the vanilla version, or are added. Ignores row name", noArgs((context)=>{ - string paramName = context.Item1.GetKeyForParam(context.Item2); - HashSet cache = context.Item1.GetVanillaDiffRows(paramName); - return (row) => cache.Contains(row.ID); - } + filterList.Add("modified", newCmd(new string[0], "Selects rows which do not match the vanilla version, or are added. Ignores row name", noArgs((context) => + { + string paramName = context.Item1.GetKeyForParam(context.Item2); + HashSet cache = context.Item1.GetVanillaDiffRows(paramName); + return (row) => cache.Contains(row.ID); + } ))); - filterList.Add("added", newCmd(new string[0], "Selects rows where the ID is not found in the vanilla param", noArgs((context)=>{ - string paramName = context.Item1.GetKeyForParam(context.Item2); - if (!ParamBank.VanillaBank.Params.ContainsKey(paramName)) - return (row) => true; - Param vanilParam = ParamBank.VanillaBank.Params[paramName]; - return (row) => vanilParam[row.ID] == null; - } + filterList.Add("added", newCmd(new string[0], "Selects rows where the ID is not found in the vanilla param", noArgs((context) => + { + string paramName = context.Item1.GetKeyForParam(context.Item2); + if (!ParamBank.VanillaBank.Params.ContainsKey(paramName)) + return (row) => true; + Param vanilParam = ParamBank.VanillaBank.Params[paramName]; + return (row) => vanilParam[row.ID] == null; + } ))); - filterList.Add("mergeable", newCmd(new string[0], "Selects rows which are not modified in the primary regulation or parambnd and there is exactly one equivalent row in another regulation or parambnd that is modified", noArgs((context)=>{ + filterList.Add("mergeable", newCmd(new string[0], "Selects rows which are not modified in the primary regulation or parambnd and there is exactly one equivalent row in another regulation or parambnd that is modified", noArgs((context) => + { string paramName = context.Item1.GetKeyForParam(context.Item2); if (paramName == null) return (row) => true; HashSet pCache = ParamBank.PrimaryBank.GetVanillaDiffRows(paramName); - var auxCaches = ParamBank.AuxBanks.Select(x=>(x.Value.GetPrimaryDiffRows(paramName), x.Value.GetVanillaDiffRows(paramName))).ToList(); + var auxCaches = ParamBank.AuxBanks.Select(x => (x.Value.GetPrimaryDiffRows(paramName), x.Value.GetVanillaDiffRows(paramName))).ToList(); return (row) => !pCache.Contains(row.ID) && auxCaches.Where((x) => x.Item2.Contains(row.ID) && x.Item1.Contains(row.ID)).Count() == 1; - } - ), ()=>ParamBank.AuxBanks.Count > 0)); - filterList.Add("conflicts", newCmd(new string[0], "Selects rows which, among all equivalents in the primary and additional regulations or parambnds, there is more than row 1 which is modified", noArgs((context)=>{ + } + ), () => ParamBank.AuxBanks.Count > 0)); + filterList.Add("conflicts", newCmd(new string[0], "Selects rows which, among all equivalents in the primary and additional regulations or parambnds, there is more than row 1 which is modified", noArgs((context) => + { string paramName = context.Item1.GetKeyForParam(context.Item2); HashSet pCache = ParamBank.PrimaryBank.GetVanillaDiffRows(paramName); - var auxCaches = ParamBank.AuxBanks.Select(x=>(x.Value.GetPrimaryDiffRows(paramName), x.Value.GetVanillaDiffRows(paramName))).ToList(); - return (row) => (pCache.Contains(row.ID)?1:0) + auxCaches.Where((x) => x.Item2.Contains(row.ID) && x.Item1.Contains(row.ID)).Count() > 1; - } - ), ()=>ParamBank.AuxBanks.Count > 0)); - filterList.Add("id", newCmd(new string[]{"row id (regex)"}, "Selects rows whose ID matches the given regex", (args, lenient)=>{ + var auxCaches = ParamBank.AuxBanks.Select(x => (x.Value.GetPrimaryDiffRows(paramName), x.Value.GetVanillaDiffRows(paramName))).ToList(); + return (row) => (pCache.Contains(row.ID) ? 1 : 0) + auxCaches.Where((x) => x.Item2.Contains(row.ID) && x.Item1.Contains(row.ID)).Count() > 1; + } + ), () => ParamBank.AuxBanks.Count > 0)); + filterList.Add("id", newCmd(new string[] { "row id (regex)" }, "Selects rows whose ID matches the given regex", (args, lenient) => + { Regex rx = lenient ? new Regex(args[0].ToLower()) : new Regex($@"^{args[0]}$"); - return noContext((row)=>rx.IsMatch(row.ID.ToString())); + return noContext((row) => rx.IsMatch(row.ID.ToString())); })); - filterList.Add("idrange", newCmd(new string[]{"row id minimum (inclusive)", "row id maximum (inclusive)"}, "Selects rows whose ID falls in the given numerical range", (args, lenient)=>{ + filterList.Add("idrange", newCmd(new string[] { "row id minimum (inclusive)", "row id maximum (inclusive)" }, "Selects rows whose ID falls in the given numerical range", (args, lenient) => + { double floor = double.Parse(args[0]); double ceil = double.Parse(args[1]); - return noContext((row)=>row.ID >= floor && row.ID <= ceil); + return noContext((row) => row.ID >= floor && row.ID <= ceil); })); - filterList.Add("name", newCmd(new string[]{"row name (regex)"}, "Selects rows whose Name matches the given regex", (args, lenient)=>{ + filterList.Add("name", newCmd(new string[] { "row name (regex)" }, "Selects rows whose Name matches the given regex", (args, lenient) => + { Regex rx = lenient ? new Regex(args[0], RegexOptions.IgnoreCase) : new Regex($@"^{args[0]}$"); - return noContext((row)=>rx.IsMatch(row.Name == null ? "" : row.Name)); + return noContext((row) => rx.IsMatch(row.Name == null ? "" : row.Name)); })); - filterList.Add("prop", newCmd(new string[]{"field internalName", "field value (regex)"}, "Selects rows where the specified field has a value that matches the given regex", (args, lenient)=>{ + filterList.Add("prop", newCmd(new string[] { "field internalName", "field value (regex)" }, "Selects rows where the specified field has a value that matches the given regex", (args, lenient) => + { Regex rx = lenient ? new Regex(args[1], RegexOptions.IgnoreCase) : new Regex($@"^{args[1]}$"); string field = args[0].Replace(@"\s", " "); - return noContext((row)=>{ - Param.Cell? cq = row[field]; - if (cq == null) throw new Exception(); - Param.Cell c = cq.Value; - string term = c.Value.ToParamEditorString(); - return rx.IsMatch(term); + return noContext((row) => + { + Param.Cell? cq = row[field]; + if (cq == null) throw new Exception(); + Param.Cell c = cq.Value; + string term = c.Value.ToParamEditorString(); + return rx.IsMatch(term); }); })); - filterList.Add("proprange", newCmd(new string[]{"field internalName", "field value minimum (inclusive)", "field value maximum (inclusive)"}, "Selects rows where the specified field has a value that falls in the given numerical range", (args, lenient)=>{ + filterList.Add("proprange", newCmd(new string[] { "field internalName", "field value minimum (inclusive)", "field value maximum (inclusive)" }, "Selects rows where the specified field has a value that falls in the given numerical range", (args, lenient) => + { string field = args[0].Replace(@"\s", " "); double floor = double.Parse(args[1]); double ceil = double.Parse(args[2]); - return noContext((row)=> + return noContext((row) => { - Param.Cell? c = row[field]; - if (c == null) throw new Exception(); - return (Convert.ToDouble(c.Value.Value)) >= floor && (Convert.ToDouble(c.Value.Value)) <= ceil; + Param.Cell? c = row[field]; + if (c == null) throw new Exception(); + return Convert.ToDouble(c.Value.Value) >= floor && Convert.ToDouble(c.Value.Value) <= ceil; }); })); - filterList.Add("propref", newCmd(new string[]{"field internalName", "referenced row name (regex)"}, "Selects rows where the specified field that references another param has a value referencing a row whose name matches the given regex", (args, lenient)=>{ + filterList.Add("propref", newCmd(new string[] { "field internalName", "referenced row name (regex)" }, "Selects rows where the specified field that references another param has a value referencing a row whose name matches the given regex", (args, lenient) => + { Regex rx = lenient ? new Regex(args[1], RegexOptions.IgnoreCase) : new Regex($@"^{args[1]}$"); string field = args[0].Replace(@"\s", " "); - return (context)=>{ - List validFields = FieldMetaData.Get(context.Item2.AppliedParamdef.Fields.Find((f)=>f.InternalName.Equals(field))).RefTypes.FindAll((p)=>bank.Params.ContainsKey(p.param)); - return (row)=> + return (context) => + { + List validFields = FieldMetaData.Get(context.Item2.AppliedParamdef.Fields.Find((f) => f.InternalName.Equals(field))).RefTypes.FindAll((p) => bank.Params.ContainsKey(p.param)); + return (row) => { Param.Cell? c = row[field]; if (c == null) throw new Exception(); - int val = (int) c.Value.Value; + int val = (int)c.Value.Value; foreach (ParamRef rt in validFields) { Param.Row r = bank.Params[rt.param][val]; @@ -338,24 +357,28 @@ internal override void Setup() return false; }; }; - }, ()=>CFG.Current.Param_AdvancedMassedit)); - filterList.Add("propwhere", newCmd(new string[]{"field internalName", "cell/field selector"}, "Selects rows where the specified field appears when the given cell/field search is given", (args, lenient)=>{ + }, () => CFG.Current.Param_AdvancedMassedit)); + filterList.Add("propwhere", newCmd(new string[] { "field internalName", "cell/field selector" }, "Selects rows where the specified field appears when the given cell/field search is given", (args, lenient) => + { string field = args[0].Replace(@"\s", " "); - return (context)=>{ + return (context) => + { string paramName = context.Item1.GetKeyForParam(context.Item2); var cols = context.Item2.Columns; var testCol = context.Item2.GetCol(field); - return (row)=> + return (row) => { var cseSearchContext = (paramName, row); - var res = CellSearchEngine.cse.Search(cseSearchContext, new List<(PseudoColumn, Param.Column)>(){testCol}, args[1], lenient, false); + var res = CellSearchEngine.cse.Search(cseSearchContext, new List<(PseudoColumn, Param.Column)>() { testCol }, args[1], lenient, false); return res.Contains(testCol); }; }; - }, ()=>CFG.Current.Param_AdvancedMassedit)); - filterList.Add("fmg", newCmd(new string[]{"fmg title (regex)"}, "Selects rows which have an attached FMG and that FMG's text matches the given regex", (args, lenient)=>{ + }, () => CFG.Current.Param_AdvancedMassedit)); + filterList.Add("fmg", newCmd(new string[] { "fmg title (regex)" }, "Selects rows which have an attached FMG and that FMG's text matches the given regex", (args, lenient) => + { Regex rx = lenient ? new Regex(args[0], RegexOptions.IgnoreCase) : new Regex($@"^{args[0]}$"); - return (context)=>{ + return (context) => + { FmgEntryCategory category = FmgEntryCategory.None; string paramName = context.Item1.GetKeyForParam(context.Item2); foreach ((string param, FmgEntryCategory cat) in ParamBank.ParamToFmgCategoryList) @@ -372,20 +395,24 @@ internal override void Setup() { _cache[fmgEntry.ID] = fmgEntry; } - return (row)=>{ + return (row) => + { if (!_cache.ContainsKey(row.ID)) return false; FMG.Entry e = _cache[row.ID]; return e != null && rx.IsMatch(e.Text ?? ""); }; }; - }, ()=>CFG.Current.Param_AdvancedMassedit)); - filterList.Add("vanillaprop", newCmd(new string[]{"field internalName", "field value (regex)"}, "Selects rows where the vanilla equivilent of that row has a value for the given field that matches the given regex", (args, lenient)=>{ + }, () => CFG.Current.Param_AdvancedMassedit)); + filterList.Add("vanillaprop", newCmd(new string[] { "field internalName", "field value (regex)" }, "Selects rows where the vanilla equivilent of that row has a value for the given field that matches the given regex", (args, lenient) => + { Regex rx = lenient ? new Regex(args[1], RegexOptions.IgnoreCase) : new Regex($@"^{args[1]}$"); string field = args[0].Replace(@"\s", " "); - return (param) => { + return (param) => + { Param vparam = ParamBank.VanillaBank.GetParamFromName(param.Item1.GetKeyForParam(param.Item2)); - return (row)=>{ + return (row) => + { Param.Row vrow = vparam[row.ID]; if (vrow == null) return false; @@ -396,32 +423,38 @@ internal override void Setup() return rx.IsMatch(term); }; }; - }, ()=>CFG.Current.Param_AdvancedMassedit)); - filterList.Add("vanillaproprange", newCmd(new string[]{"field internalName", "field value minimum (inclusive)", "field value maximum (inclusive)"}, "Selects rows where the vanilla equivilent of that row has a value for the given field that falls in the given numerical range", (args, lenient)=>{ + }, () => CFG.Current.Param_AdvancedMassedit)); + filterList.Add("vanillaproprange", newCmd(new string[] { "field internalName", "field value minimum (inclusive)", "field value maximum (inclusive)" }, "Selects rows where the vanilla equivilent of that row has a value for the given field that falls in the given numerical range", (args, lenient) => + { string field = args[0].Replace(@"\s", " "); double floor = double.Parse(args[1]); double ceil = double.Parse(args[2]); - return (param) => { + return (param) => + { Param vparam = ParamBank.VanillaBank.GetParamFromName(param.Item1.GetKeyForParam(param.Item2)); - return (row)=>{ + return (row) => + { Param.Row vrow = vparam[row.ID]; if (vrow == null) return false; Param.Cell? c = vrow[field]; if (c == null) throw new Exception(); - return (Convert.ToDouble(c.Value.Value)) >= floor && (Convert.ToDouble(c.Value.Value)) <= ceil; + return Convert.ToDouble(c.Value.Value) >= floor && Convert.ToDouble(c.Value.Value) <= ceil; }; }; - }, ()=>CFG.Current.Param_AdvancedMassedit)); - filterList.Add("auxprop", newCmd(new string[]{"parambank name", "field internalName", "field value (regex)"}, "Selects rows where the equivilent of that row in the given regulation or parambnd has a value for the given field that matches the given regex.\nCan be used to determine if an aux row exists.", (args, lenient)=>{ + }, () => CFG.Current.Param_AdvancedMassedit)); + filterList.Add("auxprop", newCmd(new string[] { "parambank name", "field internalName", "field value (regex)" }, "Selects rows where the equivilent of that row in the given regulation or parambnd has a value for the given field that matches the given regex.\nCan be used to determine if an aux row exists.", (args, lenient) => + { Regex rx = lenient ? new Regex(args[2], RegexOptions.IgnoreCase) : new Regex($@"^{args[2]}$"); string field = args[1].Replace(@"\s", " "); ParamBank bank; if (!ParamBank.AuxBanks.TryGetValue(args[0], out bank)) - throw new Exception("Unable to find auxbank "+args[0]); - return (param) => { + throw new Exception("Unable to find auxbank " + args[0]); + return (param) => + { Param vparam = bank.GetParamFromName(param.Item1.GetKeyForParam(param.Item2)); - return (row)=>{ + return (row) => + { Param.Row vrow = vparam[row.ID]; if (vrow == null) return false; @@ -432,8 +465,9 @@ internal override void Setup() return rx.IsMatch(term); }; }; - }, ()=>ParamBank.AuxBanks.Count > 0 && CFG.Current.Param_AdvancedMassedit)); - filterList.Add("auxproprange", newCmd(new string[]{"parambank name", "field internalName", "field value minimum (inclusive)", "field value maximum (inclusive)"}, "Selects rows where the equivilent of that row in the given regulation or parambnd has a value for the given field that falls in the given range", (args, lenient)=>{ + }, () => ParamBank.AuxBanks.Count > 0 && CFG.Current.Param_AdvancedMassedit)); + filterList.Add("auxproprange", newCmd(new string[] { "parambank name", "field internalName", "field value minimum (inclusive)", "field value maximum (inclusive)" }, "Selects rows where the equivilent of that row in the given regulation or parambnd has a value for the given field that falls in the given range", (args, lenient) => + { string field = args[0].Replace(@"\s", " "); double floor = double.Parse(args[1]); double ceil = double.Parse(args[2]); @@ -448,38 +482,43 @@ internal override void Setup() Param.Row vrow = vparam[row.ID]; Param.Cell? c = vrow[field]; if (c == null) throw new Exception(); - return (Convert.ToDouble(c.Value.Value)) >= floor && (Convert.ToDouble(c.Value.Value)) <= ceil; + return Convert.ToDouble(c.Value.Value) >= floor && Convert.ToDouble(c.Value.Value) <= ceil; }; }; - }, ()=>ParamBank.AuxBanks.Count > 0 && CFG.Current.Param_AdvancedMassedit)); - filterList.Add("semijoin", newCmd(new string[]{"this field internalName", "other param", "other param field internalName", "other param row search"}, "Selects all rows where the value of a given field is any of the values in the second given field found in the given param using the given row selector", (args, lenient)=>{ + }, () => ParamBank.AuxBanks.Count > 0 && CFG.Current.Param_AdvancedMassedit)); + filterList.Add("semijoin", newCmd(new string[] { "this field internalName", "other param", "other param field internalName", "other param row search" }, "Selects all rows where the value of a given field is any of the values in the second given field found in the given param using the given row selector", (args, lenient) => + { string thisField = args[0].Replace(@"\s", " "); string otherParam = args[1]; string otherField = args[2].Replace(@"\s", " "); string otherSearchTerm = args[3]; Param otherParamReal; if (!ParamBank.PrimaryBank.Params.TryGetValue(otherParam, out otherParamReal)) - throw new Exception("Could not find param "+otherParam); - List rows = RowSearchEngine.rse.Search((ParamBank.PrimaryBank, otherParamReal), otherSearchTerm, lenient, false); - (PseudoColumn, Param.Column) otherFieldReal = ParamUtils.GetCol(otherParamReal, otherField); + throw new Exception("Could not find param " + otherParam); + List rows = rse.Search((ParamBank.PrimaryBank, otherParamReal), otherSearchTerm, lenient, false); + (PseudoColumn, Param.Column) otherFieldReal = otherParamReal.GetCol(otherField); if (!otherFieldReal.IsColumnValid()) - throw new Exception("Could not find field "+otherField); + throw new Exception("Could not find field " + otherField); HashSet possibleValues = rows.Select((x) => x.Get(otherFieldReal).ToParamEditorString()).Distinct().ToHashSet(); - return (param) => { - (PseudoColumn, Param.Column) thisFieldReal = ParamUtils.GetCol(param.Item2, thisField); + return (param) => + { + (PseudoColumn, Param.Column) thisFieldReal = param.Item2.GetCol(thisField); if (!thisFieldReal.IsColumnValid()) - throw new Exception("Could not find field "+thisField); - return (row)=>{ + throw new Exception("Could not find field " + thisField); + return (row) => + { string toFind = row.Get(thisFieldReal).ToParamEditorString(); return possibleValues.Contains(toFind); }; }; - }, ()=>CFG.Current.Param_AdvancedMassedit)); - defaultFilter = newCmd(new string[]{"row ID or Name (regex)"}, "Selects rows where either the ID or Name matches the given regex, except in strict/massedit mode", (args, lenient)=>{ + }, () => CFG.Current.Param_AdvancedMassedit)); + defaultFilter = newCmd(new string[] { "row ID or Name (regex)" }, "Selects rows where either the ID or Name matches the given regex, except in strict/massedit mode", (args, lenient) => + { if (!lenient) - return noContext((row)=>false); + return noContext((row) => false); Regex rx = new Regex(args[0], RegexOptions.IgnoreCase); - return (paramContext)=>{ + return (paramContext) => + { FmgEntryCategory category = FmgEntryCategory.None; string paramName = paramContext.Item1.GetKeyForParam(paramContext.Item2); foreach ((string param, FmgEntryCategory cat) in ParamBank.ParamToFmgCategoryList) @@ -489,14 +528,15 @@ internal override void Setup() category = cat; } if (category == FmgEntryCategory.None || !FMGBank.IsLoaded) - return (row)=>rx.IsMatch(row.Name ?? "") || rx.IsMatch(row.ID.ToString()); + return (row) => rx.IsMatch(row.Name ?? "") || rx.IsMatch(row.ID.ToString()); var fmgEntries = FMGBank.GetFmgEntriesByCategory(category, false); Dictionary _cache = new Dictionary(); foreach (var fmgEntry in fmgEntries) { _cache[fmgEntry.ID] = fmgEntry; } - return (row)=>{ + return (row) => + { if (rx.IsMatch(row.Name ?? "") || rx.IsMatch(row.ID.ToString())) return true; if (!_cache.ContainsKey(row.ID)) @@ -514,18 +554,21 @@ class CellSearchEngine : SearchEngine<(string, Param.Row), (PseudoColumn, Param. public static CellSearchEngine cse = new CellSearchEngine(); internal override void Setup() { - unpacker = (row) => { + unpacker = (row) => + { var list = new List<(PseudoColumn, Param.Column)>(); list.Add((PseudoColumn.ID, null)); list.Add((PseudoColumn.Name, null)); list.AddRange(row.Item2.Columns.Select((cell, i) => (PseudoColumn.None, cell))); return list; }; - defaultFilter = newCmd(new string[]{"field internalName (regex)"}, "Selects cells/fields where the internal name of that field matches the given regex", (args, lenient) => { + defaultFilter = newCmd(new string[] { "field internalName (regex)" }, "Selects cells/fields where the internal name of that field matches the given regex", (args, lenient) => + { bool matchID = args[0] == "ID"; bool matchName = args[0] == "Name"; Regex rx = lenient ? new Regex(args[0], RegexOptions.IgnoreCase) : new Regex($@"^{args[0]}$"); - return noContext((cell) => { + return noContext((cell) => + { if (matchID && cell.Item1 == PseudoColumn.ID) return true; if (matchName && cell.Item1 == PseudoColumn.Name) @@ -541,7 +584,8 @@ internal override void Setup() return false; }); }); - filterList.Add("modified", newCmd(new string[0], "Selects cells/fields where the equivalent cell in the vanilla regulation or parambnd has a different value", (args, lenient) => (row) => { + filterList.Add("modified", newCmd(new string[0], "Selects cells/fields where the equivalent cell in the vanilla regulation or parambnd has a different value", (args, lenient) => (row) => + { if (row.Item1 == null) throw new Exception("Can't check if cell is modified - not part of a param"); Param vParam = ParamBank.VanillaBank.Params?[row.Item1]; @@ -551,18 +595,21 @@ internal override void Setup() if (r == null) return (col) => true; else - return (col) => { + return (col) => + { (PseudoColumn, Param.Column) vcol = col.GetAs(vParam); object valA = row.Item2.Get(col); object valB = r.Get(vcol); return ParamUtils.IsValueDiff(ref valA, ref valB, col.GetColumnType()); }; })); - filterList.Add("auxmodified", newCmd(new string[]{"parambank name"}, "Selects cells/fields where the equivalent cell in the specified regulation or parambnd has a different value", (args, lenient) => { + filterList.Add("auxmodified", newCmd(new string[] { "parambank name" }, "Selects cells/fields where the equivalent cell in the specified regulation or parambnd has a different value", (args, lenient) => + { if (!ParamBank.AuxBanks.ContainsKey(args[0])) throw new Exception("Can't check if cell is modified - parambank not found"); ParamBank bank = ParamBank.AuxBanks[args[0]]; - return (row) => { + return (row) => + { if (row.Item1 == null) throw new Exception("Can't check if cell is modified - not part of a param"); Param auxParam = bank.Params?[row.Item1]; @@ -578,7 +625,8 @@ internal override void Setup() else if (r2 == null) return (col) => true; else - return (col) => { + return (col) => + { (PseudoColumn, Param.Column) auxcol = col.GetAs(auxParam); (PseudoColumn, Param.Column) vcol = col.GetAs(vParam); object valA = r.Get(auxcol); @@ -586,31 +634,34 @@ internal override void Setup() return ParamUtils.IsValueDiff(ref valA, ref valB, col.GetColumnType()); }; }; - }, ()=>ParamBank.AuxBanks.Count > 0)); - filterList.Add("sftype", newCmd(new string[]{"paramdef type"}, "Selects cells/fields where the field's data type, as enumerated by soulsformats, matches the given regex", (args, lenient) => { - Regex r = new Regex('^'+args[0]+'$', lenient ? RegexOptions.IgnoreCase : RegexOptions.None); //Leniency rules break from the norm + }, () => ParamBank.AuxBanks.Count > 0)); + filterList.Add("sftype", newCmd(new string[] { "paramdef type" }, "Selects cells/fields where the field's data type, as enumerated by soulsformats, matches the given regex", (args, lenient) => + { + Regex r = new Regex('^' + args[0] + '$', lenient ? RegexOptions.IgnoreCase : RegexOptions.None); //Leniency rules break from the norm return (row) => (col) => r.IsMatch(col.GetColumnSfType()); - }, ()=>CFG.Current.Param_AdvancedMassedit)); + }, () => CFG.Current.Param_AdvancedMassedit)); } } - + class VarSearchEngine : SearchEngine { public static VarSearchEngine vse = new VarSearchEngine(); internal override void Setup() { - unpacker = (dummy) => { + unpacker = (dummy) => + { return MassParamEdit.massEditVars.Keys.ToList(); }; - filterList.Add("vars", newCmd(new string[]{"variable names (regex)"}, "Selects variables whose name matches the given regex", (args, lenient) => { + filterList.Add("vars", newCmd(new string[] { "variable names (regex)" }, "Selects variables whose name matches the given regex", (args, lenient) => + { if (args[0].StartsWith('$')) args[0] = args[0].Substring(1); Regex rx = lenient ? new Regex(args[0], RegexOptions.IgnoreCase) : new Regex($@"^{args[0]}$"); return noContext((name) => rx.IsMatch(name)); })); - + } } } \ No newline at end of file diff --git a/StudioCore/QueuedTaskScheduler.cs b/StudioCore/QueuedTaskScheduler.cs index cc71ca1d6..179a3d54e 100644 --- a/StudioCore/QueuedTaskScheduler.cs +++ b/StudioCore/QueuedTaskScheduler.cs @@ -6,12 +6,15 @@ // //-------------------------------------------------------------------------- +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; +using System.Threading.Tasks; -namespace System.Threading.Tasks.Schedulers +namespace StudioCore { /// /// Provides a TaskScheduler that provides control over priorities, fairness, and the underlying threads utilized. @@ -39,8 +42,8 @@ public IEnumerable ScheduledTasks { get { - var tasks = (_scheduler._targetScheduler != null) ? - (IEnumerable)_scheduler._nonthreadsafeTaskQueue : + var tasks = _scheduler._targetScheduler != null ? + _scheduler._nonthreadsafeTaskQueue : (IEnumerable)_scheduler._blockingTaskQueue; return tasks.Where(t => t != null).ToList(); } @@ -96,7 +99,7 @@ public IEnumerable Queues // *** /// Initializes the scheduler. - public QueuedTaskScheduler() : this(TaskScheduler.Default, 0) { } + public QueuedTaskScheduler() : this(Default, 0) { } /// Initializes the scheduler. /// The target underlying scheduler onto which this sceduler's work is queued. @@ -121,7 +124,7 @@ public QueuedTaskScheduler( // If 0, use the number of logical processors. But make sure whatever value we pick // is not greater than the degree of parallelism allowed by the underlying scheduler. _concurrencyLevel = maxConcurrencyLevel != 0 ? maxConcurrencyLevel : Environment.ProcessorCount; - if (targetScheduler.MaximumConcurrencyLevel > 0 && + if (targetScheduler.MaximumConcurrencyLevel > 0 && targetScheduler.MaximumConcurrencyLevel < _concurrencyLevel) { _concurrencyLevel = targetScheduler.MaximumConcurrencyLevel; @@ -257,8 +260,8 @@ private int DebugTaskCount { get { - return (_targetScheduler != null ? - (IEnumerable)_nonthreadsafeTaskQueue : (IEnumerable)_blockingTaskQueue) + return (_targetScheduler != null ? + _nonthreadsafeTaskQueue : (IEnumerable)_blockingTaskQueue) .Where(t => t != null).Count(); } } diff --git a/StudioCore/Resource/FlverResource.cs b/StudioCore/Resource/FlverResource.cs index 96c2d0975..989be11c4 100644 --- a/StudioCore/Resource/FlverResource.cs +++ b/StudioCore/Resource/FlverResource.cs @@ -1,2251 +1,2257 @@ -#nullable enable -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Numerics; -using System.Buffers; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; -using System.IO; -using Veldrid; -using Veldrid.Utilities; -using SoulsFormats; -using System.ComponentModel.DataAnnotations; -using StudioCore.MsbEditor; -using StudioCore.Scene; -using System.Data; -using System.IO.MemoryMappedFiles; -using System.Threading.Tasks.Dataflow; -using DotNext.IO.MemoryMappedFiles; - -namespace StudioCore.Resource -{ - public class FlverResource : IResource, IDisposable - { - private static Stack FlverCaches = new Stack(); - public static int CacheCount { get; private set; } = 0; - public static long CacheFootprint - { - get - { - long total = 0; - lock (CacheLock) - { - foreach (var c in FlverCaches) - { - total += c.MemoryUsage; - } - } - return total; - } - } - private static object CacheLock = new object(); - - //private static ArrayPool VerticesPool = ArrayPool.Create(); - - public const bool CaptureMaterialLayouts = false; - - /// - /// Cache of material layouts that can be dumped - /// - public static Dictionary MaterialLayouts = new Dictionary(); - public static object _matLayoutLock = new object(); - - private FlverCache GetCache() - { - lock (CacheLock) - { - if (FlverCaches.Count > 0) - { - return FlverCaches.Pop(); - } - CacheCount++; - } - return new FlverCache(); - } - - private void ReleaseCache(FlverCache cache) - { - if (cache != null) - { - cache.ResetUsage(); - lock (CacheLock) - { - FlverCaches.Push(cache); - } - } - } - - public static void PurgeCaches() - { - FlverCaches.Clear(); - //VerticesPool = ArrayPool.Create(); - //GC.Collect(); - //GC.WaitForPendingFinalizers(); - //GC.Collect(); - } - - public class FlverMaterial : IResourceEventListener, IDisposable - { - public string MaterialName; - public Scene.GPUBufferAllocator.GPUBufferHandle MaterialBuffer; - public Scene.Material MaterialData; - - public string ShaderName = null; - public MeshLayoutType LayoutType; - public List SpecializationConstants = null; - public VertexLayoutDescription VertexLayout; - public uint VertexSize; - - public enum TextureType - { - AlbedoTextureResource = 0, - AlbedoTextureResource2, - NormalTextureResource, - NormalTextureResource2, - SpecularTextureResource, - SpecularTextureResource2, - ShininessTextureResource, - ShininessTextureResource2, - BlendmaskTextureResource, - TextureResourceCount, - } - - public readonly ResourceHandle?[] TextureResources = new ResourceHandle[(int)TextureType.TextureResourceCount]; - public readonly bool[] TextureResourceFilled = new bool[(int)TextureType.TextureResourceCount]; - - private bool disposedValue; - - private bool _setHasIndexNoWeightTransform = false; - public bool GetHasIndexNoWeightTransform() => _setHasIndexNoWeightTransform; - - public void SetHasIndexNoWeightTransform() - { - if(!_setHasIndexNoWeightTransform) - { - _setHasIndexNoWeightTransform = true; - } - } - - private bool _setNormalWBoneTransform = false; - - public bool GetNormalWBoneTransform() => _setNormalWBoneTransform; - - public void SetNormalWBoneTransform() - { - if (!_setNormalWBoneTransform) - { - SpecializationConstants.Add(new SpecializationConstant(50, true)); - _setNormalWBoneTransform = true; - } - } - - private void SetMaterialTexture(TextureType textureType, ref ushort matTex, ushort defaultTex) - { - var handle = TextureResources[(int)textureType]; - if (handle != null && handle.IsLoaded) - { - var res = handle.Get(); - if (res != null && res.GPUTexture != null) - { - matTex = (ushort)handle.Get().GPUTexture.TexHandle; - } - else - { - matTex = defaultTex; - } - } - else - { - matTex = defaultTex; - } - } - - public void ReleaseTextures() - { - for (int i = 0; i < (int)TextureType.TextureResourceCount; i++) - { - TextureResources[i]?.Release(); - TextureResources[i] = null; - } - } - - public void UpdateMaterial() - { - SetMaterialTexture(TextureType.AlbedoTextureResource, ref MaterialData.colorTex, 0); - SetMaterialTexture(TextureType.AlbedoTextureResource2, ref MaterialData.colorTex2, 0); - SetMaterialTexture(TextureType.NormalTextureResource, ref MaterialData.normalTex, 1); - SetMaterialTexture(TextureType.NormalTextureResource2, ref MaterialData.normalTex2, 1); - SetMaterialTexture(TextureType.SpecularTextureResource, ref MaterialData.specTex, 2); - SetMaterialTexture(TextureType.SpecularTextureResource2, ref MaterialData.specTex2, 2); - SetMaterialTexture(TextureType.ShininessTextureResource, ref MaterialData.shininessTex, 2); - SetMaterialTexture(TextureType.ShininessTextureResource2, ref MaterialData.shininessTex2, 2); - SetMaterialTexture(TextureType.BlendmaskTextureResource, ref MaterialData.blendMaskTex, 0); - - Scene.Renderer.AddBackgroundUploadTask((d, cl) => - { - var ctx = Tracy.TracyCZoneN(1, $@"Material upload"); - MaterialBuffer.FillBuffer(d, cl, ref MaterialData); - Tracy.TracyCZoneEnd(ctx); - }); - } - - public void OnResourceLoaded(IResourceHandle handle, int tag) - { - var texHandle = (ResourceHandle)handle; - texHandle.Acquire(); - TextureResources[tag]?.Release(); - TextureResources[tag] = texHandle; - UpdateMaterial(); - } - - public void OnResourceUnloaded(IResourceHandle handle, int tag) - { - TextureResources[tag]?.Release(); - TextureResources[tag] = null; - UpdateMaterial(); - } - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - MaterialBuffer.Dispose(); - } - - ReleaseTextures(); - disposedValue = true; - } - } - - ~FlverMaterial() - { - Dispose(disposing: false); - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } - - public unsafe class FlverSubmesh - { - public struct FlverSubmeshFaceSet - { - public int IndexCount; - public int IndexOffset; - public int PickingIndicesCount; - //public IntPtr PickingIndices; - public bool BackfaceCulling; - public bool IsTriangleStrip; - public byte LOD; - public bool IsMotionBlur; - public bool Is32Bit; - } - - public List MeshFacesets { get; set; } = new List(); - - public Scene.VertexIndexBufferAllocator.VertexIndexBufferHandle GeomBuffer { get; set; } - - public int VertexCount { get; set; } - // This is native because using managed arrays causes a weird memory leak - public IntPtr PickingVertices = IntPtr.Zero; - - public BoundingBox Bounds { get; set; } - - public Matrix4x4 LocalTransform = Matrix4x4.Identity; - - // Use the w field in the normal as an index to a bone that has a transform - public bool UseNormalWBoneTransform { get; set; } = false; - - public int DefaultBoneIndex { get; set; } = -1; - - public FlverMaterial Material { get; set; } = null; - } - - /// - /// Low level access to the flver struct. Use only in modification mode. - /// - public FLVER0 FlverDeS = null; - public FLVER2 Flver = null; - - public FlverSubmesh[] GPUMeshes = null; - public FlverMaterial[] GPUMaterials = null; - - public BoundingBox Bounds { get; set; } - - public List Bones { get; private set; } = null; - private List FBones { get; set; } = null; - private List BoneTransforms { get; set; } = null; - - public Scene.GPUBufferAllocator.GPUBufferHandle StaticBoneBuffer { get; private set; } = null; - - private string TexturePathToVirtual(string texpath) - { - if (texpath.Contains(@"\map\")) - { - var splits = texpath.Split('\\'); - var mapid = splits[splits.Length - 3]; - return $@"map/tex/{mapid}/{Path.GetFileNameWithoutExtension(texpath)}"; - } - // Chr texture reference - else if (texpath.Contains(@"\chr\")) - { - var splits = texpath.Split('\\'); - var chrid = splits[splits.Length - 3]; - return $@"chr/{chrid}/tex/{Path.GetFileNameWithoutExtension(texpath)}"; - } - // Obj texture reference - else if (texpath.Contains(@"\obj\")) - { - var splits = texpath.Split('\\'); - var objid = splits[splits.Length - 3]; - return $@"obj/{objid}/tex/{Path.GetFileNameWithoutExtension(texpath)}"; - } - // Asset (aet) texture references - else if (texpath.Contains(@"\aet") || texpath.StartsWith("aet")) - { - var splits = texpath.Split('\\'); - var aetid = splits[splits.Length - 1].Substring(0, 6); - return $@"aet/{aetid}/{Path.GetFileNameWithoutExtension(texpath)}"; - } - // Parts texture reference - else if (texpath.Contains(@"\parts\")) - { - var splits = texpath.Split('\\'); - var partsId = splits[splits.Length - 3]; - return $@"parts/{partsId}/tex/{Path.GetFileNameWithoutExtension(texpath)}"; - } - return texpath; - } - - private void LookupTexture(FlverMaterial.TextureType textureType, FlverMaterial dest, string type, string mpath, string mtd) - { - var path = mpath; - if (mpath == "") - { - var mtdstring = Path.GetFileNameWithoutExtension(mtd); - if (MtdBank.IsMatbin) - { - if (MtdBank.Matbins.ContainsKey(mtdstring)) - { - var tex = MtdBank.Matbins[mtdstring].Samplers.Find(x => (x.Type == type)); - if (tex == null || tex.Path == "") - { - return; - } - path = tex.Path; - } - } - else - { - if (MtdBank.Mtds.ContainsKey(mtdstring)) - { - var tex = MtdBank.Mtds[mtdstring].Textures.Find(x => (x.Type == type)); - if (tex == null || !tex.Extended || tex.Path == "") - { - return; - } - path = tex.Path; - } - } - } - - if (!dest.TextureResourceFilled[(int)textureType]) - { - ResourceManager.AddResourceListener(TexturePathToVirtual(path.ToLower()), dest, - AccessLevel.AccessGPUOptimizedOnly, (int)textureType); - dest.TextureResourceFilled[(int)textureType] = true; - } - } - - private void ProcessMaterialTexture(FlverMaterial dest, string texType, string mpath, string mtd, GameType gameType, - out bool blend, out bool hasNormal2, out bool hasSpec2, out bool hasShininess2, out bool blendMask) - { - blend = false; - blendMask = false; - hasNormal2 = false; - hasSpec2 = false; - hasShininess2 = false; - - string paramNameCheck; - if (texType == null) - { - paramNameCheck = "G_DIFFUSE"; - } - else - { - paramNameCheck = texType.ToUpper(); - } - if (paramNameCheck == "G_DIFFUSETEXTURE2" || paramNameCheck == "G_DIFFUSE2" || paramNameCheck.Contains("ALBEDO_2")) - { - LookupTexture(FlverMaterial.TextureType.AlbedoTextureResource2, dest, texType, mpath, mtd); - blend = true; - } - else if (paramNameCheck == "G_DIFFUSETEXTURE" || paramNameCheck == "G_DIFFUSE" || paramNameCheck.Contains("ALBEDO")) - { - LookupTexture(FlverMaterial.TextureType.AlbedoTextureResource, dest, texType, mpath, mtd); - } - else if (paramNameCheck == "G_BUMPMAPTEXTURE2" || paramNameCheck == "G_BUMPMAP2" || paramNameCheck.Contains("NORMAL_2")) - { - LookupTexture(FlverMaterial.TextureType.NormalTextureResource2, dest, texType, mpath, mtd); - blend = true; - hasNormal2 = true; - } - else if (paramNameCheck == "G_BUMPMAPTEXTURE" || paramNameCheck == "G_BUMPMAP" || paramNameCheck.Contains("NORMAL")) - { - LookupTexture(FlverMaterial.TextureType.NormalTextureResource, dest, texType, mpath, mtd); - } - else if (paramNameCheck == "G_SPECULARTEXTURE2" || paramNameCheck == "G_SPECULAR2" || paramNameCheck.Contains("SPECULAR_2")) - { - if (gameType == GameType.DarkSoulsRemastered) - { - LookupTexture(FlverMaterial.TextureType.ShininessTextureResource2, dest, texType, mpath, mtd); - blend = true; - hasShininess2 = true; - } - else - { - LookupTexture(FlverMaterial.TextureType.SpecularTextureResource2, dest, texType, mpath, mtd); - blend = true; - hasSpec2 = true; - } - } - else if (paramNameCheck == "G_SPECULARTEXTURE" || paramNameCheck == "G_SPECULAR" || paramNameCheck.Contains("SPECULAR")) - { - if (gameType == GameType.DarkSoulsRemastered) - { - LookupTexture(FlverMaterial.TextureType.ShininessTextureResource, dest, texType, mpath, mtd); - } - else - { - LookupTexture(FlverMaterial.TextureType.SpecularTextureResource, dest, texType, mpath, mtd); - } - } - else if (paramNameCheck == "G_SHININESSTEXTURE2" || paramNameCheck == "G_SHININESS2" || paramNameCheck.Contains("SHININESS2")) - { - LookupTexture(FlverMaterial.TextureType.ShininessTextureResource2, dest, texType, mpath, mtd); - blend = true; - hasShininess2 = true; - } - else if (paramNameCheck == "G_SHININESSTEXTURE" || paramNameCheck == "G_SHININESS" || paramNameCheck.Contains("SHININESS")) - { - LookupTexture(FlverMaterial.TextureType.ShininessTextureResource, dest, texType, mpath, mtd); - } - else if (paramNameCheck.Contains("BLENDMASK")) - { - LookupTexture(FlverMaterial.TextureType.BlendmaskTextureResource, dest, texType, mpath, mtd); - blendMask = true; - } - } - - unsafe private void ProcessMaterial(IFlverMaterial mat, FlverMaterial dest, GameType type) - { - dest.MaterialName = Path.GetFileNameWithoutExtension(mat.MTD); - dest.MaterialBuffer = Scene.Renderer.MaterialBufferAllocator.Allocate((uint)sizeof(Scene.Material), sizeof(Scene.Material)); - dest.MaterialData = new Scene.Material(); - - //FLVER0 stores layouts directly in the material - if(type == GameType.DemonsSouls) - { - var desMat = (FLVER0.Material)mat; - bool foundBoneIndices = false; - bool foundBoneWeights = false; - - if(desMat.Layouts?.Count > 0) - { - foreach(var layoutType in desMat.Layouts[0]) - { - switch(layoutType.Semantic) - { - case FLVER.LayoutSemantic.Normal: - if (layoutType.Type == FLVER.LayoutType.Byte4B || layoutType.Type == FLVER.LayoutType.Byte4E) - { - dest.SetNormalWBoneTransform(); - } - break; - case FLVER.LayoutSemantic.BoneIndices: - foundBoneIndices = true; - break; - case FLVER.LayoutSemantic.BoneWeights: - foundBoneWeights = true; - break; - } - } - } - - //Transformation condition for DeS models - if(foundBoneIndices && !foundBoneWeights) - { - dest.SetHasIndexNoWeightTransform(); - } - } - else if (type == GameType.ArmoredCoreVI) - { - //TODO AC6 - } - - if (!CFG.Current.EnableTexturing) - { - dest.ShaderName = @"SimpleFlver"; - dest.LayoutType = MeshLayoutType.LayoutSky; - dest.VertexLayout = MeshLayoutUtils.GetLayoutDescription(dest.LayoutType); - dest.VertexSize = MeshLayoutUtils.GetLayoutVertexSize(dest.LayoutType); - dest.SpecializationConstants = new List(); - return; - } - - bool blend = false; - bool blendMask = false; - bool hasNormal2 = false; - bool hasSpec2 = false; - bool hasShininess2 = false; - - foreach (var matparam in mat.Textures) - { - ProcessMaterialTexture(dest, matparam.Type, matparam.Path, mat.MTD, type, - out blend, out hasNormal2, out hasSpec2, out hasShininess2, out blendMask); - } - - if (blendMask) - { - dest.ShaderName = @"FlverShader\FlverShader_blendmask"; - dest.LayoutType = MeshLayoutType.LayoutUV2; - } - else if (blend) - { - dest.ShaderName = @"FlverShader\FlverShader_blend"; - dest.LayoutType = MeshLayoutType.LayoutUV2; - } - else - { - dest.ShaderName = @"FlverShader\FlverShader"; - dest.LayoutType = MeshLayoutType.LayoutStandard; - } - - List specConstants = new List(); - specConstants.Add(new SpecializationConstant(0, (uint)type)); - if (blend || blendMask) - { - specConstants.Add(new SpecializationConstant(1, hasNormal2)); - specConstants.Add(new SpecializationConstant(2, hasSpec2)); - specConstants.Add(new SpecializationConstant(3, hasShininess2)); - } - - dest.SpecializationConstants = specConstants; - dest.VertexLayout = MeshLayoutUtils.GetLayoutDescription(dest.LayoutType); - dest.VertexSize = MeshLayoutUtils.GetLayoutVertexSize(dest.LayoutType); - - dest.UpdateMaterial(); - } - - unsafe private void ProcessMaterial(FlverMaterial dest, GameType type, BinaryReaderEx br, ref FlverMaterialDef mat, Span textures, bool isUTF) - { - string mtd = isUTF ? br.GetUTF16(mat.mtdOffset) : br.GetShiftJIS(mat.mtdOffset); - dest.MaterialName = Path.GetFileNameWithoutExtension(mtd); - dest.MaterialBuffer = Scene.Renderer.MaterialBufferAllocator.Allocate((uint)sizeof(Scene.Material), sizeof(Scene.Material)); - dest.MaterialData = new Scene.Material(); - - if (!CFG.Current.EnableTexturing) - { - dest.ShaderName = @"SimpleFlver"; - dest.LayoutType = MeshLayoutType.LayoutSky; - dest.VertexLayout = MeshLayoutUtils.GetLayoutDescription(dest.LayoutType); - dest.VertexSize = MeshLayoutUtils.GetLayoutVertexSize(dest.LayoutType); - dest.SpecializationConstants = new List(); - return; - } - - bool blend = false; - bool blendMask = false; - bool hasNormal2 = false; - bool hasSpec2 = false; - bool hasShininess2 = false; - - for (int i = mat.textureIndex; i < mat.textureIndex + mat.textureCount; i++) - { - string ttype = isUTF ? br.GetUTF16(textures[i].typeOffset) : br.GetShiftJIS(textures[i].typeOffset); - string tpath = isUTF ? br.GetUTF16(textures[i].pathOffset) : br.GetShiftJIS(textures[i].pathOffset); - ProcessMaterialTexture(dest, ttype, tpath, mtd, type, - out blend, out hasNormal2, out hasSpec2, out hasShininess2, out blendMask); - } - - if (blendMask) - { - dest.ShaderName = @"FlverShader\FlverShader_blendmask"; - dest.LayoutType = MeshLayoutType.LayoutUV2; - } - else if (blend) - { - dest.ShaderName = @"FlverShader\FlverShader_blend"; - dest.LayoutType = MeshLayoutType.LayoutUV2; - } - else - { - dest.ShaderName = @"FlverShader\FlverShader"; - dest.LayoutType = MeshLayoutType.LayoutStandard; - } - - List specConstants = new List(); - specConstants.Add(new SpecializationConstant(0, (uint)type)); - if (blend || blendMask) - { - specConstants.Add(new SpecializationConstant(1, hasNormal2)); - specConstants.Add(new SpecializationConstant(2, hasSpec2)); - specConstants.Add(new SpecializationConstant(3, hasShininess2)); - } - - dest.SpecializationConstants = specConstants; - dest.VertexLayout = MeshLayoutUtils.GetLayoutDescription(dest.LayoutType); - dest.VertexSize = MeshLayoutUtils.GetLayoutVertexSize(dest.LayoutType); - - dest.UpdateMaterial(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void FillVertex(ref Vector3 dest, ref FLVER.Vertex v) - { - dest = v.Position; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void FillVertex(Vector3 *dest, BinaryReaderEx br, FLVER.LayoutType type) - { - if (type == FLVER.LayoutType.Float3) - { - *dest = br.ReadVector3(); - } - else if (type == FLVER.LayoutType.Float4) - { - *dest = br.ReadVector3(); - br.AssertSingle(0); - } - else - { - throw new NotImplementedException($"Read not implemented for {type} vertex."); - } - - // Sanity check position to find bugs - //if (dest.X > 10000.0f || dest.Y > 10000.0f || dest.Z > 10000.0f) - //{ - // Debugger.Break(); - //} - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void FillNormalSNorm8(sbyte *dest, ref FLVER.Vertex v) - { - var n = Vector3.Normalize(new Vector3(v.Normal.X, v.Normal.Y, v.Normal.Z)); - dest[0] = (sbyte)(n.X * 127.0f); - dest[1] = (sbyte)(n.Y * 127.0f); - dest[2] = (sbyte)(n.Z * 127.0f); - dest[3] = (sbyte)v.NormalW; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void FillNormalSNorm8(sbyte* dest, BinaryReaderEx br, FLVER.LayoutType type, Vector3 *n) - { - int nw = 0; - if (type == FLVER.LayoutType.Float3) - { - *n = br.ReadVector3(); - } - else if (type == FLVER.LayoutType.Float4) - { - *n = br.ReadVector3(); - float w = br.ReadSingle(); - nw = (int)w; - if (w != nw) - throw new InvalidDataException($"Float4 Normal W was not a whole number: {w}"); - } - else if (type == FLVER.LayoutType.Byte4A) - { - *n = FLVER.Vertex.ReadByteNormXYZ(br); - nw = br.ReadByte(); - } - else if (type == FLVER.LayoutType.Byte4B) - { - *n = FLVER.Vertex.ReadByteNormXYZ(br); - nw = br.ReadByte(); - } - else if (type == FLVER.LayoutType.Short2toFloat2) - { - nw = br.ReadByte(); - *n = FLVER.Vertex.ReadSByteNormZYX(br); - } - else if (type == FLVER.LayoutType.Byte4C) - { - *n = FLVER.Vertex.ReadByteNormXYZ(br); - nw = br.ReadByte(); - } - else if (type == FLVER.LayoutType.Short4toFloat4A) - { - *n = FLVER.Vertex.ReadShortNormXYZ(br); - nw = br.ReadInt16(); - } - else if (type == FLVER.LayoutType.Short4toFloat4B) - { - //Normal = ReadUShortNormXYZ(br); - *n = FLVER.Vertex.ReadFloat16NormXYZ(br); - nw = br.ReadInt16(); - } - else if (type == FLVER.LayoutType.Byte4E) - { - *n = FLVER.Vertex.ReadByteNormXYZ(br); - nw = br.ReadByte(); - } - else - throw new NotImplementedException($"Read not implemented for {type} normal."); - - dest[0] = (sbyte)(n->X * 127.0f); - dest[1] = (sbyte)(n->Y * 127.0f); - dest[2] = (sbyte)(n->Z * 127.0f); - dest[3] = (sbyte)nw; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void FillUVShort(short* dest, ref FLVER.Vertex v, byte index) - { - var uv = v.GetUV(index); - dest[0] = (short)(uv.X * 2048.0f); - dest[1] = (short)(uv.Y * 2048.0f); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void FillUVShort(short* dest, BinaryReaderEx br, FLVER.LayoutType type, float uvFactor, bool allowv2, out bool hasv2) - { - Vector3 v; - Vector3 v2; - hasv2 = false; - if (type == FLVER.LayoutType.Float2) - { - v = new Vector3(br.ReadVector2(), 0); - } - else if (type == FLVER.LayoutType.Float3) - { - v = br.ReadVector3(); - } - else if (type == FLVER.LayoutType.Float4) - { - v = new Vector3(br.ReadVector2(), 0); - v2 = new Vector3(br.ReadVector2(), 0); - hasv2 = allowv2; - } - else if (type == FLVER.LayoutType.Byte4A) - { - v = new Vector3(br.ReadInt16(), br.ReadInt16(), 0) / uvFactor; - } - else if (type == FLVER.LayoutType.Byte4B) - { - v = new Vector3(br.ReadInt16(), br.ReadInt16(), 0) / uvFactor; - } - else if (type == FLVER.LayoutType.Short2toFloat2) - { - v = new Vector3(br.ReadInt16(), br.ReadInt16(), 0) / uvFactor; - } - else if (type == FLVER.LayoutType.Byte4C) - { - v = new Vector3(br.ReadInt16(), br.ReadInt16(), 0) / uvFactor; - } - else if (type == FLVER.LayoutType.UV) - { - v = new Vector3(br.ReadInt16(), br.ReadInt16(), 0) / uvFactor; - } - else if (type == FLVER.LayoutType.UVPair) - { - v = new Vector3(br.ReadInt16(), br.ReadInt16(), 0) / uvFactor; - v2 = new Vector3(br.ReadInt16(), br.ReadInt16(), 0) / uvFactor; - hasv2 = allowv2; - } - else if (type == FLVER.LayoutType.Short4toFloat4B) - { - //AddUV(new Vector3(br.ReadInt16(), br.ReadInt16(), br.ReadInt16()) / uvFactor); - v = FLVER.Vertex.ReadFloat16NormXYZ(br); - br.AssertInt16(0); - } - else - { - throw new NotImplementedException($"Read not implemented for {type} UV."); - } - - dest[0] = (short)(v.X * 2048.0f); - dest[1] = (short)(v.Y * 2048.0f); - if (hasv2) - { - dest[3] = (short)(v.X * 2048.0f); - dest[4] = (short)(v.Y * 2048.0f); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void FillUVShortZero(short* dest) - { - dest[0] = 0; - dest[1] = 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void FillUVFloat(ref Vector2 dest, ref FLVER.Vertex v, byte index) - { - var uv = v.GetUV(index); - dest.X = uv.X; - dest.Y = uv.Y; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void FillBinormalBitangentSNorm8(sbyte* destBinorm, sbyte* destBitan, ref FLVER.Vertex v, byte index) - { - var tan = v.GetTangent(index); - var t = Vector3.Normalize(new Vector3(tan.X, tan.Y, tan.Z)); - destBitan[0] = (sbyte)(t.X * 127.0f); - destBitan[1] = (sbyte)(t.Y * 127.0f); - destBitan[2] = (sbyte)(t.Z * 127.0f); - destBitan[3] = (sbyte)(tan.W * 127.0f); - - var bn = Vector3.Cross(Vector3.Normalize(v.Normal), Vector3.Normalize(new Vector3(t.X, t.Y, t.Z))) * tan.W; - destBinorm[0] = (sbyte)(bn.X * 127.0f); - destBinorm[1] = (sbyte)(bn.Y * 127.0f); - destBinorm[2] = (sbyte)(bn.Z * 127.0f); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void FillBinormalBitangentSNorm8(sbyte* destBinorm, sbyte* destBitan, Vector3* n, BinaryReaderEx br, FLVER.LayoutType type) - { - Vector4 tan; - if (type == FLVER.LayoutType.Float4) - { - tan = br.ReadVector4(); - } - else if (type == FLVER.LayoutType.Byte4A) - { - tan = FLVER.Vertex.ReadByteNormXYZW(br); - } - else if (type == FLVER.LayoutType.Byte4B) - { - tan = FLVER.Vertex.ReadByteNormXYZW(br); - } - else if (type == FLVER.LayoutType.Byte4C) - { - tan = FLVER.Vertex.ReadByteNormXYZW(br); - } - else if (type == FLVER.LayoutType.Short4toFloat4A) - { - tan = FLVER.Vertex.ReadByteNormXYZW(br); - } - else if (type == FLVER.LayoutType.Byte4E) - { - tan = FLVER.Vertex.ReadByteNormXYZW(br); - } - else - { - throw new NotImplementedException($"Read not implemented for {type} tangent."); - } - - var t = Vector3.Normalize(new Vector3(tan.X, tan.Y, tan.Z)); - destBitan[0] = (sbyte)(t.X * 127.0f); - destBitan[1] = (sbyte)(t.Y * 127.0f); - destBitan[2] = (sbyte)(t.Z * 127.0f); - destBitan[3] = (sbyte)(tan.W * 127.0f); - - var bn = Vector3.Cross(Vector3.Normalize(*n), Vector3.Normalize(new Vector3(t.X, t.Y, t.Z))) * tan.W; - destBinorm[0] = (sbyte)(bn.X * 127.0f); - destBinorm[1] = (sbyte)(bn.Y * 127.0f); - destBinorm[2] = (sbyte)(bn.Z * 127.0f); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void FillBinormalBitangentSNorm8Zero(sbyte* destBinorm, sbyte* destBitan) - { - destBitan[0] = 0; - destBitan[1] = 0; - destBitan[2] = 0; - destBitan[3] = 127; - - destBinorm[0] = 0; - destBinorm[1] = 0; - destBinorm[2] = 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void FillColorUNorm(byte* dest, ref FLVER.Vertex v) - { - - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - unsafe private void EatVertex(BinaryReaderEx br, FLVER.LayoutType type) - { - switch (type) - { - case FLVER.LayoutType.Byte4A: - case FLVER.LayoutType.Byte4B: - case FLVER.LayoutType.Short2toFloat2: - case FLVER.LayoutType.Byte4C: - case FLVER.LayoutType.UV: - case FLVER.LayoutType.Byte4E: - case FLVER.LayoutType.Unknown: - br.ReadUInt32(); - break; - - case FLVER.LayoutType.Float2: - case FLVER.LayoutType.UVPair: - case FLVER.LayoutType.ShortBoneIndices: - case FLVER.LayoutType.Short4toFloat4A: - case FLVER.LayoutType.Short4toFloat4B: - br.ReadUInt64(); - break; - - case FLVER.LayoutType.Float3: - br.ReadUInt32(); - br.ReadUInt64(); - break; - - case FLVER.LayoutType.Float4: - br.ReadUInt64(); - br.ReadUInt64(); - break; - - default: - throw new NotImplementedException($"No size defined for buffer layout type: {type}"); - } - } - - unsafe private void FillVerticesNormalOnly(BinaryReaderEx br, ref FlverVertexBuffer buffer, Span layouts, Span pickingVerts, IntPtr vertBuffer) - { - Span verts = new Span(vertBuffer.ToPointer(), buffer.vertexCount); - br.StepIn(buffer.bufferOffset); - for (int i = 0; i < buffer.vertexCount; i++) - { - Vector3 n = Vector3.Zero; - fixed (FlverLayoutSky* v = &verts[i]) - { - bool posfilled = false; - foreach (var l in layouts) - { - // ER meme - if (l.unk00 == -2147483647) - continue; - if (l.semantic == FLVER.LayoutSemantic.Position) - { - FillVertex(&(*v).Position, br, l.type); - posfilled = true; - } - else if (l.semantic == FLVER.LayoutSemantic.Normal) - { - FillNormalSNorm8((*v).Normal, br, l.type, &n); - } - else - { - EatVertex(br, l.type); - } - } - if (!posfilled) - { - (*v).Position = new Vector3(0, 0, 0); - } - pickingVerts[i] = (*v).Position; - } - } - br.StepOut(); - } - - unsafe private void FillVerticesNormalOnly(FLVER2.Mesh mesh, Span pickingVerts, IntPtr vertBuffer) - { - Span verts = new Span(vertBuffer.ToPointer(), mesh.VertexCount); - for (int i = 0; i < mesh.VertexCount; i++) - { - var vert = mesh.Vertices[i]; - - verts[i] = new FlverLayoutSky(); - pickingVerts[i] = new Vector3(vert.Position.X, vert.Position.Y, vert.Position.Z); - fixed (FlverLayoutSky* v = &verts[i]) - { - FillVertex(ref (*v).Position, ref vert); - FillNormalSNorm8((*v).Normal, ref vert); - } - } - } - - unsafe private void FillVerticesNormalOnly(FLVER0.Mesh mesh, Span pickingVerts, IntPtr vertBuffer) - { - Span verts = new Span(vertBuffer.ToPointer(), mesh.Vertices.Count); - for (int i = 0; i < mesh.Vertices.Count; i++) - { - var vert = mesh.Vertices[i]; - - verts[i] = new FlverLayoutSky(); - pickingVerts[i] = new Vector3(vert.Position.X, vert.Position.Y, vert.Position.Z); - fixed (FlverLayoutSky* v = &verts[i]) - { - FillVertex(ref (*v).Position, ref vert); - FillNormalSNorm8((*v).Normal, ref vert); - } - } - } - - private unsafe void FillVerticesStandard(BinaryReaderEx br, ref FlverVertexBuffer buffer, - Span layouts, Span pickingVerts, IntPtr vertBuffer, float uvFactor) - { - br.StepIn(buffer.bufferOffset); - FlverLayout* pverts = (FlverLayout*)vertBuffer; - - for (int i = 0; i < buffer.vertexCount; i++) - { - FlverLayout* v = &pverts[i]; - Vector3 n = Vector3.UnitX; - FillUVShortZero((*v).Uv1); - FillBinormalBitangentSNorm8Zero((*v).Binormal, (*v).Bitangent); - bool posfilled = false; - foreach (var l in layouts) - { - // ER meme - if (l.unk00 == -2147483647) - continue; - if (l.semantic == FLVER.LayoutSemantic.Position) - { - FillVertex(&(*v).Position, br, l.type); - posfilled = true; - } - else if (l.semantic == FLVER.LayoutSemantic.Normal) - { - FillNormalSNorm8((*v).Normal, br, l.type, &n); - } - else if (l.semantic == FLVER.LayoutSemantic.UV && l.index == 0) - { - bool hasv2; - FillUVShort((*v).Uv1, br, l.type, uvFactor, false, out hasv2); - } - else if (l.semantic == FLVER.LayoutSemantic.Tangent && l.index == 0) - { - FillBinormalBitangentSNorm8((*v).Binormal, (*v).Bitangent, &n, br, l.type); - } - else - { - EatVertex(br, l.type); - } - } - - if (!posfilled) - { - (*v).Position = new Vector3(0, 0, 0); - } - - pickingVerts[i] = (*v).Position; - } - br.StepOut(); - } - - unsafe private void FillVerticesStandard(FLVER2.Mesh mesh, Span pickingVerts, IntPtr vertBuffer) - { - Span verts = new Span(vertBuffer.ToPointer(), mesh.VertexCount); - fixed (FlverLayout* pverts = verts) - { - for (int i = 0; i < mesh.VertexCount; i++) - { - FlverLayout* v = &pverts[i]; - var vert = mesh.Vertices[i]; - - verts[i] = new FlverLayout(); - pickingVerts[i] = new Vector3(vert.Position.X, vert.Position.Y, vert.Position.Z); - FillVertex(ref (*v).Position, ref vert); - FillNormalSNorm8((*v).Normal, ref vert); - if (vert.UVCount > 0) - { - FillUVShort((*v).Uv1, ref vert, 0); - } - else - { - FillUVShortZero((*v).Uv1); - } - - if (vert.TangentCount > 0) - { - FillBinormalBitangentSNorm8((*v).Binormal, (*v).Bitangent, ref vert, 0); - } - else - { - FillBinormalBitangentSNorm8Zero((*v).Binormal, (*v).Bitangent); - } - } - } - } - - unsafe private void FillVerticesStandard(FLVER0.Mesh mesh, Span pickingVerts, IntPtr vertBuffer) - { - Span verts = new Span(vertBuffer.ToPointer(), mesh.Vertices.Count); - fixed (FlverLayout* pverts = verts) - { - for (int i = 0; i < mesh.Vertices.Count; i++) - { - FlverLayout* v = &pverts[i]; - var vert = mesh.Vertices[i]; - - verts[i] = new FlverLayout(); - pickingVerts[i] = new Vector3(vert.Position.X, vert.Position.Y, vert.Position.Z); - FillVertex(ref (*v).Position, ref vert); - FillNormalSNorm8((*v).Normal, ref vert); - if (vert.UVCount > 0) - { - FillUVShort((*v).Uv1, ref vert, 0); - } - else - { - FillUVShortZero((*v).Uv1); - } - - if (vert.TangentCount > 0) - { - FillBinormalBitangentSNorm8((*v).Binormal, (*v).Bitangent, ref vert, 0); - } - else - { - FillBinormalBitangentSNorm8Zero((*v).Binormal, (*v).Bitangent); - } - } - } - } - - private unsafe void FillVerticesUV2(BinaryReaderEx br, ref FlverVertexBuffer buffer, Span layouts, Span pickingVerts, IntPtr vertBuffer, float uvFactor) - { - Span verts = new Span(vertBuffer.ToPointer(), buffer.vertexCount); - br.StepIn(buffer.bufferOffset); - fixed (FlverLayoutUV2* pverts = verts) - { - for (int i = 0; i < buffer.vertexCount; i++) - { - FlverLayoutUV2* v = &pverts[i]; - Vector3 n = Vector3.UnitX; - FillBinormalBitangentSNorm8Zero((*v).Binormal, (*v).Bitangent); - int uvsfilled = 0; - foreach (var l in layouts) - { - // ER meme - if (l.unk00 == -2147483647) - continue; - if (l.semantic == FLVER.LayoutSemantic.Position) - { - FillVertex(&(*v).Position, br, l.type); - } - else if (l.semantic == FLVER.LayoutSemantic.Normal) - { - FillNormalSNorm8((*v).Normal, br, l.type, &n); - } - else if (l.semantic == FLVER.LayoutSemantic.UV && uvsfilled < 2) - { - bool hasv2; - FillUVShort(uvsfilled > 0 ? (*v).Uv2 : (*v).Uv1, br, l.type, uvFactor, false, out hasv2); - uvsfilled += (hasv2 ? 2 : 1); - } - else if (l.semantic == FLVER.LayoutSemantic.Tangent && l.index == 0) - { - FillBinormalBitangentSNorm8((*v).Binormal, (*v).Bitangent, &n, br, l.type); - } - else - { - EatVertex(br, l.type); - } - } - - pickingVerts[i] = (*v).Position; - } - } - - br.StepOut(); - } - - unsafe private void FillVerticesUV2(FLVER2.Mesh mesh, Span pickingVerts, IntPtr vertBuffer) - { - Span verts = new Span(vertBuffer.ToPointer(), mesh.VertexCount); - fixed (FlverLayoutUV2* pverts = verts) - { - for (int i = 0; i < mesh.VertexCount; i++) - { - var vert = mesh.Vertices[i]; - - verts[i] = new FlverLayoutUV2(); - pickingVerts[i] = new Vector3(vert.Position.X, vert.Position.Y, vert.Position.Z); - FlverLayoutUV2* v = &pverts[i]; - FillVertex(ref (*v).Position, ref vert); - FillNormalSNorm8((*v).Normal, ref vert); - FillUVShort((*v).Uv1, ref vert, 0); - FillUVShort((*v).Uv2, ref vert, 1); - if (vert.TangentCount > 0) - { - FillBinormalBitangentSNorm8((*v).Binormal, (*v).Bitangent, ref vert, 0); - } - else - { - FillBinormalBitangentSNorm8Zero((*v).Binormal, (*v).Bitangent); - } - } - } - } - - unsafe private void FillVerticesUV2(FLVER0.Mesh mesh, Span pickingVerts, IntPtr vertBuffer) - { - Span verts = new Span(vertBuffer.ToPointer(), mesh.Vertices.Count); - fixed (FlverLayoutUV2* pverts = verts) - { - for (int i = 0; i < mesh.Vertices.Count; i++) - { - var vert = mesh.Vertices[i]; - - verts[i] = new FlverLayoutUV2(); - pickingVerts[i] = new Vector3(vert.Position.X, vert.Position.Y, vert.Position.Z); - FlverLayoutUV2* v = &pverts[i]; - FillVertex(ref (*v).Position, ref vert); - FillNormalSNorm8((*v).Normal, ref vert); - FillUVShort((*v).Uv1, ref vert, 0); - FillUVShort((*v).Uv2, ref vert, 1); - if (vert.TangentCount > 0) - { - FillBinormalBitangentSNorm8((*v).Binormal, (*v).Bitangent, ref vert, 0); - } - else - { - FillBinormalBitangentSNorm8Zero((*v).Binormal, (*v).Bitangent); - } - } - } - } - - unsafe private void ProcessMesh(FLVER0.Mesh mesh, FlverSubmesh dest) - { - var factory = Scene.Renderer.Factory; - - dest.Material = GPUMaterials[mesh.MaterialIndex]; - - if (dest.Material.GetHasIndexNoWeightTransform()) - { - //Transform based on root - for (int v = 0; v < mesh.Vertices.Count; v++) - { - var vert = mesh.Vertices[v]; - var boneTransformationIndex = mesh.BoneIndices[vert.BoneIndices[0]]; - if(boneTransformationIndex > -1 && BoneTransforms.Count > boneTransformationIndex) - { - var boneTfm = BoneTransforms[boneTransformationIndex]; - - vert.Position = Vector3.Transform(vert.Position, boneTfm); - vert.Normal = Vector3.TransformNormal(vert.Normal, boneTfm); - mesh.Vertices[v] = vert; - } - } - } - - var vSize = dest.Material.VertexSize; - dest.PickingVertices = Marshal.AllocHGlobal(mesh.Vertices.Count * sizeof(Vector3)); - var pvhandle = new Span(dest.PickingVertices.ToPointer(), mesh.Vertices.Count); - uint vbuffersize = (uint)mesh.Vertices.Count * (uint)vSize; - - dest.VertexCount = mesh.Vertices.Count; - - dest.MeshFacesets = new List(); - - bool is32bit = false;//FlverDeS.Version > 0x20005 && mesh.Vertices.Count > 65535; - Span fs16 = null; - Span fs32 = null; - - var indices = mesh.Triangulate(FlverDeS.Header.Version).ToArray(); - int indicesTotal = indices.Length; - - dest.GeomBuffer = Scene.Renderer.GeometryBufferAllocator.Allocate(vbuffersize, - (uint)indicesTotal * (is32bit ? 4u : 2u), (int)vSize, 4); - var meshVertices = dest.GeomBuffer.MapVBuffer(); - var meshIndices = dest.GeomBuffer.MapIBuffer(); - - if (dest.Material.LayoutType == MeshLayoutType.LayoutSky) - { - FillVerticesNormalOnly(mesh, pvhandle, meshVertices); - } - else if (dest.Material.LayoutType == MeshLayoutType.LayoutUV2) - { - FillVerticesUV2(mesh, pvhandle, meshVertices); - } - else - { - FillVerticesStandard(mesh, pvhandle, meshVertices); - } - - if (mesh.VertexIndices.Count != 0) - { - if (is32bit) - { - fs32 = new Span(meshIndices.ToPointer(), indicesTotal); - } - else - { - fs16 = new Span(meshIndices.ToPointer(), indicesTotal); - } - - var newFaceSet = new FlverSubmesh.FlverSubmeshFaceSet() - { - BackfaceCulling = true, - IsTriangleStrip = false, - //IndexBuffer = factory.CreateBuffer(new BufferDescription(buffersize, BufferUsage.IndexBuffer)), - IndexOffset = 0, - - IndexCount = indices.Length, - Is32Bit = is32bit, - PickingIndicesCount = indices.Length, - //PickingIndices = Marshal.AllocHGlobal(indices.Length * 4), - }; - - if (is32bit) - { - for (int i = 0; i < indices.Length; i++) - { - if (indices[i] == 0xFFFF && indices[i] > mesh.Vertices.Count) - { - fs32[newFaceSet.IndexOffset + i] = -1; - } - else - { - fs32[newFaceSet.IndexOffset + i] = indices[i]; - } - } - } - else - { - for (int i = 0; i < indices.Length; i++) - { - if (indices[i] == 0xFFFF && indices[i] > mesh.Vertices.Count) - { - fs16[newFaceSet.IndexOffset + i] = 0xFFFF; - } - else - { - fs16[newFaceSet.IndexOffset + i] = (ushort)indices[i]; - } - } - } - - dest.MeshFacesets.Add(newFaceSet); - } - - dest.GeomBuffer.UnmapVBuffer(); - dest.GeomBuffer.UnmapIBuffer(); - - dest.Bounds = BoundingBox.CreateFromPoints((Vector3*)dest.PickingVertices.ToPointer(), dest.VertexCount, 12, Quaternion.Identity, Vector3.Zero, Vector3.One); - if (CaptureMaterialLayouts) - { - lock (_matLayoutLock) - { - if (!MaterialLayouts.ContainsKey(dest.Material.MaterialName)) - { - MaterialLayouts.Add(dest.Material.MaterialName, Flver.BufferLayouts[mesh.LayoutIndex]); - } - } - } - } - - unsafe private void ProcessMesh(FLVER2.Mesh mesh, FlverSubmesh dest) - { - dest.Material = GPUMaterials[mesh.MaterialIndex]; - - var vSize = dest.Material.VertexSize; - dest.PickingVertices = Marshal.AllocHGlobal(mesh.VertexCount * sizeof(Vector3)); - var pvhandle = new Span(dest.PickingVertices.ToPointer(), mesh.VertexCount); - - dest.VertexCount = mesh.VertexCount; - - dest.MeshFacesets = new List(); - var facesets = mesh.FaceSets; - - bool is32bit = Flver.Header.Version > 0x20005 && mesh.VertexCount > 65535; - int indicesTotal = 0; - Span fs16 = null; - Span fs32 = null; - foreach (var faceset in facesets) - { - indicesTotal += faceset.Indices.Length; - } - - uint vbuffersize = (uint)mesh.VertexCount * (uint)vSize; - dest.GeomBuffer = Scene.Renderer.GeometryBufferAllocator.Allocate(vbuffersize, - (uint)indicesTotal * (is32bit ? 4u : 2u), (int)vSize, 4); - var meshVertices = dest.GeomBuffer.MapVBuffer(); - var meshIndices = dest.GeomBuffer.MapIBuffer(); - - if (dest.Material.LayoutType == MeshLayoutType.LayoutSky) - { - FillVerticesNormalOnly(mesh, pvhandle, meshVertices); - } - else if (dest.Material.LayoutType == MeshLayoutType.LayoutUV2) - { - FillVerticesUV2(mesh, pvhandle, meshVertices); - } - else - { - FillVerticesStandard(mesh, pvhandle, meshVertices); - } - - if (is32bit) - { - fs32 = new Span(meshIndices.ToPointer(), indicesTotal); - } - else - { - fs16 = new Span(meshIndices.ToPointer(), indicesTotal); - } - - int idxoffset = 0; - foreach (var faceset in facesets) - { - if (faceset.Indices.Length == 0) - continue; - - //At this point they use 32-bit faceset vertex indices - var newFaceSet = new FlverSubmesh.FlverSubmeshFaceSet() - { - BackfaceCulling = faceset.CullBackfaces, - IsTriangleStrip = faceset.TriangleStrip, - IndexOffset = idxoffset, - - IndexCount = faceset.IndicesCount, - Is32Bit = is32bit - }; - - - if ((faceset.Flags & FLVER2.FaceSet.FSFlags.LodLevel1) > 0) - { - newFaceSet.LOD = 1; - newFaceSet.IsMotionBlur = false; - } - else if ((faceset.Flags & FLVER2.FaceSet.FSFlags.LodLevel2) > 0) - { - newFaceSet.LOD = 2; - newFaceSet.IsMotionBlur = false; - } - - if ((faceset.Flags & FLVER2.FaceSet.FSFlags.MotionBlur) > 0) - { - newFaceSet.IsMotionBlur = true; - } - - if (is32bit) - { - for (int i = 0; i < faceset.Indices.Length; i++) - { - if (faceset.Indices[i] == 0xFFFF && faceset.Indices[i] > mesh.Vertices.Length) - { - fs32[newFaceSet.IndexOffset + i] = -1; - } - else - { - fs32[newFaceSet.IndexOffset + i] = faceset.Indices[i]; - } - } - } - else - { - for (int i = 0; i < faceset.Indices.Length; i++) - { - if (faceset.Indices[i] == 0xFFFF && faceset.Indices[i] > mesh.Vertices.Length) - { - fs16[newFaceSet.IndexOffset + i] = 0xFFFF; - } - else - { - fs16[newFaceSet.IndexOffset + i] = (ushort)faceset.Indices[i]; - } - } - } - - dest.MeshFacesets.Add(newFaceSet); - idxoffset += faceset.Indices.Length; - } - - dest.GeomBuffer.UnmapVBuffer(); - dest.GeomBuffer.UnmapIBuffer(); - - dest.Bounds = BoundingBox.CreateFromPoints((Vector3*)dest.PickingVertices.ToPointer(), dest.VertexCount, 12, Quaternion.Identity, Vector3.Zero, Vector3.One); - - if (CaptureMaterialLayouts) - { - lock (_matLayoutLock) - { - if (!MaterialLayouts.ContainsKey(dest.Material.MaterialName)) - { - MaterialLayouts.Add(dest.Material.MaterialName, Flver.BufferLayouts[mesh.VertexBuffers[0].LayoutIndex]); - } - } - } - - if (mesh.Dynamic == 0) - { - var elements = mesh.VertexBuffers.SelectMany(b => Flver.BufferLayouts[b.LayoutIndex]); - dest.UseNormalWBoneTransform = elements.Any(e => e.Semantic == FLVER.LayoutSemantic.Normal && (e.Type == FLVER.LayoutType.Byte4B || e.Type == FLVER.LayoutType.Byte4E)); - if (dest.UseNormalWBoneTransform) - { - dest.Material.SetNormalWBoneTransform(); - } - else if (mesh.DefaultBoneIndex != -1 && mesh.DefaultBoneIndex < Bones.Count) - { - dest.LocalTransform = Utils.GetBoneObjectMatrix(Bones[mesh.DefaultBoneIndex], Bones); - } - } - - Marshal.FreeHGlobal(dest.PickingVertices); - } - - private static Matrix4x4 GetBoneObjectMatrix(FlverBone bone, List bones) - { - var res = Matrix4x4.Identity; - FlverBone? parentBone = bone; - do - { - res *= parentBone.Value.ComputeLocalTransform(); - if (parentBone?.parentIndex >= 0) - { - parentBone = bones[(int)parentBone?.parentIndex]; - } - else - { - parentBone = null; - } - } - while (parentBone != null); - - return res; - } - - unsafe private void ProcessMesh(ref FlverMesh mesh, BinaryReaderEx br, int version, - Span buffers, Span layouts, - Span facesets, FlverSubmesh dest) - { - dest.Material = GPUMaterials[mesh.materialIndex]; - - Span facesetIndices = stackalloc int[mesh.facesetCount]; - br.StepIn(mesh.facesetIndicesOffset); - for (int i = 0; i < mesh.facesetCount; i++) - { - facesetIndices[i] = br.ReadInt32(); - } - br.StepOut(); - - Span vertexBufferIndices = stackalloc int[mesh.vertexBufferCount]; - br.StepIn(mesh.vertexBufferIndicesOffset); - for (int i = 0; i < mesh.vertexBufferCount; i++) - { - vertexBufferIndices[i] = br.ReadInt32(); - } - br.StepOut(); - int vertexCount = mesh.vertexBufferCount > 0 ? buffers[vertexBufferIndices[0]].vertexCount : 0; - - var vSize = dest.Material.VertexSize; - dest.PickingVertices = Marshal.AllocHGlobal(vertexCount * sizeof(Vector3)); - var pvhandle = new Span(dest.PickingVertices.ToPointer(), vertexCount); - - bool is32bit = version > 0x20005 && vertexCount > 65535; - int indicesTotal = 0; - foreach (var fsidx in facesetIndices) - { - indicesTotal += facesets[fsidx].indexCount; - is32bit = is32bit || facesets[fsidx].indexSize != 16; - } - - uint vbuffersize = (uint)vertexCount * (uint)vSize; - dest.GeomBuffer = Renderer.GeometryBufferAllocator.Allocate(vbuffersize, - (uint)indicesTotal * (is32bit ? 4u : 2u), (int)vSize, 4); - var meshVertices = dest.GeomBuffer.MapVBuffer(); - var meshIndices = dest.GeomBuffer.MapIBuffer(); - - foreach (var vbi in vertexBufferIndices) - { - var vb = buffers[vbi]; - var layout = layouts[vb.layoutIndex]; - Span layoutmembers = stackalloc FlverBufferLayoutMember[layout.memberCount]; - br.StepIn(layout.membersOffset); - for (int i = 0; i < layout.memberCount; i++) - { - layoutmembers[i] = new FlverBufferLayoutMember(br); - if (layoutmembers[i].semantic == FLVER.LayoutSemantic.Normal && (layoutmembers[i].type == FLVER.LayoutType.Byte4B || layoutmembers[i].type == FLVER.LayoutType.Byte4E)) - dest.UseNormalWBoneTransform = true; - } - br.StepOut(); - if (dest.Material.LayoutType == MeshLayoutType.LayoutSky) - { - FillVerticesNormalOnly(br, ref vb, layoutmembers, pvhandle, meshVertices); - } - else if (dest.Material.LayoutType == MeshLayoutType.LayoutUV2) - { - FillVerticesUV2(br, ref vb, layoutmembers, pvhandle, meshVertices, version >= 0x2000F ? 2048 : 1024); - } - else - { - FillVerticesStandard(br, ref vb, layoutmembers, pvhandle, meshVertices, version >= 0x2000F ? 2048 : 1024); - } - } - - dest.VertexCount = vertexCount; - dest.MeshFacesets = new List(); - - Span fs16 = null; - Span fs32 = null; - if (is32bit) - { - fs32 = new Span(meshIndices.ToPointer(), indicesTotal); - } - else - { - fs16 = new Span(meshIndices.ToPointer(), indicesTotal); - } - - int idxoffset = 0; - foreach (var fsidx in facesetIndices) - { - var faceset = facesets[fsidx]; - if (faceset.indexCount == 0) - continue; - - //At this point they use 32-bit faceset vertex indices - var newFaceSet = new FlverSubmesh.FlverSubmeshFaceSet() - { - BackfaceCulling = faceset.cullBackfaces, - IsTriangleStrip = faceset.triangleStrip, - IndexOffset = idxoffset, - - IndexCount = faceset.indexCount, - Is32Bit = is32bit, - PickingIndicesCount = 0, - }; - - - if ((faceset.flags & FLVER2.FaceSet.FSFlags.LodLevel1) > 0) - { - newFaceSet.LOD = 1; - newFaceSet.IsMotionBlur = false; - } - else if ((faceset.flags & FLVER2.FaceSet.FSFlags.LodLevel2) > 0) - { - newFaceSet.LOD = 2; - newFaceSet.IsMotionBlur = false; - } - - if ((faceset.flags & FLVER2.FaceSet.FSFlags.MotionBlur) > 0) - { - newFaceSet.IsMotionBlur = true; - } - - br.StepIn(faceset.indicesOffset); - for (int i = 0; i < faceset.indexCount; i++) - { - if (faceset.indexSize == 16) - { - var idx = br.ReadUInt16(); - if (is32bit) - { - fs32[newFaceSet.IndexOffset + i] = (idx == 0xFFFF ? -1 : idx); - } - else - { - fs16[newFaceSet.IndexOffset + i] = idx; - } - } - else - { - var idx = br.ReadInt32(); - if (idx > vertexCount) - { - fs32[newFaceSet.IndexOffset + i] = -1; - } - else - { - fs32[newFaceSet.IndexOffset + i] = idx; - } - } - } - br.StepOut(); - - dest.MeshFacesets.Add(newFaceSet); - idxoffset += faceset.indexCount; - } - - dest.GeomBuffer.UnmapIBuffer(); - dest.GeomBuffer.UnmapVBuffer(); - - dest.Bounds = BoundingBox.CreateFromPoints((Vector3*)dest.PickingVertices.ToPointer(), dest.VertexCount, 12, Quaternion.Identity, Vector3.Zero, Vector3.One); - - /*if (CaptureMaterialLayouts) - { - lock (_matLayoutLock) - { - if (!MaterialLayouts.ContainsKey(dest.Material.MaterialName)) - { - MaterialLayouts.Add(dest.Material.MaterialName, Flver.BufferLayouts[mesh.VertexBuffers[0].LayoutIndex]); - } - } - }*/ - - if (mesh.dynamic == 0) - { - if (dest.UseNormalWBoneTransform) - { - dest.Material.SetNormalWBoneTransform(); - } - else if (mesh.defaultBoneIndex != -1 && mesh.defaultBoneIndex < FBones.Count) - { - dest.LocalTransform = GetBoneObjectMatrix(FBones[mesh.defaultBoneIndex], FBones); - } - } - - Marshal.FreeHGlobal(dest.PickingVertices); - } - - private bool LoadInternalDeS(AccessLevel al, GameType type) - { - if (al == AccessLevel.AccessFull || al == AccessLevel.AccessGPUOptimizedOnly) - { - GPUMeshes = new FlverSubmesh[FlverDeS.Meshes.Count()]; - GPUMaterials = new FlverMaterial[FlverDeS.Materials.Count()]; - Bounds = new BoundingBox(); - Bones = FlverDeS.Bones; - BoneTransforms = new List(); - for (int i = 0; i < Bones.Count; i++) - { - //BoneTransforms.Add(FlverDeS.ComputeBoneWorldMatrix(i)); - BoneTransforms.Add(Bones[i].ComputeLocalTransform()); - } - - for (int i = 0; i < FlverDeS.Materials.Count(); i++) - { - GPUMaterials[i] = new FlverMaterial(); - ProcessMaterial(FlverDeS.Materials[i], GPUMaterials[i], type); - } - - for (int i = 0; i < FlverDeS.Meshes.Count(); i++) - { - GPUMeshes[i] = new FlverSubmesh(); - - var flverMesh = FlverDeS.Meshes[i]; - ProcessMesh(flverMesh, GPUMeshes[i]); - if (i == 0) - { - Bounds = GPUMeshes[i].Bounds; - } - else - { - Bounds = BoundingBox.Combine(Bounds, GPUMeshes[i].Bounds); - } - } - - BoneTransforms.Clear(); - } - - if (al == AccessLevel.AccessGPUOptimizedOnly) - { - Flver = null; - } - return true; - } - - private bool LoadInternal(AccessLevel al, GameType type) - { - if (al == AccessLevel.AccessFull || al == AccessLevel.AccessGPUOptimizedOnly) - { - GPUMeshes = new FlverSubmesh[Flver.Meshes.Count()]; - GPUMaterials = new FlverMaterial[Flver.Materials.Count()]; - Bounds = new BoundingBox(); - Bones = Flver.Bones; - - for (int i = 0; i < Flver.Materials.Count(); i++) - { - GPUMaterials[i] = new FlverMaterial(); - ProcessMaterial(Flver.Materials[i], GPUMaterials[i], type); - } - - for (int i = 0; i < Flver.Meshes.Count(); i++) - { - GPUMeshes[i] = new FlverSubmesh(); - ProcessMesh(Flver.Meshes[i], GPUMeshes[i]); - if (i == 0) - { - Bounds = GPUMeshes[i].Bounds; - } - else - { - Bounds = BoundingBox.Combine(Bounds, GPUMeshes[i].Bounds); - } - } - - if (GPUMeshes.Any(e => e.UseNormalWBoneTransform)) - { - StaticBoneBuffer = Renderer.BoneBufferAllocator.Allocate(64 * (uint)Bones.Count, 64); - Matrix4x4[] tbones = new Matrix4x4[Bones.Count]; - for (int i = 0; i < Bones.Count; i++) - { - tbones[i] = Utils.GetBoneObjectMatrix(Bones[i], Bones); - } - - Renderer.AddBackgroundUploadTask((d, cl) => - { - StaticBoneBuffer.FillBuffer(cl, tbones); - }); - } - } - - if (al == AccessLevel.AccessGPUOptimizedOnly) - { - Flver = null; - } - return true; - } - - private struct FlverMaterialDef - { - public uint nameOffset; - public uint mtdOffset; - public int textureCount; - public int textureIndex; - public int flags; - public int gxOffset; - - public FlverMaterialDef(BinaryReaderEx br) - { - nameOffset = br.ReadUInt32(); - mtdOffset = br.ReadUInt32(); - textureCount = br.ReadInt32(); - textureIndex = br.ReadInt32(); - flags = br.ReadInt32(); - gxOffset = br.ReadInt32(); - br.ReadInt32(); // unknown - br.AssertInt32(0); - } - } - - private struct FlverBone - { - public Vector3 position; - public uint nameOffset; - public Vector3 rotation; - public short parentIndex; - public short childIndex; - public Vector3 scale; - public short nextSiblingIndex; - public short previousSiblingIndex; - public Vector3 boundingBoxMin; - public Vector3 boundingBoxMax; - - public Matrix4x4 ComputeLocalTransform() - { - return Matrix4x4.CreateScale(scale) - * Matrix4x4.CreateRotationX(rotation.X) - * Matrix4x4.CreateRotationZ(rotation.Z) - * Matrix4x4.CreateRotationY(rotation.Y) - * Matrix4x4.CreateTranslation(position); - } - - public FlverBone(BinaryReaderEx br) - { - position = br.ReadVector3(); - nameOffset = br.ReadUInt32(); - rotation = br.ReadVector3(); - parentIndex = br.ReadInt16(); - childIndex = br.ReadInt16(); - scale = br.ReadVector3(); - nextSiblingIndex = br.ReadInt16(); - previousSiblingIndex = br.ReadInt16(); - boundingBoxMin = br.ReadVector3(); - br.ReadInt32(); // unknown - boundingBoxMax = br.ReadVector3(); - br.Position += 0x34; - } - } - - private struct FlverMesh - { - public int dynamic; - public int materialIndex; - public int defaultBoneIndex; - public int boneCount; - public int facesetCount; - public uint facesetIndicesOffset; - public int vertexBufferCount; - public uint vertexBufferIndicesOffset; - - public FlverMesh(BinaryReaderEx br) - { - dynamic = br.AssertInt32(0, 1); - materialIndex = br.ReadInt32(); - br.AssertInt32(0); - br.AssertInt32(0); - defaultBoneIndex = br.ReadInt32(); - boneCount = br.ReadInt32(); - br.ReadInt32(); // bb offset - br.ReadInt32(); // bone offset - facesetCount = br.ReadInt32(); - facesetIndicesOffset = br.ReadUInt32(); - vertexBufferCount = br.AssertInt32(0, 1, 2, 3); - vertexBufferIndicesOffset = br.ReadUInt32(); - } - } - - private struct FlverFaceset - { - public FLVER2.FaceSet.FSFlags flags; - public bool triangleStrip; - public bool cullBackfaces; - public int indexCount; - public uint indicesOffset; - public int indexSize; - - public FlverFaceset(BinaryReaderEx br, int version, int headerIndexSize, uint dataOffset) - { - flags = (FLVER2.FaceSet.FSFlags)br.ReadUInt32(); - triangleStrip = br.ReadBoolean(); - cullBackfaces = br.ReadBoolean(); - br.ReadByte(); // unk - br.ReadByte(); // unk - indexCount = br.ReadInt32(); - indicesOffset = br.ReadUInt32() + dataOffset; - indexSize = 0; - if (version > 0x20005) - { - br.ReadInt32(); // Indices length - br.AssertInt32(0); - indexSize = br.AssertInt32(0, 16, 32); - br.AssertInt32(0); - } - if (indexSize == 0) - { - indexSize = headerIndexSize; - } - } - } - - private struct FlverVertexBuffer - { - public int bufferIndex; - public int layoutIndex; - public int vertexSize; - public int vertexCount; - public uint bufferOffset; - - public FlverVertexBuffer(BinaryReaderEx br, uint dataOffset) - { - bufferIndex = br.ReadInt32(); - layoutIndex = br.ReadInt32(); - vertexSize = br.ReadInt32(); - vertexCount = br.ReadInt32(); - br.AssertInt32(0); - br.AssertInt32(0); - br.ReadInt32(); // Buffer length - bufferOffset = br.ReadUInt32() + dataOffset; - } - } - - private struct FlverBufferLayoutMember - { - public int unk00; - public FLVER.LayoutType type; - public FLVER.LayoutSemantic semantic; - public int index; - - public FlverBufferLayoutMember(BinaryReaderEx br) - { - unk00 = br.ReadInt32(); // unk - br.ReadInt32(); // struct offset - type = br.ReadEnum32(); - semantic = br.ReadEnum32(); - index = br.ReadInt32(); - } - } - - private struct FlverBufferLayout - { - public int memberCount; - public uint membersOffset; - - public FlverBufferLayout(BinaryReaderEx br) - { - memberCount = br.ReadInt32(); - br.AssertInt32(0); - br.AssertInt32(0); - membersOffset = br.ReadUInt32(); - } - } - - private struct FlverTexture - { - public uint pathOffset; - public uint typeOffset; - public Vector2 scale; - - public FlverTexture(BinaryReaderEx br) - { - pathOffset = br.ReadUInt32(); - typeOffset = br.ReadUInt32(); - scale = br.ReadVector2(); - - // unks - br.ReadByte(); - br.ReadBoolean(); - br.AssertByte(0); - br.AssertByte(0); - br.ReadSingle(); - br.ReadSingle(); - br.ReadSingle(); - } - } - - // Read only flver loader designed to be very fast at reading with low memory usage - private bool LoadInternalFast(BinaryReaderEx br, GameType type) - { - // Parse header - br.BigEndian = false; - br.AssertASCII("FLVER\0"); - br.BigEndian = br.AssertASCII("L\0", "B\0") == "B\0"; - int version = br.AssertInt32(0x20005, 0x20009, 0x2000C, 0x2000D, 0x2000E, 0x2000F, 0x20010, 0x20013, 0x20014, 0x20016, 0x2001A); - uint dataOffset = br.ReadUInt32(); - br.ReadInt32(); // Data length - int dummyCount = br.ReadInt32(); - int materialCount = br.ReadInt32(); - int boneCount = br.ReadInt32(); - int meshCount = br.ReadInt32(); - int vertexBufferCount = br.ReadInt32(); - - // Eat bounding boxes because we compute them ourself - br.ReadVector3(); // min - br.ReadVector3(); // max - - br.ReadInt32(); // Face count not including motion blur meshes or degenerate faces - br.ReadInt32(); // Total face count - int vertexIndicesSize = br.AssertByte(0, 16, 32); - bool unicode = br.ReadBoolean(); - br.ReadBoolean(); // unknown - br.AssertByte(0); - br.ReadInt32(); // unknown - int faceSetCount = br.ReadInt32(); - int bufferLayoutCount = br.ReadInt32(); - int textureCount = br.ReadInt32(); - br.ReadByte(); // unknown - br.ReadByte(); // unknown - br.AssertByte(0); - br.AssertByte(0); - br.AssertInt32(0); - br.AssertInt32(0); - //br.AssertInt32(0, 1, 2, 3, 4); // unknown - br.ReadInt32(); // unknown - br.AssertInt32(0); - br.AssertInt32(0); - br.AssertInt32(0); - br.AssertInt32(0); - br.AssertInt32(0); - - // Don't care about dummies for now so skip them - br.Position += dummyCount * 64; // 64 bytes per dummy - - // Materials - Span materials = stackalloc FlverMaterialDef[materialCount]; - for (int i = 0; i < materialCount; i++) - { - materials[i] = new FlverMaterialDef(br); - } - - // bones - FBones = new List(); - for (int i = 0; i < boneCount; i++) - { - FBones.Add(new FlverBone(br)); - } - - // Meshes - Span meshes = stackalloc FlverMesh[meshCount]; - for (int i = 0; i < meshCount; i++) - { - meshes[i] = new FlverMesh(br); - } - - // Facesets - Span facesets = stackalloc FlverFaceset[faceSetCount]; - for (int i = 0; i < faceSetCount; i++) - { - facesets[i] = new FlverFaceset(br, version, vertexIndicesSize, dataOffset); - } - - // Vertex buffers - Span vertexbuffers = stackalloc FlverVertexBuffer[vertexBufferCount]; - for (int i = 0; i < vertexBufferCount; i++) - { - vertexbuffers[i] = new FlverVertexBuffer(br, dataOffset); - } - - // Buffer layouts - Span bufferLayouts = stackalloc FlverBufferLayout[bufferLayoutCount]; - for (int i = 0; i < bufferLayoutCount; i++) - { - bufferLayouts[i] = new FlverBufferLayout(br); - } - - // Textures - Span textures = stackalloc FlverTexture[textureCount]; - for (int i = 0; i < textureCount; i++) - { - textures[i] = new FlverTexture(br); - } - - // Process the materials and meshes - GPUMeshes = new FlverSubmesh[meshCount]; - GPUMaterials = new FlverMaterial[materialCount]; - Bounds = new BoundingBox(); - //Bones = Flver.Bones; - - for (int i = 0; i < materialCount; i++) - { - GPUMaterials[i] = new FlverMaterial(); - ProcessMaterial(GPUMaterials[i], type, br, ref materials[i], textures, unicode); - } - - for (int i = 0; i < meshCount; i++) - { - GPUMeshes[i] = new FlverSubmesh(); - ProcessMesh(ref meshes[i], br, version, vertexbuffers, bufferLayouts, facesets, GPUMeshes[i]); - if (i == 0) - { - Bounds = GPUMeshes[i].Bounds; - } - else - { - Bounds = BoundingBox.Combine(Bounds, GPUMeshes[i].Bounds); - } - } - - if (GPUMeshes.Any(e => e.UseNormalWBoneTransform)) - { - StaticBoneBuffer = Renderer.BoneBufferAllocator.Allocate(64 * (uint)FBones.Count, 64); - Matrix4x4[] tbones = new Matrix4x4[FBones.Count]; - for (int i = 0; i < FBones.Count; i++) - { - tbones[i] = GetBoneObjectMatrix(FBones[i], FBones); - } - - Renderer.AddBackgroundUploadTask((d, cl) => - { - StaticBoneBuffer.FillBuffer(cl, tbones); - }); - } - - return true; - } - - public bool _Load(Memory bytes, AccessLevel al, GameType type) - { - bool ret; - if (type == GameType.DemonsSouls) - { - FlverDeS = FLVER0.Read(bytes); - ret = LoadInternalDeS(al, type); - } - else - { - if (al == AccessLevel.AccessGPUOptimizedOnly && type != GameType.DarkSoulsRemastered && type != GameType.DarkSoulsPTDE) - { - BinaryReaderEx br = new BinaryReaderEx(false, bytes); - DCX.Type ctype; - br = SFUtil.GetDecompressedBR(br, out ctype); - ret = LoadInternalFast(br, type); - } - else - { - var cache = (al == AccessLevel.AccessGPUOptimizedOnly) ? GetCache() : null; - Flver = FLVER2.Read(bytes, cache); - ret = LoadInternal(al, type); - ReleaseCache(cache); - } - } - return ret; - } - - public bool _Load(string path, AccessLevel al, GameType type) - { - bool ret; - if (type == GameType.DemonsSouls) - { - FlverDeS = FLVER0.Read(path); - ret = LoadInternalDeS(al, type); - } - else - { - if (al == AccessLevel.AccessGPUOptimizedOnly && type != GameType.DarkSoulsRemastered && type != GameType.DarkSoulsPTDE) - { - using var file = MemoryMappedFile.CreateFromFile(path, FileMode.Open, null, 0, MemoryMappedFileAccess.Read); - using var accessor = file.CreateMemoryAccessor(0, 0, MemoryMappedFileAccess.Read); - BinaryReaderEx br = new BinaryReaderEx(false, accessor.Memory); - DCX.Type ctype; - br = SFUtil.GetDecompressedBR(br, out ctype); - ret = LoadInternalFast(br, type); - } - else - { - var cache = (al == AccessLevel.AccessGPUOptimizedOnly) ? GetCache() : null; - Flver = FLVER2.Read(path, cache); - ret = LoadInternal(al, type); - ReleaseCache(cache); - } - } - return ret; - } - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - } - - if (GPUMaterials != null) - { - foreach (var m in GPUMaterials) - { - m.Dispose(); - } - } - - if (GPUMeshes != null) - { - foreach (var m in GPUMeshes) - { - m.GeomBuffer.Dispose(); - //Marshal.FreeHGlobal(m.PickingVertices); - } - } - - if (StaticBoneBuffer != null) - { - StaticBoneBuffer.Dispose(); - } - - disposedValue = true; - } - } - - ~FlverResource() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(false); - } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - GC.SuppressFinalize(this); - } - #endregion - } -} +#nullable enable +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Numerics; +using System.Buffers; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.IO; +using Veldrid; +using Veldrid.Utilities; +using SoulsFormats; +using System.ComponentModel.DataAnnotations; +using StudioCore.MsbEditor; +using StudioCore.Scene; +using System.Data; +using System.IO.MemoryMappedFiles; +using System.Threading.Tasks.Dataflow; +using DotNext.IO.MemoryMappedFiles; + +namespace StudioCore.Resource +{ + public class FlverResource : IResource, IDisposable + { + private static Stack FlverCaches = new Stack(); + public static int CacheCount { get; private set; } = 0; + public static long CacheFootprint + { + get + { + long total = 0; + lock (CacheLock) + { + foreach (var c in FlverCaches) + { + total += c.MemoryUsage; + } + } + return total; + } + } + private static object CacheLock = new object(); + + //private static ArrayPool VerticesPool = ArrayPool.Create(); + + public const bool CaptureMaterialLayouts = false; + + /// + /// Cache of material layouts that can be dumped + /// + public static Dictionary MaterialLayouts = new Dictionary(); + public static object _matLayoutLock = new object(); + + private FlverCache GetCache() + { + lock (CacheLock) + { + if (FlverCaches.Count > 0) + { + return FlverCaches.Pop(); + } + CacheCount++; + } + return new FlverCache(); + } + + private void ReleaseCache(FlverCache cache) + { + if (cache != null) + { + cache.ResetUsage(); + lock (CacheLock) + { + FlverCaches.Push(cache); + } + } + } + + public static void PurgeCaches() + { + FlverCaches.Clear(); + //VerticesPool = ArrayPool.Create(); + //GC.Collect(); + //GC.WaitForPendingFinalizers(); + //GC.Collect(); + } + + public class FlverMaterial : IResourceEventListener, IDisposable + { + public string MaterialName; + public Scene.GPUBufferAllocator.GPUBufferHandle MaterialBuffer; + public Scene.Material MaterialData; + + public string ShaderName = null; + public MeshLayoutType LayoutType; + public List SpecializationConstants = null; + public VertexLayoutDescription VertexLayout; + public uint VertexSize; + + public enum TextureType + { + AlbedoTextureResource = 0, + AlbedoTextureResource2, + NormalTextureResource, + NormalTextureResource2, + SpecularTextureResource, + SpecularTextureResource2, + ShininessTextureResource, + ShininessTextureResource2, + BlendmaskTextureResource, + TextureResourceCount, + } + + public readonly ResourceHandle?[] TextureResources = new ResourceHandle[(int)TextureType.TextureResourceCount]; + public readonly bool[] TextureResourceFilled = new bool[(int)TextureType.TextureResourceCount]; + + private bool disposedValue; + + private bool _setHasIndexNoWeightTransform = false; + public bool GetHasIndexNoWeightTransform() => _setHasIndexNoWeightTransform; + + public void SetHasIndexNoWeightTransform() + { + if(!_setHasIndexNoWeightTransform) + { + _setHasIndexNoWeightTransform = true; + } + } + + private bool _setNormalWBoneTransform = false; + + public bool GetNormalWBoneTransform() => _setNormalWBoneTransform; + + public void SetNormalWBoneTransform() + { + if (!_setNormalWBoneTransform) + { + SpecializationConstants.Add(new SpecializationConstant(50, true)); + _setNormalWBoneTransform = true; + } + } + + private void SetMaterialTexture(TextureType textureType, ref ushort matTex, ushort defaultTex) + { + var handle = TextureResources[(int)textureType]; + if (handle != null && handle.IsLoaded) + { + var res = handle.Get(); + if (res != null && res.GPUTexture != null) + { + matTex = (ushort)handle.Get().GPUTexture.TexHandle; + } + else + { + matTex = defaultTex; + } + } + else + { + matTex = defaultTex; + } + } + + public void ReleaseTextures() + { + for (int i = 0; i < (int)TextureType.TextureResourceCount; i++) + { + TextureResources[i]?.Release(); + TextureResources[i] = null; + } + } + + public void UpdateMaterial() + { + SetMaterialTexture(TextureType.AlbedoTextureResource, ref MaterialData.colorTex, 0); + SetMaterialTexture(TextureType.AlbedoTextureResource2, ref MaterialData.colorTex2, 0); + SetMaterialTexture(TextureType.NormalTextureResource, ref MaterialData.normalTex, 1); + SetMaterialTexture(TextureType.NormalTextureResource2, ref MaterialData.normalTex2, 1); + SetMaterialTexture(TextureType.SpecularTextureResource, ref MaterialData.specTex, 2); + SetMaterialTexture(TextureType.SpecularTextureResource2, ref MaterialData.specTex2, 2); + SetMaterialTexture(TextureType.ShininessTextureResource, ref MaterialData.shininessTex, 2); + SetMaterialTexture(TextureType.ShininessTextureResource2, ref MaterialData.shininessTex2, 2); + SetMaterialTexture(TextureType.BlendmaskTextureResource, ref MaterialData.blendMaskTex, 0); + + Scene.Renderer.AddBackgroundUploadTask((d, cl) => + { + var ctx = Tracy.TracyCZoneN(1, $@"Material upload"); + MaterialBuffer.FillBuffer(d, cl, ref MaterialData); + Tracy.TracyCZoneEnd(ctx); + }); + } + + public void OnResourceLoaded(IResourceHandle handle, int tag) + { + var texHandle = (ResourceHandle)handle; + texHandle.Acquire(); + TextureResources[tag]?.Release(); + TextureResources[tag] = texHandle; + UpdateMaterial(); + } + + public void OnResourceUnloaded(IResourceHandle handle, int tag) + { + TextureResources[tag]?.Release(); + TextureResources[tag] = null; + UpdateMaterial(); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + MaterialBuffer.Dispose(); + } + + ReleaseTextures(); + disposedValue = true; + } + } + + ~FlverMaterial() + { + Dispose(disposing: false); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } + + public unsafe class FlverSubmesh + { + public struct FlverSubmeshFaceSet + { + public int IndexCount; + public int IndexOffset; + public int PickingIndicesCount; + //public IntPtr PickingIndices; + public bool BackfaceCulling; + public bool IsTriangleStrip; + public byte LOD; + public bool IsMotionBlur; + public bool Is32Bit; + } + + public List MeshFacesets { get; set; } = new List(); + + public Scene.VertexIndexBufferAllocator.VertexIndexBufferHandle GeomBuffer { get; set; } + + public int VertexCount { get; set; } + // This is native because using managed arrays causes a weird memory leak + public IntPtr PickingVertices = IntPtr.Zero; + + public BoundingBox Bounds { get; set; } + + public Matrix4x4 LocalTransform = Matrix4x4.Identity; + + // Use the w field in the normal as an index to a bone that has a transform + public bool UseNormalWBoneTransform { get; set; } = false; + + public int DefaultBoneIndex { get; set; } = -1; + + public FlverMaterial Material { get; set; } = null; + } + + /// + /// Low level access to the flver struct. Use only in modification mode. + /// + public FLVER0 FlverDeS = null; + public FLVER2 Flver = null; + + public FlverSubmesh[] GPUMeshes = null; + public FlverMaterial[] GPUMaterials = null; + + public BoundingBox Bounds { get; set; } + + public List Bones { get; private set; } = null; + private List FBones { get; set; } = null; + private List BoneTransforms { get; set; } = null; + + public Scene.GPUBufferAllocator.GPUBufferHandle StaticBoneBuffer { get; private set; } = null; + + private string TexturePathToVirtual(string texpath) + { + if (texpath.Contains(@"\map\")) + { + var splits = texpath.Split('\\'); + var mapid = splits[splits.Length - 3]; + return $@"map/tex/{mapid}/{Path.GetFileNameWithoutExtension(texpath)}"; + } + // Chr texture reference + else if (texpath.Contains(@"\chr\")) + { + var splits = texpath.Split('\\'); + var chrid = splits[splits.Length - 3]; + return $@"chr/{chrid}/tex/{Path.GetFileNameWithoutExtension(texpath)}"; + } + // Obj texture reference + else if (texpath.Contains(@"\obj\")) + { + var splits = texpath.Split('\\'); + var objid = splits[splits.Length - 3]; + return $@"obj/{objid}/tex/{Path.GetFileNameWithoutExtension(texpath)}"; + } + // Asset (aet) texture references + else if (texpath.Contains(@"\aet") || texpath.StartsWith("aet")) + { + var splits = texpath.Split('\\'); + var aetid = splits[splits.Length - 1].Substring(0, 6); + return $@"aet/{aetid}/{Path.GetFileNameWithoutExtension(texpath)}"; + } + // Parts texture reference + else if (texpath.Contains(@"\parts\")) + { + var splits = texpath.Split('\\'); + var partsId = splits[splits.Length - 3]; + return $@"parts/{partsId}/tex/{Path.GetFileNameWithoutExtension(texpath)}"; + } + return texpath; + } + + public static string FindTexturePath(string type, string mpath, string mtd) + { + var path = mpath; + if (mpath == "") + { + var mtdstring = Path.GetFileNameWithoutExtension(mtd); + if (MtdBank.IsMatbin) + { + if (MtdBank.Matbins.ContainsKey(mtdstring)) + { + var tex = MtdBank.Matbins[mtdstring].Samplers.Find(x => (x.Type == type)); + if (tex == null || tex.Path == "") + { + return ""; + } + path = tex.Path; + } + } + else + { + if (MtdBank.Mtds.ContainsKey(mtdstring)) + { + var tex = MtdBank.Mtds[mtdstring].Textures.Find(x => (x.Type == type)); + if (tex == null || !tex.Extended || tex.Path == "") + { + return ""; + } + path = tex.Path; + } + } + } + return path; + } + + private void LookupTexture(FlverMaterial.TextureType textureType, FlverMaterial dest, string type, string mpath, string mtd) + { + var path = FindTexturePath(type, mpath, mtd); + + if (!dest.TextureResourceFilled[(int)textureType]) + { + ResourceManager.AddResourceListener(TexturePathToVirtual(path.ToLower()), dest, + AccessLevel.AccessGPUOptimizedOnly, (int)textureType); + dest.TextureResourceFilled[(int)textureType] = true; + } + } + + private void ProcessMaterialTexture(FlverMaterial dest, string texType, string mpath, string mtd, GameType gameType, + out bool blend, out bool hasNormal2, out bool hasSpec2, out bool hasShininess2, out bool blendMask) + { + blend = false; + blendMask = false; + hasNormal2 = false; + hasSpec2 = false; + hasShininess2 = false; + + string paramNameCheck; + if (texType == null) + { + paramNameCheck = "G_DIFFUSE"; + } + else + { + paramNameCheck = texType.ToUpper(); + } + if (paramNameCheck == "G_DIFFUSETEXTURE2" || paramNameCheck == "G_DIFFUSE2" || paramNameCheck.Contains("ALBEDO_2")) + { + LookupTexture(FlverMaterial.TextureType.AlbedoTextureResource2, dest, texType, mpath, mtd); + blend = true; + } + else if (paramNameCheck == "G_DIFFUSETEXTURE" || paramNameCheck == "G_DIFFUSE" || paramNameCheck.Contains("ALBEDO")) + { + LookupTexture(FlverMaterial.TextureType.AlbedoTextureResource, dest, texType, mpath, mtd); + } + else if (paramNameCheck == "G_BUMPMAPTEXTURE2" || paramNameCheck == "G_BUMPMAP2" || paramNameCheck.Contains("NORMAL_2")) + { + LookupTexture(FlverMaterial.TextureType.NormalTextureResource2, dest, texType, mpath, mtd); + blend = true; + hasNormal2 = true; + } + else if (paramNameCheck == "G_BUMPMAPTEXTURE" || paramNameCheck == "G_BUMPMAP" || paramNameCheck.Contains("NORMAL")) + { + LookupTexture(FlverMaterial.TextureType.NormalTextureResource, dest, texType, mpath, mtd); + } + else if (paramNameCheck == "G_SPECULARTEXTURE2" || paramNameCheck == "G_SPECULAR2" || paramNameCheck.Contains("SPECULAR_2")) + { + if (gameType == GameType.DarkSoulsRemastered) + { + LookupTexture(FlverMaterial.TextureType.ShininessTextureResource2, dest, texType, mpath, mtd); + blend = true; + hasShininess2 = true; + } + else + { + LookupTexture(FlverMaterial.TextureType.SpecularTextureResource2, dest, texType, mpath, mtd); + blend = true; + hasSpec2 = true; + } + } + else if (paramNameCheck == "G_SPECULARTEXTURE" || paramNameCheck == "G_SPECULAR" || paramNameCheck.Contains("SPECULAR")) + { + if (gameType == GameType.DarkSoulsRemastered) + { + LookupTexture(FlverMaterial.TextureType.ShininessTextureResource, dest, texType, mpath, mtd); + } + else + { + LookupTexture(FlverMaterial.TextureType.SpecularTextureResource, dest, texType, mpath, mtd); + } + } + else if (paramNameCheck == "G_SHININESSTEXTURE2" || paramNameCheck == "G_SHININESS2" || paramNameCheck.Contains("SHININESS2")) + { + LookupTexture(FlverMaterial.TextureType.ShininessTextureResource2, dest, texType, mpath, mtd); + blend = true; + hasShininess2 = true; + } + else if (paramNameCheck == "G_SHININESSTEXTURE" || paramNameCheck == "G_SHININESS" || paramNameCheck.Contains("SHININESS")) + { + LookupTexture(FlverMaterial.TextureType.ShininessTextureResource, dest, texType, mpath, mtd); + } + else if (paramNameCheck.Contains("BLENDMASK")) + { + LookupTexture(FlverMaterial.TextureType.BlendmaskTextureResource, dest, texType, mpath, mtd); + blendMask = true; + } + } + + unsafe private void ProcessMaterial(IFlverMaterial mat, FlverMaterial dest, GameType type) + { + dest.MaterialName = Path.GetFileNameWithoutExtension(mat.MTD); + dest.MaterialBuffer = Scene.Renderer.MaterialBufferAllocator.Allocate((uint)sizeof(Scene.Material), sizeof(Scene.Material)); + dest.MaterialData = new Scene.Material(); + + //FLVER0 stores layouts directly in the material + if(type == GameType.DemonsSouls) + { + var desMat = (FLVER0.Material)mat; + bool foundBoneIndices = false; + bool foundBoneWeights = false; + + if(desMat.Layouts?.Count > 0) + { + foreach(var layoutType in desMat.Layouts[0]) + { + switch(layoutType.Semantic) + { + case FLVER.LayoutSemantic.Normal: + if (layoutType.Type == FLVER.LayoutType.Byte4B || layoutType.Type == FLVER.LayoutType.Byte4E) + { + dest.SetNormalWBoneTransform(); + } + break; + case FLVER.LayoutSemantic.BoneIndices: + foundBoneIndices = true; + break; + case FLVER.LayoutSemantic.BoneWeights: + foundBoneWeights = true; + break; + } + } + } + + //Transformation condition for DeS models + if(foundBoneIndices && !foundBoneWeights) + { + dest.SetHasIndexNoWeightTransform(); + } + } + else if (type == GameType.ArmoredCoreVI) + { + //TODO AC6 + } + + if (!CFG.Current.EnableTexturing) + { + dest.ShaderName = @"SimpleFlver"; + dest.LayoutType = MeshLayoutType.LayoutSky; + dest.VertexLayout = MeshLayoutUtils.GetLayoutDescription(dest.LayoutType); + dest.VertexSize = MeshLayoutUtils.GetLayoutVertexSize(dest.LayoutType); + dest.SpecializationConstants = new List(); + return; + } + + bool blend = false; + bool blendMask = false; + bool hasNormal2 = false; + bool hasSpec2 = false; + bool hasShininess2 = false; + + foreach (var matparam in mat.Textures) + { + ProcessMaterialTexture(dest, matparam.Type, matparam.Path, mat.MTD, type, + out blend, out hasNormal2, out hasSpec2, out hasShininess2, out blendMask); + } + + if (blendMask) + { + dest.ShaderName = @"FlverShader\FlverShader_blendmask"; + dest.LayoutType = MeshLayoutType.LayoutUV2; + } + else if (blend) + { + dest.ShaderName = @"FlverShader\FlverShader_blend"; + dest.LayoutType = MeshLayoutType.LayoutUV2; + } + else + { + dest.ShaderName = @"FlverShader\FlverShader"; + dest.LayoutType = MeshLayoutType.LayoutStandard; + } + + List specConstants = new List(); + specConstants.Add(new SpecializationConstant(0, (uint)type)); + if (blend || blendMask) + { + specConstants.Add(new SpecializationConstant(1, hasNormal2)); + specConstants.Add(new SpecializationConstant(2, hasSpec2)); + specConstants.Add(new SpecializationConstant(3, hasShininess2)); + } + + dest.SpecializationConstants = specConstants; + dest.VertexLayout = MeshLayoutUtils.GetLayoutDescription(dest.LayoutType); + dest.VertexSize = MeshLayoutUtils.GetLayoutVertexSize(dest.LayoutType); + + dest.UpdateMaterial(); + } + + unsafe private void ProcessMaterial(FlverMaterial dest, GameType type, BinaryReaderEx br, ref FlverMaterialDef mat, Span textures, bool isUTF) + { + string mtd = isUTF ? br.GetUTF16(mat.mtdOffset) : br.GetShiftJIS(mat.mtdOffset); + dest.MaterialName = Path.GetFileNameWithoutExtension(mtd); + dest.MaterialBuffer = Scene.Renderer.MaterialBufferAllocator.Allocate((uint)sizeof(Scene.Material), sizeof(Scene.Material)); + dest.MaterialData = new Scene.Material(); + + if (!CFG.Current.EnableTexturing) + { + dest.ShaderName = @"SimpleFlver"; + dest.LayoutType = MeshLayoutType.LayoutSky; + dest.VertexLayout = MeshLayoutUtils.GetLayoutDescription(dest.LayoutType); + dest.VertexSize = MeshLayoutUtils.GetLayoutVertexSize(dest.LayoutType); + dest.SpecializationConstants = new List(); + return; + } + + bool blend = false; + bool blendMask = false; + bool hasNormal2 = false; + bool hasSpec2 = false; + bool hasShininess2 = false; + + for (int i = mat.textureIndex; i < mat.textureIndex + mat.textureCount; i++) + { + string ttype = isUTF ? br.GetUTF16(textures[i].typeOffset) : br.GetShiftJIS(textures[i].typeOffset); + string tpath = isUTF ? br.GetUTF16(textures[i].pathOffset) : br.GetShiftJIS(textures[i].pathOffset); + ProcessMaterialTexture(dest, ttype, tpath, mtd, type, + out blend, out hasNormal2, out hasSpec2, out hasShininess2, out blendMask); + } + + if (blendMask) + { + dest.ShaderName = @"FlverShader\FlverShader_blendmask"; + dest.LayoutType = MeshLayoutType.LayoutUV2; + } + else if (blend) + { + dest.ShaderName = @"FlverShader\FlverShader_blend"; + dest.LayoutType = MeshLayoutType.LayoutUV2; + } + else + { + dest.ShaderName = @"FlverShader\FlverShader"; + dest.LayoutType = MeshLayoutType.LayoutStandard; + } + + List specConstants = new List(); + specConstants.Add(new SpecializationConstant(0, (uint)type)); + if (blend || blendMask) + { + specConstants.Add(new SpecializationConstant(1, hasNormal2)); + specConstants.Add(new SpecializationConstant(2, hasSpec2)); + specConstants.Add(new SpecializationConstant(3, hasShininess2)); + } + + dest.SpecializationConstants = specConstants; + dest.VertexLayout = MeshLayoutUtils.GetLayoutDescription(dest.LayoutType); + dest.VertexSize = MeshLayoutUtils.GetLayoutVertexSize(dest.LayoutType); + + dest.UpdateMaterial(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FillVertex(ref Vector3 dest, ref FLVER.Vertex v) + { + dest = v.Position; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void FillVertex(Vector3 *dest, BinaryReaderEx br, FLVER.LayoutType type) + { + if (type == FLVER.LayoutType.Float3) + { + *dest = br.ReadVector3(); + } + else if (type == FLVER.LayoutType.Float4) + { + *dest = br.ReadVector3(); + br.AssertSingle(0); + } + else + { + throw new NotImplementedException($"Read not implemented for {type} vertex."); + } + + // Sanity check position to find bugs + //if (dest.X > 10000.0f || dest.Y > 10000.0f || dest.Z > 10000.0f) + //{ + // Debugger.Break(); + //} + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void FillNormalSNorm8(sbyte *dest, ref FLVER.Vertex v) + { + var n = Vector3.Normalize(new Vector3(v.Normal.X, v.Normal.Y, v.Normal.Z)); + dest[0] = (sbyte)(n.X * 127.0f); + dest[1] = (sbyte)(n.Y * 127.0f); + dest[2] = (sbyte)(n.Z * 127.0f); + dest[3] = (sbyte)v.NormalW; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void FillNormalSNorm8(sbyte* dest, BinaryReaderEx br, FLVER.LayoutType type, Vector3 *n) + { + int nw = 0; + if (type == FLVER.LayoutType.Float3) + { + *n = br.ReadVector3(); + } + else if (type == FLVER.LayoutType.Float4) + { + *n = br.ReadVector3(); + float w = br.ReadSingle(); + nw = (int)w; + if (w != nw) + throw new InvalidDataException($"Float4 Normal W was not a whole number: {w}"); + } + else if (type == FLVER.LayoutType.Byte4A) + { + *n = FLVER.Vertex.ReadByteNormXYZ(br); + nw = br.ReadByte(); + } + else if (type == FLVER.LayoutType.Byte4B) + { + *n = FLVER.Vertex.ReadByteNormXYZ(br); + nw = br.ReadByte(); + } + else if (type == FLVER.LayoutType.Short2toFloat2) + { + nw = br.ReadByte(); + *n = FLVER.Vertex.ReadSByteNormZYX(br); + } + else if (type == FLVER.LayoutType.Byte4C) + { + *n = FLVER.Vertex.ReadByteNormXYZ(br); + nw = br.ReadByte(); + } + else if (type == FLVER.LayoutType.Short4toFloat4A) + { + *n = FLVER.Vertex.ReadShortNormXYZ(br); + nw = br.ReadInt16(); + } + else if (type == FLVER.LayoutType.Short4toFloat4B) + { + //Normal = ReadUShortNormXYZ(br); + *n = FLVER.Vertex.ReadFloat16NormXYZ(br); + nw = br.ReadInt16(); + } + else if (type == FLVER.LayoutType.Byte4E) + { + *n = FLVER.Vertex.ReadByteNormXYZ(br); + nw = br.ReadByte(); + } + else + throw new NotImplementedException($"Read not implemented for {type} normal."); + + dest[0] = (sbyte)(n->X * 127.0f); + dest[1] = (sbyte)(n->Y * 127.0f); + dest[2] = (sbyte)(n->Z * 127.0f); + dest[3] = (sbyte)nw; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void FillUVShort(short* dest, ref FLVER.Vertex v, byte index) + { + var uv = v.GetUV(index); + dest[0] = (short)(uv.X * 2048.0f); + dest[1] = (short)(uv.Y * 2048.0f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void FillUVShort(short* dest, BinaryReaderEx br, FLVER.LayoutType type, float uvFactor, bool allowv2, out bool hasv2) + { + Vector3 v; + Vector3 v2; + hasv2 = false; + if (type == FLVER.LayoutType.Float2) + { + v = new Vector3(br.ReadVector2(), 0); + } + else if (type == FLVER.LayoutType.Float3) + { + v = br.ReadVector3(); + } + else if (type == FLVER.LayoutType.Float4) + { + v = new Vector3(br.ReadVector2(), 0); + v2 = new Vector3(br.ReadVector2(), 0); + hasv2 = allowv2; + } + else if (type == FLVER.LayoutType.Byte4A) + { + v = new Vector3(br.ReadInt16(), br.ReadInt16(), 0) / uvFactor; + } + else if (type == FLVER.LayoutType.Byte4B) + { + v = new Vector3(br.ReadInt16(), br.ReadInt16(), 0) / uvFactor; + } + else if (type == FLVER.LayoutType.Short2toFloat2) + { + v = new Vector3(br.ReadInt16(), br.ReadInt16(), 0) / uvFactor; + } + else if (type == FLVER.LayoutType.Byte4C) + { + v = new Vector3(br.ReadInt16(), br.ReadInt16(), 0) / uvFactor; + } + else if (type == FLVER.LayoutType.UV) + { + v = new Vector3(br.ReadInt16(), br.ReadInt16(), 0) / uvFactor; + } + else if (type == FLVER.LayoutType.UVPair) + { + v = new Vector3(br.ReadInt16(), br.ReadInt16(), 0) / uvFactor; + v2 = new Vector3(br.ReadInt16(), br.ReadInt16(), 0) / uvFactor; + hasv2 = allowv2; + } + else if (type == FLVER.LayoutType.Short4toFloat4B) + { + //AddUV(new Vector3(br.ReadInt16(), br.ReadInt16(), br.ReadInt16()) / uvFactor); + v = FLVER.Vertex.ReadFloat16NormXYZ(br); + br.AssertInt16(0); + } + else + { + throw new NotImplementedException($"Read not implemented for {type} UV."); + } + + dest[0] = (short)(v.X * 2048.0f); + dest[1] = (short)(v.Y * 2048.0f); + if (hasv2) + { + dest[3] = (short)(v.X * 2048.0f); + dest[4] = (short)(v.Y * 2048.0f); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void FillUVShortZero(short* dest) + { + dest[0] = 0; + dest[1] = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void FillUVFloat(ref Vector2 dest, ref FLVER.Vertex v, byte index) + { + var uv = v.GetUV(index); + dest.X = uv.X; + dest.Y = uv.Y; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void FillBinormalBitangentSNorm8(sbyte* destBinorm, sbyte* destBitan, ref FLVER.Vertex v, byte index) + { + var tan = v.GetTangent(index); + var t = Vector3.Normalize(new Vector3(tan.X, tan.Y, tan.Z)); + destBitan[0] = (sbyte)(t.X * 127.0f); + destBitan[1] = (sbyte)(t.Y * 127.0f); + destBitan[2] = (sbyte)(t.Z * 127.0f); + destBitan[3] = (sbyte)(tan.W * 127.0f); + + var bn = Vector3.Cross(Vector3.Normalize(v.Normal), Vector3.Normalize(new Vector3(t.X, t.Y, t.Z))) * tan.W; + destBinorm[0] = (sbyte)(bn.X * 127.0f); + destBinorm[1] = (sbyte)(bn.Y * 127.0f); + destBinorm[2] = (sbyte)(bn.Z * 127.0f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void FillBinormalBitangentSNorm8(sbyte* destBinorm, sbyte* destBitan, Vector3* n, BinaryReaderEx br, FLVER.LayoutType type) + { + Vector4 tan; + if (type == FLVER.LayoutType.Float4) + { + tan = br.ReadVector4(); + } + else if (type == FLVER.LayoutType.Byte4A) + { + tan = FLVER.Vertex.ReadByteNormXYZW(br); + } + else if (type == FLVER.LayoutType.Byte4B) + { + tan = FLVER.Vertex.ReadByteNormXYZW(br); + } + else if (type == FLVER.LayoutType.Byte4C) + { + tan = FLVER.Vertex.ReadByteNormXYZW(br); + } + else if (type == FLVER.LayoutType.Short4toFloat4A) + { + tan = FLVER.Vertex.ReadByteNormXYZW(br); + } + else if (type == FLVER.LayoutType.Byte4E) + { + tan = FLVER.Vertex.ReadByteNormXYZW(br); + } + else + { + throw new NotImplementedException($"Read not implemented for {type} tangent."); + } + + var t = Vector3.Normalize(new Vector3(tan.X, tan.Y, tan.Z)); + destBitan[0] = (sbyte)(t.X * 127.0f); + destBitan[1] = (sbyte)(t.Y * 127.0f); + destBitan[2] = (sbyte)(t.Z * 127.0f); + destBitan[3] = (sbyte)(tan.W * 127.0f); + + var bn = Vector3.Cross(Vector3.Normalize(*n), Vector3.Normalize(new Vector3(t.X, t.Y, t.Z))) * tan.W; + destBinorm[0] = (sbyte)(bn.X * 127.0f); + destBinorm[1] = (sbyte)(bn.Y * 127.0f); + destBinorm[2] = (sbyte)(bn.Z * 127.0f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void FillBinormalBitangentSNorm8Zero(sbyte* destBinorm, sbyte* destBitan) + { + destBitan[0] = 0; + destBitan[1] = 0; + destBitan[2] = 0; + destBitan[3] = 127; + + destBinorm[0] = 0; + destBinorm[1] = 0; + destBinorm[2] = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void FillColorUNorm(byte* dest, ref FLVER.Vertex v) + { + + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + unsafe private void EatVertex(BinaryReaderEx br, FLVER.LayoutType type) + { + switch (type) + { + case FLVER.LayoutType.Byte4A: + case FLVER.LayoutType.Byte4B: + case FLVER.LayoutType.Short2toFloat2: + case FLVER.LayoutType.Byte4C: + case FLVER.LayoutType.UV: + case FLVER.LayoutType.Byte4E: + case FLVER.LayoutType.Unknown: + br.ReadUInt32(); + break; + + case FLVER.LayoutType.Float2: + case FLVER.LayoutType.UVPair: + case FLVER.LayoutType.ShortBoneIndices: + case FLVER.LayoutType.Short4toFloat4A: + case FLVER.LayoutType.Short4toFloat4B: + br.ReadUInt64(); + break; + + case FLVER.LayoutType.Float3: + br.ReadUInt32(); + br.ReadUInt64(); + break; + + case FLVER.LayoutType.Float4: + br.ReadUInt64(); + br.ReadUInt64(); + break; + + default: + throw new NotImplementedException($"No size defined for buffer layout type: {type}"); + } + } + + unsafe private void FillVerticesNormalOnly(BinaryReaderEx br, ref FlverVertexBuffer buffer, Span layouts, Span pickingVerts, IntPtr vertBuffer) + { + Span verts = new Span(vertBuffer.ToPointer(), buffer.vertexCount); + br.StepIn(buffer.bufferOffset); + for (int i = 0; i < buffer.vertexCount; i++) + { + Vector3 n = Vector3.Zero; + fixed (FlverLayoutSky* v = &verts[i]) + { + bool posfilled = false; + foreach (var l in layouts) + { + // ER meme + if (l.unk00 == -2147483647) + continue; + if (l.semantic == FLVER.LayoutSemantic.Position) + { + FillVertex(&(*v).Position, br, l.type); + posfilled = true; + } + else if (l.semantic == FLVER.LayoutSemantic.Normal) + { + FillNormalSNorm8((*v).Normal, br, l.type, &n); + } + else + { + EatVertex(br, l.type); + } + } + if (!posfilled) + { + (*v).Position = new Vector3(0, 0, 0); + } + pickingVerts[i] = (*v).Position; + } + } + br.StepOut(); + } + + unsafe private void FillVerticesNormalOnly(FLVER2.Mesh mesh, Span pickingVerts, IntPtr vertBuffer) + { + Span verts = new Span(vertBuffer.ToPointer(), mesh.VertexCount); + for (int i = 0; i < mesh.VertexCount; i++) + { + var vert = mesh.Vertices[i]; + + verts[i] = new FlverLayoutSky(); + pickingVerts[i] = new Vector3(vert.Position.X, vert.Position.Y, vert.Position.Z); + fixed (FlverLayoutSky* v = &verts[i]) + { + FillVertex(ref (*v).Position, ref vert); + FillNormalSNorm8((*v).Normal, ref vert); + } + } + } + + unsafe private void FillVerticesNormalOnly(FLVER0.Mesh mesh, Span pickingVerts, IntPtr vertBuffer) + { + Span verts = new Span(vertBuffer.ToPointer(), mesh.Vertices.Count); + for (int i = 0; i < mesh.Vertices.Count; i++) + { + var vert = mesh.Vertices[i]; + + verts[i] = new FlverLayoutSky(); + pickingVerts[i] = new Vector3(vert.Position.X, vert.Position.Y, vert.Position.Z); + fixed (FlverLayoutSky* v = &verts[i]) + { + FillVertex(ref (*v).Position, ref vert); + FillNormalSNorm8((*v).Normal, ref vert); + } + } + } + + private unsafe void FillVerticesStandard(BinaryReaderEx br, ref FlverVertexBuffer buffer, + Span layouts, Span pickingVerts, IntPtr vertBuffer, float uvFactor) + { + br.StepIn(buffer.bufferOffset); + FlverLayout* pverts = (FlverLayout*)vertBuffer; + + for (int i = 0; i < buffer.vertexCount; i++) + { + FlverLayout* v = &pverts[i]; + Vector3 n = Vector3.UnitX; + FillUVShortZero((*v).Uv1); + FillBinormalBitangentSNorm8Zero((*v).Binormal, (*v).Bitangent); + bool posfilled = false; + foreach (var l in layouts) + { + // ER meme + if (l.unk00 == -2147483647) + continue; + if (l.semantic == FLVER.LayoutSemantic.Position) + { + FillVertex(&(*v).Position, br, l.type); + posfilled = true; + } + else if (l.semantic == FLVER.LayoutSemantic.Normal) + { + FillNormalSNorm8((*v).Normal, br, l.type, &n); + } + else if (l.semantic == FLVER.LayoutSemantic.UV && l.index == 0) + { + bool hasv2; + FillUVShort((*v).Uv1, br, l.type, uvFactor, false, out hasv2); + } + else if (l.semantic == FLVER.LayoutSemantic.Tangent && l.index == 0) + { + FillBinormalBitangentSNorm8((*v).Binormal, (*v).Bitangent, &n, br, l.type); + } + else + { + EatVertex(br, l.type); + } + } + + if (!posfilled) + { + (*v).Position = new Vector3(0, 0, 0); + } + + pickingVerts[i] = (*v).Position; + } + br.StepOut(); + } + + unsafe private void FillVerticesStandard(FLVER2.Mesh mesh, Span pickingVerts, IntPtr vertBuffer) + { + Span verts = new Span(vertBuffer.ToPointer(), mesh.VertexCount); + fixed (FlverLayout* pverts = verts) + { + for (int i = 0; i < mesh.VertexCount; i++) + { + FlverLayout* v = &pverts[i]; + var vert = mesh.Vertices[i]; + + verts[i] = new FlverLayout(); + pickingVerts[i] = new Vector3(vert.Position.X, vert.Position.Y, vert.Position.Z); + FillVertex(ref (*v).Position, ref vert); + FillNormalSNorm8((*v).Normal, ref vert); + if (vert.UVCount > 0) + { + FillUVShort((*v).Uv1, ref vert, 0); + } + else + { + FillUVShortZero((*v).Uv1); + } + + if (vert.TangentCount > 0) + { + FillBinormalBitangentSNorm8((*v).Binormal, (*v).Bitangent, ref vert, 0); + } + else + { + FillBinormalBitangentSNorm8Zero((*v).Binormal, (*v).Bitangent); + } + } + } + } + + unsafe private void FillVerticesStandard(FLVER0.Mesh mesh, Span pickingVerts, IntPtr vertBuffer) + { + Span verts = new Span(vertBuffer.ToPointer(), mesh.Vertices.Count); + fixed (FlverLayout* pverts = verts) + { + for (int i = 0; i < mesh.Vertices.Count; i++) + { + FlverLayout* v = &pverts[i]; + var vert = mesh.Vertices[i]; + + verts[i] = new FlverLayout(); + pickingVerts[i] = new Vector3(vert.Position.X, vert.Position.Y, vert.Position.Z); + FillVertex(ref (*v).Position, ref vert); + FillNormalSNorm8((*v).Normal, ref vert); + if (vert.UVCount > 0) + { + FillUVShort((*v).Uv1, ref vert, 0); + } + else + { + FillUVShortZero((*v).Uv1); + } + + if (vert.TangentCount > 0) + { + FillBinormalBitangentSNorm8((*v).Binormal, (*v).Bitangent, ref vert, 0); + } + else + { + FillBinormalBitangentSNorm8Zero((*v).Binormal, (*v).Bitangent); + } + } + } + } + + private unsafe void FillVerticesUV2(BinaryReaderEx br, ref FlverVertexBuffer buffer, Span layouts, Span pickingVerts, IntPtr vertBuffer, float uvFactor) + { + Span verts = new Span(vertBuffer.ToPointer(), buffer.vertexCount); + br.StepIn(buffer.bufferOffset); + fixed (FlverLayoutUV2* pverts = verts) + { + for (int i = 0; i < buffer.vertexCount; i++) + { + FlverLayoutUV2* v = &pverts[i]; + Vector3 n = Vector3.UnitX; + FillBinormalBitangentSNorm8Zero((*v).Binormal, (*v).Bitangent); + int uvsfilled = 0; + foreach (var l in layouts) + { + // ER meme + if (l.unk00 == -2147483647) + continue; + if (l.semantic == FLVER.LayoutSemantic.Position) + { + FillVertex(&(*v).Position, br, l.type); + } + else if (l.semantic == FLVER.LayoutSemantic.Normal) + { + FillNormalSNorm8((*v).Normal, br, l.type, &n); + } + else if (l.semantic == FLVER.LayoutSemantic.UV && uvsfilled < 2) + { + bool hasv2; + FillUVShort(uvsfilled > 0 ? (*v).Uv2 : (*v).Uv1, br, l.type, uvFactor, false, out hasv2); + uvsfilled += (hasv2 ? 2 : 1); + } + else if (l.semantic == FLVER.LayoutSemantic.Tangent && l.index == 0) + { + FillBinormalBitangentSNorm8((*v).Binormal, (*v).Bitangent, &n, br, l.type); + } + else + { + EatVertex(br, l.type); + } + } + + pickingVerts[i] = (*v).Position; + } + } + + br.StepOut(); + } + + unsafe private void FillVerticesUV2(FLVER2.Mesh mesh, Span pickingVerts, IntPtr vertBuffer) + { + Span verts = new Span(vertBuffer.ToPointer(), mesh.VertexCount); + fixed (FlverLayoutUV2* pverts = verts) + { + for (int i = 0; i < mesh.VertexCount; i++) + { + var vert = mesh.Vertices[i]; + + verts[i] = new FlverLayoutUV2(); + pickingVerts[i] = new Vector3(vert.Position.X, vert.Position.Y, vert.Position.Z); + FlverLayoutUV2* v = &pverts[i]; + FillVertex(ref (*v).Position, ref vert); + FillNormalSNorm8((*v).Normal, ref vert); + FillUVShort((*v).Uv1, ref vert, 0); + FillUVShort((*v).Uv2, ref vert, 1); + if (vert.TangentCount > 0) + { + FillBinormalBitangentSNorm8((*v).Binormal, (*v).Bitangent, ref vert, 0); + } + else + { + FillBinormalBitangentSNorm8Zero((*v).Binormal, (*v).Bitangent); + } + } + } + } + + unsafe private void FillVerticesUV2(FLVER0.Mesh mesh, Span pickingVerts, IntPtr vertBuffer) + { + Span verts = new Span(vertBuffer.ToPointer(), mesh.Vertices.Count); + fixed (FlverLayoutUV2* pverts = verts) + { + for (int i = 0; i < mesh.Vertices.Count; i++) + { + var vert = mesh.Vertices[i]; + + verts[i] = new FlverLayoutUV2(); + pickingVerts[i] = new Vector3(vert.Position.X, vert.Position.Y, vert.Position.Z); + FlverLayoutUV2* v = &pverts[i]; + FillVertex(ref (*v).Position, ref vert); + FillNormalSNorm8((*v).Normal, ref vert); + FillUVShort((*v).Uv1, ref vert, 0); + FillUVShort((*v).Uv2, ref vert, 1); + if (vert.TangentCount > 0) + { + FillBinormalBitangentSNorm8((*v).Binormal, (*v).Bitangent, ref vert, 0); + } + else + { + FillBinormalBitangentSNorm8Zero((*v).Binormal, (*v).Bitangent); + } + } + } + } + + unsafe private void ProcessMesh(FLVER0.Mesh mesh, FlverSubmesh dest) + { + var factory = Scene.Renderer.Factory; + + dest.Material = GPUMaterials[mesh.MaterialIndex]; + + if (dest.Material.GetHasIndexNoWeightTransform()) + { + //Transform based on root + for (int v = 0; v < mesh.Vertices.Count; v++) + { + var vert = mesh.Vertices[v]; + var boneTransformationIndex = mesh.BoneIndices[vert.BoneIndices[0]]; + if(boneTransformationIndex > -1 && BoneTransforms.Count > boneTransformationIndex) + { + var boneTfm = BoneTransforms[boneTransformationIndex]; + + vert.Position = Vector3.Transform(vert.Position, boneTfm); + vert.Normal = Vector3.TransformNormal(vert.Normal, boneTfm); + mesh.Vertices[v] = vert; + } + } + } + + var vSize = dest.Material.VertexSize; + dest.PickingVertices = Marshal.AllocHGlobal(mesh.Vertices.Count * sizeof(Vector3)); + var pvhandle = new Span(dest.PickingVertices.ToPointer(), mesh.Vertices.Count); + uint vbuffersize = (uint)mesh.Vertices.Count * (uint)vSize; + + dest.VertexCount = mesh.Vertices.Count; + + dest.MeshFacesets = new List(); + + bool is32bit = false;//FlverDeS.Version > 0x20005 && mesh.Vertices.Count > 65535; + Span fs16 = null; + Span fs32 = null; + + var indices = mesh.Triangulate(FlverDeS.Header.Version).ToArray(); + int indicesTotal = indices.Length; + + dest.GeomBuffer = Scene.Renderer.GeometryBufferAllocator.Allocate(vbuffersize, + (uint)indicesTotal * (is32bit ? 4u : 2u), (int)vSize, 4); + var meshVertices = dest.GeomBuffer.MapVBuffer(); + var meshIndices = dest.GeomBuffer.MapIBuffer(); + + if (dest.Material.LayoutType == MeshLayoutType.LayoutSky) + { + FillVerticesNormalOnly(mesh, pvhandle, meshVertices); + } + else if (dest.Material.LayoutType == MeshLayoutType.LayoutUV2) + { + FillVerticesUV2(mesh, pvhandle, meshVertices); + } + else + { + FillVerticesStandard(mesh, pvhandle, meshVertices); + } + + if (mesh.VertexIndices.Count != 0) + { + if (is32bit) + { + fs32 = new Span(meshIndices.ToPointer(), indicesTotal); + } + else + { + fs16 = new Span(meshIndices.ToPointer(), indicesTotal); + } + + var newFaceSet = new FlverSubmesh.FlverSubmeshFaceSet() + { + BackfaceCulling = true, + IsTriangleStrip = false, + //IndexBuffer = factory.CreateBuffer(new BufferDescription(buffersize, BufferUsage.IndexBuffer)), + IndexOffset = 0, + + IndexCount = indices.Length, + Is32Bit = is32bit, + PickingIndicesCount = indices.Length, + //PickingIndices = Marshal.AllocHGlobal(indices.Length * 4), + }; + + if (is32bit) + { + for (int i = 0; i < indices.Length; i++) + { + if (indices[i] == 0xFFFF && indices[i] > mesh.Vertices.Count) + { + fs32[newFaceSet.IndexOffset + i] = -1; + } + else + { + fs32[newFaceSet.IndexOffset + i] = indices[i]; + } + } + } + else + { + for (int i = 0; i < indices.Length; i++) + { + if (indices[i] == 0xFFFF && indices[i] > mesh.Vertices.Count) + { + fs16[newFaceSet.IndexOffset + i] = 0xFFFF; + } + else + { + fs16[newFaceSet.IndexOffset + i] = (ushort)indices[i]; + } + } + } + + dest.MeshFacesets.Add(newFaceSet); + } + + dest.GeomBuffer.UnmapVBuffer(); + dest.GeomBuffer.UnmapIBuffer(); + + dest.Bounds = BoundingBox.CreateFromPoints((Vector3*)dest.PickingVertices.ToPointer(), dest.VertexCount, 12, Quaternion.Identity, Vector3.Zero, Vector3.One); + if (CaptureMaterialLayouts) + { + lock (_matLayoutLock) + { + if (!MaterialLayouts.ContainsKey(dest.Material.MaterialName)) + { + MaterialLayouts.Add(dest.Material.MaterialName, Flver.BufferLayouts[mesh.LayoutIndex]); + } + } + } + } + + unsafe private void ProcessMesh(FLVER2.Mesh mesh, FlverSubmesh dest) + { + dest.Material = GPUMaterials[mesh.MaterialIndex]; + + var vSize = dest.Material.VertexSize; + dest.PickingVertices = Marshal.AllocHGlobal(mesh.VertexCount * sizeof(Vector3)); + var pvhandle = new Span(dest.PickingVertices.ToPointer(), mesh.VertexCount); + + dest.VertexCount = mesh.VertexCount; + + dest.MeshFacesets = new List(); + var facesets = mesh.FaceSets; + + bool is32bit = Flver.Header.Version > 0x20005 && mesh.VertexCount > 65535; + int indicesTotal = 0; + Span fs16 = null; + Span fs32 = null; + foreach (var faceset in facesets) + { + indicesTotal += faceset.Indices.Length; + } + + uint vbuffersize = (uint)mesh.VertexCount * (uint)vSize; + dest.GeomBuffer = Scene.Renderer.GeometryBufferAllocator.Allocate(vbuffersize, + (uint)indicesTotal * (is32bit ? 4u : 2u), (int)vSize, 4); + var meshVertices = dest.GeomBuffer.MapVBuffer(); + var meshIndices = dest.GeomBuffer.MapIBuffer(); + + if (dest.Material.LayoutType == MeshLayoutType.LayoutSky) + { + FillVerticesNormalOnly(mesh, pvhandle, meshVertices); + } + else if (dest.Material.LayoutType == MeshLayoutType.LayoutUV2) + { + FillVerticesUV2(mesh, pvhandle, meshVertices); + } + else + { + FillVerticesStandard(mesh, pvhandle, meshVertices); + } + + if (is32bit) + { + fs32 = new Span(meshIndices.ToPointer(), indicesTotal); + } + else + { + fs16 = new Span(meshIndices.ToPointer(), indicesTotal); + } + + int idxoffset = 0; + foreach (var faceset in facesets) + { + if (faceset.Indices.Length == 0) + continue; + + //At this point they use 32-bit faceset vertex indices + var newFaceSet = new FlverSubmesh.FlverSubmeshFaceSet() + { + BackfaceCulling = faceset.CullBackfaces, + IsTriangleStrip = faceset.TriangleStrip, + IndexOffset = idxoffset, + + IndexCount = faceset.IndicesCount, + Is32Bit = is32bit + }; + + + if ((faceset.Flags & FLVER2.FaceSet.FSFlags.LodLevel1) > 0) + { + newFaceSet.LOD = 1; + newFaceSet.IsMotionBlur = false; + } + else if ((faceset.Flags & FLVER2.FaceSet.FSFlags.LodLevel2) > 0) + { + newFaceSet.LOD = 2; + newFaceSet.IsMotionBlur = false; + } + + if ((faceset.Flags & FLVER2.FaceSet.FSFlags.MotionBlur) > 0) + { + newFaceSet.IsMotionBlur = true; + } + + if (is32bit) + { + for (int i = 0; i < faceset.Indices.Length; i++) + { + if (faceset.Indices[i] == 0xFFFF && faceset.Indices[i] > mesh.Vertices.Length) + { + fs32[newFaceSet.IndexOffset + i] = -1; + } + else + { + fs32[newFaceSet.IndexOffset + i] = faceset.Indices[i]; + } + } + } + else + { + for (int i = 0; i < faceset.Indices.Length; i++) + { + if (faceset.Indices[i] == 0xFFFF && faceset.Indices[i] > mesh.Vertices.Length) + { + fs16[newFaceSet.IndexOffset + i] = 0xFFFF; + } + else + { + fs16[newFaceSet.IndexOffset + i] = (ushort)faceset.Indices[i]; + } + } + } + + dest.MeshFacesets.Add(newFaceSet); + idxoffset += faceset.Indices.Length; + } + + dest.GeomBuffer.UnmapVBuffer(); + dest.GeomBuffer.UnmapIBuffer(); + + dest.Bounds = BoundingBox.CreateFromPoints((Vector3*)dest.PickingVertices.ToPointer(), dest.VertexCount, 12, Quaternion.Identity, Vector3.Zero, Vector3.One); + + if (CaptureMaterialLayouts) + { + lock (_matLayoutLock) + { + if (!MaterialLayouts.ContainsKey(dest.Material.MaterialName)) + { + MaterialLayouts.Add(dest.Material.MaterialName, Flver.BufferLayouts[mesh.VertexBuffers[0].LayoutIndex]); + } + } + } + + if (mesh.Dynamic == 0) + { + var elements = mesh.VertexBuffers.SelectMany(b => Flver.BufferLayouts[b.LayoutIndex]); + dest.UseNormalWBoneTransform = elements.Any(e => e.Semantic == FLVER.LayoutSemantic.Normal && (e.Type == FLVER.LayoutType.Byte4B || e.Type == FLVER.LayoutType.Byte4E)); + if (dest.UseNormalWBoneTransform) + { + dest.Material.SetNormalWBoneTransform(); + } + else if (mesh.DefaultBoneIndex != -1 && mesh.DefaultBoneIndex < Bones.Count) + { + dest.LocalTransform = Utils.GetBoneObjectMatrix(Bones[mesh.DefaultBoneIndex], Bones); + } + } + + Marshal.FreeHGlobal(dest.PickingVertices); + } + + private static Matrix4x4 GetBoneObjectMatrix(FlverBone bone, List bones) + { + var res = Matrix4x4.Identity; + FlverBone? parentBone = bone; + do + { + res *= parentBone.Value.ComputeLocalTransform(); + if (parentBone?.parentIndex >= 0) + { + parentBone = bones[(int)parentBone?.parentIndex]; + } + else + { + parentBone = null; + } + } + while (parentBone != null); + + return res; + } + + unsafe private void ProcessMesh(ref FlverMesh mesh, BinaryReaderEx br, int version, + Span buffers, Span layouts, + Span facesets, FlverSubmesh dest) + { + dest.Material = GPUMaterials[mesh.materialIndex]; + + Span facesetIndices = stackalloc int[mesh.facesetCount]; + br.StepIn(mesh.facesetIndicesOffset); + for (int i = 0; i < mesh.facesetCount; i++) + { + facesetIndices[i] = br.ReadInt32(); + } + br.StepOut(); + + Span vertexBufferIndices = stackalloc int[mesh.vertexBufferCount]; + br.StepIn(mesh.vertexBufferIndicesOffset); + for (int i = 0; i < mesh.vertexBufferCount; i++) + { + vertexBufferIndices[i] = br.ReadInt32(); + } + br.StepOut(); + int vertexCount = mesh.vertexBufferCount > 0 ? buffers[vertexBufferIndices[0]].vertexCount : 0; + + var vSize = dest.Material.VertexSize; + dest.PickingVertices = Marshal.AllocHGlobal(vertexCount * sizeof(Vector3)); + var pvhandle = new Span(dest.PickingVertices.ToPointer(), vertexCount); + + bool is32bit = version > 0x20005 && vertexCount > 65535; + int indicesTotal = 0; + foreach (var fsidx in facesetIndices) + { + indicesTotal += facesets[fsidx].indexCount; + is32bit = is32bit || facesets[fsidx].indexSize != 16; + } + + uint vbuffersize = (uint)vertexCount * (uint)vSize; + dest.GeomBuffer = Renderer.GeometryBufferAllocator.Allocate(vbuffersize, + (uint)indicesTotal * (is32bit ? 4u : 2u), (int)vSize, 4); + var meshVertices = dest.GeomBuffer.MapVBuffer(); + var meshIndices = dest.GeomBuffer.MapIBuffer(); + + foreach (var vbi in vertexBufferIndices) + { + var vb = buffers[vbi]; + var layout = layouts[vb.layoutIndex]; + Span layoutmembers = stackalloc FlverBufferLayoutMember[layout.memberCount]; + br.StepIn(layout.membersOffset); + for (int i = 0; i < layout.memberCount; i++) + { + layoutmembers[i] = new FlverBufferLayoutMember(br); + if (layoutmembers[i].semantic == FLVER.LayoutSemantic.Normal && (layoutmembers[i].type == FLVER.LayoutType.Byte4B || layoutmembers[i].type == FLVER.LayoutType.Byte4E)) + dest.UseNormalWBoneTransform = true; + } + br.StepOut(); + if (dest.Material.LayoutType == MeshLayoutType.LayoutSky) + { + FillVerticesNormalOnly(br, ref vb, layoutmembers, pvhandle, meshVertices); + } + else if (dest.Material.LayoutType == MeshLayoutType.LayoutUV2) + { + FillVerticesUV2(br, ref vb, layoutmembers, pvhandle, meshVertices, version >= 0x2000F ? 2048 : 1024); + } + else + { + FillVerticesStandard(br, ref vb, layoutmembers, pvhandle, meshVertices, version >= 0x2000F ? 2048 : 1024); + } + } + + dest.VertexCount = vertexCount; + dest.MeshFacesets = new List(); + + Span fs16 = null; + Span fs32 = null; + if (is32bit) + { + fs32 = new Span(meshIndices.ToPointer(), indicesTotal); + } + else + { + fs16 = new Span(meshIndices.ToPointer(), indicesTotal); + } + + int idxoffset = 0; + foreach (var fsidx in facesetIndices) + { + var faceset = facesets[fsidx]; + if (faceset.indexCount == 0) + continue; + + //At this point they use 32-bit faceset vertex indices + var newFaceSet = new FlverSubmesh.FlverSubmeshFaceSet() + { + BackfaceCulling = faceset.cullBackfaces, + IsTriangleStrip = faceset.triangleStrip, + IndexOffset = idxoffset, + + IndexCount = faceset.indexCount, + Is32Bit = is32bit, + PickingIndicesCount = 0, + }; + + + if ((faceset.flags & FLVER2.FaceSet.FSFlags.LodLevel1) > 0) + { + newFaceSet.LOD = 1; + newFaceSet.IsMotionBlur = false; + } + else if ((faceset.flags & FLVER2.FaceSet.FSFlags.LodLevel2) > 0) + { + newFaceSet.LOD = 2; + newFaceSet.IsMotionBlur = false; + } + + if ((faceset.flags & FLVER2.FaceSet.FSFlags.MotionBlur) > 0) + { + newFaceSet.IsMotionBlur = true; + } + + br.StepIn(faceset.indicesOffset); + for (int i = 0; i < faceset.indexCount; i++) + { + if (faceset.indexSize == 16) + { + var idx = br.ReadUInt16(); + if (is32bit) + { + fs32[newFaceSet.IndexOffset + i] = (idx == 0xFFFF ? -1 : idx); + } + else + { + fs16[newFaceSet.IndexOffset + i] = idx; + } + } + else + { + var idx = br.ReadInt32(); + if (idx > vertexCount) + { + fs32[newFaceSet.IndexOffset + i] = -1; + } + else + { + fs32[newFaceSet.IndexOffset + i] = idx; + } + } + } + br.StepOut(); + + dest.MeshFacesets.Add(newFaceSet); + idxoffset += faceset.indexCount; + } + + dest.GeomBuffer.UnmapIBuffer(); + dest.GeomBuffer.UnmapVBuffer(); + + dest.Bounds = BoundingBox.CreateFromPoints((Vector3*)dest.PickingVertices.ToPointer(), dest.VertexCount, 12, Quaternion.Identity, Vector3.Zero, Vector3.One); + + /*if (CaptureMaterialLayouts) + { + lock (_matLayoutLock) + { + if (!MaterialLayouts.ContainsKey(dest.Material.MaterialName)) + { + MaterialLayouts.Add(dest.Material.MaterialName, Flver.BufferLayouts[mesh.VertexBuffers[0].LayoutIndex]); + } + } + }*/ + + if (mesh.dynamic == 0) + { + if (dest.UseNormalWBoneTransform) + { + dest.Material.SetNormalWBoneTransform(); + } + else if (mesh.defaultBoneIndex != -1 && mesh.defaultBoneIndex < FBones.Count) + { + dest.LocalTransform = GetBoneObjectMatrix(FBones[mesh.defaultBoneIndex], FBones); + } + } + + Marshal.FreeHGlobal(dest.PickingVertices); + } + + private bool LoadInternalDeS(AccessLevel al, GameType type) + { + if (al == AccessLevel.AccessFull || al == AccessLevel.AccessGPUOptimizedOnly) + { + GPUMeshes = new FlverSubmesh[FlverDeS.Meshes.Count()]; + GPUMaterials = new FlverMaterial[FlverDeS.Materials.Count()]; + Bounds = new BoundingBox(); + Bones = FlverDeS.Bones; + BoneTransforms = new List(); + for (int i = 0; i < Bones.Count; i++) + { + //BoneTransforms.Add(FlverDeS.ComputeBoneWorldMatrix(i)); + BoneTransforms.Add(Bones[i].ComputeLocalTransform()); + } + + for (int i = 0; i < FlverDeS.Materials.Count(); i++) + { + GPUMaterials[i] = new FlverMaterial(); + ProcessMaterial(FlverDeS.Materials[i], GPUMaterials[i], type); + } + + for (int i = 0; i < FlverDeS.Meshes.Count(); i++) + { + GPUMeshes[i] = new FlverSubmesh(); + + var flverMesh = FlverDeS.Meshes[i]; + ProcessMesh(flverMesh, GPUMeshes[i]); + if (i == 0) + { + Bounds = GPUMeshes[i].Bounds; + } + else + { + Bounds = BoundingBox.Combine(Bounds, GPUMeshes[i].Bounds); + } + } + + BoneTransforms.Clear(); + } + + if (al == AccessLevel.AccessGPUOptimizedOnly) + { + Flver = null; + } + return true; + } + + private bool LoadInternal(AccessLevel al, GameType type) + { + if (al == AccessLevel.AccessFull || al == AccessLevel.AccessGPUOptimizedOnly) + { + GPUMeshes = new FlverSubmesh[Flver.Meshes.Count()]; + GPUMaterials = new FlverMaterial[Flver.Materials.Count()]; + Bounds = new BoundingBox(); + Bones = Flver.Bones; + + for (int i = 0; i < Flver.Materials.Count(); i++) + { + GPUMaterials[i] = new FlverMaterial(); + ProcessMaterial(Flver.Materials[i], GPUMaterials[i], type); + } + + for (int i = 0; i < Flver.Meshes.Count(); i++) + { + GPUMeshes[i] = new FlverSubmesh(); + ProcessMesh(Flver.Meshes[i], GPUMeshes[i]); + if (i == 0) + { + Bounds = GPUMeshes[i].Bounds; + } + else + { + Bounds = BoundingBox.Combine(Bounds, GPUMeshes[i].Bounds); + } + } + + if (GPUMeshes.Any(e => e.UseNormalWBoneTransform)) + { + StaticBoneBuffer = Renderer.BoneBufferAllocator.Allocate(64 * (uint)Bones.Count, 64); + Matrix4x4[] tbones = new Matrix4x4[Bones.Count]; + for (int i = 0; i < Bones.Count; i++) + { + tbones[i] = Utils.GetBoneObjectMatrix(Bones[i], Bones); + } + + Renderer.AddBackgroundUploadTask((d, cl) => + { + StaticBoneBuffer.FillBuffer(cl, tbones); + }); + } + } + + if (al == AccessLevel.AccessGPUOptimizedOnly) + { + Flver = null; + } + return true; + } + + private struct FlverMaterialDef + { + public uint nameOffset; + public uint mtdOffset; + public int textureCount; + public int textureIndex; + public int flags; + public int gxOffset; + + public FlverMaterialDef(BinaryReaderEx br) + { + nameOffset = br.ReadUInt32(); + mtdOffset = br.ReadUInt32(); + textureCount = br.ReadInt32(); + textureIndex = br.ReadInt32(); + flags = br.ReadInt32(); + gxOffset = br.ReadInt32(); + br.ReadInt32(); // unknown + br.AssertInt32(0); + } + } + + private struct FlverBone + { + public Vector3 position; + public uint nameOffset; + public Vector3 rotation; + public short parentIndex; + public short childIndex; + public Vector3 scale; + public short nextSiblingIndex; + public short previousSiblingIndex; + public Vector3 boundingBoxMin; + public Vector3 boundingBoxMax; + + public Matrix4x4 ComputeLocalTransform() + { + return Matrix4x4.CreateScale(scale) + * Matrix4x4.CreateRotationX(rotation.X) + * Matrix4x4.CreateRotationZ(rotation.Z) + * Matrix4x4.CreateRotationY(rotation.Y) + * Matrix4x4.CreateTranslation(position); + } + + public FlverBone(BinaryReaderEx br) + { + position = br.ReadVector3(); + nameOffset = br.ReadUInt32(); + rotation = br.ReadVector3(); + parentIndex = br.ReadInt16(); + childIndex = br.ReadInt16(); + scale = br.ReadVector3(); + nextSiblingIndex = br.ReadInt16(); + previousSiblingIndex = br.ReadInt16(); + boundingBoxMin = br.ReadVector3(); + br.ReadInt32(); // unknown + boundingBoxMax = br.ReadVector3(); + br.Position += 0x34; + } + } + + private struct FlverMesh + { + public int dynamic; + public int materialIndex; + public int defaultBoneIndex; + public int boneCount; + public int facesetCount; + public uint facesetIndicesOffset; + public int vertexBufferCount; + public uint vertexBufferIndicesOffset; + + public FlverMesh(BinaryReaderEx br) + { + dynamic = br.AssertInt32(0, 1); + materialIndex = br.ReadInt32(); + br.AssertInt32(0); + br.AssertInt32(0); + defaultBoneIndex = br.ReadInt32(); + boneCount = br.ReadInt32(); + br.ReadInt32(); // bb offset + br.ReadInt32(); // bone offset + facesetCount = br.ReadInt32(); + facesetIndicesOffset = br.ReadUInt32(); + vertexBufferCount = br.AssertInt32(0, 1, 2, 3); + vertexBufferIndicesOffset = br.ReadUInt32(); + } + } + + private struct FlverFaceset + { + public FLVER2.FaceSet.FSFlags flags; + public bool triangleStrip; + public bool cullBackfaces; + public int indexCount; + public uint indicesOffset; + public int indexSize; + + public FlverFaceset(BinaryReaderEx br, int version, int headerIndexSize, uint dataOffset) + { + flags = (FLVER2.FaceSet.FSFlags)br.ReadUInt32(); + triangleStrip = br.ReadBoolean(); + cullBackfaces = br.ReadBoolean(); + br.ReadByte(); // unk + br.ReadByte(); // unk + indexCount = br.ReadInt32(); + indicesOffset = br.ReadUInt32() + dataOffset; + indexSize = 0; + if (version > 0x20005) + { + br.ReadInt32(); // Indices length + br.AssertInt32(0); + indexSize = br.AssertInt32(0, 16, 32); + br.AssertInt32(0); + } + if (indexSize == 0) + { + indexSize = headerIndexSize; + } + } + } + + private struct FlverVertexBuffer + { + public int bufferIndex; + public int layoutIndex; + public int vertexSize; + public int vertexCount; + public uint bufferOffset; + + public FlverVertexBuffer(BinaryReaderEx br, uint dataOffset) + { + bufferIndex = br.ReadInt32(); + layoutIndex = br.ReadInt32(); + vertexSize = br.ReadInt32(); + vertexCount = br.ReadInt32(); + br.AssertInt32(0); + br.AssertInt32(0); + br.ReadInt32(); // Buffer length + bufferOffset = br.ReadUInt32() + dataOffset; + } + } + + private struct FlverBufferLayoutMember + { + public int unk00; + public FLVER.LayoutType type; + public FLVER.LayoutSemantic semantic; + public int index; + + public FlverBufferLayoutMember(BinaryReaderEx br) + { + unk00 = br.ReadInt32(); // unk + br.ReadInt32(); // struct offset + type = br.ReadEnum32(); + semantic = br.ReadEnum32(); + index = br.ReadInt32(); + } + } + + private struct FlverBufferLayout + { + public int memberCount; + public uint membersOffset; + + public FlverBufferLayout(BinaryReaderEx br) + { + memberCount = br.ReadInt32(); + br.AssertInt32(0); + br.AssertInt32(0); + membersOffset = br.ReadUInt32(); + } + } + + private struct FlverTexture + { + public uint pathOffset; + public uint typeOffset; + public Vector2 scale; + + public FlverTexture(BinaryReaderEx br) + { + pathOffset = br.ReadUInt32(); + typeOffset = br.ReadUInt32(); + scale = br.ReadVector2(); + + // unks + br.ReadByte(); + br.ReadBoolean(); + br.AssertByte(0); + br.AssertByte(0); + br.ReadSingle(); + br.ReadSingle(); + br.ReadSingle(); + } + } + + // Read only flver loader designed to be very fast at reading with low memory usage + private bool LoadInternalFast(BinaryReaderEx br, GameType type) + { + // Parse header + br.BigEndian = false; + br.AssertASCII("FLVER\0"); + br.BigEndian = br.AssertASCII("L\0", "B\0") == "B\0"; + int version = br.AssertInt32(0x20005, 0x20009, 0x2000C, 0x2000D, 0x2000E, 0x2000F, 0x20010, 0x20013, 0x20014, 0x20016, 0x2001A); + uint dataOffset = br.ReadUInt32(); + br.ReadInt32(); // Data length + int dummyCount = br.ReadInt32(); + int materialCount = br.ReadInt32(); + int boneCount = br.ReadInt32(); + int meshCount = br.ReadInt32(); + int vertexBufferCount = br.ReadInt32(); + + // Eat bounding boxes because we compute them ourself + br.ReadVector3(); // min + br.ReadVector3(); // max + + br.ReadInt32(); // Face count not including motion blur meshes or degenerate faces + br.ReadInt32(); // Total face count + int vertexIndicesSize = br.AssertByte(0, 16, 32); + bool unicode = br.ReadBoolean(); + br.ReadBoolean(); // unknown + br.AssertByte(0); + br.ReadInt32(); // unknown + int faceSetCount = br.ReadInt32(); + int bufferLayoutCount = br.ReadInt32(); + int textureCount = br.ReadInt32(); + br.ReadByte(); // unknown + br.ReadByte(); // unknown + br.AssertByte(0); + br.AssertByte(0); + br.AssertInt32(0); + br.AssertInt32(0); + //br.AssertInt32(0, 1, 2, 3, 4); // unknown + br.ReadInt32(); // unknown + br.AssertInt32(0); + br.AssertInt32(0); + br.AssertInt32(0); + br.AssertInt32(0); + br.AssertInt32(0); + + // Don't care about dummies for now so skip them + br.Position += dummyCount * 64; // 64 bytes per dummy + + // Materials + Span materials = stackalloc FlverMaterialDef[materialCount]; + for (int i = 0; i < materialCount; i++) + { + materials[i] = new FlverMaterialDef(br); + } + + // bones + FBones = new List(); + for (int i = 0; i < boneCount; i++) + { + FBones.Add(new FlverBone(br)); + } + + // Meshes + Span meshes = stackalloc FlverMesh[meshCount]; + for (int i = 0; i < meshCount; i++) + { + meshes[i] = new FlverMesh(br); + } + + // Facesets + Span facesets = stackalloc FlverFaceset[faceSetCount]; + for (int i = 0; i < faceSetCount; i++) + { + facesets[i] = new FlverFaceset(br, version, vertexIndicesSize, dataOffset); + } + + // Vertex buffers + Span vertexbuffers = stackalloc FlverVertexBuffer[vertexBufferCount]; + for (int i = 0; i < vertexBufferCount; i++) + { + vertexbuffers[i] = new FlverVertexBuffer(br, dataOffset); + } + + // Buffer layouts + Span bufferLayouts = stackalloc FlverBufferLayout[bufferLayoutCount]; + for (int i = 0; i < bufferLayoutCount; i++) + { + bufferLayouts[i] = new FlverBufferLayout(br); + } + + // Textures + Span textures = stackalloc FlverTexture[textureCount]; + for (int i = 0; i < textureCount; i++) + { + textures[i] = new FlverTexture(br); + } + + // Process the materials and meshes + GPUMeshes = new FlverSubmesh[meshCount]; + GPUMaterials = new FlverMaterial[materialCount]; + Bounds = new BoundingBox(); + //Bones = Flver.Bones; + + for (int i = 0; i < materialCount; i++) + { + GPUMaterials[i] = new FlverMaterial(); + ProcessMaterial(GPUMaterials[i], type, br, ref materials[i], textures, unicode); + } + + for (int i = 0; i < meshCount; i++) + { + GPUMeshes[i] = new FlverSubmesh(); + ProcessMesh(ref meshes[i], br, version, vertexbuffers, bufferLayouts, facesets, GPUMeshes[i]); + if (i == 0) + { + Bounds = GPUMeshes[i].Bounds; + } + else + { + Bounds = BoundingBox.Combine(Bounds, GPUMeshes[i].Bounds); + } + } + + if (GPUMeshes.Any(e => e.UseNormalWBoneTransform)) + { + StaticBoneBuffer = Renderer.BoneBufferAllocator.Allocate(64 * (uint)FBones.Count, 64); + Matrix4x4[] tbones = new Matrix4x4[FBones.Count]; + for (int i = 0; i < FBones.Count; i++) + { + tbones[i] = GetBoneObjectMatrix(FBones[i], FBones); + } + + Renderer.AddBackgroundUploadTask((d, cl) => + { + StaticBoneBuffer.FillBuffer(cl, tbones); + }); + } + + return true; + } + + public bool _Load(Memory bytes, AccessLevel al, GameType type) + { + bool ret; + if (type == GameType.DemonsSouls) + { + FlverDeS = FLVER0.Read(bytes); + ret = LoadInternalDeS(al, type); + } + else + { + if (al == AccessLevel.AccessGPUOptimizedOnly && type != GameType.DarkSoulsRemastered && type != GameType.DarkSoulsPTDE) + { + BinaryReaderEx br = new BinaryReaderEx(false, bytes); + DCX.Type ctype; + br = SFUtil.GetDecompressedBR(br, out ctype); + ret = LoadInternalFast(br, type); + } + else + { + var cache = (al == AccessLevel.AccessGPUOptimizedOnly) ? GetCache() : null; + Flver = FLVER2.Read(bytes, cache); + ret = LoadInternal(al, type); + ReleaseCache(cache); + } + } + return ret; + } + + public bool _Load(string path, AccessLevel al, GameType type) + { + bool ret; + if (type == GameType.DemonsSouls) + { + FlverDeS = FLVER0.Read(path); + ret = LoadInternalDeS(al, type); + } + else + { + if (al == AccessLevel.AccessGPUOptimizedOnly && type != GameType.DarkSoulsRemastered && type != GameType.DarkSoulsPTDE) + { + using var file = MemoryMappedFile.CreateFromFile(path, FileMode.Open, null, 0, MemoryMappedFileAccess.Read); + using var accessor = file.CreateMemoryAccessor(0, 0, MemoryMappedFileAccess.Read); + BinaryReaderEx br = new BinaryReaderEx(false, accessor.Memory); + DCX.Type ctype; + br = SFUtil.GetDecompressedBR(br, out ctype); + ret = LoadInternalFast(br, type); + } + else + { + var cache = (al == AccessLevel.AccessGPUOptimizedOnly) ? GetCache() : null; + Flver = FLVER2.Read(path, cache); + ret = LoadInternal(al, type); + ReleaseCache(cache); + } + } + return ret; + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + } + + if (GPUMaterials != null) + { + foreach (var m in GPUMaterials) + { + m.Dispose(); + } + } + + if (GPUMeshes != null) + { + foreach (var m in GPUMeshes) + { + m.GeomBuffer.Dispose(); + //Marshal.FreeHGlobal(m.PickingVertices); + } + } + + if (StaticBoneBuffer != null) + { + StaticBoneBuffer.Dispose(); + } + + disposedValue = true; + } + } + + ~FlverResource() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(false); + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/StudioCore/Resource/ResourceManager.cs b/StudioCore/Resource/ResourceManager.cs index 2ba3b9da4..8ab54d481 100644 --- a/StudioCore/Resource/ResourceManager.cs +++ b/StudioCore/Resource/ResourceManager.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using System.Threading.Tasks.Schedulers; using System.Threading; using System.Numerics; using System.IO; @@ -865,6 +864,18 @@ public static void OnGuiDrawResourceList() ImGui.End(); } + public static string GetModelPath(string modelName) + { + foreach (var item in ResourceDatabase) + { + if(item.Key.ToUpper().IndexOf(modelName.ToUpper()) >= 0) + { + return item.Key; + } + } + return ""; + } + public static void Shutdown() { JobScheduler.Dispose(); diff --git a/StudioCore/StudioCore.csproj b/StudioCore/StudioCore.csproj index 0799de734..ed7526abc 100644 --- a/StudioCore/StudioCore.csproj +++ b/StudioCore/StudioCore.csproj @@ -4,6 +4,7 @@ net7.0-windows 11 + faa5a10d-c786-42f5-bca0-31bc8d975cb9 @@ -425,9 +426,11 @@ + +