Exemplo n.º 1
0
        /// <summary>
        /// Calcuates the previous state hitboxes positions and returns them as a dictionary
        /// </summary>
        /// <returns></returns>
        private Dictionary <int, Vector3> CalculatePreviousState()
        {
            if (viewport.Frame == 0 || !interpolationToolStripMenuItem.Checked)
            {
                return(null);
            }

            Dictionary <int, Vector3> previousPosition = new Dictionary <int, Vector3>();

            JointManager.Frame = viewport.Frame - 1;
            JointManager.UpdateNoRender();
            SubactionProcess.SetFrame(viewport.Frame - 1);

            foreach (var hb in SubactionProcess.Hitboxes)
            {
                var boneID = hb.BoneID;
                if (boneID == 0)
                {
                    boneID = 1;
                }
                var transform = Matrix4.CreateTranslation(hb.Point1) * JointManager.GetWorldTransform(boneID);
                transform = transform.ClearScale();
                var pos = Vector3.TransformPosition(Vector3.Zero, transform);
                previousPosition.Add(hb.ID, pos);
            }

            return(previousPosition);
        }
Exemplo n.º 2
0
        public void Render(JOBJManager jobjManager, List <SBM_Hurtbox> hurtboxes, HSDAccessor selected, Dictionary <int, int> states = null, int bodyState = -1)
        {
            foreach (SBM_Hurtbox v in hurtboxes)
            {
                var clr = HurtboxColor;
                var a   = 0.25f;
                if (selected == v)
                {
                    clr = SelectedHurtboxColor;
                    a   = 0.6f;
                }

                if (states != null)
                {
                    if (states.ContainsKey(v.BoneIndex))
                    {
                        switch (states[v.BoneIndex])
                        {
                        case 1:
                            clr = InvulColor;
                            break;

                        case 2:
                            clr = IntanColor;
                            break;
                        }
                    }
                }

                if (bodyState == 1)
                {
                    clr = InvulColor;
                }
                if (bodyState == 2)
                {
                    clr = IntanColor;
                }

                var transform = jobjManager.GetWorldTransform(v.BoneIndex);

                if (!HurtboxToCapsule.ContainsKey(v))
                {
                    HurtboxToCapsule.Add(v, new Capsule(new Vector3(v.X1, v.Y1, v.Z1), new Vector3(v.X2, v.Y2, v.Z2), v.Size));
                }

                var cap = HurtboxToCapsule[v];
                cap.SetParameters(new Vector3(v.X1, v.Y1, v.Z1), new Vector3(v.X2, v.Y2, v.Z2), v.Size);
                cap.Draw(transform, new Vector4(clr, a));
            }
        }
Exemplo n.º 3
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="cam"></param>
        /// <param name="windowWidth"></param>
        /// <param name="windowHeight"></param>
        public void Draw(Camera cam, int windowWidth, int windowHeight)
        {
            // store previous hitbox state info
            Dictionary <int, Vector3> previousPosition = CalculatePreviousState();

            // reset model parts
            if (ModelPartsIndices != null)
            {
                for (int i = 0; i < ModelPartsIndices.Length; i++)
                {
                    ModelPartsIndices[i].AnimIndex = -1;
                }
            }

            // process ftcmd
            SubactionProcess.SetFrame(viewport.Frame);

            // update display info
            JointManager.DOBJManager.OverlayColor = SubactionProcess.OverlayColor;
            JointManager._settings.RenderBones    = bonesToolStripMenuItem.Checked;

            // apply model animations
            JointManager.Frame = viewport.Frame;
            JointManager.UpdateNoRender();

            // character invisibility
            if (!SubactionProcess.CharacterInvisibility && modelToolStripMenuItem.Checked)
            {
                JointManager.Render(cam, false);
            }

            // hurtbox collision
            if (hurtboxesToolStripMenuItem.Checked)
            {
                HurtboxRenderer.Render(JointManager, Hurtboxes, null, SubactionProcess.BoneCollisionStates, SubactionProcess.BodyCollisionState);
            }

            // hitbox collision
            foreach (var hb in SubactionProcess.Hitboxes)
            {
                var boneID = hb.BoneID;
                if (boneID == 0)
                {
                    if (JointManager.GetJOBJ(1).Child == null) // special case for character like mewtwo with a leading bone
                    {
                        boneID = 2;
                    }
                    else
                    {
                        boneID = 1;
                    }
                }

                var transform = Matrix4.CreateTranslation(hb.Point1) * JointManager.GetWorldTransform(boneID);

                transform = transform.ClearScale();

                float   alpha   = 0.4f;
                Vector3 hbColor = HitboxColor;

                if (hb.Element == 8)
                {
                    hbColor = GrabboxColor;
                }

                // drawing a capsule takes more processing power, so only draw it if necessary
                if (interpolationToolStripMenuItem.Checked && previousPosition != null && previousPosition.ContainsKey(hb.ID))
                {
                    var pos = Vector3.TransformPosition(Vector3.Zero, transform);
                    var cap = new Capsule(pos, previousPosition[hb.ID], hb.Size);
                    cap.Draw(Matrix4.Identity, new Vector4(hbColor, alpha));
                }
                else
                {
                    DrawShape.DrawSphere(transform, hb.Size, 16, 16, hbColor, alpha);
                }

                // draw hitbox angle
                if (hitboxInfoToolStripMenuItem.Checked)
                {
                    if (hb.Angle != 361)
                    {
                        DrawShape.DrawAngleLine(cam, transform, hb.Size, MathHelper.DegreesToRadians(hb.Angle));
                    }
                    else
                    {
                        DrawShape.DrawSakuraiAngle(cam, transform, hb.Size);
                    }
                    GLTextRenderer.RenderText(cam, hb.ID.ToString(), transform, StringAlignment.Center, true);
                }
            }

            // environment collision
            if (ECB != null)
            {
                var topN = JointManager.GetWorldTransform(1).ExtractTranslation();

                var bone1 = Vector3.TransformPosition(Vector3.Zero, JointManager.GetWorldTransform(ECB.ECBBone1));
                var bone2 = Vector3.TransformPosition(Vector3.Zero, JointManager.GetWorldTransform(ECB.ECBBone2));
                var bone3 = Vector3.TransformPosition(Vector3.Zero, JointManager.GetWorldTransform(ECB.ECBBone3));
                var bone4 = Vector3.TransformPosition(Vector3.Zero, JointManager.GetWorldTransform(ECB.ECBBone4));
                var bone5 = Vector3.TransformPosition(Vector3.Zero, JointManager.GetWorldTransform(ECB.ECBBone5));
                var bone6 = Vector3.TransformPosition(Vector3.Zero, JointManager.GetWorldTransform(ECB.ECBBone6));

                var minx = float.MaxValue;
                var miny = float.MaxValue;
                var maxx = float.MinValue;
                var maxy = float.MinValue;

                foreach (var p in new Vector3[] { bone1, bone2, bone3, bone4, bone5, bone6 })
                {
                    minx = Math.Min(minx, p.Z);
                    maxx = Math.Max(maxx, p.Z);
                    miny = Math.Min(miny, p.Y);
                    maxy = Math.Max(maxy, p.Y);
                }

                // ecb diamond
                if (eCBToolStripMenuItem.Checked)
                {
                    DrawShape.DrawECB(topN, minx, miny, maxx, maxy, groundECH.Checked);
                }

                // ledge grav
                if (ledgeGrabBoxToolStripMenuItem.Checked)
                {
                    var correct = Math.Abs(minx - maxx) / 2;

                    //behind
                    DrawShape.DrawLedgeBox(
                        topN.Z,
                        topN.Y + ECB.VerticalOffsetFromTop - ECB.VerticalScale / 2,
                        topN.Z - (correct + ECB.HorizontalScale),
                        topN.Y + ECB.VerticalOffsetFromTop + ECB.VerticalScale / 2,
                        Color.Red);

                    // in front
                    DrawShape.DrawLedgeBox(
                        topN.Z,
                        topN.Y + ECB.VerticalOffsetFromTop - ECB.VerticalScale / 2,
                        topN.Z + correct + ECB.HorizontalScale,
                        topN.Y + ECB.VerticalOffsetFromTop + ECB.VerticalScale / 2,
                        Color.Blue);
                }
            }

            // throw dummy
            if (throwModelToolStripMenuItem.Checked && !SubactionProcess.ThrownFighter && ThrowDummyManager.JointCount > 0)
            {
                if (viewport.Frame < ThrowDummyManager.Animation.FrameCount)
                {
                    ThrowDummyManager.Frame = viewport.Frame;
                }
                ThrowDummyManager.SetWorldTransform(4, JointManager.GetWorldTransform(JointManager.JointCount - 2));
                ThrowDummyManager.Render(cam, false);

                DrawShape.DrawSphere(ThrowDummyManager.GetWorldTransform(35), 1.5f, 16, 16, ThrowDummyColor, 0.5f);
                DrawShape.DrawSphere(ThrowDummyManager.GetWorldTransform(4), 1.5f, 16, 16, ThrowDummyColor, 0.5f);
                DrawShape.DrawSphere(ThrowDummyManager.GetWorldTransform(10), 1f, 16, 16, ThrowDummyColor, 0.5f);
                DrawShape.DrawSphere(ThrowDummyManager.GetWorldTransform(15), 1f, 16, 16, ThrowDummyColor, 0.5f);
                DrawShape.DrawSphere(ThrowDummyManager.GetWorldTransform(22), 1f, 16, 16, ThrowDummyColor, 0.5f);
                DrawShape.DrawSphere(ThrowDummyManager.GetWorldTransform(40), 1f, 16, 16, ThrowDummyColor, 0.5f);
            }

            // sword trail
            //AfterImageRenderer.RenderAfterImage(JointManager, viewport.Frame, after_desc);
        }
Exemplo n.º 4
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="table"></param>
        /// <param name="icons"></param>
        /// <returns></returns>
        public static MEX_mexSelectChr GenerateMEXMapFromVanilla(SBM_SelectChrDataTable table, MEX_CSSIconEntry[] icons)
        {
            // generate icon model
            var icon_joint = HSDAccessor.DeepClone <HSD_JOBJ>(table.MenuModel.Children[2].Child);

            icon_joint.TX   = 0;
            icon_joint.TY   = 0;
            icon_joint.TZ   = 0;
            icon_joint.Next = null;
            var center = RegenerateIcon(icon_joint);

            var icon_matanim_joint = HSDAccessor.DeepClone <HSD_MatAnimJoint>(table.MenuMaterialAnimation.Children[2].Child);

            icon_matanim_joint.Next = null;

            // generate mat anim node
            var joints = table.MenuModel.BreathFirstList;

            HSD_TOBJ[] tobjs = new HSD_TOBJ[icons.Max(e => e.FighterExternalID) + 1];

            JOBJManager m = new JOBJManager();

            m.SetJOBJ(table.MenuModel);
            m.SetAnimJoint(table.MenuAnimation);
            m.Frame = 600;
            m.UpdateNoRender();

            var csps    = table.MenuMaterialAnimation.Children[6].Child.MaterialAnimation.TextureAnimation.ToTOBJs();
            var cspKeys = table.MenuMaterialAnimation.Children[6].Child.MaterialAnimation.TextureAnimation.AnimationObject.FObjDesc.GetDecodedKeys();

            var stride = 30;

            foreach (var ico in icons)
            {
                if (joints[ico.icon.JointID].Dobj == null)
                {
                    continue;
                }

                HSD_JOBJ pos = HSDAccessor.DeepClone <HSD_JOBJ>(icon_joint);
                pos.Dobj.Pobj.Attributes      = icon_joint.Dobj.Pobj.Attributes;
                pos.Dobj.Next.Pobj.Attributes = icon_joint.Dobj.Pobj.Attributes;

                pos.Dobj.Next.Mobj.Textures = HSDAccessor.DeepClone <HSD_TOBJ>(joints[ico.icon.JointID].Dobj.Next.Mobj.Textures);

                var worldPosition = Vector3.TransformPosition(Vector3.Zero, m.GetWorldTransform(ico.icon.JointID));
                pos.TX = worldPosition.X + center.X;
                pos.TY = worldPosition.Y + center.Y;
                pos.TZ = worldPosition.Z + center.Z;

                ico.Joint        = pos;
                ico.Animation    = new MexMenuAnimation();
                ico.MatAnimJoint = HSDAccessor.DeepClone <HSD_MatAnimJoint>(icon_matanim_joint);

                // load csps
                // find key at stride
                var icocsps  = new List <HSD_TOBJ>();
                int cspIndex = 0;
                while (true)
                {
                    var key = ico.FighterExternalID + (cspIndex * stride);
                    if (ico.FighterExternalID > 0x13)
                    {
                        key = ico.FighterExternalID + (cspIndex * stride) - 1;
                    }

                    var k = cspKeys.Find(e => e.Frame == key);

                    if (k == null)
                    {
                        break;
                    }

                    icocsps.Add(csps[(int)k.Value]);

                    cspIndex++;
                }
                ico.CSPs = icocsps.Select(e => new TOBJProxy()
                {
                    TOBJ = e
                }).ToArray();
            }

            m.RefreshRendering = true;

            MEX_mexSelectChr mex = new MEX_mexSelectChr();

            SetMexNode(mex, icons);

            return(mex);
        }
Exemplo n.º 5
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        /// <param name="animation"></param>
        /// <param name="sourceMap"></param>
        /// <param name="targetMap"></param>
        /// <returns></returns>
        public static JointAnimManager SmartPort(HSD_JOBJ source, HSD_JOBJ target, JointAnimManager animation, JointMap sourceMap = null, JointMap targetMap = null)
        {
            var sourceManager = new JOBJManager();

            sourceManager.SetJOBJ(source);

            var sourceInverseWorldTransforms = new List <Matrix4>();

            for (int i = 0; i < sourceManager.JointCount; i++)
            {
                sourceInverseWorldTransforms.Add(sourceManager.GetWorldTransform(i).Inverted());
            }

            sourceManager.Animation = animation;


            var targetManager = new JOBJManager();

            targetManager.SetJOBJ(target);

            JointAnimManager newAnim = new JointAnimManager();

            newAnim.FrameCount = sourceManager.Animation.FrameCount;

            newAnim.Nodes.Clear();
            for (int i = 0; i < targetManager.JointCount; i++)
            {
                newAnim.Nodes.Add(new AnimNode());
            }
            for (int i = 0; i < sourceManager.JointCount; i++)
            {
                int targetIndex = i;
                int sourceIndex = i;

                Console.WriteLine(sourceMap[i]);

                // remap bone if joint maps are present
                if (sourceMap != null && targetMap != null && !string.IsNullOrEmpty(sourceMap[i]))
                {
                    targetIndex = targetMap.IndexOf(sourceMap[i]);
                }

                if (targetIndex == -1)
                {
                    continue;
                }

                var sourceJoint = sourceManager.GetJOBJ(sourceIndex);
                var targetJoint = targetManager.GetJOBJ(targetIndex);

                Console.WriteLine($"\t {sourceJoint.RX} {sourceJoint.RY} {sourceJoint.RZ}");
                Console.WriteLine($"\t {targetJoint.RX} {targetJoint.RY} {targetJoint.RZ}");

                /*if(sourceMap[i] == "RLegJ")
                 * {
                 *  var track = animation.Nodes[sourceIndex].Tracks.Find(e => e.JointTrackType == JointTrackType.HSD_A_J_ROTX);
                 *
                 *  if (track != null)
                 *      foreach (var k in track.Keys)
                 *          k.Value += (float)Math.PI / 2;
                 *
                 *  track = animation.Nodes[sourceIndex].Tracks.Find(e => e.JointTrackType == JointTrackType.HSD_A_J_ROTZ);
                 *
                 *  if (track != null)
                 *      foreach (var k in track.Keys)
                 *          k.Value += (float)Math.PI / 2;
                 * }*/

                newAnim.Nodes[targetIndex].Tracks = animation.Nodes[sourceIndex].Tracks;;
            }

            return(newAnim);
        }
Exemplo n.º 6
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        /// <param name="animation"></param>
        /// <returns></returns>
        public static JointAnimManager Retarget(HSD_JOBJ source, HSD_JOBJ target, JointAnimManager animation, JointMap sourceMap = null, JointMap targetMap = null)
        {
            var sourceManager = new JOBJManager();

            sourceManager.SetJOBJ(source);

            var sourceInverseWorldTransforms = new List <Matrix4>();

            for (int i = 0; i < sourceManager.JointCount; i++)
            {
                sourceInverseWorldTransforms.Add(sourceManager.GetWorldTransform(i).Inverted());
            }

            sourceManager.Animation = animation;


            var targetManager = new JOBJManager();

            targetManager.SetJOBJ(target);

            JointAnimManager newAnim = new JointAnimManager();

            newAnim.FrameCount = sourceManager.Animation.FrameCount;
            var targetWorldTransforms = new List <Matrix4>();

            for (int i = 0; i < targetManager.JointCount; i++)
            {
                targetWorldTransforms.Add(targetManager.GetWorldTransform(i));
                newAnim.Nodes.Add(new AnimNode());
            }

            targetManager.Animation = newAnim;
            for (int f = 0; f <= sourceManager.Animation.FrameCount; f++)
            {
                sourceManager.Frame = f - 1;
                sourceManager.UpdateNoRender();

                targetManager.Frame = f;
                targetManager.UpdateNoRender();

                // bake animation on target skeleton
                for (int i = 0; i < sourceManager.JointCount; i++)
                {
                    int targetIndex = i;
                    int sourceIndex = i;

                    // remap bone if joint maps are present
                    if (sourceMap != null && targetMap != null && !string.IsNullOrEmpty(sourceMap[i]))
                    {
                        targetIndex = targetMap.IndexOf(sourceMap[i]);
                    }

                    if (targetIndex == -1)
                    {
                        continue;
                    }

                    int targetParentIndex = targetManager.ParentIndex(targetManager.GetJOBJ(targetIndex));

                    var inverseSourceWorldRotation  = sourceInverseWorldTransforms[sourceIndex];
                    var sourceAnimatedWorldRotation = sourceManager.GetWorldTransform(sourceIndex);

                    var targetWorldRotation = targetWorldTransforms[targetIndex];

                    var rel = targetWorldRotation *
                              inverseSourceWorldRotation *
                              sourceAnimatedWorldRotation;

                    if (targetParentIndex != -1)
                    {
                        rel *= targetManager.GetWorldTransform(targetParentIndex).Inverted();
                    }

                    var rot = Math3D.ToEulerAngles(rel.ExtractRotation().Inverted());

                    var targetJOBJ = targetManager.GetJOBJ(targetIndex);
                    var sourceJOBJ = sourceManager.GetJOBJ(sourceIndex);


                    sourceManager.Animation.GetAnimatedState(f - 1, sourceIndex, sourceJOBJ, out float TX, out float TY, out float TZ, out float RX, out float RY, out float RZ, out float SX, out float SY, out float SZ);


                    var relTranslate = new Vector3(sourceJOBJ.TX - TX, sourceJOBJ.TY - TY, sourceJOBJ.TZ - TZ);
                    //relTranslate = Vector3.TransformNormal(relTranslate, Matrix4.CreateFromQuaternion(Math3D.FromEulerAngles(sourceJOBJ.RZ, sourceJOBJ.RY, sourceJOBJ.RX).Inverted()));
                    //relTranslate = Vector3.TransformNormal(relTranslate, Matrix4.CreateFromQuaternion(Math3D.FromEulerAngles(targetJOBJ.RZ, targetJOBJ.RY, targetJOBJ.RX)));


                    var relScale = new Vector3(sourceJOBJ.SX - SX, sourceJOBJ.SY - SY, sourceJOBJ.SZ - SZ);
                    //relScale = Vector3.TransformNormal(relScale, inverseSourceWorldRotation);
                    //relScale = Vector3.TransformNormal(relScale, targetWorldRotation);


                    AddKey(targetIndex, f, targetJOBJ.TX - relTranslate.X, JointTrackType.HSD_A_J_TRAX, newAnim);
                    AddKey(targetIndex, f, targetJOBJ.TY - relTranslate.Y, JointTrackType.HSD_A_J_TRAY, newAnim);
                    AddKey(targetIndex, f, targetJOBJ.TZ - relTranslate.Z, JointTrackType.HSD_A_J_TRAZ, newAnim);

                    AddKey(targetIndex, f, rot.X, JointTrackType.HSD_A_J_ROTX, newAnim);
                    AddKey(targetIndex, f, rot.Y, JointTrackType.HSD_A_J_ROTY, newAnim);
                    AddKey(targetIndex, f, rot.Z, JointTrackType.HSD_A_J_ROTZ, newAnim);

                    AddKey(targetIndex, f, targetJOBJ.SX - relScale.X, JointTrackType.HSD_A_J_SCAX, newAnim);
                    AddKey(targetIndex, f, targetJOBJ.SY - relScale.Y, JointTrackType.HSD_A_J_SCAY, newAnim);
                    AddKey(targetIndex, f, targetJOBJ.SZ - relScale.Z, JointTrackType.HSD_A_J_SCAZ, newAnim);

                    targetManager.UpdateNoRender();
                }
            }

            EulerFilter(newAnim);

            var targetAnim = newAnim.ToAnimJoint(target, AOBJ_Flags.ANIM_LOOP);

            AnimationCompressor.AdaptiveCompressAnimation(targetAnim, targetMap);
            RemoveUnusedTracks(target, targetAnim);
            newAnim.FromAnimJoint(targetAnim);

            // apply additional single frame filter check
            for (int i = 0; i < sourceManager.JointCount; i++)
            {
                int targetIndex = i;
                int sourceIndex = i;

                // remap bone if joint maps are present
                if (sourceMap != null && targetMap != null && !string.IsNullOrEmpty(sourceMap[i]))
                {
                    targetIndex = targetMap.IndexOf(sourceMap[i]);
                }

                if (targetIndex == -1)
                {
                    continue;
                }

                var sourceNode = animation.Nodes[sourceIndex];
                var targetNode = newAnim.Nodes[targetIndex];

                FOBJ_Player sourceXRot = null;
                FOBJ_Player sourceYRot = null;
                FOBJ_Player sourceZRot = null;

                foreach (var t in sourceNode.Tracks)
                {
                    if (t.JointTrackType == JointTrackType.HSD_A_J_ROTX)
                    {
                        sourceXRot = t;
                    }
                    if (t.JointTrackType == JointTrackType.HSD_A_J_ROTY)
                    {
                        sourceYRot = t;
                    }
                    if (t.JointTrackType == JointTrackType.HSD_A_J_ROTZ)
                    {
                        sourceZRot = t;
                    }
                }

                if (sourceXRot != null && sourceYRot != null && sourceZRot != null &&
                    sourceXRot.Keys.Count == 1 && sourceYRot.Keys.Count == 1 && sourceZRot.Keys.Count == 1)
                {
                    foreach (var t in targetNode.Tracks)
                    {
                        if (t.JointTrackType == JointTrackType.HSD_A_J_ROTX ||
                            t.JointTrackType == JointTrackType.HSD_A_J_ROTY ||
                            t.JointTrackType == JointTrackType.HSD_A_J_ROTZ)
                        {
                            t.Keys.RemoveAll(e => e.Frame > 0);
                            t.Keys[0].InterpolationType = GXInterpolationType.HSD_A_OP_KEY;
                        }
                    }
                }
            }

            return(newAnim);
        }
Exemplo n.º 7
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="manager"></param>
        /// <param name="after_image"></param>
        public static void RenderAfterImage(JOBJManager manager, float frame, AfterImageDesc after_image)
        {
            if (manager.Animation == null)
            {
                return;
            }

            // process previous keys
            AfterImageKey[] keys = new AfterImageKey[3];

            for (int i = 0; i < keys.Length; i++)
            {
                manager.Frame = frame - i;
                manager.UpdateNoRender();

                var bone = manager.GetWorldTransform(after_image.Bone);

                keys[i] = new AfterImageKey()
                {
                    pos = bone.ExtractTranslation(),
                    rot = bone.ExtractRotation()
                };
            }


            // calculate point positions
            var top = new Vector3(after_image.Top, 0, 0);
            var bot = new Vector3(after_image.Bottom, 0, 0);

            Vector3[] points_top    = new Vector3[keys.Length];
            Vector3[] points_bottom = new Vector3[keys.Length];
            for (int i = 0; i < keys.Length; i++)
            {
                points_top[i]    = keys[i].Transform(top);
                points_bottom[i] = keys[i].Transform(bot);
            }

            float   term1      = 1 / (keys[0].pos - keys[1].pos).Length;
            float   term2      = 1 / (keys[1].pos - keys[2].pos).Length;
            Vector3 tan_bottom =
                new Vector3(
                    CalculateTangent(term1, term2, points_bottom[0].X, points_bottom[1].X, points_bottom[2].X),
                    CalculateTangent(term1, term2, points_bottom[0].Y, points_bottom[1].Y, points_bottom[2].Y),
                    CalculateTangent(term1, term2, points_bottom[0].Z, points_bottom[1].Z, points_bottom[2].Z));
            Vector3 tan_top =
                new Vector3(
                    CalculateTangent(term1, term2, points_top[0].X, points_top[1].X, points_top[2].X),
                    CalculateTangent(term1, term2, points_top[0].Y, points_top[1].Y, points_top[2].Y),
                    CalculateTangent(term1, term2, points_top[0].Z, points_top[1].Z, points_top[2].Z));


            // render sword trail
            GL.PushAttrib(AttribMask.AllAttribBits);
            GL.Disable(EnableCap.CullFace);
            GL.Disable(EnableCap.DepthTest);
            GL.Enable(EnableCap.Blend);
            GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);

            // draw strips interpolated
            GL.Begin(PrimitiveType.TriangleStrip);

            int   slices        = 10;
            float total_indices = (keys.Length - 1) * slices;
            float index         = total_indices;

            for (int s = 0; s < keys.Length - 1; s++)
            {
                float length = (keys[s].pos - keys[s + 1].pos).Length;

                for (float i = 0; i < length; i += length / slices)
                {
                    float alpha = index / total_indices;

                    GL.Color4(after_image.Color1.X, after_image.Color1.Y, after_image.Color1.Z, alpha);
                    GL.Vertex3(Herp(1 / length, i, points_top[s], points_top[s + 1], tan_top));

                    GL.Color4(after_image.Color2.X, after_image.Color2.Y, after_image.Color2.Z, alpha);
                    GL.Vertex3(Herp(1 / length, i, points_bottom[s], points_bottom[s + 1], tan_bottom));

                    index--;
                }
                tan_bottom *= -1;
                tan_top    *= -1;
            }

            /*
             * GL.Color3(after_image.Color1);
             * GL.Vertex3(keys[0].Transform(top));
             * GL.Color3(after_image.Color2);
             * GL.Vertex3(keys[0].Transform(bot));
             *
             * GL.Color3(after_image.Color1);
             * GL.Vertex3(keys[1].Transform(top));
             * GL.Color3(after_image.Color2);
             * GL.Vertex3(keys[1].Transform(bot));
             *
             * GL.Color3(after_image.Color1);
             * GL.Vertex3(keys[2].Transform(top));
             * GL.Color3(after_image.Color2);
             * GL.Vertex3(keys[2].Transform(bot));
             */

            GL.End();

            GL.PopAttrib();
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="manager"></param>
        /// <param name="after_image"></param>
        public static void RenderAfterImage(JOBJManager manager, float frame, AfterImageDesc after_image)
        {
            if (manager.Animation == null)
            {
                return;
            }

            // process previous keys
            AfterImageKey[] keys = new AfterImageKey[3];

            for (int i = 0; i < keys.Length; i++)
            {
                manager.Frame = frame - i;
                manager.UpdateNoRender();

                var bone = manager.GetWorldTransform(after_image.Bone);

                keys[i] = new AfterImageKey()
                {
                    pos = bone.ExtractTranslation(),
                    rot = bone.ExtractRotation()
                };
            }

            //// render sword trail
            GL.PushAttrib(AttribMask.AllAttribBits);
            GL.Disable(EnableCap.CullFace);
            GL.Disable(EnableCap.DepthTest);
            GL.Enable(EnableCap.Blend);
            GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);

            // draw strips interpolated
            GL.Begin(PrimitiveType.TriangleStrip);

            var top = new Vector3(after_image.Top, 0, 0);
            var bot = new Vector3(after_image.Bottom, 0, 0);

            int   slices        = 10;
            float total_indices = (keys.Length - 1) * slices;
            float index         = total_indices;

            for (int s = 0; s < keys.Length - 1; s++)
            {
                var key1 = keys[s];
                var key2 = keys[s + 1];

                float length = (keys[s].pos - keys[s + 1].pos).Length;

                for (float i = 0; i < length; i += length / slices)
                {
                    float alpha = index / total_indices;
                    index--;

                    float blend = i / length;

                    // lerp angle
                    var rot = Quaternion.Slerp(key1.rot, key2.rot, blend);
                    var pos = Vector3.Lerp(key1.pos, key2.pos, blend);
                    var mat = Matrix4.CreateFromQuaternion(rot) * Matrix4.CreateTranslation(pos);

                    var start_top = Vector3.TransformPosition(top, mat);
                    var start_bot = Vector3.TransformPosition(bot, mat);

                    GL.Color4(after_image.Color1.X, after_image.Color1.Y, after_image.Color1.Z, alpha);
                    GL.Vertex3(start_top);

                    GL.Color4(after_image.Color2.X, after_image.Color2.Y, after_image.Color2.Z, alpha);
                    GL.Vertex3(start_bot);
                }
            }

            GL.End();

            GL.PopAttrib();
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="cam"></param>
        /// <param name="windowWidth"></param>
        /// <param name="windowHeight"></param>
        public void Draw(Camera cam, int windowWidth, int windowHeight)
        {
            // if model not loaded do nothing
            if (JointManager == null)
            {
                return;
            }

            // store previous hitbox state info
            CalculatePreviousState();

            // reset model parts
            if (ModelPartsIndices != null)
            {
                for (int i = 0; i < ModelPartsIndices.Length; i++)
                {
                    ModelPartsIndices[i].AnimIndex = -1;
                }
            }

            // process ftcmd
            SubactionProcess.SetFrame(viewport.Frame);

            // update display info
            JointManager.DOBJManager.OverlayColor = SubactionProcess.OverlayColor;
            JointManager._settings.RenderBones    = bonesToolStripMenuItem.Checked;

            // apply model animations
            JointManager.Frame = viewport.Frame;
            JointManager.UpdateNoRender();

            // character invisibility
            if (!SubactionProcess.CharacterInvisibility && modelToolStripMenuItem.Checked)
            {
                JointManager.Render(cam, false);
            }

            // hurtbox collision
            if (hurtboxesToolStripMenuItem.Checked)
            {
                HurtboxRenderer.Render(JointManager, Hurtboxes, null, SubactionProcess.BoneCollisionStates, SubactionProcess.BodyCollisionState);
            }

            // hitbox collision
            int  hitboxId         = 0;
            bool isHitboxSelected = false;

            foreach (var hb in SubactionProcess.Hitboxes)
            {
                if (!hb.Active)
                {
                    hitboxId++;
                    continue;
                }

                float   alpha   = 0.4f;
                Vector3 hbColor = HitboxColor;

                var worldPosition  = hb.GetWorldPosition(JointManager);
                var worldTransform = Matrix4.CreateTranslation(worldPosition);

                if (hb.Element == 8)
                {
                    hbColor = GrabboxColor;
                }

                if (subActionList.SelectedIndices.Count == 1 && hb.CommandIndex == subActionList.SelectedIndex)
                {
                    hbColor                = HitboxSelectedColor;
                    isHitboxSelected       = true;
                    _transWidget.Transform = hb.GetWorldTransform(JointManager);
                }

                // drawing a capsule takes more processing power, so only draw it if necessary
                if (hb.Interpolate &&
                    interpolationToolStripMenuItem.Checked)
                {
                    capsule.SetParameters(worldPosition, PreviousPositions[hitboxId], hb.Size);
                    capsule.Draw(Matrix4.Identity, new Vector4(hbColor, alpha));
                }
                else
                {
                    DrawShape.DrawSphere(worldTransform, hb.Size, 16, 16, hbColor, alpha);
                }

                // draw hitbox angle
                if (hitboxInfoToolStripMenuItem.Checked)
                {
                    if (hb.Angle != 361)
                    {
                        DrawShape.DrawAngleLine(cam, worldTransform, hb.Size, MathHelper.DegreesToRadians(hb.Angle));
                    }
                    else
                    {
                        DrawShape.DrawSakuraiAngle(cam, worldTransform, hb.Size);
                    }

                    GLTextRenderer.RenderText(cam, hitboxId.ToString(), worldTransform, StringAlignment.Center, true);
                }
                hitboxId++;
            }

            // draw shield during guard animation
            if (DisplayShieldSize > 0)
            {
                DrawShape.DrawSphere(JointManager.GetWorldTransform(JointManager.JointCount - 2), DisplayShieldSize, 16, 16, ShieldColor, 0.5f);
            }

            // gfx spawn indicator
            foreach (var gfx in SubactionProcess.GFXOnFrame)
            {
                var boneID = gfx.Bone;
                if (boneID == 0)
                {
                    if (JointManager.GetJOBJ(1) != null && JointManager.GetJOBJ(1).Child == null) // special case for character like mewtwo with a leading bone
                    {
                        boneID = 2;
                    }
                    else
                    {
                        boneID = 1;
                    }
                }
                var transform = Matrix4.CreateTranslation(gfx.Position) * JointManager.GetWorldTransform(boneID);
                transform = transform.ClearScale();

                DrawShape.DrawSphere(transform, 1f, 16, 16, ThrowDummyColor, 0.5f);
            }

            // environment collision
            if (ECB != null)
            {
                var topN = JointManager.GetWorldTransform(1).ExtractTranslation();

                var bone1 = Vector3.TransformPosition(Vector3.Zero, JointManager.GetWorldTransform(ECB.ECBBone1));
                var bone2 = Vector3.TransformPosition(Vector3.Zero, JointManager.GetWorldTransform(ECB.ECBBone2));
                var bone3 = Vector3.TransformPosition(Vector3.Zero, JointManager.GetWorldTransform(ECB.ECBBone3));
                var bone4 = Vector3.TransformPosition(Vector3.Zero, JointManager.GetWorldTransform(ECB.ECBBone4));
                var bone5 = Vector3.TransformPosition(Vector3.Zero, JointManager.GetWorldTransform(ECB.ECBBone5));
                var bone6 = Vector3.TransformPosition(Vector3.Zero, JointManager.GetWorldTransform(ECB.ECBBone6));

                var minx = float.MaxValue;
                var miny = float.MaxValue;
                var maxx = float.MinValue;
                var maxy = float.MinValue;

                foreach (var p in new Vector3[] { bone1, bone2, bone3, bone4, bone5, bone6 })
                {
                    minx = Math.Min(minx, p.Z);
                    maxx = Math.Max(maxx, p.Z);
                    miny = Math.Min(miny, p.Y);
                    maxy = Math.Max(maxy, p.Y);
                }

                // ecb diamond
                if (eCBToolStripMenuItem.Checked)
                {
                    DrawShape.DrawECB(topN, minx, miny, maxx, maxy, groundECH.Checked);
                }

                // ledge grav
                if (ledgeGrabBoxToolStripMenuItem.Checked)
                {
                    var correct = Math.Abs(minx - maxx) / 2;

                    //behind
                    DrawShape.DrawLedgeBox(
                        topN.Z,
                        topN.Y + ECB.VerticalOffsetFromTop - ECB.VerticalScale / 2,
                        topN.Z - (correct + ECB.HorizontalScale),
                        topN.Y + ECB.VerticalOffsetFromTop + ECB.VerticalScale / 2,
                        Color.Red);

                    // in front
                    DrawShape.DrawLedgeBox(
                        topN.Z,
                        topN.Y + ECB.VerticalOffsetFromTop - ECB.VerticalScale / 2,
                        topN.Z + correct + ECB.HorizontalScale,
                        topN.Y + ECB.VerticalOffsetFromTop + ECB.VerticalScale / 2,
                        Color.Blue);
                }
            }

            // throw dummy
            if (ThrowDummyManager.Animation.NodeCount != 0 &&
                throwModelToolStripMenuItem.Checked &&
                !SubactionProcess.ThrownFighter &&
                ThrowDummyManager.JointCount > 0)
            {
                if (viewport.Frame < ThrowDummyManager.Animation.FrameCount)
                {
                    ThrowDummyManager.Frame = viewport.Frame;
                }

                ThrowDummyManager.SetWorldTransform(4, JointManager.GetWorldTransform(JointManager.JointCount - 2));
                ThrowDummyManager.Render(cam, false);

                if (ThrowDummyLookupTable.Count == 0)
                {
                    DrawShape.DrawSphere(ThrowDummyManager.GetWorldTransform(35), 1.5f, 16, 16, ThrowDummyColor, 0.5f);
                    DrawShape.DrawSphere(ThrowDummyManager.GetWorldTransform(4), 1.5f, 16, 16, ThrowDummyColor, 0.5f);
                    DrawShape.DrawSphere(ThrowDummyManager.GetWorldTransform(10), 1f, 16, 16, ThrowDummyColor, 0.5f);
                    DrawShape.DrawSphere(ThrowDummyManager.GetWorldTransform(15), 1f, 16, 16, ThrowDummyColor, 0.5f);
                    DrawShape.DrawSphere(ThrowDummyManager.GetWorldTransform(22), 1f, 16, 16, ThrowDummyColor, 0.5f);
                    DrawShape.DrawSphere(ThrowDummyManager.GetWorldTransform(40), 1f, 16, 16, ThrowDummyColor, 0.5f);
                }
            }

            if (isHitboxSelected)
            {
                _transWidget.Render(cam);
            }

            // sword trail
            //AfterImageRenderer.RenderAfterImage(JointManager, viewport.Frame, after_desc);
        }