Files
FlaxEngine/Source/Editor/Editor.cs
Wojciech Figat a7e428a21c Merge branch 'master' into 1.5
# Conflicts:
#	Content/Shaders/GI/DDGI.flax
#	Content/Shaders/GI/GlobalSurfaceAtlas.flax
#	Content/Shaders/TAA.flax
#	Content/Shaders/VolumetricFog.flax
#	Source/Editor/CustomEditors/Editors/ActorTagEditor.cs
#	Source/Engine/Core/Config/GraphicsSettings.cpp
#	Source/Engine/Engine/PostProcessEffect.cs
#	Source/Engine/Graphics/GPUResourcesCollection.cpp
#	Source/Engine/Graphics/GPUResourcesCollection.h
#	Source/Engine/Graphics/PostProcessBase.h
#	Source/FlaxEngine.Gen.cs
2023-01-10 15:37:55 +01:00

1537 lines
56 KiB
C#

// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using FlaxEditor.Content;
using FlaxEditor.Content.Import;
using FlaxEditor.Content.Settings;
using FlaxEditor.Content.Thumbnails;
using FlaxEditor.Modules;
using FlaxEditor.Modules.SourceCodeEditing;
using FlaxEditor.Options;
using FlaxEditor.States;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.Assertions;
using FlaxEngine.GUI;
using FlaxEngine.Json;
namespace FlaxEditor
{
/// <summary>
/// The main managed editor class. Editor root object.
/// </summary>
public sealed partial class Editor
{
/// <summary>
/// Gets the Editor instance.
/// </summary>
public static Editor Instance { get; private set; }
/// <summary>
/// The path to the local cache folder shared by all the installed editor instance for a given user (used also by the Flax Launcher).
/// </summary>
public static readonly string LocalCachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Flax");
static Editor()
{
JsonSerializer.Settings.Converters.Add(new SceneTreeNodeConverter());
}
private readonly List<EditorModule> _modules = new List<EditorModule>(16);
private bool _isAfterInit, _areModulesInited, _areModulesAfterInitEnd, _isHeadlessMode;
private string _projectToOpen;
private float _lastAutoSaveTimer;
private Guid _startupSceneCmdLine;
private const string ProjectDataLastScene = "LastScene";
private const string ProjectDataLastSceneSpawn = "LastSceneSpawn";
/// <summary>
/// Gets a value indicating whether Flax Engine is the best in the world.
/// </summary>
public bool IsFlaxEngineTheBest => true;
/// <summary>
/// Gets a value indicating whether this Editor is running a dev instance of the engine.
/// </summary>
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool IsDevInstance();
/// <summary>
/// Gets a value indicating whether this Editor is running as official build (distributed via Flax services).
/// </summary>
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool IsOfficialBuild();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_IsPlayMode();
/// <summary>
/// True if the editor is running now in a play mode. Assigned by the managed editor instance.
/// </summary>
public static bool IsPlayMode => Internal_IsPlayMode();
/// <summary>
/// Gets the build number of the last editor instance that opened the current project. Value 0 means the undefined version.
/// </summary>
public static int LastProjectOpenedEngineBuild => Internal_GetLastProjectOpenedEngineBuild();
/// <summary>
/// The windows module.
/// </summary>
public WindowsModule Windows;
/// <summary>
/// The UI module.
/// </summary>
public UIModule UI;
/// <summary>
/// The thumbnails module.
/// </summary>
public ThumbnailsModule Thumbnails;
/// <summary>
/// The simulation module.
/// </summary>
public SimulationModule Simulation;
/// <summary>
/// The scene module.
/// </summary>
public SceneModule Scene;
/// <summary>
/// The prefabs module.
/// </summary>
public PrefabsModule Prefabs;
/// <summary>
/// The scene editing module.
/// </summary>
public SceneEditingModule SceneEditing;
/// <summary>
/// The progress reporting module.
/// </summary>
public ProgressReportingModule ProgressReporting;
/// <summary>
/// The content editing module.
/// </summary>
public ContentEditingModule ContentEditing;
/// <summary>
/// The content database module.
/// </summary>
public ContentDatabaseModule ContentDatabase;
/// <summary>
/// The content importing module.
/// </summary>
public ContentImportingModule ContentImporting;
/// <summary>
/// The content finder module.
/// </summary>
public ContentFindingModule ContentFinding;
/// <summary>
/// The scripts editing
/// </summary>
public CodeEditingModule CodeEditing;
/// <summary>
/// The scripts documentation
/// </summary>
public CodeDocsModule CodeDocs;
/// <summary>
/// The editor state machine.
/// </summary>
public EditorStateMachine StateMachine;
/// <summary>
/// The editor options manager.
/// </summary>
public OptionsModule Options;
/// <summary>
/// The editor per-project cache manager.
/// </summary>
public ProjectCacheModule ProjectCache;
/// <summary>
/// The undo/redo
/// </summary>
public EditorUndo Undo;
/// <summary>
/// The icons container.
/// </summary>
public EditorIcons Icons;
/// <summary>
/// Gets the main transform gizmo used by the <see cref="SceneEditorWindow"/>.
/// </summary>
public Gizmo.TransformGizmo MainTransformGizmo => Windows.EditWin.Viewport.TransformGizmo;
/// <summary>
/// Gets a value indicating whether this Editor is running in `headless` mode. No windows or popups should be shown. Used in CL environment (without a graphical user interface).
/// </summary>
public bool IsHeadlessMode => _isHeadlessMode;
/// <summary>
/// Gets a value indicating whether Editor instance is initialized.
/// </summary>
public bool IsInitialized => _areModulesAfterInitEnd;
/// <summary>
/// Occurs when editor initialization starts. All editor modules already received OnInit callback and editor splash screen is visible.
/// </summary>
public event Action InitializationStart;
/// <summary>
/// Occurs when editor initialization ends. All editor modules already received OnEndInit callback and editor splash screen will be closed.
/// </summary>
public event Action InitializationEnd;
/// <summary>
/// The custom data container that is stored in Editor instance. Can be used by plugins to store the state during editor session (state is preserved during scripts reloads).
/// </summary>
public Dictionary<string, string> CustomData = new Dictionary<string, string>();
/// <summary>
/// The game project info.
/// </summary>
public ProjectInfo GameProject;
/// <summary>
/// The engine project info.
/// </summary>
public ProjectInfo EngineProject;
/// <summary>
/// Occurs when play mode is beginning (before entering play mode).
/// </summary>
public event Action PlayModeBeginning;
/// <summary>
/// Occurs when play mode begins (after entering play mode).
/// </summary>
public event Action PlayModeBegin;
/// <summary>
/// Occurs when play mode is ending (before leaving play mode).
/// </summary>
public event Action PlayModeEnding;
/// <summary>
/// Occurs when play mode ends (after leaving play mode).
/// </summary>
public event Action PlayModeEnd;
internal Editor()
{
Instance = this;
}
internal void Init(bool isHeadless, bool skipCompile, bool newProject, Guid startupScene)
{
Log("Setting up C# Editor...");
_isHeadlessMode = isHeadless;
_startupSceneCmdLine = startupScene;
Profiler.BeginEvent("Projects");
EngineProject = ProjectInfo.Load(StringUtils.CombinePaths(Globals.StartupFolder, "Flax.flaxproj"));
GameProject = ProjectInfo.Load(Internal_GetProjectPath());
Profiler.EndEvent();
Profiler.BeginEvent("Icons");
Icons = new EditorIcons();
Icons.LoadIcons();
Profiler.EndEvent();
// Create common editor modules
Profiler.BeginEvent("Modules");
RegisterModule(Options = new OptionsModule(this));
RegisterModule(ProjectCache = new ProjectCacheModule(this));
RegisterModule(Scene = new SceneModule(this));
RegisterModule(Windows = new WindowsModule(this));
RegisterModule(UI = new UIModule(this));
RegisterModule(Thumbnails = new ThumbnailsModule(this));
RegisterModule(Simulation = new SimulationModule(this));
RegisterModule(Prefabs = new PrefabsModule(this));
RegisterModule(SceneEditing = new SceneEditingModule(this));
RegisterModule(ContentEditing = new ContentEditingModule(this));
RegisterModule(ContentDatabase = new ContentDatabaseModule(this));
RegisterModule(ContentImporting = new ContentImportingModule(this));
RegisterModule(CodeEditing = new CodeEditingModule(this));
RegisterModule(CodeDocs = new CodeDocsModule(this));
RegisterModule(ProgressReporting = new ProgressReportingModule(this));
RegisterModule(ContentFinding = new ContentFindingModule(this));
Profiler.EndEvent();
StateMachine = new EditorStateMachine(this);
Undo = new EditorUndo(this);
UIControl.FallbackParentGetDelegate += OnUIControlFallbackParentGet;
if (newProject)
InitProject();
EnsureState<LoadingState>();
Log("Editor init");
if (isHeadless)
Log("Running in headless mode");
// Note: we don't sort modules before Init (optimized)
_modules.Sort((a, b) => a.InitOrder - b.InitOrder);
_isAfterInit = true;
// Initialize modules (from front to back)
for (int i = 0; i < _modules.Count; i++)
{
var module = _modules[i];
Profiler.BeginEvent(module.GetType().Name);
module.OnInit();
Profiler.EndEvent();
}
_areModulesInited = true;
// Preload initial scene asset
{
var startupSceneMode = Options.Options.General.StartupSceneMode;
if (startupSceneMode == GeneralOptions.StartupSceneModes.LastOpened && !ProjectCache.HasCustomData(ProjectDataLastScene))
startupSceneMode = GeneralOptions.StartupSceneModes.ProjectDefault;
switch (startupSceneMode)
{
case GeneralOptions.StartupSceneModes.ProjectDefault:
{
if (string.IsNullOrEmpty(GameProject.DefaultScene))
break;
JsonSerializer.ParseID(GameProject.DefaultScene, out var defaultSceneId);
Internal_LoadAsset(ref defaultSceneId);
break;
}
case GeneralOptions.StartupSceneModes.LastOpened:
{
if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName) && Guid.TryParse(lastSceneIdName, out var lastSceneId))
Internal_LoadAsset(ref lastSceneId);
break;
}
}
}
InitializationStart?.Invoke();
// Start Editor initialization ending phrase (will wait for scripts compilation result)
StateMachine.LoadingState.StartInitEnding(skipCompile);
}
private ContainerControl OnUIControlFallbackParentGet(UIControl control)
{
// Check if prefab root control is this UIControl
var loadingPreview = Viewport.Previews.PrefabPreview.LoadingPreview;
if (loadingPreview != null)
{
// Link it to the prefab preview to see it in the editor
loadingPreview.customControlLinked = control;
return loadingPreview;
}
return null;
}
internal void RegisterModule(EditorModule module)
{
Log("Register Editor module " + module);
_modules.Add(module);
if (_isAfterInit)
_modules.Sort((a, b) => a.InitOrder - b.InitOrder);
if (_areModulesInited)
module.OnInit();
if (_areModulesAfterInitEnd)
module.OnEndInit();
}
internal void EndInit()
{
EnsureState<LoadingState>();
Log("Editor end init");
// Change state
StateMachine.GoToState<EditingSceneState>();
// Initialize modules (from front to back)
for (int i = 0; i < _modules.Count; i++)
{
try
{
var module = _modules[i];
Profiler.BeginEvent(module.GetType().Name);
module.OnEndInit();
Profiler.EndEvent();
}
catch (Exception ex)
{
LogWarning(ex);
LogError("Failed to initialize editor module " + _modules[i]);
}
}
_areModulesAfterInitEnd = true;
_lastAutoSaveTimer = Time.UnscaledGameTime;
InitializationEnd?.Invoke();
// Close splash and show main window
CloseSplashScreen();
Assert.IsNotNull(Windows.MainWindow);
if (!IsHeadlessMode)
{
Windows.MainWindow.Show();
Windows.MainWindow.Focus();
}
// Load scene
// scene cmd line argument
var scene = ContentDatabase.Find(_startupSceneCmdLine);
if (scene is SceneItem)
{
Editor.Log("Loading scene specified in command line");
Scene.OpenScene(_startupSceneCmdLine);
return;
}
// if no scene cmd line argument is provided
var startupSceneMode = Options.Options.General.StartupSceneMode;
if (startupSceneMode == GeneralOptions.StartupSceneModes.LastOpened && !ProjectCache.HasCustomData(ProjectDataLastScene))
{
// Fallback to default project scene if nothing saved in the cache
startupSceneMode = GeneralOptions.StartupSceneModes.ProjectDefault;
}
switch (startupSceneMode)
{
case GeneralOptions.StartupSceneModes.ProjectDefault:
{
if (string.IsNullOrEmpty(GameProject.DefaultScene))
break;
JsonSerializer.ParseID(GameProject.DefaultScene, out var defaultSceneId);
var defaultScene = ContentDatabase.Find(defaultSceneId);
if (defaultScene is SceneItem)
{
Editor.Log("Loading default project scene");
Scene.OpenScene(defaultSceneId);
// Use spawn point
Windows.EditWin.Viewport.ViewRay = GameProject.DefaultSceneSpawn;
}
break;
}
case GeneralOptions.StartupSceneModes.LastOpened:
{
if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName) && Guid.TryParse(lastSceneIdName, out var lastSceneId))
{
var lastScene = ContentDatabase.Find(lastSceneId);
if (lastScene is SceneItem)
{
Editor.Log("Loading last opened scene");
Scene.OpenScene(lastSceneId);
// Restore view
if (ProjectCache.TryGetCustomData(ProjectDataLastSceneSpawn, out var lastSceneSpawnName))
Windows.EditWin.Viewport.ViewRay = JsonSerializer.Deserialize<Ray>(lastSceneSpawnName);
}
}
break;
}
}
}
internal void Update()
{
Profiler.BeginEvent("Editor.Update");
try
{
StateMachine.Update();
UpdateAutoSave();
if (!StateMachine.IsPlayMode)
{
StateMachine.CurrentState.UpdateFPS();
}
// Update modules
for (int i = 0; i < _modules.Count; i++)
{
_modules[i].OnUpdate();
}
}
catch (Exception ex)
{
Debug.LogException(ex);
}
Profiler.EndEvent();
}
private void UpdateAutoSave()
{
string msg = null;
var options = Options.Options.General;
var canSave = StateMachine.IsEditMode && options.EnableAutoSave;
if (canSave)
{
var timeSinceLastSave = Time.UnscaledGameTime - _lastAutoSaveTimer;
var timeToNextSave = options.AutoSaveFrequency * 60.0f - timeSinceLastSave;
var countDownDuration = 4.0f;
if (timeToNextSave <= 0.0f)
{
Log("Auto save");
_lastAutoSaveTimer = Time.UnscaledGameTime;
if (options.AutoSaveScenes)
Scene.SaveScenes();
if (options.AutoSaveContent)
SaveContent();
}
else if (timeToNextSave < countDownDuration)
{
msg = string.Format("Auto save in {0}s...", Mathf.CeilToInt(timeToNextSave));
}
}
if (StateMachine.EditingSceneState.AutoSaveStatus != msg)
{
StateMachine.EditingSceneState.AutoSaveStatus = msg;
UI.UpdateStatusBar();
}
}
private void InitProject()
{
// Initialize empty project with default game configuration
Log("Initialize new project");
var project = GameProject;
var gameSettings = new GameSettings
{
ProductName = project.Name,
CompanyName = project.Company,
};
GameSettings.Save(gameSettings);
GameSettings.Save(new TimeSettings());
GameSettings.Save(new AudioSettings());
GameSettings.Save(new PhysicsSettings());
GameSettings.Save(new LayersAndTagsSettings());
GameSettings.Save(new InputSettings());
GameSettings.Save(new GraphicsSettings());
GameSettings.Save(new NetworkSettings());
GameSettings.Save(new NavigationSettings());
GameSettings.Save(new LocalizationSettings());
GameSettings.Save(new BuildSettings());
GameSettings.Save(new StreamingSettings());
GameSettings.Save(new WindowsPlatformSettings());
GameSettings.Save(new LinuxPlatformSettings());
GameSettings.Save(new AndroidPlatformSettings());
GameSettings.Save(new UWPPlatformSettings());
}
internal void OnPlayBeginning()
{
for (int i = 0; i < _modules.Count; i++)
_modules[i].OnPlayBeginning();
PlayModeBeginning?.Invoke();
}
internal void OnPlayBegin()
{
for (int i = 0; i < _modules.Count; i++)
_modules[i].OnPlayBegin();
PlayModeBegin?.Invoke();
}
internal void OnPlayEnding()
{
FlaxEngine.Networking.NetworkManager.Stop(); // Shutdown any multiplayer from playmode
PlayModeEnding?.Invoke();
}
internal void OnPlayEnd()
{
PlayModeEnd?.Invoke();
for (int i = 0; i < _modules.Count; i++)
_modules[i].OnPlayEnd();
}
internal void Exit()
{
Log("Editor exit");
// Deinitialize Editor Plugins
foreach (var plugin in PluginManager.EditorPlugins)
{
if (plugin is EditorPlugin editorPlugin && editorPlugin._isEditorInitialized)
{
editorPlugin._isEditorInitialized = false;
editorPlugin.DeinitializeEditor();
}
}
// Start exit
StateMachine.GoToState<ClosingState>();
// Cache last opened scene
{
var lastSceneId = Level.ScenesCount > 0 ? Level.Scenes[0].ID : Guid.Empty;
var lastSceneSpawn = Windows.EditWin.Viewport.ViewRay;
ProjectCache.SetCustomData(ProjectDataLastScene, lastSceneId.ToString());
ProjectCache.SetCustomData(ProjectDataLastSceneSpawn, JsonSerializer.Serialize(lastSceneSpawn));
}
// Cleanup
Scene.ClearRefsToSceneObjects(true);
// Release modules (from back to front)
for (int i = _modules.Count - 1; i >= 0; i--)
{
var module = _modules[i];
Profiler.BeginEvent(module.GetType().Name);
module.OnExit();
Profiler.EndEvent();
}
// Cleanup
Undo.Dispose();
Surface.VisualScriptSurface.NodesCache.Clear();
Surface.AnimGraphSurface.NodesCache.Clear();
Instance = null;
// Invoke new instance if need to open a project
if (!string.IsNullOrEmpty(_projectToOpen))
{
string args = string.Format("-project \"{0}\"", _projectToOpen);
_projectToOpen = null;
Platform.StartProcess(Platform.ExecutableFilePath, args, null);
}
}
/// <summary>
/// Undo last action.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PerformUndo()
{
Undo.PerformUndo();
}
/// <summary>
/// Redo last action.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PerformRedo()
{
Undo.PerformRedo();
}
/// <summary>
/// Saves all changes (scenes, assets, etc.).
/// </summary>
public void SaveAll()
{
Windows.SaveCurrentLayout();
Scene.SaveScenes();
SaveContent();
UI.AddStatusMessage("Saved!");
}
/// <summary>
/// Saves all content (assets, etc.).
/// </summary>
public void SaveContent()
{
for (int i = 0; i < Windows.Windows.Count; i++)
{
if (Windows.Windows[i] is AssetEditorWindow win)
{
win.Save();
}
}
}
/// <summary>
/// Closes this project with running editor and opens the given project.
/// </summary>
/// <param name="projectFilePath">The project file path.</param>
public void OpenProject(string projectFilePath)
{
if (projectFilePath == null || !File.Exists(projectFilePath))
{
// Error
MessageBox.Show("Missing project");
return;
}
// Cache project path and start editor exit (it will open new instance on valid closing)
_projectToOpen = StringUtils.NormalizePath(Path.GetDirectoryName(projectFilePath));
Windows.MainWindow.Close(ClosingReason.User);
}
/// <summary>
/// Ensure that editor is in a given state, otherwise throws <see cref="InvalidStateException"/>.
/// </summary>
/// <param name="state">Valid state to check.</param>
/// <exception cref="InvalidStateException"></exception>
public void EnsureState(EditorState state)
{
if (StateMachine.CurrentState != state)
throw new InvalidStateException($"Operation cannot be performed in the current editor state. Current: {StateMachine.CurrentState}, Expected: {state}");
}
/// <summary>
/// Ensure that editor is in a state of given type, otherwise throws <see cref="InvalidStateException"/>.
/// </summary>
/// <typeparam name="TStateType">The type of the state type.</typeparam>
public void EnsureState<TStateType>()
{
var state = StateMachine.GetState<TStateType>() as EditorState;
EnsureState(state);
}
/// <summary>
/// Logs the specified message to the log file.
/// </summary>
/// <param name="msg">The message.</param>
[HideInEditor]
public static void Log(string msg)
{
Debug.Logger.LogHandler.LogWrite(LogType.Info, msg);
}
/// <summary>
/// Logs the specified warning message to the log file.
/// </summary>
/// <param name="msg">The message.</param>
[HideInEditor]
public static void LogWarning(string msg)
{
Debug.Logger.LogHandler.LogWrite(LogType.Warning, msg);
}
/// <summary>
/// Logs the specified warning exception to the log file.
/// </summary>
/// <param name="ex">The exception.</param>
[HideInEditor]
public static void LogWarning(Exception ex)
{
LogWarning("Exception: " + ex.Message);
LogWarning(ex.StackTrace);
}
/// <summary>
/// Logs the specified error message to the log file.
/// </summary>
/// <param name="msg">The message.</param>
[HideInEditor]
public static void LogError(string msg)
{
Debug.Logger.LogHandler.LogWrite(LogType.Error, msg);
}
/// <summary>
/// New asset types allowed to create.
/// </summary>
public enum NewAssetType
{
/// <summary>
/// The <see cref="FlaxEngine.Material"/>.
/// </summary>
Material = 0,
/// <summary>
/// The <see cref="FlaxEngine.MaterialInstance"/>.
/// </summary>
MaterialInstance = 1,
/// <summary>
/// The <see cref="FlaxEngine.CollisionData"/>.
/// </summary>
CollisionData = 2,
/// <summary>
/// The <see cref="FlaxEngine.AnimationGraph"/>.
/// </summary>
AnimationGraph = 3,
/// <summary>
/// The <see cref="FlaxEngine.SkeletonMask"/>.
/// </summary>
SkeletonMask = 4,
/// <summary>
/// The <see cref="FlaxEngine.ParticleEmitter"/>.
/// </summary>
ParticleEmitter = 5,
/// <summary>
/// The <see cref="FlaxEngine.ParticleSystem"/>.
/// </summary>
ParticleSystem = 6,
/// <summary>
/// The <see cref="FlaxEngine.SceneAnimation"/>.
/// </summary>
SceneAnimation = 7,
/// <summary>
/// The <see cref="FlaxEngine.MaterialFunction"/>.
/// </summary>
MaterialFunction = 8,
/// <summary>
/// The <see cref="FlaxEngine.ParticleEmitterFunction"/>.
/// </summary>
ParticleEmitterFunction = 9,
/// <summary>
/// The <see cref="FlaxEngine.AnimationGraphFunction"/>.
/// </summary>
AnimationGraphFunction = 10,
/// <summary>
/// The <see cref="FlaxEngine.Animation"/>.
/// </summary>
Animation = 11,
}
/// <summary>
/// Imports the asset file to the target location.
/// </summary>
/// <param name="inputPath">The source file path.</param>
/// <param name="outputPath">The result asset file path.</param>
/// <returns>True if importing failed, otherwise false.</returns>
public static bool Import(string inputPath, string outputPath)
{
return Internal_Import(inputPath, outputPath, IntPtr.Zero);
}
/// <summary>
/// Imports the texture asset file to the target location.
/// </summary>
/// <param name="inputPath">The source file path.</param>
/// <param name="outputPath">The result asset file path.</param>
/// <param name="settings">The settings.</param>
/// <returns>True if importing failed, otherwise false.</returns>
public static bool Import(string inputPath, string outputPath, TextureImportSettings settings)
{
if (settings == null)
throw new ArgumentNullException();
settings.ToInternal(out var internalOptions);
return Internal_ImportTexture(inputPath, outputPath, ref internalOptions);
}
/// <summary>
/// Imports the model asset file to the target location.
/// </summary>
/// <param name="inputPath">The source file path.</param>
/// <param name="outputPath">The result asset file path.</param>
/// <param name="settings">The settings.</param>
/// <returns>True if importing failed, otherwise false.</returns>
public static bool Import(string inputPath, string outputPath, ModelImportSettings settings)
{
if (settings == null)
throw new ArgumentNullException();
settings.ToInternal(out var internalOptions);
return Internal_ImportModel(inputPath, outputPath, ref internalOptions);
}
/// <summary>
/// Imports the audio asset file to the target location.
/// </summary>
/// <param name="inputPath">The source file path.</param>
/// <param name="outputPath">The result asset file path.</param>
/// <param name="settings">The settings.</param>
/// <returns>True if importing failed, otherwise false.</returns>
public static bool Import(string inputPath, string outputPath, AudioImportSettings settings)
{
if (settings == null)
throw new ArgumentNullException();
settings.ToInternal(out var internalOptions);
return Internal_ImportAudio(inputPath, outputPath, ref internalOptions);
}
/// <summary>
/// Serializes the given object to json asset.
/// </summary>
/// <param name="outputPath">The result asset file path.</param>
/// <param name="obj">The obj to serialize.</param>
/// <returns>True if saving failed, otherwise false.</returns>
public static bool SaveJsonAsset(string outputPath, object obj)
{
if (obj == null)
throw new ArgumentNullException();
string str = JsonSerializer.Serialize(obj);
return Internal_SaveJsonAsset(outputPath, str, obj.GetType().FullName);
}
/// <summary>
/// Cooks the mesh collision data and saves it to the asset using <see cref="CollisionData"/> format. action cannot be performed on a main thread.
/// </summary>
/// <param name="path">The output asset path.</param>
/// <param name="type">The collision data type.</param>
/// <param name="model">The source model.</param>
/// <param name="modelLodIndex">The source model LOD index.</param>
/// <param name="materialSlotsMask">The source model material slots mask. One bit per-slot. Can be used to exclude particular material slots from collision cooking.</param>
/// <param name="convexFlags">The convex mesh generation flags.</param>
/// <param name="convexVertexLimit">The convex mesh vertex limit. Use values in range [8;255]</param>
/// <returns>True if failed, otherwise false.</returns>
public static bool CookMeshCollision(string path, CollisionDataType type, ModelBase model, int modelLodIndex = 0, uint materialSlotsMask = uint.MaxValue, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags.None, int convexVertexLimit = 255)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException(nameof(path));
if (model == null)
throw new ArgumentNullException(nameof(model));
if (type == CollisionDataType.None)
throw new ArgumentException(nameof(type));
return Internal_CookMeshCollision(path, type, FlaxEngine.Object.GetUnmanagedPtr(model), modelLodIndex, materialSlotsMask, convexFlags, convexVertexLimit);
}
/// <summary>
/// Gets the shader source code (HLSL shader code).
/// </summary>
/// <param name="asset">The shader asset.</param>
/// <returns>The generated source code.</returns>
public static string GetShaderSourceCode(Shader asset)
{
return GetShaderAssetSourceCode(asset);
}
/// <summary>
/// Gets the material shader source code (HLSL shader code).
/// </summary>
/// <param name="asset">The material asset.</param>
/// <returns>The generated source code.</returns>
public static string GetShaderSourceCode(Material asset)
{
return GetShaderAssetSourceCode(asset);
}
/// <summary>
/// Gets the particle emitter GPU simulation shader source code (HLSL shader code).
/// </summary>
/// <param name="asset">The particle emitter asset.</param>
/// <returns>The generated source code.</returns>
public static string GetShaderSourceCode(ParticleEmitter asset)
{
return GetShaderAssetSourceCode(asset);
}
private static string GetShaderAssetSourceCode(Asset asset)
{
if (asset == null)
throw new ArgumentNullException(nameof(asset));
if (asset.WaitForLoaded())
throw new Exception("Failed to load asset.");
var source = Internal_GetShaderAssetSourceCode(FlaxEngine.Object.GetUnmanagedPtr(asset));
if (source == null)
throw new Exception("Failed to get source code.");
return source;
}
/// <summary>
/// Gets the actor bounding sphere (including child actors).
/// </summary>
/// <param name="actor">The actor.</param>
/// <param name="sphere">The bounding sphere.</param>
public static void GetActorEditorSphere(Actor actor, out BoundingSphere sphere)
{
if (actor)
{
Internal_GetEditorBoxWithChildren(FlaxEngine.Object.GetUnmanagedPtr(actor), out var box);
BoundingSphere.FromBox(ref box, out sphere);
sphere.Radius = Math.Max(sphere.Radius, 15.0f);
}
else
{
sphere = BoundingSphere.Empty;
}
}
/// <summary>
/// Closes editor splash screen popup window.
/// </summary>
public static void CloseSplashScreen()
{
Internal_CloseSplashScreen();
}
/// <summary>
/// Creates new asset at the target location.
/// </summary>
/// <param name="type">New asset type.</param>
/// <param name="outputPath">Output asset path.</param>
public static bool CreateAsset(NewAssetType type, string outputPath)
{
return Internal_CreateAsset(type, outputPath);
}
/// <summary>
/// Creates new Visual Script asset at the target location.
/// </summary>
/// <param name="outputPath">Output asset path.</param>
/// <param name="baseTypename">Base class typename.</param>
public static bool CreateVisualScript(string outputPath, string baseTypename)
{
return Internal_CreateVisualScript(outputPath, baseTypename);
}
/// <summary>
/// Checks if can import asset with the given extension.
/// </summary>
/// <param name="extension">The file extension.</param>
/// <param name="outputExtension">The output file extension (flax, json, etc.).</param>
/// <returns>True if can import files with given extension, otherwise false.</returns>
public static bool CanImport(string extension, out string outputExtension)
{
outputExtension = Internal_CanImport(extension);
return outputExtension != null;
}
/// <summary>
/// Checks if the given asset can be exported.
/// </summary>
/// <param name="path">The asset path (absolute path with an extension).</param>
/// <returns>True if can export given asset, otherwise false.</returns>
public static bool CanExport(string path)
{
return Internal_CanExport(path);
}
/// <summary>
/// Exports the given asset to the specified file location.
/// </summary>
/// <param name="inputPath">The input asset path (absolute path with an extension).</param>
/// <param name="outputFolder">The output folder path (filename with extension is computed by auto).</param>
/// <returns>True if given asset has been exported, otherwise false.</returns>
public static bool Export(string inputPath, string outputFolder)
{
return Internal_Export(inputPath, outputFolder);
}
/// <summary>
/// Checks if every managed assembly has been loaded (including user scripts assembly).
/// </summary>
public static bool IsEveryAssemblyLoaded => Internal_GetIsEveryAssemblyLoaded();
#region Env Probes Baking
/// <summary>
/// Occurs when environment probe baking starts.
/// </summary>
public static event Action<EnvironmentProbe> EnvProbeBakeStart;
/// <summary>
/// Occurs when environment probe baking ends.
/// </summary>
public static event Action<EnvironmentProbe> EnvProbeBakeEnd;
internal static void Internal_EnvProbeBake(bool started, EnvironmentProbe probe)
{
if (started)
EnvProbeBakeStart?.Invoke(probe);
else
EnvProbeBakeEnd?.Invoke(probe);
}
#endregion
#region Lightmaps Baking
/// <summary>
/// Lightmaps baking steps.
/// </summary>
public enum LightmapsBakeSteps
{
/// <summary>
/// The service initialize stage.
/// </summary>
Initialize,
/// <summary>
/// The cache entries stage.
/// </summary>
CacheEntries,
/// <summary>
/// The generate lightmap charts stage.
/// </summary>
GenerateLightmapCharts,
/// <summary>
/// The pack lightmap charts stage.
/// </summary>
PackLightmapCharts,
/// <summary>
/// The update lightmaps collection stage.
/// </summary>
UpdateLightmapsCollection,
/// <summary>
/// The update entries stage.
/// </summary>
UpdateEntries,
/// <summary>
/// The generate hemispheres cache stage.
/// </summary>
GenerateHemispheresCache,
/// <summary>
/// The render hemispheres stage.
/// </summary>
RenderHemispheres,
/// <summary>
/// The cleanup stage.
/// </summary>
Cleanup
}
/// <summary>
/// Lightmaps baking progress event delegate.
/// </summary>
/// <param name="step">The current step.</param>
/// <param name="stepProgress">The current step progress (normalized to [0;1]).</param>
/// <param name="totalProgress">The total baking progress (normalized to [0;1]).</param>
public delegate void LightmapsBakeProgressDelegate(LightmapsBakeSteps step, float stepProgress, float totalProgress);
/// <summary>
/// Lightmaps baking nd event delegate.
/// </summary>
/// <param name="failed">True if baking failed or has been canceled, otherwise false.</param>
public delegate void LightmapsBakeEndDelegate(bool failed);
/// <summary>
/// Occurs when lightmaps baking starts.
/// </summary>
public static event Action LightmapsBakeStart;
/// <summary>
/// Occurs when lightmaps baking ends.
/// </summary>
public static event LightmapsBakeEndDelegate LightmapsBakeEnd;
/// <summary>
/// Occurs when lightmaps baking progress changes.
/// </summary>
public static event LightmapsBakeProgressDelegate LightmapsBakeProgress;
internal void Internal_StartLightingBake()
{
StateMachine.GoToState<BuildingLightingState>();
}
internal static void Internal_LightmapsBake(LightmapsBakeSteps step, float stepProgress, float totalProgress, bool isProgressEvent)
{
if (isProgressEvent)
LightmapsBakeProgress?.Invoke(step, stepProgress, totalProgress);
else if (step == LightmapsBakeSteps.Initialize)
LightmapsBakeStart?.Invoke();
else if (step == LightmapsBakeSteps.GenerateLightmapCharts)
LightmapsBakeEnd?.Invoke(false);
else
LightmapsBakeEnd?.Invoke(true);
}
/// <summary>
/// Starts building scenes data or cancels it if already running.
/// </summary>
public void BuildScenesOrCancel()
{
var isActive = StateMachine.BuildingScenesState.IsActive;
var msg = isActive ? "Cancel baking scenes data?" : "Start baking scenes data?";
if (MessageBox.Show(msg, "Scene Data building", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK)
return;
if (isActive)
StateMachine.BuildingScenesState.Cancel();
else
StateMachine.GoToState<BuildingScenesState>();
}
/// <summary>
/// Starts lightmaps baking for the open scenes or cancels it if already running.
/// </summary>
public void BakeLightmapsOrCancel()
{
bool isBakingLightmaps = ProgressReporting.BakeLightmaps.IsActive;
if (isBakingLightmaps)
Internal_BakeLightmaps(true);
else
StateMachine.GoToState<BuildingLightingState>();
}
/// <summary>
/// Clears the lightmaps linkage for all open scenes.
/// </summary>
public void ClearLightmaps()
{
var scenes = Level.Scenes;
for (int i = 0; i < scenes.Length; i++)
{
scenes[i].ClearLightmaps();
}
Scene.MarkSceneEdited(scenes);
}
#endregion
#region Internal Calls
[StructLayout(LayoutKind.Sequential)]
internal struct InternalOptions
{
public byte AutoReloadScriptsOnMainWindowFocus;
public byte ForceScriptCompilationOnStartup;
public byte UseAssetImportPathRelative;
public byte AutoRebuildCSG;
public float AutoRebuildCSGTimeoutMs;
public byte AutoRebuildNavMesh;
public float AutoRebuildNavMeshTimeoutMs;
}
[StructLayout(LayoutKind.Sequential)]
internal struct VisualScriptLocal
{
public string Value;
public string ValueTypeName;
public uint NodeId;
public int BoxId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct VisualScriptStackFrame
{
public VisualScript Script;
public uint NodeId;
public int BoxId;
}
internal void BuildCommand(string arg)
{
if (TryBuildCommand(arg))
Engine.RequestExit();
}
private bool TryBuildCommand(string arg)
{
if (GameCooker.IsRunning)
return true;
if (arg == null)
return true;
Editor.Log("Using CL build for \"" + arg + "\"");
int dotPos = arg.IndexOf('.');
string presetName, targetName;
if (dotPos == -1)
{
presetName = arg;
targetName = string.Empty;
}
else
{
presetName = arg.Substring(0, dotPos);
targetName = arg.Substring(dotPos + 1);
}
var settings = GameSettings.Load<BuildSettings>();
var preset = settings.GetPreset(presetName);
if (preset == null)
{
Editor.LogWarning("Missing preset.");
return true;
}
if (string.IsNullOrEmpty(targetName))
{
Windows.GameCookerWin.BuildAll(preset);
}
else
{
var target = preset.GetTarget(targetName);
if (target == null)
{
Editor.LogWarning("Missing target.");
return true;
}
Windows.GameCookerWin.Build(preset, target);
}
Windows.GameCookerWin.ExitOnBuildQueueEnd();
return false;
}
internal IntPtr GetMainWindowPtr()
{
return FlaxEngine.Object.GetUnmanagedPtr(Windows.MainWindow);
}
internal bool Internal_CanReloadScripts()
{
return StateMachine.CurrentState.CanReloadScripts;
}
internal bool Internal_CanAutoBuildCSG()
{
return StateMachine.CurrentState.CanEditScene;
}
internal bool Internal_CanAutoBuildNavMesh()
{
return StateMachine.CurrentState.CanEditScene;
}
internal bool Internal_HasGameViewportFocus()
{
if (Windows.GameWin != null && Windows.GameWin.ContainsFocus)
{
var win = Windows.GameWin.Root;
if (win?.RootWindow is WindowRootControl root && root.Window && root.Window.IsFocused)
{
if (StateMachine.IsPlayMode && StateMachine.PlayingState.IsPaused)
return false;
return true;
}
}
return false;
}
internal void Internal_FocusGameViewport()
{
if (Windows.GameWin != null)
{
if (StateMachine.IsPlayMode && !StateMachine.PlayingState.IsPaused)
{
Windows.GameWin.FocusGameViewport();
}
}
}
internal void Internal_ScreenToGameViewport(ref Float2 pos)
{
var win = Windows.GameWin?.Root;
if (win?.RootWindow is WindowRootControl root)
{
pos = Float2.Round(Windows.GameWin.Viewport.PointFromScreen(pos) * root.DpiScale);
}
else
{
pos = Float2.Minimum;
}
}
internal void Internal_GameViewportToScreen(ref Float2 pos)
{
var win = Windows.GameWin?.Root;
if (win?.RootWindow is WindowRootControl root)
{
pos = Float2.Round(Windows.GameWin.Viewport.PointToScreen(pos / root.DpiScale));
}
else
{
pos = Float2.Minimum;
}
}
internal void Internal_GetGameWinPtr(bool forceGet, out IntPtr result)
{
result = IntPtr.Zero;
if (Windows.GameWin != null && (forceGet || Windows.GameWin.ContainsFocus))
{
var win = Windows.GameWin.Root as WindowRootControl;
if (win != null)
result = FlaxEngine.Object.GetUnmanagedPtr(win.Window);
}
}
internal void Internal_GetGameWindowSize(out Float2 result)
{
result = new Float2(1280, 720);
var gameWin = Windows.GameWin;
if (gameWin?.Root?.RootWindow is WindowRootControl root)
{
// Handle case when Game window is not selected in tab view
var dockedTo = gameWin.ParentDockPanel;
if (dockedTo != null && dockedTo.SelectedTab != gameWin && dockedTo.SelectedTab != null)
result = dockedTo.SelectedTab.Size * root.DpiScale;
else
result = gameWin.Size * root.DpiScale;
result = Float2.Round(result);
}
}
internal bool Internal_OnAppExit()
{
// In editor play mode (when main window is not closed) just skip engine exit and leave the play mode
if (StateMachine.IsPlayMode && Windows.MainWindow != null)
{
Simulation.RequestStopPlay();
return false;
}
return true;
}
[StructLayout(LayoutKind.Sequential)]
internal struct VisualScriptingDebugFlowInfo
{
public VisualScript Script;
public FlaxEngine.Object ScriptInstance;
public uint NodeId;
public int BoxId;
}
internal static event Action<VisualScriptingDebugFlowInfo> VisualScriptingDebugFlow;
internal static void Internal_OnVisualScriptingDebugFlow(ref VisualScriptingDebugFlowInfo debugFlow)
{
VisualScriptingDebugFlow?.Invoke(debugFlow);
}
private static void RequestStartPlayOnEditMode()
{
if (Instance.StateMachine.IsEditMode)
Instance.Simulation.RequestStartPlay();
if (Instance.StateMachine.IsPlayMode)
Instance.StateMachine.StateChanged -= RequestStartPlayOnEditMode;
}
internal static void Internal_RequestStartPlayOnEditMode()
{
Instance.StateMachine.StateChanged += RequestStartPlayOnEditMode;
}
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int Internal_ReadOutputLogs(string[] outMessages, byte[] outLogTypes, long[] outLogTimes);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_SetPlayMode(bool value);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern string Internal_GetProjectPath();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_CloneAssetFile(string dstPath, string srcPath, ref Guid dstId);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_Import(string inputPath, string outputPath, IntPtr arg);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_ImportTexture(string inputPath, string outputPath, ref TextureImportSettings.InternalOptions options);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_ImportModel(string inputPath, string outputPath, ref ModelImportSettings.InternalOptions options);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_ImportAudio(string inputPath, string outputPath, ref AudioImportSettings.InternalOptions options);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_GetAudioClipMetadata(IntPtr obj, out int originalSize, out int importedSize);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_SaveJsonAsset(string outputPath, string data, string typename);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_CopyCache(ref Guid dstId, ref Guid srcId);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_BakeLightmaps(bool cancel);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern string Internal_GetShaderAssetSourceCode(IntPtr obj);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_CookMeshCollision(string path, CollisionDataType type, IntPtr model, int modelLodIndex, uint materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int convexVertexLimit);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_GetCollisionWires(IntPtr collisionData, out Float3[] triangles, out int[] indices);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_GetEditorBoxWithChildren(IntPtr obj, out BoundingBox resultAsRef);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_SetOptions(ref InternalOptions options);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_DrawNavMesh();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_CloseSplashScreen();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_CreateAsset(NewAssetType type, string outputPath);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_CreateVisualScript(string outputPath, string baseTypename);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern string Internal_CanImport(string extension);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_CanExport(string path);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_Export(string inputPath, string outputFolder);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_GetIsEveryAssemblyLoaded();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int Internal_GetLastProjectOpenedEngineBuild();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_GetIsCSGActive();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_RunVisualScriptBreakpointLoopTick(float deltaTime);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern VisualScriptLocal[] Internal_GetVisualScriptLocals();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern VisualScriptStackFrame[] Internal_GetVisualScriptStackFrames();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern VisualScriptStackFrame Internal_GetVisualScriptPreviousScopeFrame();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_EvaluateVisualScriptLocal(IntPtr script, ref VisualScriptLocal local);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_DeserializeSceneObject(IntPtr sceneObject, string json);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_LoadAsset(ref Guid id);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_CanSetToRoot(IntPtr prefab, IntPtr newRoot);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern float Internal_GetAnimationTime(IntPtr animatedModel);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_SetAnimationTime(IntPtr animatedModel, float time);
#endregion
}
}