551 lines
22 KiB
C#
Executable File
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); }
|
|
}
|
|
|
|
}
|
|
|
|
} |