using GraphProcessor; using UnityEngine.UIElements; using UnityEngine; using System.Linq; using System.Collections.Generic; [NodeCustomEditor(typeof(KeyframeNode))] public class KeyframeNodeView : BaseNodeView { Button generateButton; const string ERROR_TYPE = "GameObject input has wrong type"; const string ERROR_REND = "GameObject must have a MeshRenderer component"; public override void Enable() { generateButton = new Button(OnGenerateClick) { text = "Generate Keyframe" }; controlsContainer.Add(generateButton); onPortConnected += _ => Validate(); onPortDisconnected += _ => Validate(); schedule.Execute(Validate).Every(200); Validate(); } void OnGenerateClick() { var go = GetConnectedGameObject(); if (go == null) return; var rend = go.GetComponent(); if (rend == null) return; var foldNode = GetConnectedFoldNode(); // Accumulate nodes. We are going backwards from the output node // (KeyframeNode) to the root node. var foldNodes = new Stack(); var cur = foldNode; while (cur != null) { foldNodes.Push(cur); cur = GetInputFoldNode(cur); } var mpb = new MaterialPropertyBlock(); rend.GetPropertyBlock(mpb); // Set material properties for all 16 slots. for (int i = 0; i < 16; i++) { string slotPrefix = $"_Vertex_Deformation_Slot_{i}_"; BaseFoldNode node = (foldNodes.Count > 0) ? foldNodes.Pop() : null; FoldNodeSerialized data = node?.Serialize(); bool active = data != null; mpb.SetFloat(slotPrefix + "Enabled", active ? 1.0f : 0.0f); mpb.SetInt(slotPrefix + "Opcode", active ? data.opcode : 0); mpb.SetFloat(slotPrefix + "Float_0", active ? data.float0 : 0.0f); mpb.SetFloat(slotPrefix + "Float_1", active ? data.float1 : 0.0f); mpb.SetFloat(slotPrefix + "Float_2", active ? data.float2 : 0.0f); mpb.SetFloat(slotPrefix + "Float_3", active ? data.float3 : 0.0f); mpb.SetVector(slotPrefix + "Vector_0", active ? data.vec0 : Vector4.zero); mpb.SetVector(slotPrefix + "Vector_1", active ? data.vec1 : Vector4.zero); mpb.SetVector(slotPrefix + "Vector_2", active ? data.vec2 : Vector4.zero); mpb.SetVector(slotPrefix + "Vector_3", active ? data.vec3 : Vector4.zero); } rend.SetPropertyBlock(mpb); Debug.Log($"Generated Keyframe for '{go.name}' with properties applied to all 16 slots."); } BaseFoldNode GetInputFoldNode(BaseFoldNode node) { // Traverse via edges in the graph model, as 'input' field might not be populated in Editor var port = node.GetPort(nameof(BaseFoldNode.input), null); if (port == null) return null; // Find the edge connected to this input port var edge = node.graph.edges.FirstOrDefault(e => e.inputPort == port); return edge?.outputNode as BaseFoldNode; } T GetConnectedNode(string fieldName) where T : class { var edge = GetFirstPortViewFromFieldName(fieldName)?.GetEdges().FirstOrDefault(); return (edge?.output.node as BaseNodeView)?.nodeTarget as T; } GameObject GetConnectedGameObject() => GetConnectedNode(nameof(KeyframeNode.targetObject))?.output; BaseFoldNode GetConnectedFoldNode() => GetConnectedNode(nameof(KeyframeNode.foldData)); void Validate() { var go = GetConnectedGameObject(); bool hasGoConn = GetFirstPortViewFromFieldName(nameof(KeyframeNode.targetObject))?.GetEdges().Any() ?? false; bool hasFoldConn = GetFirstPortViewFromFieldName(nameof(KeyframeNode.foldData))?.GetEdges().Any() ?? false; RemoveMessageView(ERROR_TYPE); RemoveMessageView(ERROR_REND); if (hasGoConn) { if (go == null) AddMessageView(ERROR_TYPE, NodeMessageType.Error); else if (go.GetComponent() == null) AddMessageView(ERROR_REND, NodeMessageType.Error); } generateButton.SetEnabled(hasGoConn && hasFoldConn && go?.GetComponent() != null); } }