Rewinding and running camera follow script

Hi,

I forked the camera following a path script, and used HTML and jQuery to make it such that the path following only starts when you click the button. However, I want the path to restart from the beginning every time I press the button. How do I do this?

My code-

var CameraPath = pc.createScript('cameraPath');

CameraPath.attributes.add('html', {type: 'asset', assetType:'html', title: 'HTML Asset'});
CameraPath.attributes.add('css', {type: 'asset', assetType:'css', title: 'CSS Asset'});

CameraPath.attributes.add("pathRoot", {type: "entity", title: "Path Root"});
CameraPath.attributes.add("duration", {type: "number", default: 10, title: "Duration Secs"});

CameraPath.attributes.add("startTime", {
    type: "number", 
    default: 0, 
    title: "Start Time (Secs)", 
    description: "Start the path from a specific point in time"
});

// initialize code called once per entity
CameraPath.prototype.initialize = function() {
    
    // Add the UI (see sample for using HTML: https://developer.playcanvas.com/en/tutorials/htmlcss-ui/)
    this.addUi();
    
    // Learn more about live attribute tweaking from this project: https://playcanvas.com/editor/scene/475560
    // If the user decides to change the path while the app is running, this allows for quicker iteration
    this.on("attr:pathRoot", function (value, prev) {
        if (value) {
            this.createPath();
            this.time = 0;    
        }
    });
    
    this.on("attr:time", function (value, prev) {
        this.time = pc.math.clamp(this.startTime, 0, this.duration);    
    });
    
    this.time = pc.math.clamp(this.startTime, 0, this.duration);    
    
    // Caching some Vec3 objects that will be used in the update loop continously 
    // so we don't keep triggering the garbage collector
    this.lookAt = new pc.Vec3();
    this.up = new pc.Vec3();
    
    this.flyingThrough = true; 
    
    this.check = false;
    
    $(".button").click(function(){
        if (this.check === false) {
            this.time = 0;
            // Generate the camera path using pc.Curve: http://developer.playcanvas.com/en/api/pc.Curve.html
            this.createPath();
            this.check = true;
        }
    }.bind(this));
    
};


// update code called every frame
CameraPath.prototype.update = function(dt) {

    if (this.check === true) {
        var percent;
    
        if (this.flyingThrough) {
            this.time += dt;

            // Work out how far we are in time we have progressed along the path
            percent = this.time / this.duration;
        }


        // Get the interpolated values for the position from the curves     
        this.entity.setPosition(this.px.value(percent), this.py.value(percent), this.pz.value(percent));

        // Get the interpolated values for the look at point from the curves 
        this.lookAt.set(this.tx.value(percent), this.ty.value(percent), this.tz.value(percent));

        // Get the interpolated values for the up vector from the curves     
        this.up.set(this.ux.value(percent), this.uy.value(percent), this.uz.value(percent));

        // Make the camera look at the interpolated target position with the correct
        // up direction to allow for camera roll and to avoid glimbal lock
        this.entity.lookAt(this.lookAt, this.up);
    }
};


CameraPath.prototype.createPath = function () {
    var curveMode = pc.CURVE_CARDINAL;
    
    // Create curves for position
    this.px = new pc.Curve(); 
    this.px.type = curveMode;
    
    this.py = new pc.Curve(); 
    this.py.type = curveMode;    
    
    this.pz = new pc.Curve(); 
    this.pz.type = curveMode;
    
    // Create curves for target look at position
    this.tx = new pc.Curve();
    this.tx.type = curveMode;
    
    this.ty = new pc.Curve();
    this.ty.type = curveMode;
    
    this.tz = new pc.Curve();
    this.tz.type = curveMode;
    
    // Create curves for the 'up' vector for use with the lookAt function to 
    // allow for roll and avoid gimbal lock
    this.ux = new pc.Curve();
    this.ux.type = curveMode;
    
    this.uy = new pc.Curve();
    this.uy.type = curveMode;
    
    this.uz = new pc.Curve();
    this.uz.type = curveMode;
    
    var nodes = this.pathRoot.children;  
    
    // Get the total linear distance of the path (this isn't correct but gives a decent approximation in length)
    var pathLength = 0;
    
    // Store the distance from the start of the path for each path node
    var nodePathLength = [];
    
    // For use when calculating the distance between two nodes on the path
    var distanceBetween = new pc.Vec3();
    
    // Push 0 as we are starting our loop from 1 for ease
    nodePathLength.push(0);
    
    for (i = 1; i < nodes.length; i++) {
        var prevNode = nodes[i-1];
        var nextNode = nodes[i];
        
        // Work out the distance between the current node and the one before in the path
        distanceBetween.sub2(prevNode.getPosition(), nextNode.getPosition());
        pathLength += distanceBetween.length();
        
        nodePathLength.push(pathLength);
    }
        
    for (i = 0; i < nodes.length; i++) {
        // Calculate the time for the curve key based on the distance of the path to the node
        // and the total path length so the speed of the camera travel stays relatively
        // consistent throughout
        var t = nodePathLength[i] / pathLength;
        
        var node = nodes[i];
        
        var pos = node.getPosition();
        this.px.add(t, pos.x);
        this.py.add(t, pos.y);
        this.pz.add(t, pos.z);
        
        // Create and store a lookAt position based on the node position and the forward direction
        var lookAt = pos.clone().add(node.forward);
        this.tx.add(t, lookAt.x);
        this.ty.add(t, lookAt.y);
        this.tz.add(t, lookAt.z);
        
        var up = node.up;
        this.ux.add(t, up.x);
        this.uy.add(t, up.y);
        this.uz.add(t, up.z);
    }
};

CameraPath.prototype.addUi = function() {
    var self = this;
    
    var style = document.createElement('style');
    document.head.appendChild(style);
    style.innerHTML = this.css.resource || '';
    this.div = document.createElement('div');
    this.div.classList.add('container');
    this.div.innerHTML = this.html.resource || '';
    document.body.appendChild(this.div);
};

My project - https://playcanvas.com/editor/project/789802

1 Like

The animation path is using a set of pc.Curve, where each curve corresponds to an axis of that path. The way you decide where to place the camera is to sample the curve for normalized value. That is your “percent”, which means that at 0% you will get position at the start of the path and at 100% you will get the position at the very end of it. Because the values are normalized, you sample with a range from 0 to 1, where 1 is 100%.

As such, in order for you to get the position in the beginning of the path, you need to sample the curve values with a value of 0. You can for example reset “this.time” to 0 or do something else to make percent equal to 0.

2 Likes

Got it working like so, thanks to @LeXXik’s help. Previously, setting this.time to zero would only cause the button to work as a pause/play button. Changing the time object to zero and then recreating the path on the event seems to have resolved it. Thanks @LeXXik.

1 Like

Generating curves for a static path is a relatively expensive operation for CPU. If the path doesn’t change, you don’t need to generate it more than once.

1 Like