You're breathtaking!
This commit is contained in:
596
Source/Editor/Modules/SceneModule.cs
Normal file
596
Source/Editor/Modules/SceneModule.cs
Normal file
@@ -0,0 +1,596 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.SceneGraph.Actors;
|
||||
using FlaxEngine;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Modules
|
||||
{
|
||||
/// <summary>
|
||||
/// Scenes and actors management module.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Modules.EditorModule" />
|
||||
public sealed class SceneModule : EditorModule
|
||||
{
|
||||
/// <summary>
|
||||
/// The root node for the scene graph created for the loaded scenes and actors hierarchy.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.SceneGraph.RootNode" />
|
||||
public class ScenesRootNode : RootNode
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Spawn(Actor actor, Actor parent)
|
||||
{
|
||||
Editor.Instance.SceneEditing.Spawn(actor, parent);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Undo Undo => Editor.Instance.Undo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The root tree node for the whole scene graph.
|
||||
/// </summary>
|
||||
public ScenesRootNode Root;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when actor gets removed. Editor and all submodules should remove references to that actor.
|
||||
/// </summary>
|
||||
public event Action<ActorNode> ActorRemoved;
|
||||
|
||||
internal SceneModule(Editor editor)
|
||||
: base(editor)
|
||||
{
|
||||
// After editor cache but before the windows
|
||||
InitOrder = -900;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the scene as modified.
|
||||
/// </summary>
|
||||
/// <param name="scene">The scene.</param>
|
||||
public void MarkSceneEdited(Scene scene)
|
||||
{
|
||||
MarkSceneEdited(GetActorNode(scene) as SceneNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the scene as modified.
|
||||
/// </summary>
|
||||
/// <param name="scene">The scene.</param>
|
||||
public void MarkSceneEdited(SceneNode scene)
|
||||
{
|
||||
if (scene != null)
|
||||
scene.IsEdited = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the scenes as modified.
|
||||
/// </summary>
|
||||
/// <param name="scenes">The scenes.</param>
|
||||
public void MarkSceneEdited(IEnumerable<Scene> scenes)
|
||||
{
|
||||
foreach (var scene in scenes)
|
||||
MarkSceneEdited(scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks all the scenes as modified.
|
||||
/// </summary>
|
||||
public void MarkAllScenesEdited()
|
||||
{
|
||||
MarkSceneEdited(Level.Scenes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified scene is edited.
|
||||
/// </summary>
|
||||
/// <param name="scene">The scene.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified scene is edited; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public bool IsEdited(Scene scene)
|
||||
{
|
||||
var node = GetActorNode(scene) as SceneNode;
|
||||
return node?.IsEdited ?? false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether any scene is edited.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if any scene is edited; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public bool IsEdited()
|
||||
{
|
||||
foreach (var scene in Root.ChildNodes)
|
||||
{
|
||||
if (scene is SceneNode node && node.IsEdited)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether every scene is edited.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if every scene is edited; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public bool IsEverySceneEdited()
|
||||
{
|
||||
foreach (var scene in Root.ChildNodes)
|
||||
{
|
||||
if (scene is SceneNode node && !node.IsEdited)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the new scene file. The default scene contains set of simple actors.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
public void CreateSceneFile(string path)
|
||||
{
|
||||
// Create a sample scene
|
||||
var scene = new Scene();
|
||||
scene.StaticFlags = StaticFlags.FullyStatic;
|
||||
//
|
||||
var sun = scene.AddChild<DirectionalLight>();
|
||||
sun.Name = "Sun";
|
||||
sun.LocalPosition = new Vector3(40, 160, 0);
|
||||
sun.LocalEulerAngles = new Vector3(45, 0, 0);
|
||||
sun.StaticFlags = StaticFlags.FullyStatic;
|
||||
//
|
||||
var sky = scene.AddChild<Sky>();
|
||||
sky.Name = "Sky";
|
||||
sky.LocalPosition = new Vector3(40, 150, 0);
|
||||
sky.SunLight = sun;
|
||||
sky.StaticFlags = StaticFlags.FullyStatic;
|
||||
//
|
||||
var skyLight = scene.AddChild<SkyLight>();
|
||||
skyLight.Mode = SkyLight.Modes.CustomTexture;
|
||||
skyLight.Brightness = 2.5f;
|
||||
skyLight.CustomTexture = FlaxEngine.Content.LoadAsyncInternal<CubeTexture>(EditorAssets.DefaultSkyCubeTexture);
|
||||
skyLight.StaticFlags = StaticFlags.FullyStatic;
|
||||
//
|
||||
var floor = scene.AddChild<StaticModel>();
|
||||
floor.Name = "Floor";
|
||||
floor.Scale = new Vector3(4, 0.5f, 4);
|
||||
floor.Model = FlaxEngine.Content.LoadAsync<Model>(StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Primitives/Cube.flax"));
|
||||
if (floor.Model)
|
||||
{
|
||||
floor.Model.WaitForLoaded();
|
||||
floor.SetMaterial(0, FlaxEngine.Content.LoadAsync<MaterialBase>(StringUtils.CombinePaths(Globals.EngineContentFolder, "Engine/WhiteMaterial.flax")));
|
||||
}
|
||||
floor.StaticFlags = StaticFlags.FullyStatic;
|
||||
//
|
||||
var cam = scene.AddChild<Camera>();
|
||||
cam.Name = "Camera";
|
||||
cam.Position = new Vector3(0, 150, -300);
|
||||
|
||||
// Serialize
|
||||
var bytes = Level.SaveSceneToBytes(scene);
|
||||
|
||||
// Cleanup
|
||||
Object.Destroy(ref scene);
|
||||
|
||||
if (bytes == null || bytes.Length == 0)
|
||||
throw new Exception("Failed to serialize scene.");
|
||||
|
||||
// Write to file
|
||||
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
fileStream.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves scene (async).
|
||||
/// </summary>
|
||||
/// <param name="scene">Scene to save.</param>
|
||||
public void SaveScene(Scene scene)
|
||||
{
|
||||
SaveScene(GetActorNode(scene) as SceneNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves scene (async).
|
||||
/// </summary>
|
||||
/// <param name="scene">Scene to save.</param>
|
||||
public void SaveScene(SceneNode scene)
|
||||
{
|
||||
if (!scene.IsEdited)
|
||||
return;
|
||||
|
||||
scene.IsEdited = false;
|
||||
Level.SaveSceneAsync(scene.Scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves all open scenes (async).
|
||||
/// </summary>
|
||||
public void SaveScenes()
|
||||
{
|
||||
if (!IsEdited())
|
||||
return;
|
||||
|
||||
foreach (var scene in Root.ChildNodes)
|
||||
{
|
||||
if (scene is SceneNode node)
|
||||
node.IsEdited = false;
|
||||
}
|
||||
Level.SaveAllScenesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens scene (async).
|
||||
/// </summary>
|
||||
/// <param name="sceneId">Scene ID</param>
|
||||
/// <param name="additive">True if don't close opened scenes and just add new scene to them, otherwise will release current scenes and load single one.</param>
|
||||
public void OpenScene(Guid sceneId, bool additive = false)
|
||||
{
|
||||
// Check if cannot change scene now
|
||||
if (!Editor.StateMachine.CurrentState.CanChangeScene)
|
||||
return;
|
||||
|
||||
if (!additive)
|
||||
{
|
||||
// Ensure to save all pending changes
|
||||
if (CheckSaveBeforeClose())
|
||||
return;
|
||||
}
|
||||
|
||||
// Load scene
|
||||
Editor.StateMachine.ChangingScenesState.LoadScene(sceneId, additive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes scene (async).
|
||||
/// </summary>
|
||||
/// <param name="scene">The scene.</param>
|
||||
public void CloseScene(Scene scene)
|
||||
{
|
||||
// Check if cannot change scene now
|
||||
if (!Editor.StateMachine.CurrentState.CanChangeScene)
|
||||
return;
|
||||
|
||||
// Ensure to save all pending changes
|
||||
if (CheckSaveBeforeClose())
|
||||
return;
|
||||
|
||||
// Unload scene
|
||||
Editor.StateMachine.ChangingScenesState.UnloadScene(scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes all opened scene (async).
|
||||
/// </summary>
|
||||
public void CloseAllScenes()
|
||||
{
|
||||
// Check if cannot change scene now
|
||||
if (!Editor.StateMachine.CurrentState.CanChangeScene)
|
||||
return;
|
||||
|
||||
// Ensure to save all pending changes
|
||||
if (CheckSaveBeforeClose())
|
||||
return;
|
||||
|
||||
// Unload scenes
|
||||
Editor.StateMachine.ChangingScenesState.UnloadScene(Level.Scenes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show save before scene load/unload action.
|
||||
/// </summary>
|
||||
/// <param name="scene">The scene that will be closed.</param>
|
||||
/// <returns>True if action has been canceled, otherwise false</returns>
|
||||
public bool CheckSaveBeforeClose(SceneNode scene)
|
||||
{
|
||||
// Check if scene was edited after last saving
|
||||
if (scene.IsEdited)
|
||||
{
|
||||
// Ask user for further action
|
||||
var result = MessageBox.Show(
|
||||
string.Format("Scene \'{0}\' has been edited. Save before closing?", scene.Name),
|
||||
"Close without saving?",
|
||||
MessageBoxButtons.YesNoCancel,
|
||||
MessageBoxIcon.Question
|
||||
);
|
||||
if (result == DialogResult.OK || result == DialogResult.Yes)
|
||||
{
|
||||
// Save and close
|
||||
SaveScene(scene);
|
||||
}
|
||||
else if (result == DialogResult.Cancel || result == DialogResult.Abort)
|
||||
{
|
||||
// Cancel closing
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ClearRefsToSceneObjects();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show save before scene load/unload action.
|
||||
/// </summary>
|
||||
/// <returns>True if action has been canceled, otherwise false</returns>
|
||||
public bool CheckSaveBeforeClose()
|
||||
{
|
||||
// Check if scene was edited after last saving
|
||||
if (IsEdited())
|
||||
{
|
||||
// Ask user for further action
|
||||
var scenes = Level.Scenes;
|
||||
var result = MessageBox.Show(
|
||||
scenes.Length == 1 ? string.Format("Scene \'{0}\' has been edited. Save before closing?", scenes[0].Name) : string.Format("{0} scenes have been edited. Save before closing?", scenes.Length),
|
||||
"Close without saving?",
|
||||
MessageBoxButtons.YesNoCancel,
|
||||
MessageBoxIcon.Question
|
||||
);
|
||||
if (result == DialogResult.OK || result == DialogResult.Yes)
|
||||
{
|
||||
// Save and close
|
||||
SaveScenes();
|
||||
}
|
||||
else if (result == DialogResult.Cancel || result == DialogResult.Abort)
|
||||
{
|
||||
// Cancel closing
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ClearRefsToSceneObjects();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears references to the scene objects by the editor. Deselects objects.
|
||||
/// </summary>
|
||||
/// <param name="fullCleanup">True if cleanup all data (including serialized and cached data). Otherwise will just clear living references to the scene objects.</param>
|
||||
public void ClearRefsToSceneObjects(bool fullCleanup = false)
|
||||
{
|
||||
Editor.SceneEditing.Deselect();
|
||||
|
||||
// TODO: this works in most cases fine but we still need to handle the case when assembly gets reloaded and type references are invalid
|
||||
// TODO: To solve this: serialize type reference as 'namespace.name' in undo data storage
|
||||
//if (fullCleanup)
|
||||
{
|
||||
Undo.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSceneLoaded(Scene scene, Guid sceneId)
|
||||
{
|
||||
var startTime = DateTime.UtcNow;
|
||||
|
||||
// Build scene tree
|
||||
var sceneNode = SceneGraphFactory.BuildSceneTree(scene);
|
||||
var treeNode = sceneNode.TreeNode;
|
||||
treeNode.IsLayoutLocked = true;
|
||||
treeNode.Expand(true);
|
||||
|
||||
// Add to the tree
|
||||
var rootNode = Root.TreeNode;
|
||||
rootNode.IsLayoutLocked = true;
|
||||
//
|
||||
sceneNode.ParentNode = Root;
|
||||
rootNode.SortChildren();
|
||||
//
|
||||
treeNode.UnlockChildrenRecursive();
|
||||
rootNode.IsLayoutLocked = false;
|
||||
rootNode.Parent.PerformLayout();
|
||||
|
||||
var endTime = DateTime.UtcNow;
|
||||
var milliseconds = (int)(endTime - startTime).TotalMilliseconds;
|
||||
Editor.Log($"Created graph for scene \'{scene.Name}\' in {milliseconds} ms");
|
||||
}
|
||||
|
||||
private void OnSceneUnloading(Scene scene, Guid sceneId)
|
||||
{
|
||||
// Find scene tree node
|
||||
var node = Root.FindChildActor(scene);
|
||||
if (node != null)
|
||||
{
|
||||
Editor.Log($"Cleanup graph for scene \'{scene.Name}\'");
|
||||
|
||||
// Cleanup
|
||||
node.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnActorSpawned(Actor actor)
|
||||
{
|
||||
// Skip for not loaded scenes (spawning actors during scene loading in script Start function)
|
||||
var sceneNode = GetActorNode(actor.Scene);
|
||||
if (sceneNode == null)
|
||||
return;
|
||||
|
||||
// Skip for missing parent
|
||||
var parent = actor.Parent;
|
||||
if (parent == null)
|
||||
return;
|
||||
|
||||
var parentNode = GetActorNode(parent);
|
||||
if (parentNode == null)
|
||||
{
|
||||
// Missing parent node when adding child actor to not spawned or unlinked actor
|
||||
return;
|
||||
}
|
||||
|
||||
var node = SceneGraphFactory.BuildActorNode(actor);
|
||||
if (node != null)
|
||||
{
|
||||
node.TreeNode.UnlockChildrenRecursive();
|
||||
node.ParentNode = parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnActorDeleted(Actor actor)
|
||||
{
|
||||
var node = GetActorNode(actor);
|
||||
if (node != null)
|
||||
{
|
||||
OnActorDeleted(node);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnActorDeleted(ActorNode node)
|
||||
{
|
||||
for (int i = 0; i < node.ChildNodes.Count; i++)
|
||||
{
|
||||
if (node.ChildNodes[i] is ActorNode child)
|
||||
{
|
||||
i--;
|
||||
OnActorDeleted(child);
|
||||
}
|
||||
}
|
||||
|
||||
ActorRemoved?.Invoke(node);
|
||||
|
||||
// Cleanup part of the graph
|
||||
node.Dispose();
|
||||
}
|
||||
|
||||
private void OnActorParentChanged(Actor actor, Actor prevParent)
|
||||
{
|
||||
ActorNode node = null;
|
||||
var parentNode = GetActorNode(actor.Parent);
|
||||
|
||||
// Try use previous parent actor to find actor node
|
||||
var prevParentNode = GetActorNode(prevParent);
|
||||
if (prevParentNode != null)
|
||||
{
|
||||
// If should be one of the children
|
||||
node = prevParentNode.FindChildActor(actor);
|
||||
|
||||
// Search whole tree if node was not found
|
||||
if (node == null)
|
||||
{
|
||||
node = GetActorNode(actor);
|
||||
}
|
||||
}
|
||||
else if (parentNode != null)
|
||||
{
|
||||
// Create new node for that actor (user may unlink it from the scene before and now link it)
|
||||
node = SceneGraphFactory.BuildActorNode(actor);
|
||||
}
|
||||
if (node == null)
|
||||
return;
|
||||
|
||||
// Get the new parent node (may be missing)
|
||||
if (parentNode != null)
|
||||
{
|
||||
// Change parent
|
||||
node.TreeNode.UnlockChildrenRecursive();
|
||||
node.ParentNode = parentNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if actor is selected in editor
|
||||
if (Editor.SceneEditing.Selection.Contains(node))
|
||||
Editor.SceneEditing.Deselect();
|
||||
|
||||
// Remove node (user may unlink actor from the scene but not destroy the actor)
|
||||
node.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnActorOrderInParentChanged(Actor actor)
|
||||
{
|
||||
ActorNode node = GetActorNode(actor);
|
||||
node?.TreeNode.OnOrderInParentChanged();
|
||||
}
|
||||
|
||||
private void OnActorNameChanged(Actor actor)
|
||||
{
|
||||
ActorNode node = GetActorNode(actor);
|
||||
node?.TreeNode.OnNameChanged();
|
||||
}
|
||||
|
||||
private void OnActorActiveChanged(Actor actor)
|
||||
{
|
||||
//ActorNode node = GetActorNode(actor);
|
||||
//node?.TreeNode.OnActiveChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actor node.
|
||||
/// </summary>
|
||||
/// <param name="actor">The actor.</param>
|
||||
/// <returns>Found actor node or null if missing. Actor may not be linked to the scene tree so node won't be found in that case.</returns>
|
||||
public ActorNode GetActorNode(Actor actor)
|
||||
{
|
||||
if (actor == null)
|
||||
return null;
|
||||
|
||||
// ActorNode has the same ID as actor does
|
||||
return SceneGraphFactory.FindNode(actor.ID) as ActorNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actor node.
|
||||
/// </summary>
|
||||
/// <param name="actorId">The actor id.</param>
|
||||
/// <returns>Found actor node or null if missing. Actor may not be linked to the scene tree so node won't be found in that case.</returns>
|
||||
public ActorNode GetActorNode(Guid actorId)
|
||||
{
|
||||
// ActorNode has the same ID as actor does
|
||||
return SceneGraphFactory.FindNode(actorId) as ActorNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the custom action on the graph nodes.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback.</param>
|
||||
public void ExecuteOnGraph(SceneGraphTools.GraphExecuteCallbackDelegate callback)
|
||||
{
|
||||
Root.ExecuteOnGraph(callback);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnInit()
|
||||
{
|
||||
Root = new ScenesRootNode();
|
||||
|
||||
// Bind events
|
||||
Level.SceneLoaded += OnSceneLoaded;
|
||||
Level.SceneUnloading += OnSceneUnloading;
|
||||
Level.ActorSpawned += OnActorSpawned;
|
||||
Level.ActorDeleted += OnActorDeleted;
|
||||
Level.ActorParentChanged += OnActorParentChanged;
|
||||
Level.ActorOrderInParentChanged += OnActorOrderInParentChanged;
|
||||
Level.ActorNameChanged += OnActorNameChanged;
|
||||
Level.ActorActiveChanged += OnActorActiveChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnExit()
|
||||
{
|
||||
// Unbind events
|
||||
Level.SceneLoaded -= OnSceneLoaded;
|
||||
Level.SceneUnloading -= OnSceneUnloading;
|
||||
Level.ActorSpawned -= OnActorSpawned;
|
||||
Level.ActorDeleted -= OnActorDeleted;
|
||||
Level.ActorParentChanged -= OnActorParentChanged;
|
||||
Level.ActorOrderInParentChanged -= OnActorOrderInParentChanged;
|
||||
Level.ActorNameChanged -= OnActorNameChanged;
|
||||
Level.ActorActiveChanged -= OnActorActiveChanged;
|
||||
|
||||
// Cleanup graph
|
||||
Root.Dispose();
|
||||
|
||||
if (SceneGraphFactory.Nodes.Count > 0)
|
||||
{
|
||||
Editor.LogWarning("Not all scene graph nodes has been disposed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user