/// <summary> /// Recursively draws the joints and bones of a skeleton. /// </summary> /// <param name="camera">Camera to use.</param> /// <param name="sb"><see cref="ISpriteBatch"/> to draw to.</param> /// <param name="selectedNode">SpriteBatch to draw to.</param> /// <param name="node">Current node being drawn.</param> /// <param name="colorIndex">Index of the color to use from the ColorList.</param> static void RecursiveDraw(ICamera2D camera, ISpriteBatch sb, SkeletonNode selectedNode, SkeletonNode node, int colorIndex) { // Find the color of the joint var color = _colorList[colorIndex]; if (node == selectedNode) color = _nodeColorSelected; else if (node.Parent == null) color = _nodeColorRoot; // Draw the joint var scale = 1f / camera.Scale; var origin = SkeletonNode.HalfJointVector; _joint.Draw(sb, node.Position, color, SpriteEffects.None, 0f, origin, scale); // Iterate through the children foreach (var child in node.Nodes) { colorIndex++; if (colorIndex == _colorList.Length) colorIndex = 0; // Draw the bone to the child RenderLine.Draw(sb, node.Position, child.Position, _colorList[colorIndex], (1f / camera.Scale) * 2f); // Draw the child RecursiveDraw(camera, sb, selectedNode, child, colorIndex); } }
/// <summary> /// Draws a <see cref="Skeleton"/>. /// </summary> /// <param name="skeleton">The <see cref="Skeleton"/> to draw.</param> /// <param name="camera">Camera to use.</param> /// <param name="sb">The <see cref="ISpriteBatch"/> to draw with.</param> /// <param name="selectedNode">The <see cref="SkeletonNode"/> to draw as selected.</param> public static void Draw(Skeleton skeleton, ICamera2D camera, ISpriteBatch sb, SkeletonNode selectedNode = null) { if (skeleton == null) { Debug.Fail("skeleton is null."); return; } if (skeleton.RootNode == null) { Debug.Fail("skeleton contains no root node."); return; } if (sb == null) { Debug.Fail("sb is null."); return; } if (sb.IsDisposed) { Debug.Fail("sb is disposed."); return; } if (camera == null) { Debug.Fail("camera is null."); return; } RecursiveDraw(camera, sb, selectedNode, skeleton.RootNode, 0); }
/// <summary> /// Attaches the <see cref="SkeletonBodyItem"/> to a <see cref="Skeleton"/> using the name of the joints. /// </summary> /// <param name="skeleton"><see cref="Skeleton"/> to attach to.</param> internal void Attach(Skeleton skeleton) { // Source node if (skeleton == null) Source = null; else Source = skeleton.FindNode(ItemInfo.SourceName); // Destination node if (skeleton == null || ItemInfo.DestName.Length == 0) Dest = null; else Dest = skeleton.FindNode(ItemInfo.DestName); }
/// <summary> /// Draws the <see cref="SkeletonBodyItem"/>. /// </summary> /// <param name="sb"><see cref="ISpriteBatch"/> to draw to.</param> /// <param name="position">Position to draw at.</param> /// <param name="scale">Amount to scale the Grh in percent (1.0f for no scaling).</param> /// <param name="color">The color.</param> /// <param name="effect">SpriteEffects to use when drawing.</param> internal void Draw(ISpriteBatch sb, Vector2 position, float scale, Color color, SpriteEffects effect) { // Validate if (Source == null) { return; } // Find the effect Vector2 m; switch (effect) { case SpriteEffects.FlipHorizontally: m = new Vector2(-1, 1); break; case SpriteEffects.FlipVertically: m = new Vector2(1, -1); break; default: m = new Vector2(1, 1); break; } // Calculate the angle float angle; if (Dest == null) { angle = 0.0f; } else { angle = SkeletonNode.GetAngle(Source.Position * m, Dest.Position * m) - MathHelper.PiOver2; } // Draw var v = Source.Position + ItemInfo.Offset; Grh.Draw(sb, (v * m) + position, color, effect, angle, ItemInfo.Origin, scale); }
/// <summary> /// Checks if a given point hits the node /// </summary> /// <param name="camera">World camera (determines node size)</param> /// <param name="position">World position</param> /// <param name="node">Node to test against</param> /// <returns>True if the node was hit, else false</returns> public static bool HitTest(ICamera2D camera, Vector2 position, SkeletonNode node) { if (node == null) { Debug.Fail("node is null."); return(false); } if (camera == null) { Debug.Fail("camera is null."); return(false); } var size = Vector2.Divide(HalfJointVector, camera.Scale); var min = node.Position - size; var max = node.Position + size; return(position.X >= min.X && position.X <= max.X && position.Y >= min.Y && position.Y <= max.Y); }
/// <summary> /// Recursively copies the length of one set of nodes to another set of nodes /// </summary> /// <param name="src">Source root SkeletonNode to copy from</param> /// <param name="dest">Destination root SkeletonNode to copy to</param> public static void CopyLength(SkeletonNode src, SkeletonNode dest) { if (src == null) { Debug.Fail("src is null."); return; } if (dest == null) { Debug.Fail("dest is null."); return; } dest.SetLength(src.GetLength()); for (var i = 0; i < src.Nodes.Count(); i++) { CopyLength(src.Nodes.ElementAt(i), dest.Nodes.ElementAt(i)); } }
/// <summary> /// Recursively copies the IsModifier property of one set of nodes to another set of nodes /// </summary> /// <param name="src">Source root SkeletonNode to copy from</param> /// <param name="dest">Destination root SkeletonNode to copy to</param> static void CopyIsModifier(SkeletonNode src, SkeletonNode dest) { if (src == null) { Debug.Fail("src is null."); return; } if (dest == null) { Debug.Fail("dest is null."); return; } dest.IsModifier = src.IsModifier; for (var i = 0; i < src.Nodes.Count(); i++) { CopyIsModifier(src.Nodes.ElementAt(i), dest.Nodes.ElementAt(i)); } }
/// <summary> /// Used to assist the recursive node acquisition /// </summary> /// <param name="root">Root node to work from</param> /// <returns>Children of the root</returns> static List <SkeletonNode> GetNodes(SkeletonNode root) { if (root == null) { Debug.Fail("root is null."); return(new List <SkeletonNode>()); } var ret = new List <SkeletonNode>(); foreach (var node in root.Nodes) { ret.Add(node); foreach (var subNode in GetNodes(node)) { ret.Add(subNode); } } return(ret); }
/// <summary> /// Recursively updates all the children of a node. /// </summary> /// <param name="srcA">Source skeleton node for the current frame.</param> /// <param name="srcB">Source skeleton node for the next frame.</param> /// <param name="srcP">Parent skeleton node (use null if theres no parent).</param> /// <param name="dest">Destination skeleton node to have the two sources applied to.</param> /// <param name="framePercent">A value between 0.0 and 1.0 stating how far along the animation is /// from the current frame.</param> void RecursiveUpdate(SkeletonNode srcA, SkeletonNode srcB, SkeletonNode srcP, SkeletonNode dest, float framePercent) { // Set the position Vector2 vA; Vector2 vB; if (_scale == 1.0f) { vA = srcA.Position; vB = srcB.Position; } else { vA = srcA.Position * _scale; vB = srcB.Position * _scale; } dest.Position = Vector2.Lerp(vA, vB, framePercent); // Check if the node is part of a modifier animation if (srcP == null) { // Set the length dest.SetLength(srcA.GetLength() * _scale); } else { // This is a modifier so check for inheriting node values dest.SetLength(srcP.GetLength()); if (!srcP.IsModifier && srcP.Parent != null) { dest.SetAngle(srcP.GetAngle()); } } // Update the child nodes (if there is any) for (var i = 0; i < srcA.internalNodes.Count; i++) { var nextSrcP = (srcP == null ? null : srcP.internalNodes[i]); RecursiveUpdate(srcA.internalNodes[i], srcB.internalNodes[i], nextSrcP, dest.internalNodes[i], framePercent); } }
/// <summary> /// Reads a <see cref="Skeleton"/> from an <see cref="IValueReader"/>. /// </summary> /// <param name="reader">The <see cref="IValueReader"/> to read from.</param> /// <exception cref="ArgumentNullException"><paramref name="reader" /> is <c>null</c>.</exception> /// <exception cref="InvalidOperationException">The loaded <see cref="Skeleton"/> was not properly structured.</exception> public void Read(IValueReader reader) { if (reader == null) { throw new ArgumentNullException("reader"); } var nodesWithParents = new List <KeyValuePair <SkeletonNode, string> >(); var loadedNodes = reader.ReadManyNodes(_nodesNodeName, x => SkeletonNodeReadHandler(x, nodesWithParents)); // Add the root node (which should be the only one without a parent) var nodesWithoutParents = loadedNodes.Except(nodesWithParents.Select(x => x.Key)); if (nodesWithoutParents.Count() != 1) { throw new InvalidOperationException("Invalid number of parentless nodes. Was only expected one!"); } _rootNode = nodesWithoutParents.First(); // Set up the parents foreach (var nodeWithParent in nodesWithParents) { var node = nodeWithParent.Key; var parentName = nodeWithParent.Value; var parentNode = loadedNodes.FirstOrDefault(x => parentName.Equals(x.Name, StringComparison.OrdinalIgnoreCase)); if (parentNode == null) { const string errmsg = "Unable to find parent node `{0}` for node `{1}`."; var err = string.Format(errmsg, parentName, node.Name); throw new InvalidOperationException(err); } node.Parent = parentNode; parentNode.internalNodes.Add(node); } }
/// <summary> /// Attaches the <see cref="SkeletonBodyItem"/> to a <see cref="Skeleton"/> using the name of the joints. /// </summary> /// <param name="skeleton"><see cref="Skeleton"/> to attach to.</param> internal void Attach(Skeleton skeleton) { // Source node if (skeleton == null) { Source = null; } else { Source = skeleton.FindNode(ItemInfo.SourceName); } // Destination node if (skeleton == null || ItemInfo.DestName.Length == 0) { Dest = null; } else { Dest = skeleton.FindNode(ItemInfo.DestName); } }
/// <summary> /// Recursively creates a deep-copy of the node and all its child nodes /// </summary> /// <param name="root">Node to duplicate</param> /// <returns>Duplicate copy of the node</returns> static SkeletonNode Duplicate(SkeletonNode root) { if (root == null) { Debug.Fail("root is null."); return(null); } var ret = new SkeletonNode(new Vector2(root.Position.X, root.Position.Y)) { Name = root.Name }; foreach (var node in root.Nodes) { var newChild = Duplicate(node); newChild.Parent = ret; newChild.Name = node.Name; newChild.IsModifier = node.IsModifier; ret._nodes.Add(newChild); } return(ret); }
/// <summary> /// Recursively draws the joints and bones of a skeleton. /// </summary> /// <param name="camera">Camera to use.</param> /// <param name="sb"><see cref="ISpriteBatch"/> to draw to.</param> /// <param name="selectedNode">SpriteBatch to draw to.</param> /// <param name="node">Current node being drawn.</param> /// <param name="colorIndex">Index of the color to use from the ColorList.</param> static void RecursiveDraw(ICamera2D camera, ISpriteBatch sb, SkeletonNode selectedNode, SkeletonNode node, int colorIndex) { // Find the color of the joint var color = _colorList[colorIndex]; if (node == selectedNode) { color = _nodeColorSelected; } else if (node.Parent == null) { color = _nodeColorRoot; } // Draw the joint var scale = 1f / camera.Scale; var origin = SkeletonNode.HalfJointVector; _joint.Draw(sb, node.Position, color, SpriteEffects.None, 0f, origin, scale); // Iterate through the children foreach (var child in node.Nodes) { colorIndex++; if (colorIndex == _colorList.Length) { colorIndex = 0; } // Draw the bone to the child RenderLine.Draw(sb, node.Position, child.Position, _colorList[colorIndex], (1f / camera.Scale) * 2f); // Draw the child RecursiveDraw(camera, sb, selectedNode, child, colorIndex); } }
/// <summary> /// Initializes a new instance of the <see cref="SkeletonNode"/> class. /// </summary> /// <param name="position">The position of the node relative to the <see cref="Skeleton"/>.</param> public SkeletonNode(Vector2 position) { _parent = null; _position = position; }
/// <summary> /// Recursively moves a node and all its children by a given value /// </summary> /// <param name="node">Root node to move</param> /// <param name="diff">Distance to move the node</param> static void RecursiveMove(SkeletonNode node, Vector2 diff) { // Move the node node.Position -= diff; // Update the children foreach (var childNode in node.Nodes) { RecursiveMove(childNode, diff); } }
/// <summary> /// Checks if a given point hits the node /// </summary> /// <param name="camera">World camera (determines node size)</param> /// <param name="position">World position</param> /// <param name="node">Node to test against</param> /// <returns>True if the node was hit, else false</returns> public static bool HitTest(ICamera2D camera, Vector2 position, SkeletonNode node) { if (node == null) { Debug.Fail("node is null."); return false; } if (camera == null) { Debug.Fail("camera is null."); return false; } var size = Vector2.Divide(HalfJointVector, camera.Scale); var min = node.Position - size; var max = node.Position + size; return (position.X >= min.X && position.X <= max.X && position.Y >= min.Y && position.Y <= max.Y); }
/// <summary> /// Used to assist the recursive node acquisition /// </summary> /// <param name="root">Root node to work from</param> /// <returns>Children of the root</returns> static List<SkeletonNode> GetNodes(SkeletonNode root) { if (root == null) { Debug.Fail("root is null."); return new List<SkeletonNode>(); } var ret = new List<SkeletonNode>(); foreach (var node in root.Nodes) { ret.Add(node); foreach (var subNode in GetNodes(node)) { ret.Add(subNode); } } return ret; }
/// <summary> /// Finds the angle between this node and another /// </summary> /// <param name="node">Node to compare to</param> /// <returns>Angle between the two nodes</returns> public float GetAngle(SkeletonNode node) { if (node == null) return 0f; return GetAngle(node.Position, Position); }
/// <summary> /// Recursively creates a deep-copy of the node and all its child nodes /// </summary> /// <param name="root">Node to duplicate</param> /// <returns>Duplicate copy of the node</returns> static SkeletonNode Duplicate(SkeletonNode root) { if (root == null) { Debug.Fail("root is null."); return null; } var ret = new SkeletonNode(new Vector2(root.Position.X, root.Position.Y)) { Name = root.Name }; foreach (var node in root.Nodes) { var newChild = Duplicate(node); newChild.Parent = ret; newChild.Name = node.Name; newChild.IsModifier = node.IsModifier; ret._nodes.Add(newChild); } return ret; }
/// <summary> /// Initializes a new instance of the <see cref="SkeletonNode"/> class. /// </summary> /// <param name="position">The position of the node relative to the <see cref="Skeleton"/>.</param> public SkeletonNode(Vector2 position) { _parent = null; _position = position; }
/// <summary> /// Updates the parent <see cref="SkeletonAnimation"/> that this modifier modifies. /// </summary> /// <param name="src">Source root <see cref="SkeletonNode"/>.</param> /// <param name="dest">Destination root <see cref="SkeletonNode"/>.</param> static void RecursiveUpdateParent(SkeletonNode src, SkeletonNode dest) { // Update modified values if (src.IsModifier && dest.Parent != null) dest.SetAngle(src.GetAngle()); // Update the child nodes (if there is any) for (var i = 0; i < src.internalNodes.Count; i++) { RecursiveUpdateParent(src.internalNodes[i], dest.internalNodes[i]); } }
static SkeletonNode SkeletonNodeReadHandler(IValueReader r, ICollection<KeyValuePair<SkeletonNode, string>> nodesWithParents) { string parentName; var node = new SkeletonNode(r, out parentName); if (parentName != null) { var kvp = new KeyValuePair<SkeletonNode, string>(node, parentName); nodesWithParents.Add(kvp); } return node; }
/// <summary> /// Finds a SkeletonNode by a given name /// </summary> /// <param name="rootNode">Root node to look through</param> /// <param name="name">Name of the node to look for</param> /// <returns>SkeletonNode with the specified name, or null if none found</returns> static SkeletonNode FindNode(SkeletonNode rootNode, string name) { if (rootNode == null) { Debug.Fail("rootNode is null."); return null; } if (name == null) { Debug.Fail("name is null."); return null; } if (rootNode.Name == name) { // Root node matched the name return rootNode; } else { // Check the child nodes for a name match foreach (var child in rootNode.Nodes) { var ret = FindNode(child, name); if (ret != null) return ret; } } // Node with the given name not found return null; }
/// <summary> /// Draws a <see cref="Skeleton"/>. /// </summary> /// <param name="skeleton">The <see cref="Skeleton"/> to draw.</param> /// <param name="camera">Camera to use.</param> /// <param name="sb">The <see cref="ISpriteBatch"/> to draw with.</param> /// <param name="selectedNode">The <see cref="SkeletonNode"/> to draw as selected.</param> public static void Draw(Skeleton skeleton, ICamera2D camera, ISpriteBatch sb, SkeletonNode selectedNode = null) { if (skeleton == null) { Debug.Fail("skeleton is null."); return; } if (skeleton.RootNode == null) { Debug.Fail("skeleton contains no root node."); return; } if (sb == null) { Debug.Fail("sb is null."); return; } if (sb.IsDisposed) { Debug.Fail("sb is disposed."); return; } if (camera == null) { Debug.Fail("camera is null."); return; } RecursiveDraw(camera, sb, selectedNode, skeleton.RootNode, 0); }
/// <summary> /// Initializes a new instance of the <see cref="SkeletonNode"/> class. /// </summary> /// <param name="parent">The parent <see cref="SkeletonNode"/>. If null, this will be treated as the root node in the /// <see cref="Skeleton"/>.</param> /// <param name="position">The position of the node relative to the <see cref="Skeleton"/>.</param> public SkeletonNode(SkeletonNode parent, Vector2 position) { _parent = parent; _parent._nodes.Add(this); _position = position; }
/// <summary> /// Initializes a new instance of the <see cref="SkeletonNode"/> class. /// </summary> /// <param name="parent">The parent <see cref="SkeletonNode"/>. If null, this will be treated as the root node in the /// <see cref="Skeleton"/>.</param> /// <param name="position">The position of the node relative to the <see cref="Skeleton"/>.</param> public SkeletonNode(SkeletonNode parent, Vector2 position) { _parent = parent; _parent._nodes.Add(this); _position = position; }
/// <summary> /// Recursively copies the length of one set of nodes to another set of nodes /// </summary> /// <param name="src">Source root SkeletonNode to copy from</param> /// <param name="dest">Destination root SkeletonNode to copy to</param> public static void CopyLength(SkeletonNode src, SkeletonNode dest) { if (src == null) { Debug.Fail("src is null."); return; } if (dest == null) { Debug.Fail("dest is null."); return; } dest.SetLength(src.GetLength()); for (var i = 0; i < src.Nodes.Count(); i++) { CopyLength(src.Nodes.ElementAt(i), dest.Nodes.ElementAt(i)); } }
/// <summary> /// Adds a child node to the node /// </summary> /// <param name="node">Child node to add</param> public void Add(SkeletonNode node) { if (node == null) { Debug.Fail("node is null."); return; } _nodes.Add(node); node.Parent = this; }
/// <summary> /// Reads a <see cref="Skeleton"/> from an <see cref="IValueReader"/>. /// </summary> /// <param name="reader">The <see cref="IValueReader"/> to read from.</param> /// <exception cref="ArgumentNullException"><paramref name="reader" /> is <c>null</c>.</exception> /// <exception cref="InvalidOperationException">The loaded <see cref="Skeleton"/> was not properly structured.</exception> public void Read(IValueReader reader) { if (reader == null) throw new ArgumentNullException("reader"); var nodesWithParents = new List<KeyValuePair<SkeletonNode, string>>(); var loadedNodes = reader.ReadManyNodes(_nodesNodeName, x => SkeletonNodeReadHandler(x, nodesWithParents)); // Add the root node (which should be the only one without a parent) var nodesWithoutParents = loadedNodes.Except(nodesWithParents.Select(x => x.Key)); if (nodesWithoutParents.Count() != 1) throw new InvalidOperationException("Invalid number of parentless nodes. Was only expected one!"); _rootNode = nodesWithoutParents.First(); // Set up the parents foreach (var nodeWithParent in nodesWithParents) { var node = nodeWithParent.Key; var parentName = nodeWithParent.Value; var parentNode = loadedNodes.FirstOrDefault(x => parentName.Equals(x.Name, StringComparison.OrdinalIgnoreCase)); if (parentNode == null) { const string errmsg = "Unable to find parent node `{0}` for node `{1}`."; var err = string.Format(errmsg, parentName, node.Name); throw new InvalidOperationException(err); } node.Parent = parentNode; parentNode.internalNodes.Add(node); } }
/// <summary> /// Handles the MouseDown event of the GameScreen control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param> void GameScreen_MouseDown(object sender, MouseEventArgs e) { if (radioEdit.Checked) { if (e.Button == MouseButtons.Left) { // Select new node var nodes = _skeleton.RootNode.GetAllNodes(); foreach (var node in nodes) { if (node.HitTest(_camera, _cursorPos)) { SelectedNode = node; // Select the node _moveSelectedNode = true; // Enable dragging break; } } } else if (e.Button == MouseButtons.Right) { // Add new node if (!chkCanAlter.Checked) { MessageBox.Show("Node adding and removing locked. Enable node add/remove in the settings panel.", "Invalid operation", MessageBoxButtons.OK); return; } if (_skeleton.RootNode == null) { // Create the root node _skeleton.RootNode = new SkeletonNode(_cursorPos); SelectedNode = _skeleton.RootNode; SelectedNode.Name = "New Root"; } else { // Create a child node if (SelectedNode == null) { const string errmsg = "You must first select a node before creating a new node. The selected node will be used as the new node's parent."; MessageBox.Show(errmsg, "Select a node", MessageBoxButtons.OK); } else { var newNode = new SkeletonNode(SelectedNode, _cursorPos); UpdateFrameNodeCBs(); SelectedNode = newNode; } } } } }
/// <summary> /// Recursively copies the IsModifier property of one set of nodes to another set of nodes /// </summary> /// <param name="src">Source root SkeletonNode to copy from</param> /// <param name="dest">Destination root SkeletonNode to copy to</param> static void CopyIsModifier(SkeletonNode src, SkeletonNode dest) { if (src == null) { Debug.Fail("src is null."); return; } if (dest == null) { Debug.Fail("dest is null."); return; } dest.IsModifier = src.IsModifier; for (var i = 0; i < src.Nodes.Count(); i++) { CopyIsModifier(src.Nodes.ElementAt(i), dest.Nodes.ElementAt(i)); } }
/// <summary> /// Recursively updates all the children of a node. /// </summary> /// <param name="srcA">Source skeleton node for the current frame.</param> /// <param name="srcB">Source skeleton node for the next frame.</param> /// <param name="srcP">Parent skeleton node (use null if theres no parent).</param> /// <param name="dest">Destination skeleton node to have the two sources applied to.</param> /// <param name="framePercent">A value between 0.0 and 1.0 stating how far along the animation is /// from the current frame.</param> void RecursiveUpdate(SkeletonNode srcA, SkeletonNode srcB, SkeletonNode srcP, SkeletonNode dest, float framePercent) { // Set the position Vector2 vA; Vector2 vB; if (_scale == 1.0f) { vA = srcA.Position; vB = srcB.Position; } else { vA = srcA.Position * _scale; vB = srcB.Position * _scale; } dest.Position = Vector2.Lerp(vA, vB, framePercent); // Check if the node is part of a modifier animation if (srcP == null) { // Set the length dest.SetLength(srcA.GetLength() * _scale); } else { // This is a modifier so check for inheriting node values dest.SetLength(srcP.GetLength()); if (!srcP.IsModifier && srcP.Parent != null) dest.SetAngle(srcP.GetAngle()); } // Update the child nodes (if there is any) for (var i = 0; i < srcA.internalNodes.Count; i++) { var nextSrcP = (srcP == null ? null : srcP.internalNodes[i]); RecursiveUpdate(srcA.internalNodes[i], srcB.internalNodes[i], nextSrcP, dest.internalNodes[i], framePercent); } }