Home > Blockchain >  How to equally distribute points to a LineRenderer's width curve as I'm "drawing"
How to equally distribute points to a LineRenderer's width curve as I'm "drawing"

Time:11-17

I'm using the line renderer to create a "drawing" app, and now I'm trying to enable pen pressure with the width curve on the LineRenderer. The problem is that the "time" values (horizontal axis) of the AnimationCurve are normalized from 0 to 1, so I can't just add a value to the end of it every time a position is added. Unless there's a function I'm unaware of, the only solution I can think of is finding a way to shift all hundreds of previous values by an exact percentage as I'm drawing the line, and do this as positions are being added. This seems excessive.

I'm at a loss on what to do here.

This is the basic line that adds the point every frame as I draw with the pen.

curve.AddKey(1.0f, penPressureValue);

The "1.0f" is the position on the curve (1 being the last), so this just adds a point at the end every frame, changing the entire line's width as I draw.

CodePudding user response:

Unfortunately I don't think there is a way of achieveing this that is not in some way uncanny and performance intense.

Of course you can calculate it (assuming the LineRenderer starts with position count = 0) like e.g.

public LineRenderer line;
private int positionCount;
private float totalLengthOld;

private void AddPoint(Vector3 position, float width)
{
    // increase the position count by one
    positionCount  ;

    // set the count back to the line
    line.positionCount = positionCount;

    // add our new point
    line.SetPosition(positionCount - 1, position);

    // now get the current width curve
    var curve = line.widthCurve;

    // Is this the beginning of the line?
    if (positionCount == 1)
    {
        // First point => simply set the first keyframe 
        curve.MoveKey(0, new Keyframe(0f, width));
    }
    else
    {
        // otherwise get all positions
        var positions = new Vector3[positionCount];
        line.GetPositions(positions);

        // sum up the distances between positions to obtain the length of the line
        var totalLengthNew = 0f;
        for (var i = 1; i < positionCount; i  )
        {
            totalLengthNew  = Vector3.Distance(positions[i - 1], positions[i]);
        }

        // calculate the time factor we have to apply to all already existing keyframes
        var factor = totalLengthOld / totalLengthNew;

        // then store for the next added point
        totalLengthOld = totalLengthNew;

        // now move all existing keys which are currently based on the totalLengthOld to according positions based on the totalLengthNew
        // we can skip the first one as it will stay at 0 always
        var keys = curve.keys;
        for (var i = 1; i < keys.Length; i  )
        {
            var key = keys[i];
            key.time *= factor;

            curve.MoveKey(i, key);
        }

        // add the new last keyframe
        curve.AddKey(1f, width);
    }

    // finally write the curve back to the line
    line.widthCurve = curve;
}

Just as a little demo

public class Example : MonoBehaviour
{
    public LineRenderer line;
    public Transform pen;
    [Range(0.01f, 0.5f)] public float width;
    public float drawThreshold = 0.1f;

    private int positionCount;
    private float totalLengthOld;
    private Vector3 lastPenPosition;
    
    private void Awake()
    {
        line = GetComponent<LineRenderer>();
        line.useWorldSpace = true;
        line.positionCount = 0;

        lastPenPosition = pen.position;
    }

    private void Update()
    {
        // just for the demo simply ping-pong the width over time
        width = Mathf.Lerp(0.01f, 0.8f, Mathf.PingPong(Time.time, 1f));

        var currentPenPosition = pen.position;
        
        if (Vector3.Distance(lastPenPosition, currentPenPosition) >= drawThreshold)
        {
            lastPenPosition = currentPenPosition;
            AddPoint(currentPenPosition, width);
        }
    }
    
    private void AddPoint(Vector3 position, float width)
    {
        positionCount  ;

        line.positionCount = positionCount;
        line.SetPosition(positionCount - 1, position);

        var curve = line.widthCurve;

        if (positionCount == 1)
        {
            curve.MoveKey(0, new Keyframe(0f, width));
        }
        else
        {
            var positions = new Vector3[positionCount];
            line.GetPositions(positions);

            var totalLengthNew = 0f;
            for (var i = 1; i < positionCount; i  )
            {
                totalLengthNew  = Vector3.Distance(positions[i - 1], positions[i]);
            }

            var factor = totalLengthOld / totalLengthNew;

            totalLengthOld = totalLengthNew;

            var keys = curve.keys;
            for (var i = 1; i < keys.Length; i  )
            {
                var key = keys[i];
                key.time *= factor;

                curve.MoveKey(i, key);
            }

            curve.AddKey(1f, width);
        }

        line.widthCurve = curve;
    }
}

enter image description here


Of course this will hit performance limits after a certain amount of points. But I think this is as far as you can go for now using the LineRenderer. Otherwise the LineRenderer is maybe just not the correct tool for drawing.

You could of course go tricky and after certain amount of points bake the existing line into a separate fixed mesh using enter image description here

You'll have to play around a bit with the threshold, 50 is probably a bit low, just used it for the demo. You want to find a balance between performance cost of iterating all the keyframes and baking meshes ;)

  • Related