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 @@
+
+