Files
2025-09-29 00:52:08 +02:00

551 lines
22 KiB
C#
Executable File

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Collections.ObjectModel;
using System.IO;
using RSG.SceneXml;
using RSG.Base.ConfigParser;
using System.ComponentModel;
namespace LODBrowserSystem
{
public static class ContentMapNodeExtensions
{
//Gets the SceneXmlFilePath for the ContentNodeMap
public static String GetSceneXmlFilePath(this ContentNodeMap Target)
{
//Get the ContentNodeMapZip for out InputContentNodeMap
ContentNodeMapZip TargetContentMapNodeZip;
if (Target.HasOutputContentNode())
{
TargetContentMapNodeZip = Target.Outputs[0] as ContentNodeMapZip;
}
else
{
return null; //No valid outputs so return null
}
//Derive our SceneXML path and return it
return Path.GetFullPath((Path.Combine(TargetContentMapNodeZip.Path, Target.Name + ".xml")));
}
//Returns true if the passed ContentNodeMap has some output nodes set
public static Boolean HasOutputContentNode(this ContentNodeMap Target)
{
if (Target.Outputs.Count() > 0)
{ return true; }
else
{ return false; }
}
}
public class DelegateCommand : ICommand
{
Action<Object> _ExecuteDelegate;
public DelegateCommand(Action<Object> ExecuteDelegate)
{
_ExecuteDelegate = ExecuteDelegate;
}
public void Execute(Object Parameter)
{
_ExecuteDelegate(Parameter);
}
public bool CanExecute(Object parameter) { return true; }
public event EventHandler CanExecuteChanged;
}
public class LODObjectEventArgs : EventArgs
{
public LODObject[] TargetLODObjects { get; set; }
public LODObjectEventArgs(LODObject[] InputLODObjects)
{
this.TargetLODObjects = InputLODObjects;
}
}
public class LODBrowser : INotifyPropertyChanged
{
public LODBrowser()
{
_ConfigGameView = new ConfigGameView();
_ContentModel = _ConfigGameView.Content;
ContainerLodContentNameList = (from ContentNodeMap SubContentNodeMap
in _ConfigGameView.Content.Root.FindAll("map")
where SubContentNodeMap.MapType == ContentNodeMap.MapSubType.ContainerLod
select SubContentNodeMap.Name).ToArray();
//Register events for the scene xml background worker
_SceneXMLWorker.DoWork += this.SceneXMLWorker_StartWork;
_SceneXMLWorker.RunWorkerCompleted += this.SceneXMLWorker_CompletedWork;
_SceneXMLWorker.ProgressChanged += new ProgressChangedEventHandler(SceneXMLWorker_ProgressChanged);
//Create our merge command
this.Command_MergeLODObjects = new DelegateCommand(o =>
{
if (TargetLODContainer != null)
{
LODObject[] TargetLODObjects = TargetLODContainer.GetLODObjectsForMerge();
RaiseRequestMergeLODObjects(TargetLODObjects);
}
});
}
//Binds the incoming view to this view model by setting the data context of the view to this
//instance of LODBrowserTool and setting up and adding attached event listeners.
FrameworkElement _ViewRootElement;
public void BindView(FrameworkElement InputViewRootElement)
{
this._ViewRootElement = InputViewRootElement;
//Set data context
this._ViewRootElement.DataContext = this;
}
//Command for merging LODObjects into a max scene
public DelegateCommand Command_MergeLODObjects { get; private set; }
//Event to signal that the tool wants to merge some LODObjects
public event EventHandler<LODObjectEventArgs> RequestMergeLODObjects = delegate { };
void RaiseRequestMergeLODObjects(LODObject[] TargetLODObjects)
{
RequestMergeLODObjects(this, new LODObjectEventArgs(TargetLODObjects));
}
//Background worker for reading large amounts of Scene .xml files
readonly BackgroundWorker _SceneXMLWorker = new BackgroundWorker { WorkerReportsProgress = true };
ConfigGameView _ConfigGameView;
ContentModel _ContentModel;
//Content name of all the lod containers in the content model
public String[] ContainerLodContentNameList { get; set; }
//Property which lets the UI know if the system is available to switch to another container lod
public Boolean _WorkerAvailable = true;
public Boolean WorkerAvailable
{
get { return _WorkerAvailable; }
set
{
_WorkerAvailable = value;
RaisePropertyChanged("WorkerAvailable");
}
}
//Selected ContainerLod content name
String _SelectedContainerLodContentName;
public String SelectedContainerLodContentName
{
get { return _SelectedContainerLodContentName; }
set
{
_SelectedContainerLodContentName = value;
//Get the target ContentNodeMap object - this will be of type ContainerLod
ContentNodeMap TargetContentNodeMap = _ContentModel.Root.FindFirst(_SelectedContainerLodContentName) as ContentNodeMap;
//Get the scene xml file path for all sibliing ContentNodeMap objects of type Container or ContainerProps
var SceneXmlFilePaths = (from ContentNodeMap SourceContentMapNode in (TargetContentNodeMap.Parent as ContentNodeGroup).Children
where SourceContentMapNode.MapType == ContentNodeMap.MapSubType.Container ||
SourceContentMapNode.MapType == ContentNodeMap.MapSubType.ContainerProps &&
SourceContentMapNode.HasOutputContentNode()
select SourceContentMapNode.GetSceneXmlFilePath()).ToArray();
//Process the list so it only contains scene xml file paths which haven't yet been cached
SceneXmlFilePaths = LODContainer.ProcessSceneXMLFilePaths(SceneXmlFilePaths);
//Cache the require scene xml files in the background.
_SceneXMLWorker.RunWorkerAsync(SceneXmlFilePaths);
//Set that the worker is now unavailable. Stops us despatching multiple container lod requests.
WorkerAvailable = false;
//Raise property changed
RaisePropertyChanged("SelectedContainerLodContentName");
}
}
//The loaded LODContainer
LODContainer _TargetLODContainer;
public LODContainer TargetLODContainer
{
get { return _TargetLODContainer; }
set
{
_TargetLODContainer = value;
RaisePropertyChanged("TargetLODContainer");
}
}
//Current progress value for reading SceneXml files
Double _LoadProgressValue = 0.0;
public Double LoadProgressValue
{
get { return _LoadProgressValue; }
set
{
_LoadProgressValue = value;
RaisePropertyChanged("LoadProgressValue");
}
}
// Declare the PropertyChanged event
public event PropertyChangedEventHandler PropertyChanged;
// NotifyPropertyChanged will raise the PropertyChanged event passing the
// source property that is being updated.
public void RaisePropertyChanged(String PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
//Event handler for when the SceneXMLWorker is asked to start work
void SceneXMLWorker_StartWork(object sender, DoWorkEventArgs e)
{
//Get the files to be processed and based off this get percentage step value
var SceneXMLFilePathArray = e.Argument as String[];
Double PercentageStep = 100.0 / SceneXMLFilePathArray.Count();
Double CurrentPercentage = 0.0;
//Load each of the scene def
List<Scene> ReturnSceneDefList = new List<Scene>();
foreach (var SceneXMLFilePath in SceneXMLFilePathArray)
{
ReturnSceneDefList.Add(Scene.Load(SceneXMLFilePath, LoadOptions.Objects, false));
_SceneXMLWorker.ReportProgress((Int32)(CurrentPercentage += PercentageStep));
}
//Set our result to the collected SceneDef objects
e.Result = ReturnSceneDefList;
}
//Event handler for when the SceneXMLWorker has finsihed it's current batch of work
void SceneXMLWorker_CompletedWork(object sender, RunWorkerCompletedEventArgs e)
{
//Get our result and append it to our cached SceneDefs
List<Scene> SceneDefListResult = e.Result as List<Scene>;
LODContainer.SceneDefList.AddRange(SceneDefListResult);
//Get the target ContentNodeMap object - this will be of type ContainerLod
ContentNodeMap TargetContentNodeMap = _ContentModel.Root.FindFirst(_SelectedContainerLodContentName) as ContentNodeMap;
TargetLODContainer = new LODContainer(TargetContentNodeMap);
//Set that the work completed this operation so is now available again
WorkerAvailable = true;
}
//Event handler for when the SceneXMLWorker posts an increase in progress
void SceneXMLWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.LoadProgressValue = e.ProgressPercentage;
}
}
public class LODContainer
{
//The source content node for the LOD container
ContentNodeMap SourceContentNodeMap;
//Cache of SceneDef objects
public static List<Scene> SceneDefList = new List<Scene>();
//Resolves a scene def from the passed ContentNodeMap. Essentially it tries to find one in our
//cache and if it fails to find it will try to create a new one.
public static Scene ResolveSceneDef(ContentNodeMap InputContentNodeMap)
{
//Get the SceneDef from our cache (if it exists)
var SceneDef = (from CachedSceneDef in SceneDefList
where CachedSceneDef.Filename == InputContentNodeMap.GetSceneXmlFilePath()
select CachedSceneDef).FirstOrDefault();
//If we didn't find one in the cache then we'll make a new one
if (SceneDef == null)
{
if (InputContentNodeMap.HasOutputContentNode())
{
SceneDef = Scene.Load(InputContentNodeMap.GetSceneXmlFilePath(), LoadOptions.Objects, false);
//If it's valid then add it into our cache.
if (SceneDef != null)
{
SceneDefList.Add(SceneDef);
}
}
}
return SceneDef;
}
//Processes and returns an array of scene xml file path strings which haven't yet been cached
public static String[] ProcessSceneXMLFilePaths(String[] InputSceneXMLFilePaths)
{
//Get all currently cached scene xml file paths
var CachedSceneXMLFilePaths = from CachedSceneDef in SceneDefList select CachedSceneDef.Filename;
//Get all the uncached scene xml file paths by doing a comparison against the cached list
var ValidSceneXMLFilePaths = from InputSceneXMLFilePath in InputSceneXMLFilePaths
where CachedSceneXMLFilePaths.Contains(InputSceneXMLFilePath) == false
select InputSceneXMLFilePath;
return ValidSceneXMLFilePaths.ToArray();
}
//A handle to all of the LOD objects related to this LOD container
List<LODObject> LODObjectList = new List<LODObject>();
//The root LODObjects (SLOD3 - in theory) related to this container.
public ObservableCollection<LODObject> RootLODObjects { get; set; }
public LODContainer(ContentNodeMap InputContentNodeMap)
{
this.SourceContentNodeMap = InputContentNodeMap;
this.RootLODObjects = new ObservableCollection<LODObject>();
//Process the scene xml for the LOD container first
ProcessContentMapNode(this.SourceContentNodeMap);
//Fill out our root LODObjects. These are objects in the LODContainer that have
//no parent so are at the top of the heirarchy. Or in other words they
//are SLOD3 (or should be at least)
foreach (var SubLODObject in LODObjectList)
{
if (SubLODObject.LODParent == null)
{
RootLODObjects.Add(SubLODObject);
}
}
//Then we load in the scene xml for every source content node and processing the SceneXML.
//This should file out the remaining information.
foreach (ContentNodeMap SubSourceContentNodeMap in (SourceContentNodeMap.Parent as ContentNodeGroup).Children)
{
ProcessContentMapNode(SubSourceContentNodeMap);
}
}
//Takes an ObjectDef as input and returns a LODObject which wraps the ObjectDef
LODObject ResolveLODObject(TargetObjectDef InputObjectDef, ContentNodeMap InputContentNodeMap)
{
//If the ObjectDef is a rscontainerlodref we'll dig into our LODObject list and try and find a match by a name
if (InputObjectDef.Class == "rscontainerlodref")
{
if (InputObjectDef.ParamBlocks.Count() > 0)
{
String TargetObjectName;
if (InputObjectDef.ParamBlocks[0].ContainsKey("objectname"))
{
TargetObjectName = InputObjectDef.ParamBlocks[0]["objectname"] as String;
}
else
{ return null; } //If we don't have an object name param then there is likely something wrong with the data.
//Now try and get the target LODObject from the TargetObjectName
var MatchingLODObject = (from QueryLODObject in LODObjectList
where String.Compare(QueryLODObject.SourceObjectDef.Name, TargetObjectName, true) == 0
select QueryLODObject).FirstOrDefault();
return MatchingLODObject;
}
else { return null; } //If we don't have any params blocks then something is likely wrong with data.
}
else
{
//Find our LODObject by objectdef. If we fail to find one we make one.
var MatchingLODObject = (from QueryLODObject in LODObjectList
where QueryLODObject.SourceObjectDef.Guid == InputObjectDef.Guid
select QueryLODObject).FirstOrDefault();
if (MatchingLODObject == null)
{
MatchingLODObject = new LODObject(InputObjectDef, InputContentNodeMap);
LODObjectList.Add(MatchingLODObject);
}
return MatchingLODObject;
}
}
//Process LOD information from the passed ContentMapNode.
void ProcessContentMapNode(ContentNodeMap InputContentNodeMap)
{
//first we'll check that we are passing in a valid ContentNodeMap
if (InputContentNodeMap.MapType == ContentNodeMap.MapSubType.Container ||
InputContentNodeMap.MapType == ContentNodeMap.MapSubType.ContainerProps ||
InputContentNodeMap.MapType == ContentNodeMap.MapSubType.ContainerLod)
{
//Get our scene def
var SceneDef = ResolveSceneDef(InputContentNodeMap);
//If the sceneDef doesn't have any children then we'll return and do nothing
if (SceneDef == null || SceneDef.Objects.Count() != 1 || SceneDef.Objects[0].Children.Count() == 0)
{
return;
}
//Go through all the root level object defs looking for objects which have some lod hierarchy information
foreach (var RootLevelObjectDef in SceneDef.Objects[0].Children)
{
//If we have some LOD information we'll act on it
if (RootLevelObjectDef.LOD != null)
{
//Get the LOD object for this ObjectDef
var TargetLODObject = ResolveLODObject(RootLevelObjectDef, InputContentNodeMap);
//If the ObjectDef has a parent then process it
if (RootLevelObjectDef.LOD.Parent != null)
{
var ParentLODObject = ResolveLODObject(RootLevelObjectDef.LOD.Parent, InputContentNodeMap);
TargetLODObject.LODParent = ParentLODObject;
}
//If the ObjectDef has any children we'll process them as well
foreach (var LODChildObjectDef in RootLevelObjectDef.LOD.Children)
{
var ChildLODObject = ResolveLODObject(LODChildObjectDef, InputContentNodeMap);
TargetLODObject.AddChildLODObject(ChildLODObject);
}
}
}
}
}
internal LODObject[] GetLODObjectsForMerge()
{
return (from SubLODObject in LODObjectList where SubLODObject.MarkedForMerge select SubLODObject).ToArray();
}
}
public class LODObject : INotifyPropertyChanged
{
//Source ContentNodeMap that this LODObject comes from
public ContentNodeMap SourceContentNodeMap { get; private set; }
//Source ObjectDef this LODObject wraps
public TargetObjectDef SourceObjectDef { get; private set; }
//Source art file (.maxc) for the LODObject
public String SourceFilePath
{
get { return Path.Combine(SourceContentNodeMap.Path, SourceContentNodeMap.Name + ".maxc"); }
}
//Source container name
public String SourceContainerName
{
get { return SourceContentNodeMap.Name; }
}
//Source object name
public String SourceObjectName
{
get { return SourceObjectDef.Name; }
}
//Source transform of object
public RSG.Base.Math.Matrix34f SourceObjectTransform
{
get { return SourceObjectDef.NodeTransform; }
}
//Parent LODObject. Can only have one parent
public LODObject LODParent { get; set; }
//Children LODObjects.
public List<LODObject> LODChildren { get; set; }
//Returns true if the LODObject has children
public Boolean HasChildren
{
get { return LODChildren.Count > 0; }
}
//Indicates if the object is marked for merge
Boolean _MarkedForMerge = false;
public Boolean MarkedForMerge
{
get { return _MarkedForMerge; }
set
{
_MarkedForMerge = value;
RaisePropertyChanged("MarkedForMerge");
}
}
// Declare the PropertyChanged event
public event PropertyChangedEventHandler PropertyChanged;
// NotifyPropertyChanged will raise the PropertyChanged event passing the
// source property that is being updated.
public void RaisePropertyChanged(String PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
//Checks that the passed LODObject hasn't already been added as a child - adds it if need be.
internal void AddChildLODObject(LODObject InputLODObject)
{
if (!LODChildren.Contains(InputLODObject))
{
LODChildren.Add(InputLODObject);
}
}
//Primary constuctor for a LOD object
public LODObject(TargetObjectDef InputSourceObjectDef, ContentNodeMap InputContentNodeMap)
{
this.SourceObjectDef = InputSourceObjectDef;
this.SourceContentNodeMap = InputContentNodeMap;
this.LODChildren = new List<LODObject>();
//Setup commands
this.Command_MarkDirectChildren = new DelegateCommand(o => { MarkChildrenForMerge(false); });
this.Command_MarkAllChildren = new DelegateCommand(o => { MarkChildrenForMerge(true); });
}
//Marks children for merge. Recurses if passed true.
void MarkChildrenForMerge(Boolean Recursive)
{
foreach (var ChildLODObject in this.LODChildren)
{
ChildLODObject.MarkedForMerge = true;
if (Recursive)
{
ChildLODObject.MarkChildrenForMerge(Recursive);
}
}
}
//Command for marking direct child LODObjects for merge
public DelegateCommand Command_MarkDirectChildren { get; private set; }
//Command for marking all child LODObjects for merge
public DelegateCommand Command_MarkAllChildren { get; private set; }
//Returns a display friendly name for the LODObject
public String HeaderString
{
get { return String.Format("{0} [{1}]", SourceObjectDef.Name, SourceContentNodeMap.Name); }
}
}
}