// Copyright (c) Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using System.Threading.Tasks; using FlaxEditor.Content; using FlaxEditor.Content.Settings; using FlaxEditor.Content.Thumbnails; using FlaxEditor.Modules; using FlaxEditor.Modules.SourceCodeEditing; using FlaxEditor.Options; using FlaxEditor.SceneGraph.Actors; using FlaxEditor.States; using FlaxEditor.Windows; using FlaxEditor.Windows.Assets; using FlaxEngine; using FlaxEngine.Assertions; using FlaxEngine.GUI; using FlaxEngine.Interop; using FlaxEngine.Json; #pragma warning disable CS1591 namespace FlaxEditor { /// /// The main managed editor class. Editor root object. /// public sealed partial class Editor { /// /// Gets the Editor instance. /// public static Editor Instance { get; private set; } /// /// The path to the local cache folder shared by all the installed editor instance for a given user (used also by the Flax Launcher). /// public static readonly string LocalCachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Flax"); static Editor() { JsonSerializer.Settings.Converters.Add(new SceneTreeNodeConverter()); } private readonly List _modules = new List(16); private bool _isAfterInit, _areModulesInited, _areModulesAfterInitEnd, _isHeadlessMode, _autoExit; private string _projectToOpen; private float _lastAutoSaveTimer, _autoExitTimeout = 0.1f; private Button _saveNowButton; private Button _cancelSaveButton; private bool _autoSaveNow; private Guid _startupSceneCmdLine; private const string ProjectDataLastScene = "LastScene"; private const string ProjectDataLastSceneSpawn = "LastSceneSpawn"; /// /// Gets a value indicating whether Flax Engine is the best in the world. /// public bool IsFlaxEngineTheBest => true; /// /// Gets a value indicating whether this Editor is running a dev instance of the engine. /// [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_IsDevInstance", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool IsDevInstance(); /// /// Gets a value indicating whether this Editor is running as official build (distributed via Flax services). /// [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_IsOfficialBuild", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool IsOfficialBuild(); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_IsPlayMode", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_IsPlayMode(); /// /// True if the editor is running now in a play mode. Assigned by the managed editor instance. /// public static bool IsPlayMode => Internal_IsPlayMode(); /// /// Gets the build number of the last editor instance that opened the current project. Value 0 means the undefined version. /// public static int LastProjectOpenedEngineBuild => Internal_GetLastProjectOpenedEngineBuild(); /// /// The windows module. /// public WindowsModule Windows; /// /// The UI module. /// public UIModule UI; /// /// The thumbnails module. /// public ThumbnailsModule Thumbnails; /// /// The simulation module. /// public SimulationModule Simulation; /// /// The scene module. /// public SceneModule Scene; /// /// The prefabs module. /// public PrefabsModule Prefabs; /// /// The scene editing module. /// public SceneEditingModule SceneEditing; /// /// The progress reporting module. /// public ProgressReportingModule ProgressReporting; /// /// The content editing module. /// public ContentEditingModule ContentEditing; /// /// The content database module. /// public ContentDatabaseModule ContentDatabase; /// /// The content importing module. /// public ContentImportingModule ContentImporting; /// /// The content finder module. /// public ContentFindingModule ContentFinding; /// /// The scripts editing. /// public CodeEditingModule CodeEditing; /// /// The scripts documentation. /// public CodeDocsModule CodeDocs; /// /// The editor state machine. /// public EditorStateMachine StateMachine; /// /// The editor options manager. /// public OptionsModule Options; /// /// The editor per-project cache manager. /// public ProjectCacheModule ProjectCache; /// /// The undo/redo. /// public EditorUndo Undo; /// /// The icons container. /// public EditorIcons Icons; /// /// Gets the main transform gizmo used by the . /// public Gizmo.TransformGizmo MainTransformGizmo => Windows.EditWin.Viewport.TransformGizmo; /// /// 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). /// public bool IsHeadlessMode => _isHeadlessMode; /// /// Gets a value indicating whether Editor instance is initialized. /// public bool IsInitialized => _areModulesAfterInitEnd; /// /// Occurs when editor initialization starts. All editor modules already received OnInit callback and editor splash screen is visible. /// public event Action InitializationStart; /// /// Occurs when editor initialization ends. All editor modules already received OnEndInit callback and editor splash screen will be closed. /// public event Action InitializationEnd; /// /// 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). /// public Dictionary CustomData = new Dictionary(); /// /// The game project info. /// public ProjectInfo GameProject; /// /// The engine project info. /// public ProjectInfo EngineProject; /// /// Occurs when play mode is beginning (before entering play mode). /// public event Action PlayModeBeginning; /// /// Occurs when play mode begins (after entering play mode). /// public event Action PlayModeBegin; /// /// Occurs when play mode is ending (before leaving play mode). /// public event Action PlayModeEnding; /// /// Occurs when play mode ends (after leaving play mode). /// public event Action PlayModeEnd; /// /// Fired on Editor update /// public event Action EditorUpdate; internal Editor() { Instance = this; } internal void Init(StartupFlags flags, Guid startupScene) { Log("Setting up C# Editor..."); _isHeadlessMode = flags.HasFlag(StartupFlags.Headless); _autoExit = flags.HasFlag(StartupFlags.Exit); _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); if (flags.HasFlag(StartupFlags.NewProject)) InitProject(); EnsureState(); Log("Editor init"); if (_isHeadlessMode) 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 try { 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 string lastSceneIdName)) { var lastScenes = JsonSerializer.Deserialize(lastSceneIdName); foreach (var scene in lastScenes) { var lastScene = scene; Internal_LoadAsset(ref lastScene); } } break; } } } catch (Exception) { // Ignore errors } InitializationStart?.Invoke(); // Start Editor initialization ending phrase (will wait for scripts compilation result) StateMachine.LoadingState.StartInitEnding(flags.HasFlag(StartupFlags.SkipCompile)); } 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(); Log("Editor end init"); // Change state StateMachine.GoToState(); // 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 try { // 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; } 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 string lastSceneIdName)) { var lastScenes = JsonSerializer.Deserialize(lastSceneIdName); foreach (var sceneId in lastScenes) { var lastScene = ContentDatabase.Find(sceneId); if (!(lastScene is SceneItem)) continue; Editor.Log($"Loading last opened scene: {lastScene.ShortName}"); if (sceneId == lastScenes[0]) Scene.OpenScene(sceneId); else Level.LoadSceneAsync(sceneId); } // Restore view if (ProjectCache.TryGetCustomData(ProjectDataLastSceneSpawn, out string lastSceneSpawnName)) Windows.EditWin.Viewport.ViewRay = JsonSerializer.Deserialize(lastSceneSpawnName); } break; } } } catch (Exception) { // Ignore errors } } internal void Update() { Profiler.BeginEvent("Editor.Update"); try { StateMachine.Update(); UpdateAutoSave(); if (_autoExit && StateMachine.CurrentState == StateMachine.EditingSceneState) { _autoExitTimeout -= Time.UnscaledGameTime; if (_autoExitTimeout < 0.0f) { Log("Auto exit"); Engine.RequestExit(0); } } if (!StateMachine.IsPlayMode) { StateMachine.CurrentState.UpdateFPS(); } EditorUpdate?.Invoke(); // 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; if (timeToNextSave <= 0.0f || _autoSaveNow) { Log("Auto save"); _lastAutoSaveTimer = Time.UnscaledGameTime; if (options.AutoSaveScenes) Scene.SaveScenes(); if (options.AutoSaveContent) SaveContent(); _autoSaveNow = false; // Hide save now and cancel save buttons if (_saveNowButton != null && _cancelSaveButton != null) { _saveNowButton.Visible = false; _cancelSaveButton.Visible = false; } } else if (timeToNextSave <= options.AutoSaveReminderTime) { msg = string.Format("Auto save in {0}s...", Mathf.CeilToInt(timeToNextSave)); // Create save now and cancel save buttons if needed if (_saveNowButton == null) { _saveNowButton = new Button { Parent = UI.StatusBar, Height = 14, Width = 60, AnchorPreset = AnchorPresets.MiddleLeft, BackgroundColor = Color.Transparent, BorderColor = Color.Transparent, BackgroundColorHighlighted = Color.Transparent, BackgroundColorSelected = Color.Transparent, BorderColorHighlighted = Color.Transparent, Text = "Save Now", TooltipText = "Saves now and restarts the auto save timer." }; _saveNowButton.LocalX += 120; _saveNowButton.Clicked += () => _autoSaveNow = true; _saveNowButton.HoverBegin += () => _saveNowButton.TextColor = Style.Current.BackgroundHighlighted; _saveNowButton.HoverEnd += () => _saveNowButton.TextColor = UI.StatusBar.TextColor; } if (_cancelSaveButton == null) { _cancelSaveButton = new Button { Parent = UI.StatusBar, Height = 14, Width = 70, AnchorPreset = AnchorPresets.MiddleLeft, BackgroundColor = Color.Transparent, BorderColor = Color.Transparent, BackgroundColorHighlighted = Color.Transparent, BackgroundColorSelected = Color.Transparent, BorderColorHighlighted = Color.Transparent, Text = "Cancel", TooltipText = "Cancels this auto save." }; _cancelSaveButton.LocalX += 180; _cancelSaveButton.Clicked += () => { Log("Auto save canceled"); _saveNowButton.Visible = false; _cancelSaveButton.Visible = false; _lastAutoSaveTimer = Time.UnscaledGameTime; // Reset timer }; _cancelSaveButton.HoverBegin += () => _cancelSaveButton.TextColor = Style.Current.BackgroundHighlighted; _cancelSaveButton.HoverEnd += () => _cancelSaveButton.TextColor = UI.StatusBar.TextColor; } // Show save now and cancel save buttons if (!_saveNowButton.Visible || !_cancelSaveButton.Visible) { _saveNowButton.Visible = true; _cancelSaveButton.Visible = true; } } } if (StateMachine.EditingSceneState.AutoSaveStatus != msg) { StateMachine.EditingSceneState.AutoSaveStatus = msg; UI.UpdateStatusBar(); } if (UI?.StatusBar?.Text != null && !UI.StatusBar.Text.Contains("Auto") && _saveNowButton != null && _cancelSaveButton != null && (_saveNowButton.Visible || _cancelSaveButton.Visible)) { _saveNowButton.Visible = false; _cancelSaveButton.Visible = false; } } 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(); // Cache last opened scenes { var lastScenes = Level.Scenes; var lastSceneIds = new Guid[lastScenes.Length]; for (int i = 0; i < lastScenes.Length; i++) lastSceneIds[i] = lastScenes[i].ID; var lastSceneSpawn = Windows.EditWin.Viewport.ViewRay; ProjectCache.SetCustomData(ProjectDataLastScene, JsonSerializer.Serialize(lastSceneIds)); 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(); foreach (var cache in Surface.VisjectSurface.NodesCache.Caches.ToArray()) cache.Clear(); Instance = null; // Invoke new instance if need to open a project if (!string.IsNullOrEmpty(_projectToOpen)) { var procSettings = new CreateProcessSettings { FileName = Platform.ExecutableFilePath, Arguments = string.Format("-project \"{0}\"", _projectToOpen), ShellExecute = true, WaitForEnd = false, HiddenWindow = false, }; _projectToOpen = null; Platform.CreateProcess(ref procSettings); } } /// /// Undo last action. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PerformUndo() { Undo.PerformUndo(); } /// /// Redo last action. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PerformRedo() { Undo.PerformRedo(); } /// /// Saves all changes (scenes, assets, etc.). /// public void SaveAll() { Windows.SaveCurrentLayout(); Scene.SaveScenes(); SaveContent(); UI.AddStatusMessage("Saved!"); } /// /// Saves all content (assets, etc.). /// public void SaveContent() { for (int i = 0; i < Windows.Windows.Count; i++) { if (Windows.Windows[i] is AssetEditorWindow win) { win.Save(); } } } /// /// Closes this project with running editor and opens the given project. /// /// The project file path. public void OpenProject(string projectFilePath) { if (projectFilePath == null || !File.Exists(projectFilePath)) { 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); } /// /// Ensure that editor is in a given state, otherwise throws . /// /// Valid state to check. /// 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}"); } /// /// Ensure that editor is in a state of given type, otherwise throws . /// /// The type of the state type. public void EnsureState() { var state = StateMachine.GetState() as EditorState; EnsureState(state); } /// /// Logs the specified message to the log file. /// /// The message. [HideInEditor] public static void Log(string msg) { Debug.Logger.LogHandler.LogWrite(LogType.Info, msg); } /// /// Logs the specified warning message to the log file. /// /// The message. [HideInEditor] public static void LogWarning(string msg) { Debug.Logger.LogHandler.LogWrite(LogType.Warning, msg); } /// /// Logs the specified warning exception to the log file. /// /// The exception. [HideInEditor] public static void LogWarning(Exception ex) { LogWarning("Exception: " + ex.Message); LogWarning(ex.StackTrace); if (ex.InnerException != null) { LogWarning("Inner exception:"); LogWarning(ex.InnerException); } } /// /// Logs the specified error message to the log file. /// /// The message. [HideInEditor] public static void LogError(string msg) { Debug.Logger.LogHandler.LogWrite(LogType.Error, msg); } /// /// New asset types allowed to create. /// [Deprecated in v1.8] /// [Obsolete("Use CreateAsset with named tag instead")] public enum NewAssetType { /// /// The . /// Material = 0, /// /// The . /// MaterialInstance = 1, /// /// The . /// CollisionData = 2, /// /// The . /// AnimationGraph = 3, /// /// The . /// SkeletonMask = 4, /// /// The . /// ParticleEmitter = 5, /// /// The . /// ParticleSystem = 6, /// /// The . /// SceneAnimation = 7, /// /// The . /// MaterialFunction = 8, /// /// The . /// ParticleEmitterFunction = 9, /// /// The . /// AnimationGraphFunction = 10, /// /// The . /// Animation = 11, /// /// The . /// BehaviorTree = 12, } /// /// Serializes the given object to json asset. /// /// The result asset file path. /// The obj to serialize. /// True if saving failed, otherwise false. 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); } /// /// Cooks the mesh collision data and saves it to the asset using format. action cannot be performed on a main thread. /// /// The output asset path. /// The collision data type. /// The source model. /// The source model LOD index. /// The source model material slots mask. One bit per-slot. Can be used to exclude particular material slots from collision cooking. /// The convex mesh generation flags. /// The convex mesh vertex limit. Use values in range [8;255] /// True if failed, otherwise false. 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); } /// /// Gets the shader source code (HLSL shader code). /// /// The shader asset. /// The generated source code. public static string GetShaderSourceCode(Shader asset) { return GetShaderAssetSourceCode(asset); } /// /// Gets the material shader source code (HLSL shader code). /// /// The material asset. /// The generated source code. public static string GetShaderSourceCode(Material asset) { return GetShaderAssetSourceCode(asset); } /// /// Gets the particle emitter GPU simulation shader source code (HLSL shader code). /// /// The particle emitter asset. /// The generated source code. 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; } /// /// Gets the actor bounding sphere (including child actors). /// /// The actor. /// The bounding sphere. 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); if (sphere == BoundingSphere.Empty) sphere = new BoundingSphere(actor.Position, sphere.Radius); sphere.Radius = Math.Max(sphere.Radius, 15.0f); } else { sphere = BoundingSphere.Empty; } } /// /// Closes editor splash screen popup window. /// public static void CloseSplashScreen() { Internal_CloseSplashScreen(); } /// /// Creates new asset at the target location. /// [Deprecated in v1.8] /// /// New asset type. /// Output asset path. [Obsolete("Use CreateAsset with named tag instead")] public static bool CreateAsset(NewAssetType type, string outputPath) { // [Deprecated on 18.02.2024, expires on 18.02.2025] string tag; switch (type) { case NewAssetType.Material: tag = "Material"; break; case NewAssetType.MaterialInstance: tag = "MaterialInstance"; break; case NewAssetType.CollisionData: tag = "CollisionData"; break; case NewAssetType.AnimationGraph: tag = "AnimationGraph"; break; case NewAssetType.SkeletonMask: tag = "SkeletonMask"; break; case NewAssetType.ParticleEmitter: tag = "ParticleEmitter"; break; case NewAssetType.ParticleSystem: tag = "ParticleSystem"; break; case NewAssetType.SceneAnimation: tag = "SceneAnimation"; break; case NewAssetType.MaterialFunction: tag = "MaterialFunction"; break; case NewAssetType.ParticleEmitterFunction: tag = "ParticleEmitterFunction"; break; case NewAssetType.AnimationGraphFunction: tag = "AnimationGraphFunction"; break; case NewAssetType.Animation: tag = "Animation"; break; case NewAssetType.BehaviorTree: tag = "BehaviorTree"; break; default: return true; } return CreateAsset(tag, outputPath); } /// /// Creates new Visual Script asset at the target location. /// /// Output asset path. /// Base class typename. public static bool CreateVisualScript(string outputPath, string baseTypename) { return Internal_CreateVisualScript(outputPath, baseTypename); } /// /// Checks if can import asset with the given extension. /// /// The file extension. /// The output file extension (flax, json, etc.). /// True if can import files with given extension, otherwise false. public static bool CanImport(string extension, out string outputExtension) { outputExtension = Internal_CanImport(extension); return outputExtension != null; } /// /// Checks if the given asset can be exported. /// /// The asset path (absolute path with an extension). /// True if can export given asset, otherwise false. public static bool CanExport(string path) { return Internal_CanExport(path); } /// /// Exports the given asset to the specified file location. /// /// The input asset path (absolute path with an extension). /// The output folder path (filename with extension is computed by auto). /// True if given asset has been exported, otherwise false. public static bool Export(string inputPath, string outputFolder) { return Internal_Export(inputPath, outputFolder); } /// /// Checks if every managed assembly has been loaded (including user scripts assembly). /// public static bool IsEveryAssemblyLoaded => Internal_GetIsEveryAssemblyLoaded(); #region Env Probes Baking /// /// Occurs when environment probe baking starts. /// public static event Action EnvProbeBakeStart; /// /// Occurs when environment probe baking ends. /// public static event Action EnvProbeBakeEnd; internal static void Internal_EnvProbeBake(bool started, EnvironmentProbe probe) { if (started) EnvProbeBakeStart?.Invoke(probe); else EnvProbeBakeEnd?.Invoke(probe); } #endregion #region Lightmaps Baking /// /// Lightmaps baking steps. /// public enum LightmapsBakeSteps { /// /// The service initialize stage. /// Initialize, /// /// The cache entries stage. /// CacheEntries, /// /// The generate lightmap charts stage. /// GenerateLightmapCharts, /// /// The pack lightmap charts stage. /// PackLightmapCharts, /// /// The update lightmaps collection stage. /// UpdateLightmapsCollection, /// /// The update entries stage. /// UpdateEntries, /// /// The generate hemispheres cache stage. /// GenerateHemispheresCache, /// /// The render hemispheres stage. /// RenderHemispheres, /// /// The cleanup stage. /// Cleanup } /// /// Lightmaps baking progress event delegate. /// /// The current step. /// The current step progress (normalized to [0;1]). /// The total baking progress (normalized to [0;1]). public delegate void LightmapsBakeProgressDelegate(LightmapsBakeSteps step, float stepProgress, float totalProgress); /// /// Lightmaps baking nd event delegate. /// /// True if baking failed or has been canceled, otherwise false. public delegate void LightmapsBakeEndDelegate(bool failed); /// /// Occurs when lightmaps baking starts. /// public static event Action LightmapsBakeStart; /// /// Occurs when lightmaps baking ends. /// public static event LightmapsBakeEndDelegate LightmapsBakeEnd; /// /// Occurs when lightmaps baking progress changes. /// public static event LightmapsBakeProgressDelegate LightmapsBakeProgress; internal void Internal_StartLightingBake() { StateMachine.GoToState(); } 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); } /// /// Starts building scenes data or cancels it if already running. /// 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(); } /// /// Starts lightmaps baking for the open scenes or cancels it if already running. /// public void BakeLightmapsOrCancel() { bool isBakingLightmaps = ProgressReporting.BakeLightmaps.IsActive; if (isBakingLightmaps) Internal_BakeLightmaps(true); else StateMachine.GoToState(); } /// /// Clears the lightmaps linkage for all open scenes. /// public void ClearLightmaps() { var scenes = Level.Scenes; for (int i = 0; i < scenes.Length; i++) { scenes[i].ClearLightmaps(); } Scene.MarkSceneEdited(scenes); } /// /// Bakes all environmental probes in the scene. /// public void BakeAllEnvProbes() { Scene.ExecuteOnGraph(node => { if (node is EnvironmentProbeNode envProbeNode && envProbeNode.IsActive) { ((EnvironmentProbe)envProbeNode.Actor).Bake(); node.ParentScene.IsEdited = true; } else if (node is SkyLightNode skyLightNode && skyLightNode.IsActive && skyLightNode.Actor is SkyLight skyLight && skyLight.Mode == SkyLight.Modes.CaptureScene) { skyLight.Bake(); node.ParentScene.IsEdited = true; } return node.IsActive; }); } /// /// Builds CSG for all open scenes. /// public void BuildCSG() { var scenes = Level.Scenes; scenes.ToList().ForEach(x => x.BuildCSG(0)); Scene.MarkSceneEdited(scenes); } /// /// Builds Nav mesh for all open scenes. /// public void BuildNavMesh() { var scenes = Level.Scenes; scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0)); Scene.MarkSceneEdited(scenes); } /// /// Builds SDF for all static models in the scene. /// public void BuildAllMeshesSDF() { var models = new List(); Scene.ExecuteOnGraph(node => { if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel) { var model = staticModel.Model; if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && model != null && !models.Contains(model) && !model.IsVirtual && model.SDF.Texture == null) { models.Add(model); } } return true; }); Task.Run(() => { for (int i = 0; i < models.Count; i++) { var model = models[i]; Log($"[{i}/{models.Count}] Generating SDF for {model}"); if (!model.GenerateSDF()) model.Save(); } }); } #endregion #region Internal Calls [StructLayout(LayoutKind.Sequential)] internal struct InternalOptions { public byte AutoReloadScriptsOnMainWindowFocus; public byte ForceScriptCompilationOnStartup; public byte UseAssetImportPathRelative; public byte EnableParticlesPreview; public byte AutoRebuildCSG; public float AutoRebuildCSGTimeoutMs; public byte AutoRebuildNavMesh; public float AutoRebuildNavMeshTimeoutMs; } internal void BuildCommand(string arg) { if (TryBuildCommand(arg)) Engine.RequestExit(1); } 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(); 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 = Windows.GameWin.Viewport.PointFromScreen(pos); pos = Float2.Round(pos); } else { pos = Float2.Minimum; } } internal void Internal_GameViewportToScreen(ref Float2 pos) { var win = Windows.GameWin?.Root; if (win?.RootWindow is WindowRootControl root) { pos = Windows.GameWin.Viewport.PointToScreen(pos); pos = Float2.Round(pos); } 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; else result = gameWin.Viewport.Size; result *= 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 VisualScriptingDebugFlow; internal static void Internal_OnVisualScriptingDebugFlow(ref VisualScriptingDebugFlowInfo debugFlow) { VisualScriptingDebugFlow?.Invoke(debugFlow); } private static void RequestStartPlayOnEditMode() { if (Instance.StateMachine.IsEditMode) Instance.Simulation.RequestStartPlayScenes(); if (Instance.StateMachine.IsPlayMode) Instance.StateMachine.StateChanged -= RequestStartPlayOnEditMode; } internal static void Internal_RequestStartPlayOnEditMode() { Instance.StateMachine.StateChanged += RequestStartPlayOnEditMode; } [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_ReadOutputLogs", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.Interop.StringMarshaller))] internal static partial int Internal_ReadOutputLogs([MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "outCapacity")] ref string[] outMessages, [MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "outCapacity")] ref byte[] outLogTypes, [MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "outCapacity")] ref long[] outLogTimes, int outCapacity); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_SetPlayMode", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.Interop.StringMarshaller))] internal static partial void Internal_SetPlayMode([MarshalAs(UnmanagedType.U1)] bool value); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetProjectPath", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial string Internal_GetProjectPath(); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CloneAssetFile", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_CloneAssetFile(string dstPath, string srcPath, ref Guid dstId); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAudioClipMetadata", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_GetAudioClipMetadata(IntPtr obj, out int originalSize, out int importedSize); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_SaveJsonAsset", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_SaveJsonAsset(string outputPath, string data, string typename); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CopyCache", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_CopyCache(ref Guid dstId, ref Guid srcId); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_BakeLightmaps", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_BakeLightmaps([MarshalAs(UnmanagedType.U1)] bool cancel); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetShaderAssetSourceCode", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial string Internal_GetShaderAssetSourceCode(IntPtr obj); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CookMeshCollision", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_CookMeshCollision(string path, CollisionDataType type, IntPtr model, int modelLodIndex, uint materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int convexVertexLimit); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetCollisionWires", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_GetCollisionWires(IntPtr collisionData, [MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "trianglesCount")] out Float3[] triangles, [MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "indicesCount")] out int[] indices, out int trianglesCount, out int indicesCount); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetEditorBoxWithChildren", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_GetEditorBoxWithChildren(IntPtr obj, out BoundingBox resultAsRef); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_SetOptions", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_SetOptions(ref InternalOptions options); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_DrawNavMesh", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_DrawNavMesh(); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CloseSplashScreen", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_CloseSplashScreen(); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CreateVisualScript", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_CreateVisualScript(string outputPath, string baseTypename); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CanImport", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial string Internal_CanImport(string extension); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CanExport", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_CanExport(string path); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_Export", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_Export(string inputPath, string outputFolder); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetIsEveryAssemblyLoaded", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_GetIsEveryAssemblyLoaded(); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetLastProjectOpenedEngineBuild", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial int Internal_GetLastProjectOpenedEngineBuild(); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetIsCSGActive", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_GetIsCSGActive(); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_RunVisualScriptBreakpointLoopTick", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_RunVisualScriptBreakpointLoopTick(float deltaTime); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_DeserializeSceneObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_DeserializeSceneObject(IntPtr sceneObject, string json); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_LoadAsset", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_LoadAsset(ref Guid id); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CanSetToRoot", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_CanSetToRoot(IntPtr prefab, IntPtr newRoot); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAnimationTime", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial float Internal_GetAnimationTime(IntPtr animatedModel); [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_SetAnimationTime", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_SetAnimationTime(IntPtr animatedModel, float time); #endregion } }