// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using FlaxEditor.Content; using FlaxEditor.Scripting; using FlaxEditor.Surface.ContextMenu; using FlaxEngine; using Animation = FlaxEditor.Surface.Archetypes.Animation; namespace FlaxEditor.Surface { /// /// The Visject Surface implementation for the animation graph editor. /// /// [HideInEditor] public class AnimGraphSurface : VisjectSurface { private static readonly List StateMachineGroupArchetypes = new List(new[] { // Customized Animations group with special nodes to use here new GroupArchetype { GroupID = 9, Name = "State Machine", Color = new Color(105, 179, 160), Archetypes = new[] { new NodeArchetype { TypeID = 20, Create = (id, context, arch, groupArch) => new Animation.StateMachineState(id, context, arch, groupArch), Title = "State", Description = "The animation states machine state node", Flags = NodeFlags.AnimGraph, DefaultValues = new object[] { "State", Utils.GetEmptyArray(), Utils.GetEmptyArray(), }, Size = new Vector2(100, 0), }, } } }); private static readonly GroupArchetype StateMachineTransitionGroupArchetype = new GroupArchetype { GroupID = 9, Name = "Transition", Color = new Color(105, 179, 160), Archetypes = new[] { new NodeArchetype { TypeID = 23, Title = "Transition Source State Anim", Description = "The animation state machine transition source state animation data information", Flags = NodeFlags.AnimGraph, Size = new Vector2(270, 110), Elements = new[] { NodeElementArchetype.Factory.Output(0, "Length", typeof(float), 0), NodeElementArchetype.Factory.Output(1, "Time", typeof(float), 1), NodeElementArchetype.Factory.Output(2, "Normalized Time", typeof(float), 2), NodeElementArchetype.Factory.Output(3, "Reaming Time", typeof(float), 3), NodeElementArchetype.Factory.Output(4, "Reaming Normalized Time", typeof(float), 4), } }, } }; /// /// The state machine editing context menu. /// protected VisjectCM _cmStateMachineMenu; /// /// The state machine transition editing context menu. /// protected VisjectCM _cmStateMachineTransitionMenu; private bool _isRegisteredForScriptsReload; /// public AnimGraphSurface(IVisjectSurfaceOwner owner, Action onSave, FlaxEditor.Undo undo) : base(owner, onSave, undo) { // Find custom nodes for Anim Graph var customNodes = Editor.Instance.CodeEditing.AnimGraphNodes.GetArchetypes(); if (customNodes != null && customNodes.Count > 0) { AddCustomNodes(customNodes); // Check if any of the nodes comes from the game scripts - those can be reloaded at runtime so prevent crashes if (Editor.Instance.CodeEditing.AnimGraphNodes.HasTypeFromGameScripts) { _isRegisteredForScriptsReload = true; ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; } } } private void OnScriptsReloadBegin() { Owner.OnSurfaceClose(); // TODO: make reload soft: dispose default primary context menu, update existing custom nodes to new ones or remove if invalid } /// protected override void OnContextChanged() { base.OnContextChanged(); VisjectCM menu = null; // Override surface primary context menu for state machine editing if (Context?.Context is Animation.StateMachine) { if (_cmStateMachineMenu == null) { _cmStateMachineMenu = new VisjectCM(new VisjectCM.InitInfo { Groups = StateMachineGroupArchetypes, CanSpawnNode = arch => true, }); _cmStateMachineMenu.ShowExpanded = true; } menu = _cmStateMachineMenu; } // Override surface primary context menu for state machine transition editing if (Context?.Context is Animation.StateMachineTransition) { if (_cmStateMachineTransitionMenu == null) { _cmStateMachineTransitionMenu = new VisjectCM(new VisjectCM.InitInfo { Groups = NodeFactory.DefaultGroups, CanSpawnNode = CanUseNodeType, ParametersGetter = null, CustomNodesGroup = GetCustomNodes(), }); _cmStateMachineTransitionMenu.AddGroup(StateMachineTransitionGroupArchetype); } menu = _cmStateMachineTransitionMenu; } SetPrimaryMenu(menu); } /// public override string GetTypeName(ScriptType type) { if (type.Type == typeof(void)) return "Skeleton Pose (local space)"; return base.GetTypeName(type); } /// public override bool CanUseNodeType(NodeArchetype nodeArchetype) { return (nodeArchetype.Flags & NodeFlags.AnimGraph) != 0 && base.CanUseNodeType(nodeArchetype); } /// protected override bool ValidateDragItem(AssetItem assetItem) { if (assetItem.IsOfType()) return true; if (assetItem.IsOfType()) return true; if (assetItem.IsOfType()) return true; if (assetItem.IsOfType()) return true; return base.ValidateDragItem(assetItem); } /// protected override void HandleDragDropAssets(List objects, DragDropEventArgs args) { for (int i = 0; i < objects.Count; i++) { var assetItem = objects[i]; SurfaceNode node = null; if (assetItem.IsOfType()) { node = Context.SpawnNode(9, 2, args.SurfaceLocation, new object[] { assetItem.ID, 1.0f, true, 0.0f, }); } else if (assetItem.IsOfType()) { node = Context.SpawnNode(9, 11, args.SurfaceLocation, new object[] { 0.0f, assetItem.ID, }); } else if (assetItem.IsOfType()) { node = Context.SpawnNode(9, 24, args.SurfaceLocation, new object[] { assetItem.ID, }); } else if (assetItem.IsOfType()) { node = Context.SpawnNode(7, 16, args.SurfaceLocation, new object[] { assetItem.ID, string.Empty, }); } if (node != null) { args.SurfaceLocation.X += node.Width + 10; } } base.HandleDragDropAssets(objects, args); } /// public override void OnDestroy() { if (_cmStateMachineMenu != null) { _cmStateMachineMenu.Dispose(); _cmStateMachineMenu = null; } if (_cmStateMachineTransitionMenu != null) { _cmStateMachineTransitionMenu.Dispose(); _cmStateMachineTransitionMenu = null; } if (_isRegisteredForScriptsReload) { _isRegisteredForScriptsReload = false; ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; } base.OnDestroy(); } } }