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 _ExecuteDelegate; public DelegateCommand(Action 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 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 ReturnSceneDefList = new List(); 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 SceneDefListResult = e.Result as List; 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 SceneDefList = new List(); //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 LODObjectList = new List(); //The root LODObjects (SLOD3 - in theory) related to this container. public ObservableCollection RootLODObjects { get; set; } public LODContainer(ContentNodeMap InputContentNodeMap) { this.SourceContentNodeMap = InputContentNodeMap; this.RootLODObjects = new ObservableCollection(); //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 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(); //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); } } } }