using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using JD.SceneExplorer.Utils; using JD.WPF.Models; namespace JD.SceneExplorer.ViewModels { public class SceneTree : ObjectWithRef { public const string SceneFileExtension = "isc"; private readonly ObservableCollection m_childScenesInternal = new ObservableCollection(); private readonly ObservableCollection m_actorsInternal = new ObservableCollection(); private readonly ObservableCollection m_frisesInternal = new ObservableCollection(); private readonly ReadOnlyObservableCollection m_childScenes; private readonly ReadOnlyObservableCollection m_actors; private readonly ReadOnlyObservableCollection m_frises; private String m_uniqueName; private String m_path; private uint m_relatedActorRef; public SceneTree(uint objectRef, SceneTree parent, System.ComponentModel.PropertyChangedEventHandler propertyChangedHandler) : base(objectRef, parent, propertyChangedHandler) { m_childScenes = new ReadOnlyObservableCollection(m_childScenesInternal); m_actors = new ReadOnlyObservableCollection(m_actorsInternal); m_frises = new ReadOnlyObservableCollection(m_frisesInternal); m_relatedActorRef = ObjectWithRef.InvalidObject.ObjectRef; } public SceneTree(uint objectRef, SceneTree parent) : this(objectRef, parent, parent != null ? parent.PropertyChangedHandler : null) { } public override ObjectWithRefType Type { get { return ObjectWithRefType.Scene; } } protected override void LoadChildren() { List itemsToAdd = new List(); itemsToAdd.Capacity = m_childScenes.Count + m_actors.Count + m_frises.Count; foreach (ObjectWithRef itemToAdd in m_childScenes.Union(m_actors).Union(m_frises)) { //if (itemToAdd.IsFiltered) // continue; if (Children.FirstOrDefault(child => child == itemToAdd) != null) continue; // TODO : Triggers a lot of events. Override ObservableCollection to add an AddRange function? if (Children.Count == 0) Children.Add(itemToAdd); else Children.Insert(FindInsertPosition(itemToAdd), itemToAdd); itemToAdd.UpdateChildren(); } // Use optimized filling if available. //if (Children is MultiThreadedObservableRangeCollection) //{ // (Children as MultiThreadedObservableRangeCollection).AddRange(itemsToAdd); //} //else //{ // foreach (ObjectWithRef itemToAdd in itemsToAdd) // Children.Add(itemToAdd); //} UpdateFlags(false); } protected override int GetChildrenCount() { return m_childScenes.Count + m_actors.Count + m_frises.Count; } public override uint PickableRef { get { return ObjectWithRef.InvalidObject.ObjectRef; } } public override bool CanHide { get { return false; } } public override bool Visible { get { if (!ChildrenLoaded) return false; if (Children.Count == 0) return true; else return Children.Cast().Any(child => child.Visible); } set { if (Visible != value) { if (ChildrenLoaded) { foreach (ObjectWithRef child in Children) child.Visible = value; } SceneTree parent = Parent as SceneTree; while (parent != null) { (Parent as SceneTree).RaisePropertyChanged("Visible"); parent = parent.Parent as SceneTree; } m_visible = value; RaisePropertyChanged("Visible"); } } } public string UniqueName { get { return m_uniqueName; } set { if (m_uniqueName != value) { m_uniqueName = value; RaisePropertyChanged("UniqueName"); } } } public string Path { get { return m_path; } set { if (m_path != value) { m_path = value; FilePath = m_path; RaisePropertyChanged("Path"); } } } public uint RelatedActorRef { get { return m_relatedActorRef; } set { if (m_relatedActorRef != value) { m_relatedActorRef = value; RaisePropertyChanged("RelatedActorRef"); } } } public virtual bool IsLockAvailable { get { return false; } } /// /// Applies filter to elements of the current scene. /// /// The filter string. /// True if the scene is filtered, false otherwise. public override bool SetFilter(object filterData) { return SetFilter(filterData, System.Threading.CancellationToken.None); } public override bool SetFilter(object filterData, System.Threading.CancellationToken cancellationToken) { bool isSceneFiltered = base.SetFilter(filterData, cancellationToken); TagSelectorData newFilter = (TagSelectorData)filterData; if (isSceneFiltered || (newFilter != null && !newFilter.IsFiltering)) { foreach (Actor actor in m_actors) { cancellationToken.ThrowIfCancellationRequested(); IsFiltered &= actor.SetFilter(newFilter, cancellationToken); } foreach (Frise frise in m_frises) { cancellationToken.ThrowIfCancellationRequested(); IsFiltered &= frise.SetFilter(newFilter, cancellationToken); } foreach (SceneTree subScene in m_childScenes) { cancellationToken.ThrowIfCancellationRequested(); IsFiltered &= subScene.SetFilter(newFilter, cancellationToken); } if (newFilter.IsFiltering && !IsFiltered) IsExpanded = true; } else { SetFilterAndForceVisibility(newFilter); IsFiltered = false; } return IsFiltered; } /// /// Applies filter to elements of the current scene. All child elements are visible. This function acts only on expanded state. /// /// The filter string. /// True if the scene is filtered, false otherwise. private bool SetFilterAndForceVisibility(TagSelectorData newFilter) { return SetFilterAndForceVisibility(newFilter, System.Threading.CancellationToken.None); } private bool SetFilterAndForceVisibility(TagSelectorData newFilter, System.Threading.CancellationToken cancellationToken) { // Force parent expand, but do not expand the current node. It will stay collapsed unless a child matches the filter. IsExpanded = true; bool childMatchFilter = false; foreach (Actor actor in m_actors) { cancellationToken.ThrowIfCancellationRequested(); childMatchFilter |= !actor.SetFilter(newFilter, cancellationToken); actor.IsFiltered = false; } foreach (Frise frise in m_frises) { cancellationToken.ThrowIfCancellationRequested(); childMatchFilter |= !frise.SetFilter(newFilter, cancellationToken); frise.IsFiltered = false; } foreach (SceneTree subScene in m_childScenes) { cancellationToken.ThrowIfCancellationRequested(); bool sceneFiltered = subScene.SetFilterAndForceVisibility(newFilter, cancellationToken); childMatchFilter |= !sceneFiltered; subScene.IsFiltered = sceneFiltered; subScene.IsFiltered = false; } if (!childMatchFilter) { IsExpanded = false; } return !childMatchFilter; } public ReadOnlyObservableCollection ChildScenes { get { return m_childScenes; } } public ReadOnlyObservableCollection Actors { get { return m_actors; } } public ReadOnlyObservableCollection Frises { get { return m_frises; } } public static SceneTree LoadFromBlob(JD.Remoting.IBlob _blob, SceneTree parent) { string uniqueName = _blob.ExtractString(); string path = _blob.ExtractPath(); string friendlyName = _blob.ExtractString(); uint objectRef = _blob.ExtractUInt(); uint relatedActorRef = _blob.ExtractUInt(); bool isWorldActive = _blob.ExtractBool(); uint tagsCount = _blob.ExtractUInt(); List tags = new List(); for (int i = 0; i < tagsCount; i++) tags.Add(_blob.ExtractString()); SceneTree newSceneTree = new SceneTree(objectRef, parent); newSceneTree.LoadFromBlobInternal(_blob, uniqueName, path, friendlyName, relatedActorRef, isWorldActive, tags); return newSceneTree; } protected void LoadFromBlobInternal(JD.Remoting.IBlob _blob, string uniqueName, string path, string friendlyName, uint relatedActorRef, bool isWorldActive, List tags) { UniqueName = uniqueName; Path = path; FriendlyName = friendlyName; RelatedActorRef = relatedActorRef; Active = isWorldActive; foreach (string tag in tags) Tags.Add(tag); AddSpecialTags(); //extract actors uint actorCount = _blob.ExtractUInt(); for (uint actorIndex = 0; actorIndex < actorCount; actorIndex++) { var newActor = Actor.LoadFromBlob(_blob, this); InsertActor(newActor); } //extract frises uint friseCount = _blob.ExtractUInt(); for (uint friseIndex = 0; friseIndex < friseCount; friseIndex++) { var newFrise = Frise.LoadFromBlob(_blob, this); InsertFrise(newFrise); } ExtractSubScenes(_blob); IconSource = EditorIconsManager.Instance.GetIconLocationFromTags(Tags); NeedUpdate = true; } protected void AddSpecialTags() { if (Path.EndsWith(".isc") && (Tags.FirstOrDefault(tag => tag == "isc") == null)) Tags.Add("isc"); if (FriendlyName.EndsWith("_LD")) Tags.Add("isc_ld_square"); else if (FriendlyName.EndsWith("_GRAPH")) Tags.Add("isc_graph_square"); else if (FriendlyName.EndsWith("_CINE")) Tags.Add("isc_cine_square"); else if (FriendlyName.EndsWith("_SOUND")) Tags.Add("isc_sound_square"); else if (FriendlyName.EndsWith("_FX")) Tags.Add("isc_fx_square"); } protected virtual void ExtractSubScenes(JD.Remoting.IBlob _blob) { //extract subscenes uint childCount = _blob.ExtractUInt(); for (uint childIndex = 0; childIndex < childCount; childIndex++) { var newChildSceneTree = SubSceneTree.LoadFromBlob(_blob, this); InsertSubScene(newChildSceneTree); } } /// /// find a scene by sceneRef among all hierarchy /// /// /// public SceneTree FindScene(uint _sceneRef) { if (_sceneRef == m_objectRef) return this; foreach (SceneTree subscene in m_childScenes) { SceneTree foundScene = subscene.FindScene(_sceneRef); if (foundScene != null) { return foundScene; } } return null; } public SceneTree FindScene(string friendlyName, bool recursive = true) { foreach (SceneTree subScene in m_childScenes) { if (subScene.FriendlyName == friendlyName) return subScene; if (recursive) { SceneTree foundScene = subScene.FindScene(friendlyName); if (foundScene != null) return foundScene; } } return null; } public SceneTree FindSceneActor(uint _sceneActorRef) { if (_sceneActorRef == m_relatedActorRef) return this; foreach (SceneTree subscene in m_childScenes) { SceneTree foundScene = subscene.FindSceneActor(_sceneActorRef); if (foundScene != null) { return foundScene; } } return null; } public ObjectWithRef FindObject(uint _objRef) { // FIXME : Ugly. return FindObject(new ObjectWithRef(_objRef, null, null)); } public ObjectWithRef FindObject(ObjectWithRef _objRef) { if (Equals(_objRef)) return _objRef; ObjectWithRef objRef; objRef = Actors.FirstOrDefault(actor => actor.Equals(_objRef)); if (objRef != null) return objRef; objRef = Frises.FirstOrDefault(frise => frise.Equals(_objRef)); if (objRef != null) return objRef; foreach (SceneTree subScene in ChildScenes) { if (subScene.RelatedActorRef == _objRef.ObjectRef) return subScene; objRef = subScene.FindObject(_objRef); if (objRef != null) return objRef; } return null; } public ObjectWithRef FindObject(string friendlyName, bool recursive = true) { foreach (Actor actor in m_actors) { if (actor.FriendlyName == friendlyName) return actor; } foreach (Frise frise in m_frises) { if (frise.FriendlyName == friendlyName) return frise; } SceneTree subScene = FindScene(friendlyName, recursive); if (subScene != null) return subScene; return null; } //private int FindInsertPosition(List targetList, ObjectWithRef objectRef) //{ // return FindInsertPosition(targetList, objectRef, 0, targetList.Count); //} //private int FindInsertPosition(List targetList, ObjectWithRef objectRef, int begin, int end) //{ // if (begin == end) // return begin; // int current = (begin + end) / 2; // ObjectWithRef curObjectRef = targetList[current] as ObjectWithRef; // int compareResult = curObjectRef.CompareTo(objectRef); // if (compareResult == 0) // return current; // else if (compareResult < 0) // return FindInsertPosition(targetList, objectRef, current + 1, end); // else // return FindInsertPosition(targetList, objectRef, begin, current); //} private int FindInsertPosition(ObjectWithRef objectRef) { return FindInsertPosition(objectRef, 0, Children.Count); } private int FindInsertPosition(ObjectWithRef objectRef, int begin, int end) { if (begin == end) return begin; int current = (begin + end) / 2; ObjectWithRef curObjectRef = Children[current] as ObjectWithRef; int compareResult = curObjectRef.CompareTo(objectRef); if (compareResult == 0) return current; else if (compareResult < 0) return FindInsertPosition(objectRef, current + 1, end); else return FindInsertPosition(objectRef, begin, current); } public void InsertActor(Actor newActor) { m_actorsInternal.Add(newActor); NeedUpdate = true; } public void InsertFrise(Frise newFrise) { m_frisesInternal.Add(newFrise); NeedUpdate = true; } public void InsertSubScene(SceneTree newSubScene) { m_childScenesInternal.Add(newSubScene); Actor actor = m_actorsInternal.FirstOrDefault(act => act.ObjectRef == newSubScene.m_relatedActorRef); if (actor != null) m_actorsInternal.Remove(actor); NeedUpdate = true; } public void RemoveActor(Actor actor) { Actor objectRef = m_actorsInternal.FirstOrDefault(curActor => curActor == actor); if (objectRef != null) { m_actorsInternal.Remove(objectRef); NeedUpdate = true; } } public void RemoveFrise(Frise frise) { Frise objectRef = m_frisesInternal.FirstOrDefault(curFrise => curFrise == frise); if (objectRef != null) { m_frisesInternal.Remove(objectRef); NeedUpdate = true; } } public void ClearScene() { m_actorsInternal.Clear(); m_frisesInternal.Clear(); m_childScenesInternal.Clear(); UnregisterPropertyChanged(); NeedUpdate = true; } public void ClearSelection() { IsSelected = false; foreach (Actor actor in Actors) actor.IsSelected = false; foreach (Frise frise in Frises) frise.IsSelected = false; foreach (SceneTree subSceneTree in ChildScenes) { subSceneTree.IsSelected = false; subSceneTree.ClearSelection(); } } public void GetSelection(List selectedItems, bool includeScenes) { selectedItems.AddRange(Actors.Union(Frises).Where(objRef => objRef.IsSelected)); if (includeScenes) selectedItems.AddRange(ChildScenes.Where(objRef => objRef.IsSelected)); foreach (SceneTree subScene in ChildScenes) subScene.GetSelection(selectedItems, includeScenes); } } public class RootSceneTree : SceneTree, IEquatable { public RootSceneTree(System.ComponentModel.PropertyChangedEventHandler propertyChangedHandler) : base(0, null, propertyChangedHandler) { ShouldShowRoot = false; } public override ObjectWithRefType Type { get { return ObjectWithRefType.RootScene; } } public void LoadFromBlob(JD.Remoting.IBlob _blob) { string uniqueName = _blob.ExtractString(); string path = _blob.ExtractPath(); string friendlyName = _blob.ExtractString(); uint objectRef = _blob.ExtractUInt(); uint relatedActorRef = _blob.ExtractUInt(); bool isWorldActive = _blob.ExtractBool(); uint tagsCount = _blob.ExtractUInt(); List tags = new List(); for (int i = 0; i < tagsCount; i++) tags.Add(_blob.ExtractString()); if (ObjectRef != objectRef) throw new InvalidOperationException("Object references not matching. "); LoadFromBlobInternal(_blob, uniqueName, path, friendlyName, relatedActorRef, isWorldActive, tags); NeedUpdate = true; } protected override void ExtractSubScenes(JD.Remoting.IBlob _blob) { //extract subscenes uint childCount = _blob.ExtractUInt(); for (uint childIndex = 0; childIndex < childCount; childIndex++) { var newChildSceneTree = SceneTree.LoadFromBlob(_blob, this); InsertSubScene(newChildSceneTree); } } public override uint PickableRef { get { return ObjectWithRef.InvalidObject.ObjectRef; } } /// public bool Equals(RootSceneTree other) { return this.Id == (other?.Id).GetValueOrDefault(Guid.Empty); } /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; return Equals((RootSceneTree) obj); } /// public override int GetHashCode() { return this.Id.GetHashCode(); } public Guid Id { get; } = Guid.NewGuid(); } public class SubSceneTree : SceneTree { public SubSceneTree(uint objectRef, SceneTree parent) : base(objectRef, parent) { } public override ObjectWithRefType Type { get { return ObjectWithRefType.SubScene; } } // TODO : Find a clean way to remove this duplicate code ... public new static SubSceneTree LoadFromBlob(JD.Remoting.IBlob _blob, SceneTree parent) { string uniqueName = _blob.ExtractString(); string path = _blob.ExtractPath(); string friendlyName = _blob.ExtractString(); uint objectRef = _blob.ExtractUInt(); uint relatedActorRef = _blob.ExtractUInt(); bool isWorldActive = _blob.ExtractBool(); uint tagsCount = _blob.ExtractUInt(); List tags = new List(); for (int i = 0; i < tagsCount; i++) tags.Add(_blob.ExtractString()); SubSceneTree newSceneTree = new SubSceneTree(objectRef, parent); newSceneTree.LoadFromBlobInternal(_blob, uniqueName, path, friendlyName, relatedActorRef, isWorldActive, tags); return newSceneTree; } public override bool CanHide { get { return true; } } public override bool Visible { get { if (!ChildrenLoaded) return false; if (Children.Count == 0) return m_visible; else return Children.Cast().Any(child => child.Visible); } set { base.Visible = value; } } public override uint PickableRef { get { if (RelatedActorRef != ObjectWithRef.InvalidObject.ObjectRef) return RelatedActorRef; else return base.PickableRef; } } public override bool IsLockAvailable { get { if (Parent is SceneTree && !(Parent is SubSceneTree)) return true; return base.IsLockAvailable; } } } }