private void LaunchCharacter(Vector3 m_JumpDirectionVector, Vector3 GrabPosition, float LinecastStart, Collider ignoreCollider = null)
        {
            Vector3 offset = Vector3.zero;      // Start without offset

            JumpParameters parameter = new JumpParameters();

            // Choose jump parameters based on Jump Type
            switch (m_JumpType)
            {
            case ClimbJumpType.Up:
                parameter = HopUpParameters;
                break;

            case ClimbJumpType.Right:
                parameter = HopRightParameters;
                offset    = transform.right * m_System.m_Capsule.radius;    // Add a small offset to the right to avoid grab on corners
                break;

            case ClimbJumpType.Left:
                parameter = HopLeftParameters;
                offset    = -transform.right * m_System.m_Capsule.radius;    // Add a small offset to the left to avoid grab on corners
                break;

            case ClimbJumpType.Back:
                parameter = JumpBackParameters;
                break;
            }

            // Set initial velocity and rotation considering that no data will be found
            m_CharacterDesiredRotation = GetRotationFromDirection(m_JumpDirectionVector);
            m_CharacterDesiredVelocity = m_JumpDirectionVector * parameter.HorizontalSpeed + Vector3.up * parameter.VerticalSpeed;

            // Try find a possible launch
            LaunchData launch = GetLaunchData(GrabPosition, LinecastStart, parameter,
                                              m_JumpDirectionVector, m_MaxAngle, offset, m_ClimbablesLayers, ignoreCollider);

            if (IsLaunchDataOnlyWay(launch, m_JumpDirectionVector))
            {
                // Found a launch solution
                SetLaunchParameters(launch, m_JumpType);

                // Check if character is jumping right or left
                // After, cast a ray to the target point, if find a ledge and
                // its normal is not perpendicular to character forward, don't change character rotation
                if (m_JumpType == ClimbJumpType.Right || m_JumpType == ClimbJumpType.Left)
                {
                    Vector3 startRay  = launch.target - transform.forward;
                    Vector3 direction = (launch.target - startRay).normalized;

                    RaycastHit hit;
                    if (Physics.SphereCast(startRay, 0.1f, direction, out hit, 3f, m_ClimbablesLayers, QueryTriggerInteraction.Collide))
                    {
                        if (Vector3.Dot(transform.forward, -hit.normal) > 0.5f)
                        {
                            m_CharacterDesiredRotation = transform.rotation;
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Star a climb jump
        /// </summary>
        /// <param name="jumpType">Desired jump type</param>
        /// <param name="directionVector">Direction to jump</param>
        /// <param name="GrabPosition">Grab position on character</param>
        /// <param name="LinecastStartHeight">Point that starts cast</param>
        /// <returns>Time to reach the target point</returns>
        public float StartClimbJump(ClimbJumpType jumpType, Vector3 directionVector, Vector3 GrabPosition, float LinecastStartHeight, bool abilityUseMath, Collider ignoreCollider = null)
        {
            m_JumpType = jumpType;
            if (m_UseLaunchMath && abilityUseMath)
            {
                LaunchCharacter(directionVector, GrabPosition, LinecastStartHeight, ignoreCollider);
            }
            else
            {
                JumpParameters parameter = new JumpParameters();

                // Choose jump parameters based on Jump Type
                switch (m_JumpType)
                {
                case ClimbJumpType.Right:
                    parameter = HopRightParameters;
                    break;

                case ClimbJumpType.Left:
                    parameter = HopLeftParameters;
                    break;

                case ClimbJumpType.Back:
                    parameter = JumpBackParameters;
                    break;
                }

                SetLaunchParameters(GetLaunchFromParameters(parameter, directionVector), m_JumpType);
                if (m_JumpType != ClimbJumpType.Back)
                {
                    m_CharacterDesiredRotation = transform.rotation;
                }
            }

            m_ForceEnterAbility = true;
            m_DotResult         = Quaternion.Dot(transform.rotation, m_CharacterDesiredRotation);

            return(m_JumpTimeToTarget);
        }
        /// <summary>
        /// Calculate velocity to reach desired point
        /// </summary>
        /// <returns>Data for the launch</returns>
        public LaunchData CalculateLaunchData(Vector3 startPoint, Vector3 targetPoint, JumpParameters parameter)
        {
            LaunchData nullData = new LaunchData(Vector3.zero, targetPoint, -1, false);

            // Full displacement
            Vector3 Displacement = targetPoint - startPoint;

            // Organize by vertical and horizontal displacements
            float   displacementY  = Displacement.y;
            Vector3 displacementXZ = new Vector3(Displacement.x, 0, Displacement.z);

            // Check if target point is too high
            // When target point is higher than character maximum jump height, it means that point is not reachable
            if (displacementY - parameter.m_MaxJumpHeight > 0)
            {
                return(nullData);
            }

            // Get a jump height if target point is between min height and maximum height
            float m_JumpHeight = Mathf.Clamp(displacementY, parameter.m_MinJumpHeight, parameter.m_MaxJumpHeight);

            // Time to reach point
            // time: Time using the maximum height jump
            float time = Mathf.Sqrt(-2 * parameter.m_MaxJumpHeight / m_Gravity) +
                         Mathf.Sqrt(2 * (displacementY - parameter.m_MaxJumpHeight) / m_Gravity);

            // timeLower: Time using height of the ledge
            float timeLower = Mathf.Sqrt(-2 * m_JumpHeight / m_Gravity) +
                              Mathf.Sqrt(2 * (displacementY - m_JumpHeight) / m_Gravity);

            // Velocities for each time calculated
            Vector3 velocity      = displacementXZ / time;
            Vector3 velocityLower = displacementXZ / timeLower;

            // If velocity is greater than maximum horizontal speed, means that this launch is not possible
            if (velocity.magnitude > parameter.HorizontalSpeed)
            {
                return(nullData);
            }

            // Check which launch to use
            bool useLower = timeLower < time && velocityLower.magnitude <= parameter.HorizontalSpeed;

            // Set vertical speed
            float vy = Mathf.Sqrt(-2 * m_Gravity * (useLower ? m_JumpHeight : parameter.m_MaxJumpHeight));

            // Get final velocity
            Vector3 finalVelocity = (useLower) ? velocityLower : velocity;

            finalVelocity.y = vy * -Mathf.Sign(m_Gravity);

            return(new LaunchData(finalVelocity, targetPoint, (useLower) ? timeLower : time, true));
        }
        /// <summary>
        /// Search ledges around, calculate possible trajectories and choose the best one
        /// </summary>
        /// <returns>Best possible launch data</returns>
        public LaunchData GetLaunchData(Vector3 launchOriginPoint, float LinecastStartPoint, JumpParameters parameter, Vector3 moveDirection, float maxAngle, Vector3 offset, LayerMask climbableMask, Collider ignoreCollider = null)
        {
            List <Collider> m_LedgesFound;

            // Check ledges around
            if (FoundLedgeToGrab(out m_LedgesFound, moveDirection, LinecastStartPoint, climbableMask, ignoreCollider))
            {
                // Start a list of launches
                List <LaunchData> launches = new List <LaunchData>();

                // Get all possible target points
                List <Vector3> points = GetTargetPoints(m_LedgesFound, launchOriginPoint, moveDirection, offset);

                // Loop trough all points
                foreach (Vector3 point in points)
                {
                    // Angle between desired direction and target point
                    float angle = Vector3.Angle(moveDirection, Vector3.Scale(point - launchOriginPoint, new Vector3(1, 0, 1)).normalized);

                    // Check angle between character and target point
                    if (angle < maxAngle)
                    {
                        LaunchData data = CalculateLaunchData(launchOriginPoint, point, parameter);
                        if (data.foundSolution) // Is this launch possible?
                        {
                            launches.Add(data); // Add this launch to the list
                        }
                    }
                }

                // Found at least one launch
                if (launches.Count > 0)
                {
                    LaunchData bestLaunch = ChooseBestLaunchData(launchOriginPoint, launches, moveDirection); // Choose best launch in all possible launches

                    if (debugTrajectory)
                    {
                        DrawPath(bestLaunch, launchOriginPoint);
                    }

                    return(bestLaunch);
                }
            }

            return(new LaunchData()); // Return a new empty launch data that means the character did not find any launch
        }
        // Launch Data for no launch calculation
        LaunchData GetLaunchFromParameters(JumpParameters jumpParameters, Vector3 horizontalDir)
        {
            Vector3 targetVelocity = jumpParameters.HorizontalSpeed * horizontalDir + jumpParameters.VerticalSpeed * Vector3.up;

            return(new LaunchData(targetVelocity, Vector3.zero, 0, false));
        }