Search code examples
paperjs

Can I control how smooth handles the handlebar of a segment?


I'm working with Paper.js for the first time now and I'm working on a yellow cube with fluid/waving sides. What I want is the side of a cube wave like this:

http://paperjs.org/tutorials/animation/creating-animations/#animating-path-segments

But I want the corners of the cube to be fixed. I'm almost getting there but I walk into one problem segment1 (topleft corner is [0]) is behaving strange. The handles are diagonal and not horizontal like the others.

enter image description here

How can I fix this?

My current sketch can be found here.

The full code is

var values = {
    margin: 64,
    header: 132,
    height: 60,
    amount: 5
};

 var path, springs;

function createPath(strength) {
    var size = view.size;

    var blockSize = {
        width: size.width - values.margin,
        height: size.height - values.header,
        segmentX: (size.width - values.margin)/(values.amount + 1),
        segmentY: (size.height - values.header)/(values.amount + 1)
    };

    var blockPosition = {
        top: size.height - blockSize.height,
        left: 0
    }

    var path = new Path({
        fillColor: 'yellow'
    });

    for (var i = 0; i <= values.amount; i++) {
        //top
        path.add(new Point((blockPosition.left + (blockSize.segmentX * i)), blockPosition.top));
    }

    for (var i = 0; i <= values.amount; i++) {
        //left
        path.add(new Point(blockSize.width, blockPosition.top + (blockSize.segmentY * i)));
    }

    path.add(new Point(blockSize.width, blockSize.height + blockPosition.top));
    path.add(new Point(blockPosition.left, blockSize.height + blockPosition.top));


    path.fullySelected = true;
    path.closed = true;
    return path;
}

function onResize() {
    if (path)
        path.remove();
    path = createPath();
}

function onFrame(event) {
    for (var i = 1; i <= values.amount - 1; i++) {
        var segmentTop = path.segments[i];
        var segmentRight = path.segments[i + values.amount + 1];

        // A cylic value between -1 and 1
        var sinus = Math.sin(event.time * 3 + i);

        // Change the y position of the segment point:
        segmentTop.point.y = sinus * values.height + 100;
       // segmentRight.point.x = sinus * 1;

        path.smooth();
    }

    path.segments[0].handleIn.x = 0;
    path.segments[0].handleIn.y = 100;
    path.segments[0].handleOut.x = 100;
    path.segments[0].handleOut.y = 0;

    path.segments[6].handleIn.x = -100;
    path.segments[6].handleIn.y = 0;
    path.segments[6].handleOut.x = 0;
    path.segments[6].handleOut.y = 100;
}

Solution

  • The problem you are encountering is due to the fact that you are applying the smoothing algorithm on the whole square, including corners. So if you take a screenshot before setting corners handles to be horizontal/vertical, you see this: enter image description here I think that explain quite clearly why your second segment has a diagonal handle, this is because the previous segment has one too...

    Here is a sketch demonstrating an effect similar to what you are trying to achieve.

    // Define constants.
    const POINTS_AMOUNT = 7;
    const SQUARE_WIDTH = 500;
    const WAVE_AMPLITUDE = 25;
    const SQUARE_OFFSET = new Point(100, 100);
    
    // Initialize path variable, we will use this reference to delete the previous
    // path on each frame.
    let path;
    
    function draw(time) {
        // Delete existing path.
        if (path) {
            path.remove();
        }
    
        // Create new path.
        path = new Path();
        // First, draw the top side.
        for (let i = 0; i < POINTS_AMOUNT; i++) {
            // For each point of the line, create a wave effect by using sine
            // function. Do not apply it on first and last point to make square
            // corners stay constant.
            const sinus = i === 0 || i === POINTS_AMOUNT - 1
                ? 0
                : Math.sin(time * 3 + i);
            const x = SQUARE_WIDTH / POINTS_AMOUNT * i;
            const y = sinus * WAVE_AMPLITUDE;
            path.add(new Point(x, y));
        }
        // Apply smoothing algorithm on the line.
        path.smooth();
    
        // Duplicate this line 3 times to form a square.
        const right = path.clone().rotate(-90, path.lastSegment.point);
        right.reverse();
        const bottom = right.clone().rotate(-90, right.lastSegment.point);
        bottom.reverse();
        const left = bottom.clone().rotate(-90, bottom.lastSegment.point);
    
        // Join all parts together.
        path.join(right).join(bottom).join(left);
        path.closed = true;
    
        // Place the square somewhere we can see it.
        path.translate(SQUARE_OFFSET);
    
        // Stylize the square.
        path.fullySelected = true;
        path.fillColor = 'yellow';
    }
    
    function onFrame(event) {
        draw(event.time);
    }