3
\$\begingroup\$

I've written this small code chunk to implement a very basic spline system for object motions. As I've only used C# on Unity3D so far I would appreciate feedbacks on this, especially on access levels, naming and the use of C# API. Most of this code is executed before runtime so I'm not really concerned about performance here. Feel free to comment on anything though, every advice/tips will be very appreciated.

Note: The tooltips are for designers with no programming and poor mathematical background that'll use these spline in the editor.

Thanks a lot for taking the time to read this! I hope there isn't too much code here, I decided to not put the class that handle the object movement on the spline to reduce the volume.

BezierCurve.cs

/* 
 * This class is used to store the informations of every point on the spline.
 * These informations are calcultated at editor time and serialized.
 */
[System.Serializable]
public class BezierTickInformation
{
    public Vector2 WorldPosition;
    public Vector3 Rotation;
    public BezierRotationMode RotationMode;
    public float LinearSpeedFactor;
    public float RotationSpeedFactor;

    public BezierTickInformation()
    {
        WorldPosition = Vector2.zero;
        Rotation = Vector3.zero;
        RotationMode = BezierRotationMode.FollowSpline;
        LinearSpeedFactor = 1;
        RotationSpeedFactor = 1;
    }

    public BezierTickInformation(Vector3 position, Vector3 rotation, 
        BezierRotationMode rotationMode, float linearSpeed, 
        float rotationSpeed)
    {
        WorldPosition = position;
        Rotation = rotation;
        RotationMode = rotationMode;
        LinearSpeedFactor = linearSpeed;
        RotationSpeedFactor = rotationSpeed;
    }
}

[System.Serializable]
public class BezierCurve : MonoBehaviour
{
    //The length of the curve calculated at editor time
    [HideInInspector]
    [SerializeField]
    private float length;

    [HideInInspector]
    [SerializeField]
    private List<BezierTickInformation> TickInfosList;

    [Tooltip("The time in seconds that an object at speed=1 will need to travel the whole spline")]
    [SerializeField]
    private float travelTime;
    public float TravelTime { get { return travelTime; } }

#if UNITY_EDITOR
    public List<BezierTickInformation> TickInfosList { get { return tickInfosList; } }
    //For a better editor experience, the designer can hide the handles
    public bool hideHandles;

    [Tooltip("The number of generated points between two spline points.")]
    [SerializeField]
    private int precision = 30;

    [SerializeField]
    public List<BezierPoint> Points = new List<BezierPoint>();

    //Calculate the position of a point between point1 and point2 with t in unit interval
    Vector3 calculateBezierPoint(float t, Vector3 point1, Vector3 handlePoint1, Vector3 point2, Vector3 handlePoint2)
    {
        float u = 1.0f - t;
        float tt = t * t;
        float uu = u * u;
        float uuu = uu * u;
        float ttt = tt * t;

        Vector3 p = uuu * point1;
        p += 3f * uu * t * handlePoint1;
        p += 3f * u * tt * handlePoint2;
        p += ttt * point2;
        return p;
    }

    public void calculateTickInformations(bool withPositionOffset = true)
    {
        Vector3 worldPositionOffset = (withPositionOffset ? transform.position : Vector3.zero);

        TickInfosList = new List<BezierTickInformation>();
        length = 0;

        for (int p = 0; p < Points.Count - 1; ++p)
        {
            BezierPoint p1 = Points[p];
            BezierPoint p2 = Points[p + 1];

            Vector2 previousPosition = p1.Position;
            float dt = (1f / precision);
            for (int t = 0; t < precision; t++)
            {
                float proportion = dt * t;

                //Calculate the position of this point in the bezier curve and add the offset
                Vector2 tPos = calculateBezierPoint(proportion, p1.Position, p1.Handle2Position, p2.Position, p2.Handle1Position) + worldPositionOffset;

                //Calculate the speed gained by acceleration (if any) and finaly store all these informations in a BezierTickInformation
                float cumulSpeed = (p1.AccelerationMode == BezierAccelerationMode.Linear ? p1.Acceleraction * proportion : 0);
                TickInfosList.Add(new BezierTickInformation(tPos, p2.Rotation, p1.RotationMode, p1.LinearSpeedFactor + cumulSpeed, p1.RotationSpeedFactor));

                //Calcultate the length of the spline
                length += Vector2.Distance(previousPosition, tPos);
                previousPosition = tPos;
            }
        }
    }
#endif

    public BezierTickInformation getPointAtPercent(float unitInterval)
    {
        float distance = length * unitInterval;
        float curLength = 0;
        for (int p = 0; p < TickInfosList.Count - 1; ++p)
        {
            if (curLength > distance)
                return TickInfosList[p];
            curLength += Vector2.Distance(TickInfosList[p].WorldPosition, TickInfosList[p + 1].WorldPosition);
        }
        return new BezierTickInformation();
    }
}

BezierPoint.cs

public enum BezierRotationMode
{
    FollowSpline,
    GoalRotation,
    Static
}

public enum BezierAccelerationMode
{
    None,
    Linear
}

#if UNITY_EDITOR
//This class is used in the editor to draw the spline and keep all the necessary informations
//Informations used at runtime are stored in the BezierTickInformation class
[System.Serializable]
public class BezierPoint
{
    //Spline parameters
    [Tooltip("World position")]
    public Vector3 Position = Vector3.one;
    [Tooltip("First handle position")]
    public Vector3 Handle1Position = Vector3.one;
    [Tooltip("Second handle position")]
    public Vector3 Handle2Position = Vector3.one;

    //Target behaviour parameters
    [Tooltip("Defines how the object will rotate on this portion of the spline.")]
    public BezierRotationMode RotationMode;
    [Tooltip("Defines how the object's speed will change on this portion of the spline.")]
    public BezierAccelerationMode AccelerationMode;
    [Tooltip("Target world roation, only considered if Rotation Mode = GoalRotation. By default, the object rotation follows the spline curve.")]
    public Vector3 Rotation = Vector3.one;
    [Tooltip("This factor will multiply the linear speed of the target on this portion of the spline.")]
    public float LinearSpeedFactor;
    [Tooltip("This factor will multiply the rotation speed of the target on this portion of the spline.")]
    public float RotationSpeedFactor;
    [Tooltip("Acceleration, only used if accelerationMode != None")]
    public float Acceleraction;

    public bool isEqual(BezierPoint p)
    {
        return Position == p.Position && Handle1Position == p.Handle1Position &&Handle2Position == p.Handle2Position;
    }

    public BezierPoint clone()
    {
        BezierPoint b = new BezierPoint();
        b.Position = Position;
        b.Handle1Position = Handle1Position;
        b.Handle2Position = Handle2Position;
        b.Rotation = Rotation;
        b.AccelerationMode = AccelerationMode;
        b.Acceleraction = Acceleraction;
        return b;
    }
}
#endif
\$\endgroup\$
  • 1
    \$\begingroup\$ The volume of the code is almost never a problem on CodeReview. \$\endgroup\$ – Denis Nov 26 '16 at 21:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.