<|start_filename|>projects/vrrroom/i18n/it-IT/locale.json<|end_filename|>
{
    "i18n-storyName": "Vrrroom",
    "i18n-storySlug": "vrrroom",
    "i18n-storyDescription": "Vrrroom",

    "intro-text": "L’AVVENTURA DELLA REALTA VIRTUALE INCOMINCIA,<br>VIENI VERDERE COSI !",
    "intro-btn": "i18n/it-IT/start_it.png"
}

<|start_filename|>projects/vrrroom/plugins/Ranking/src/Ranking.js<|end_filename|>
var ForgePlugins = ForgePlugins || {};

ForgePlugins.Ranking = function()
{
    this._drivers = [];

    // The reference to the video, for synchronization
    this._video = null;

    // Main container of the plugin
    this._container = null;

    // Tween for the animation between
    this._tween = null;

    // The current selected driver
    this._currentDriver = null;

    // Is the plugin ready
    this._ready = false;
};

ForgePlugins.Ranking.prototype = {

    /**
     * Boot function
     */
    boot: function()
    {
        // Setup the reference to the video
        this._setupVideo();

        // Create a div containing the informations
        this._container = this.plugin.create.displayObjectContainer();
        this._container.width = this.plugin.options.width;
        this._container.height = (this.plugin.options.height * this.plugin.options.drivers.length) + (this.plugin.options.margin * (this.plugin.options.drivers.length - 1));
        this._container.top = this.plugin.options.top;
        this._container.bottom = this.plugin.options.bottom;
        this._container.left = this.plugin.options.left;
        this._container.right = this.plugin.options.right;

        // Add it to the main container
        this.plugin.container.addChild(this._container);

        // Create the elements for each drivers
        this._createDrivers();

        // Highlight the current driver
        this._higlightCurrentDriver();

        // Plugin is ready
        this._ready = true;
    },

    /**
     * Get the video to sync with, and set it as a reference for
     * synchronization.
     */
    _setupVideo: function()
    {
        if (this.plugin.options.source === "media")
        {
            this._video = this.viewer.story.scene.media.displayObject;
        }
        else if (FORGE.UID.isTypeOf(this.plugin.options.source, "Plugin") === true)
        {
            var plugin = FORGE.UID.get(this.plugin.options.source);

            if (plugin.instanceReady === true)
            {
                this._video = plugin.instance.video;
            }
            else
            {
                if (plugin.onInstanceReady.has(this._setupVideo, this) === false)
                {
                    plugin.onInstanceReady.addOnce(this._setupVideo, this);
                }
            }
        }

        if (this._video !== null && (FORGE.Utils.isTypeOf(this._video, "VideoHTML5") === true || FORGE.Utils.isTypeOf(this._video, "VideoDash") === true))
        {
            this._video.onSeeked.add(this._onSeekedHandler, this);
            this._video.onPlay.add(this._onPlayHandler, this);
            this._video.onPause.add(this._onPauseHandler, this);

            if (typeof this.plugin.persistentData.time !== "undefined" && this.plugin.options.timeSync === true)
            {
                this._video.currentTime = this.plugin.persistentData.time;
            }

            if (typeof this.plugin.persistentData.camera !== "undefined")
            {
                this.viewer.renderer.camera.yaw = this.plugin.persistentData.camera.yaw;
                this.viewer.renderer.camera.pitch = this.plugin.persistentData.camera.pitch;
                this.viewer.renderer.camera.fov = this.plugin.persistentData.camera.fov;
            }
        }
    },

    /**
     * Reset the reference to the video, and highlight the currently selected driver.
     */
    reset: function()
    {
        this._clearVideo();
        this._setupVideo();

        this._higlightCurrentDriver();
    },

    /**
     * Clear the reference to the video
     */
    _clearVideo: function()
    {
        if (this._video !== null)
        {
            this._video.onSeeked.remove(this._onSeekedHandler, this);
            this._video.onPlay.remove(this._onPlayHandler, this);
            this._video.onPause.remove(this._onPauseHandler, this);

            this._video = null;
        }
    },

    /**
     * Create the drivers
     */
    _createDrivers: function()
    {
        var driver;

        for (var i = 0, ii = this.plugin.options.drivers.length; i < ii; i++)
        {
            // Driver object
            driver = {
                container: null,
                infoContainer: null,
                name: null,
                position: null,
                thumbContainer: null,
                type: null,
                video: null,
                image: null,
                tween: null,
                timeline: null,
                keyframes: this.plugin.options.drivers[i].keyframes,
                scenes: this.plugin.options.drivers[i].scenes
            }

            // Main container for a driver
            driver.container = this.plugin.create.displayObjectContainer();
            driver.container.width = this.plugin.options.width;
            driver.container.height = this.plugin.options.height;

            // Container for the informations: driver + position
            driver.infoContainer = this.plugin.create.displayObjectContainer();
            driver.infoContainer.width = 5 * this.plugin.options.width / 6;
            driver.infoContainer.height = 2 * this.plugin.options.height / 3;
            driver.infoContainer.top = this.plugin.options.height / 6;
            driver.infoContainer.left = this.plugin.options.height / 2;
            driver.infoContainer.background = "rgba(255, 255, 255, 0.4)";
            driver.container.addChild(driver.infoContainer);

            // Driver name
            driver.name = this.plugin.create.textField();
            driver.name.value = this.plugin.options.drivers[i].name;
            driver.name.color = this.plugin.options.font.color;
            driver.name.fontFamily = this.plugin.options.font.family;
            driver.name.fontSize = this.plugin.options.font.size;
            driver.name.left = 15;
            driver.name.padding = "0px " + this.plugin.options.height / 2 + "px";
            driver.name.verticalCenter = true;
            driver.infoContainer.addChild(driver.name);

            // Driver position
            driver.position = this.plugin.create.textField();
            driver.position.value = "1st";
            driver.position.color = this.plugin.options.font.color;
            driver.position.fontFamily = this.plugin.options.font.family;
            driver.position.fontSize = this.plugin.options.font.size;
            driver.position.right = 15;
            driver.position.verticalCenter = true;
            driver.infoContainer.addChild(driver.position);

            // The container of the video
            driver.thumbContainer = this.plugin.create.displayObjectContainer();
            driver.thumbContainer.width = this.plugin.options.height;
            driver.thumbContainer.height = this.plugin.options.height;
            driver.thumbContainer.borderColor = this.plugin.options.borderColor;
            driver.thumbContainer.borderRadius = driver.thumbContainer.width / 2;
            driver.container.addChild(driver.thumbContainer);

            if (this.plugin.options.drivers[i].type === "image")
            {
                // The image (face of the driver)
                driver.image = this.plugin.create.image(this.plugin.options.drivers[i].thumb, false);
                driver.image.width = driver.thumbContainer.width - 2 * this.plugin.options.borderSize;
                driver.image.height = driver.thumbContainer.height - 2 * this.plugin.options.borderSize;
                driver.image.borderRadius = driver.thumbContainer.borderRadius;
                driver.thumbContainer.addChild(driver.image);
            }
            else
            {
                // The video (face of the driver)
                driver.video = this.plugin.create.video(this.plugin.uid + "-video-"+i, this.plugin.options.drivers[i].thumb, this.plugin.options.drivers[i].streaming, "manual");
                driver.video.width = driver.thumbContainer.width - 2 * this.plugin.options.borderSize;
                driver.video.height = driver.thumbContainer.height - 2 * this.plugin.options.borderSize;
                driver.video.borderRadius = driver.thumbContainer.borderRadius;
                driver.thumbContainer.addChild(driver.video);
            }

            // Data to save
            driver.container.data = this.plugin.options.drivers[i].scenes;

            // Event handlers
            driver.container.pointer.enabled = true;
            driver.container.pointer.cursor = "pointer";
            driver.container.pointer.onClick.add(this._driverClickHandler, this);

            // Tween when changing ranking
            driver.tween = this.plugin.create.tween(driver.container);

            // Create keyframes for synchronization with the main video
            var keyframes = [];
            for (var j = 0, jj = driver.keyframes.length, keyframe; j < jj; j++)
            {
                keyframe = new FORGE.Keyframe(driver.keyframes[j].ts, driver.keyframes[j].rank);
                keyframes.push(keyframe);
            }

            // Create a timeline for the keyframes
            driver.timeline = new FORGE.Timeline(keyframes);

            // Add all of this in the main container of the plugin
            this._container.addChild(driver.container);

            if (driver.video !== null)
            {
                // Play the video
                driver.video.volume = 0;
                driver.video.play();
            }

            // Add it to the list
            this._drivers.push(driver);
        }
    },

    /**
     * Highlight the current driver
     */
    _higlightCurrentDriver: function()
    {
        var driver;

        for (var i = 0, ii = this._drivers.length; i < ii; i++)
        {
            driver = this._drivers[i];

            for (var j = 0, jj = driver.scenes.length; j < jj; j++)
            {
                if (driver.scenes[j] === this.viewer.story.sceneUid)
                {
                    this._currentDriver = driver;
                    driver.thumbContainer.borderWidth = this.plugin.options.borderSize;
                    if (driver.video === null)
                    {
                        driver.image.top = 0;
                        driver.image.left = 0;
                    }
                    else
                    {
                        driver.video.top = 0;
                        driver.video.left = 0;
                    }
                    break;
                }
                else
                {
                    driver.thumbContainer.borderWidth = 0;
                    if (driver.video === null)
                    {
                        driver.image.top = this.plugin.options.borderSize;
                        driver.image.left = this.plugin.options.borderSize;
                    }
                    else
                    {
                        driver.video.top = this.plugin.options.borderSize;
                        driver.video.left = this.plugin.options.borderSize;
                    }
                }
            }
        }
    },

    /**
     * Handle the click on a driver to change the current view.
     */
    _driverClickHandler: function(event)
    {
        if (this._video !== null && this.plugin.options.timeSync === true)
        {
            this.plugin.persistentData.time = this._video.currentTime;
        }

        this.plugin.persistentData.camera = {
            yaw: this.viewer.renderer.camera.yaw,
            pitch: this.viewer.renderer.camera.pitch,
            fov: this.viewer.renderer.camera.fov
        };

        var scene = event.emitter.data;

        // If array, there is two view
        if (Array.isArray(scene))
        {
            this.viewer.story.scene = scene[this._currentDriver.scenes.indexOf(this.viewer.story.scene.uid)];
        }
        else
        {
            this.viewer.story.scene = scene;
        }
    },

    /**
     * Synchronize the drivers frame with the current time of the main video.
     */
    _onSeekedHandler: function()
    {
        var driver;

        for (var i = 0, ii = this._drivers.length; i < ii; i++)
        {
            driver = this._drivers[i];

            var kf = driver.timeline.getKeyframes(this._video.currentTimeMS);
            if (kf !== null)
            {
                driver.keyframe = kf.previous;
                if (driver.video !== null)
                {
                    driver.video.currentTime = this._video.currentTime;
                }
            }
        }
    },

    /**
     * Play everything when the video is triggered to be played
     */
    _onPlayHandler: function()
    {
        var driver;

        for (var i = 0, ii = this._drivers.length; i < ii; i++)
        {
            driver = this._drivers[i];
            if (driver.video !== null)
            {
                driver.video.play(this._video.currentTime);
            }
        }
    },

    /**
     * Pause everything when the video is paused
     */
    _onPauseHandler: function()
    {
        var driver;

        for (var i = 0, ii = this._drivers.length; i < ii; i++)
        {
            driver = this._drivers[i];
            if (driver.video !== null)
            {
                driver.video.pause();
            }
        }
    },

    /**
     * Update the synchronization with the main video, and update the position
     * of the driver if necessary.
     */
    update: function()
    {
        if (this._ready === false || this._video === null)
        {
            return;
        }

        var driver;

        for (var i = 0, ii = this._drivers.length; i < ii; i++)
        {
            driver = this._drivers[i];

            // Get the current keyframe
            var kf = driver.timeline.getKeyframes(this._video.currentTimeMS);
            if (kf !== null)
            {
                var keyframe = kf.previous;

                if (keyframe !== driver.keyframe)
                {
                    // Update the current keyframe
                    driver.keyframe = keyframe;

                    // Compute and move the player to its position on the leaderboard
                    var top = driver.keyframe.data * this.plugin.options.height + driver.keyframe.data * this.plugin.options.margin;

                    driver.tween.to(
                    {
                        top: top
                    }, 350).start();

                    // Get the text displaying the position of the driver
                    var pos = (driver.keyframe.data + 1).toString(), suffix = null;
                    switch (pos.charAt(pos.length - 1))
                    {
                        case "1":
                            suffix = "st";
                            break;
                        case "2":
                            suffix = "nd";
                            break;
                        case "3":
                            suffix = "rd";
                            break;
                        default:
                            suffix = "th";
                            break;
                    }

                    driver.position.value = pos + suffix;
                }

                // Update the synchronization
                if (driver.video !== null && Math.abs(this._video.currentTimeMS - driver.video.currentTimeMS) > 200)
                {
                    driver.video.currentTime = this._video.currentTime;
                }
            }
        }
    },

    /**
     * Destroy routine
     */
    destroy: function()
    {
        for (var i = 0, ii = this._drivers.length; i < ii; i++)
        {
            this._drivers[i] = null;
        }
        this._drivers = null;

        this._video = null;
        this._container = null;
        this._tween = null;
        this._currentDriver = null;
    }

};

<|start_filename|>projects/multiple-locales/i18n/fr-FR/locale.json<|end_filename|>
{
    "pano-welcome": "Bienvenue dans la forêt !",
    "pano-instructions": "Cliquer et bouger la souris pour se déplacer dans le panorama."
}

<|start_filename|>projects/vrrroom/plugins/KartingDriverSpot/src/KartingDriverSpot.js<|end_filename|>
var ForgePlugins = ForgePlugins || {};

/**
 * This plugin is a custom one for the VROOM project, displaying on top of a
 * driver (in the selection screen) information about him.
 */
ForgePlugins.KartingDriverSpot = function()
{
    // Information will be drawn in a canvas
    this._canvas = null;

    // Color of the text; the setTextColor method can interact with this value
    this._textColor = "#fff";
};

ForgePlugins.KartingDriverSpot.prototype = {

    /**
     * The boot function
     */
    boot: function()
    {
        // Create a FORGE.Canvas instance
        this._canvas = this.plugin.create.canvas();

        // Set properties to canvas
        this._canvas.width = this.plugin.options.width;
        this._canvas.height = this.plugin.options.height;
        this._canvas.top = this.plugin.options.top;
        this._canvas.right = this.plugin.options.right;
        this._canvas.bottom = this.plugin.options.bottom;
        this._canvas.left = this.plugin.options.left;
        this._canvas.horizontalCenter = this.plugin.options.horizontalCenter;
        this._canvas.verticalCenter = this.plugin.options.verticalCenter;
        this._canvas.background = this.plugin.options.background;

        // set default options
        this._textColor = this.plugin.options.color || this._textColor;

        // trick for Edge / Internet Explorer
        // Issue #8167417 https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8167417/
        this._canvas.dom.style.display = "none";

        this.plugin.notifyInstanceReady();
    },

    /**
     * Redraw every frame the informations. We are obliged to do that, as it is
     * the only way to update the color of the font on hovering.
     */
    update: function()
    {
        var ctx = this._canvas.context2D;
        var x = this._canvas.dom.width / 2;
        var y = this._canvas.dom.height / 2;

        // Clear the previous informations
        ctx.clearRect(0, 0, this._canvas.dom.width, this._canvas.dom.height);

        // Draw the rectangle containing the informations
        ctx.fillStyle = this.plugin.options.background;
        ctx.fillRect(0, 0, this._canvas.dom.width, this._canvas.dom.height);

        // General font style
        ctx.fillStyle = this._textColor;
        ctx.textAlign = 'center';
        ctx.lineWidth = 1;
        ctx.strokeStyle = "#000";

        // Name of the driver
        ctx.font = "35px " + this.plugin.options.nameFontFamily;
        ctx.fillText(this.plugin.options.name, x, 40);

        // Height and weight of the driver
        ctx.font = "20px " + this.plugin.options.infoFontFamily;
        ctx.fillText(this.plugin.options.size, x, 70);
        ctx.fillText(this.plugin.options.weight, x, 90);

        // trick for Edge / Internet Explorer
        // Issue #8167417 https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8167417/
        if (FORGE.Device.browser === "edge" || FORGE.Device.browser === "internetexplorer")
        {
            document.body.appendChild(this._canvas.dom);
        }
    },

    /**
     * Placeholder, do nothing
     */
    reset: function() {},

    /**
     * Show the informations
     */
    show: function()
    {
        this._canvas.show();
    },

    /**
     * Hide the informations
     */
    hide: function()
    {
        this._canvas.hide();
    },

    /**
     * Change the color of the text
     */
    setTextColor: function(value)
    {
        this._textColor = value;
    },

    /**
     * Destroy the canvas
     */
    destroy: function()
    {
        this._canvas = null;
    }
};

/**
 * Return the canvas, to use it with a parallax effect.
 */
Object.defineProperty(ForgePlugins.KartingDriverSpot.prototype, "texture",
{
    get: function()
    {
        return this._canvas;
    }
})

<|start_filename|>projects/vrrroom/styles/style.css<|end_filename|>

@font-face
{
    font-family: dosislight;
    src: url("https://cdn.forgejs.org/samples/vrrroom/fonts/Dosis-Light.ttf");
}

@font-face
{
    font-family: dosisbold;
    src: url("https://cdn.forgejs.org/samples/vrrroom/fonts/Dosis-Bold.ttf");
}

/**
 * Gooey Menu
 */
.menu-i18n.menu-item-inactive
{
  background-image: url('https://cdn.forgejs.org/samples/vrrroom/assets/gooeymenu/menu-i18n-white.png');
}
.menu-i18n.menu-item-active
{
  background-image: url('https://cdn.forgejs.org/samples/vrrroom/assets/gooeymenu/menu-i18n-blue.png');
}

.menu-map.menu-item-inactive
{
  background-image: url('https://cdn.forgejs.org/samples/vrrroom/assets/gooeymenu/menu-map-white.png');
}
.menu-map.menu-item-active
{
  background-image: url('https://cdn.forgejs.org/samples/vrrroom/assets/gooeymenu/menu-map-blue.png');
}

.menu-rank.menu-item-inactive
{
  background-image: url('https://cdn.forgejs.org/samples/vrrroom/assets/gooeymenu/menu-rank-white.png');
}
.menu-rank.menu-item-active
{
  background-image: url('https://cdn.forgejs.org/samples/vrrroom/assets/gooeymenu/menu-rank-blue.png');
}

.menu-cinema.menu-item-inactive
{
  background-image: url('https://cdn.forgejs.org/samples/vrrroom/assets/gooeymenu/menu-cinema-white.png');
}
.menu-cinema.menu-item-active
{
  background-image: url('https://cdn.forgejs.org/samples/vrrroom/assets/gooeymenu/menu-cinema-blue.png');
}

.menu-heart.menu-item-inactive
{
  background-image: url('https://cdn.forgejs.org/samples/vrrroom/assets/gooeymenu/menu-heart-white.png');
}
.menu-heart.menu-item-active
{
  background-image: url('https://cdn.forgejs.org/samples/vrrroom/assets/gooeymenu/menu-heart-blue.png');
}

.menu-home.menu-item-inactive
{
  background-image: url('https://cdn.forgejs.org/samples/vrrroom/assets/gooeymenu/menu-home-white.png');
}
.menu-home.menu-item-active
{
  background-image: url('https://cdn.forgejs.org/samples/vrrroom/assets/gooeymenu/menu-home-blue.png');
}

.menu-music.menu-item-inactive
{
  background-image: url('https://cdn.forgejs.org/samples/vrrroom/assets/gooeymenu/menu-music-white.png');
}
.menu-music.menu-item-active
{
  background-image: url('https://cdn.forgejs.org/samples/vrrroom/assets/gooeymenu/menu-music-blue.png');
}

<|start_filename|>projects/vrrroom/i18n/en-US/locale.json<|end_filename|>
{
    "i18n-storyName": "Vrrroom",
    "i18n-storySlug": "vrrroom",
    "i18n-storyDescription": "Vrrroom",

    "intro-text": "THE VR JOURNEY IS BEGINNING,<br>COME ON AND START THE RACE!",
    "intro-btn": "i18n/en-US/start_en.png"
}

<|start_filename|>projects/vrrroom/plugins/Letterbox/src/Letterbox.js<|end_filename|>
var ForgePlugins = ForgePlugins || {};

/**
 * Plugin allowing a letterboxing effect on a scene.
 *
 * From wikipedia: Letterboxing is the practice of transferring film shot in a
 * widescreen aspect ratio to standard-width video formats while preserving the
 * film's original aspect ratio.
 *
 * Here, we're only adding black bars.
 */
ForgePlugins.Letterbox = function()
{
    this._stripeTop = null;
    this._stripeBottom = null;
};

ForgePlugins.Letterbox.prototype = {

    /**
     * The boot function, instantiate two black display object and position them
     * on the correct side of the scene.
     */
    boot: function()
    {
        // The top stripe
        this._stripeTop = this.plugin.create.displayObject();
        this._stripeTop.width = "100%";
        this._stripeTop.height = this.plugin.options.stripeHeight;
        this._stripeTop.top = 0;
        this._stripeTop.background = this.plugin.options.stripeColor;
        this._stripeTop.pointer.enabled = false;

        // The bottom stripe
        this._stripeBottom = this.plugin.create.displayObject();
        this._stripeBottom.width = "100%";
        this._stripeBottom.height = this.plugin.options.stripeHeight;
        this._stripeBottom.bottom = 0;
        this._stripeBottom.background = this.plugin.options.stripeColor;
        this._stripeBottom.pointer.enabled = false;

        // Add each stripe to the main container
        this.viewer.container.addChild(this._stripeTop);
        this.viewer.container.addChild(this._stripeBottom);
    },

    /**
     * Placeholder, do nothing
     */
    update: function() {},

    /**
     * Placeholder, do nothing
     */
    reset: function() {},

    /**
     * Destroy the two stripes.
     */
    destroy: function()
    {
        this._stripeTop = null;
        this._stripeBottom = null;
    }
};

<|start_filename|>projects/telemetry/src/TelemetryModule.js<|end_filename|>
 FORGE.TelemetryModule = function(viewer, config)
{
    this._viewer = viewer;

    this._config = config;

    this._displayMode = 2;
};

FORGE.TelemetryModule.displayModes =
{
    NONE: 0,
    SCREEN: 1,
    SPHERE: 2
};

FORGE.TelemetryModule.prototype._setDisplayMode = function(value)
{
    this._displayMode = Number(value);

    switch(this._displayMode)
    {
        case FORGE.TelemetryModule.displayModes.NONE:
            this._displayNone();
            break;

        case FORGE.TelemetryModule.displayModes.SCREEN:
            this._displayScreen();
            break;

        case FORGE.TelemetryModule.displayModes.SPHERE:
            this._displaySphere();
            break;
    }
};

FORGE.TelemetryModule.prototype._displayNone = function()
{
    var plugin = FORGE.UID.get(this._config.plugin);
    plugin.instance.hide();

    var hotspot = FORGE.UID.get(this._config.hotspot);
    hotspot.visible = false;
};

FORGE.TelemetryModule.prototype._displayScreen = function()
{
    var plugin = FORGE.UID.get(this._config.plugin);
    plugin.instance.show();

    var hotspot = FORGE.UID.get(this._config.hotspot);
    hotspot.visible = false;
};

FORGE.TelemetryModule.prototype._displaySphere = function()
{
    var plugin = FORGE.UID.get(this._config.plugin);
    plugin.instance.hide();

    var hotspot = FORGE.UID.get(this._config.hotspot);
    hotspot.visible = true;
};


Object.defineProperty(FORGE.TelemetryModule.prototype, "name",
{
    get: function()
    {
        return this._config.name;
    }
});


Object.defineProperty(FORGE.TelemetryModule.prototype, "displayMode",
{
    get: function()
    {
        return this._displayMode;
    },

    set: function(value)
    {
        this._setDisplayMode(value);
    }
});





<|start_filename|>projects/vrrroom/plugins/Heartbeat/src/Heartbeat.js<|end_filename|>
var ForgePlugins = ForgePlugins || {};

/**
 * This plugin displays a simple heartbeat meter on the screen, indicating the
 * speed of the vehicule the camera is in.
 */
ForgePlugins.Heartbeat = function()
{
    // The reference to the video, for synchronization
    this._video = null;

    // Is the plugin ready for update
    this._ready = true;

    // Main container of the plugin
    this._container = null;

    // The FORGE.Sprite containing the animation for the heart
    this._heartbeat = null;

    // The FORGE.TextField containing the beating value
    this._heartbeatPulse = null;

    // Timeline for sync
    this._timeline = null;

    // Current keyframe
    this._keyframe = null;
};

ForgePlugins.Heartbeat.prototype = {

    /**
     * The boot function
     */
    boot: function()
    {
        // Setup the reference to the video
        this._setupVideo();

        // Create a timeline
        this._timeline = new FORGE.Timeline();

        // Create a div containing the informations
        this._container = this.plugin.create.displayObjectContainer();
        this._container.width = this.plugin.options.width;
        this._container.height = this.plugin.options.height;
        this._container.top = this.plugin.options.top;
        this._container.left = this.plugin.options.left;
        this._container.right = this.plugin.options.right;
        this._container.bottom = this.plugin.options.bottom;
        this._container.background = this.plugin.options.background;

        // Add it to the main container
        this.plugin.container.addChild(this._container);

        // Create the elements composing the heartbeat meter (one textfield and an image)
        this._createHeartbeat();

        // Get the keyframes for sync
        this._loadKeyframes();
    },

    /**
     * Get the video to sync with, and set it as a reference for
     * synchronization.
     */
    _setupVideo: function(video)
    {
        if (this.plugin.options.source === "media")
        {
            this._video = this.viewer.story.scene.media.displayObject;
        }
        else if (FORGE.UID.isTypeOf(this.plugin.options.source, "Plugin") === true)
        {
            var plugin = FORGE.UID.get(this.plugin.options.source);

            if (plugin.instanceReady === true)
            {
                this._video = plugin.instance.video;
            }
            else
            {
                if (plugin.onInstanceReady.has(this._setupVideo, this) === false)
                {
                    plugin.onInstanceReady.addOnce(this._setupVideo, this);
                }
            }
        }

        if (FORGE.Utils.isTypeOf(this._video, "VideoHTML5") === true || FORGE.Utils.isTypeOf(this._video, "VideoDash") === true)
        {
            this._video.onSeeked.add(this._onSeekedHandler, this);
            this._video.onPlay.add(this._onPlayHandler, this);
            this._video.onPause.add(this._onPauseHandler, this);
        }
    },

    /**
     * Remove the video reference and its event handlers
     */
    _clearVideo: function()
    {
        if (this._video !== null)
        {
            this._video.onSeeked.remove(this._onSeekedHandler, this);
            this._video.onPlay.remove(this._onPlayHandler, this);
            this._video.onPause.remove(this._onPauseHandler, this);
        }

        this._video = null;
    },

    /**
     * Create the textfield and image containing informations about the heartbeat
     */
    _createHeartbeat: function()
    {
        var config = {
            "key": "heartbeat",
            "url": this.plugin.options.urlIcon,
            "frames": this.plugin.options.urlAnimation
        };

        this._heartbeat = this.plugin.create.sprite(config);
        this._heartbeat.animations.add("pulse", 0, this.plugin.options.frames, 60, true);
        this._heartbeat.animations.stop();
        this._heartbeat.width = this._container.width;
        this._heartbeat.height = this._container.height;
        this._heartbeat.right = 5;
        this._heartbeat.verticalCenter = true;
        this._container.addChild(this._heartbeat);

        this._heartbeatPulse = this.plugin.create.textField();
        this._heartbeatPulse.value = "";
        this._heartbeatPulse.left = 15;
        this._heartbeatPulse.verticalCenter = true;
        this._heartbeatPulse.color = this.plugin.options.font.color;
        this._heartbeatPulse.fontSize = this.plugin.options.font.size;
        this._heartbeatPulse.fontFamily = this.plugin.options.font.family;
        this._container.addChild(this._heartbeatPulse);
    },

    /**
     * Create the timeline given the keyframes
     * @param {Object} data - the data containing the keyframes
     */
    _createTimeline: function(data)
    {
        var keyframes = [];
        var keyframe;
        for (var i = 0, ii = data.length; i < ii; i++)
        {
            keyframe = new FORGE.Keyframe(data[i].ts, data[i].data);
            keyframes.push(keyframe);
        }

        this._timeline.keyframes = keyframes;

        this._ready = true;
        this.plugin.notifyInstanceReady();
    },

    /**
     * Load the keyframes from the json file, or directly from the json configuration
     */
    _loadKeyframes: function()
    {
        if (typeof this.plugin.data.keyframes === "object")
        {
            this._createTimeline(this.plugin.data.keyframes);
        }
        else if (typeof this.plugin.data.keyframes)
        {
            this.viewer.load.json(this.plugin.uid + "-keyframes", this.plugin.data.keyframes, this._keyframesLoadCompleteHandler, this);
        }
    },

    /**
     * Keyframe load complete handler
     */
    _keyframesLoadCompleteHandler: function(event)
    {
        if (event.data !== null && typeof event.data.keyframes === "object")
        {
            this._createTimeline(event.data.keyframes);
        }
    },

    /**
     * Handler when the video is seeked, so the heartbeat can be sync with
     */
    _onSeekedHandler: function()
    {
        this._currentTime = this._video.currentTime;
    },

    /**
     * Handler when the video is played, so the heart can be played too
     */
    _onPlayHandler: function()
    {
        if (this._heartbeat.loaded === false)
        {
            this._heartbeat.onLoadComplete.addOnce(this._heartbeatLoadCompleteHandler, this);
            return;
        }

        if (this._heartbeat.animations.current.paused === true)
        {
            this._heartbeat.resume();
        }
        else
        {
            this._heartbeat.play("pulse", true);
        }
    },

    /**
     * Handler when the video is paused, so the heartbeat can be paused too
     */
    _onPauseHandler: function()
    {
        if (this._heartbeat.animations.current.playing === true)
        {
            this._heartbeat.pause();
        }
    },

    /**
     * Heartbeat load complete handler
     */
    _heartbeatLoadCompleteHandler: function()
    {
        if (this._video !== null && this._video.playing === true)
        {
            this._heartbeat.play("pulse", true);
        }
    },

    /**
     * Reset the entire plugin, on scene change. Reload everything, useful if
     * the driver is changing.
     */
    reset: function()
    {
        this._ready = false;

        this._clearVideo();
        this._setupVideo();

        this._loadKeyframes();
    },

    /**
     * Update if needed the displayed value, if the next jeyframe is attained.
     */
    update: function()
    {
        if (this._heartbeat.loaded === false || this._video === null || this._ready === false || this._video.playing === false || (this._video.duration - this._video.currentTime) <= 0)
        {
            return;
        }

        var kf = this._timeline.getKeyframes(this._video.currentTimeMS);
        if (kf !== null)
        {
            var keyframe = kf.previous;

            if (typeof keyframe !== "undefined" && keyframe !== this._keyframe)
            {
                this._keyframe = keyframe;

                //pulse value
                this._heartbeatPulse.value = this._keyframe.data;

                var totalFrames = this._heartbeat.animations.current.frames.length;
                var frameRate = Math.round((this._keyframe.data * totalFrames) / 60);

                this._heartbeat.animations.current.frameRate = frameRate;
            }
        }
    },

    /**
     * Destroy routine
     */
    destroy: function()
    {
        this._video = null;
        this._container = null;
        this._heartbeat = null;
        this._heartbeatPulse = null;
        this._timeline = null;
        this._keyframe = null;
    }

};

<|start_filename|>projects/immersive-and-narrative/plugins/MediaEvents/src/MediaEvents.js<|end_filename|>
var ForgePlugins = ForgePlugins || {};

ForgePlugins.MediaEvents = function()
{
    this._video = null;
};

ForgePlugins.MediaEvents.prototype =
{
    boot: function()
    {
        this._setupVideo();
    },

    reset: function()
    {
        this._clearVideo();
        this._setupVideo();
    },

    _setupVideo: function()
    {
        this._video = this.viewer.story.scene.media.displayObject;
        this._video.onEnded.add(this._onEndedHandler, this);
    },

    _clearVideo: function()
    {
        if(this._video !== null)
        {
            this._video.onEnded.remove(this._onEndedHandler, this);
        }

        this._video = null;
    },

    _onEndedHandler: function()
    {
        this.plugin.events.onEnded.dispatch();
    },

    destroy: function()
    {
        this._clearVideo();
    }
};

<|start_filename|>package.json<|end_filename|>
{
  "name": "forgejs-samples",
  "version": "0.9.6",
  "description": "A collection of samples to demonstrate the capabilities of ForgeJS",
  "author": "GoPro, Inc.",
  "license": "Apache-2.0",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/gopro/forgejs-samples.git"
  },
  "keywords": [
    "forge",
    "forgejs",
    "webvr",
    "vr",
    "gopro",
    "kolor"
  ]
}


<|start_filename|>projects/vrrroom/plugins/ChromaKey/src/ChromaKeyAuthoring.js<|end_filename|>
/**
 * All code authoring and preview related is present here.
 */

/**
 * Update preview canvas
 */
ForgePlugins.ChromaKey.prototype.updatePreview = function()
{
    if (this._authoring === false || this._canvasReady === false || this._ui.previewCanvas.context2D === null || this._datGUIControls.showPreview === false || this._canvasDst === null || this._canvasSrc === null)
    {
        return;
    }

    // if not visible, add to the bottom of the plugin and draw content
    // otherwise
    if (this._ui.previewCanvas.visible === false)
    {
        this.showPreviewCanvas();
    }

    // this._log("update preview");
    var source = this._datGUIControls.bypass === true ? this._canvasSrc : this._canvasDst;
    var ctx = this._ui.previewCanvas.context2D;

    // Clear, apply background pattern (chessboard) and draw new frame
    ctx.clearRect(0, 0, this._ui.previewCanvas.pixelWidth, this._ui.previewCanvas.pixelHeight);
    ctx.fillRect(0, 0, this._ui.previewCanvas.pixelWidth, this._ui.previewCanvas.pixelHeight);

    ctx.drawImage(source.dom,
        0, 0, source.pixelWidth, source.pixelHeight, 0, 0,
        this._ui.previewCanvas.pixelWidth, this._ui.previewCanvas.pixelHeight);
}


/**
 * Has preview
 * @return {Boolean} true if plugin has a preview canvas
 */
ForgePlugins.ChromaKey.prototype.hasPreview = function()
{
    return this._ui.previewCanvas.visible;
}

/**
 * Show preview canvas
 */
ForgePlugins.ChromaKey.prototype.showPreview = function()
{
    this._ui.previewCanvas.visible = true;
    this._autoResizeContainer();
}

/**
 * Hide preview canvas
 */
ForgePlugins.ChromaKey.prototype.hidePreview = function()
{
    this._ui.previewCanvas.visible = false;
    this._autoResizeContainer();
}

/**
 * Toggle play/pause
 */
ForgePlugins.ChromaKey.prototype.togglePlayPauseAuthoring = function()
{
    var datGuiController = null;
    for (var i = 0, ii = this._datGUI.__controllers.length; i < ii; i++)
    {
        var controller = this._datGUI.__controllers[i];
        if (controller.property === "togglePlayPause")
        {
            datGuiController = controller;
            break;
        }
    }

    if (this._media.playing)
    {
        if (datGuiController !== null)
        {
            datGuiController.__li.innerText = "Pause";
        }
    }
    else
    {
        if (datGuiController !== null)
        {
            datGuiController.__li.innerText = "Play";
        }
    }
}


/**
 * Init GUI objects
 * @private
 */
ForgePlugins.ChromaKey.prototype._initGUI = function()
{
    this._log("init gui");

    this._ui = {};
    this._ui.container = this.plugin.create.displayObjectContainer();
    this.plugin.container.addChild(this._ui.container);

    this._ui.container.width = this.plugin.options.width;
    this._ui.container.height = this.plugin.options.height || "800px";
    this._ui.container.top = this.plugin.options.top;
    this._ui.container.right = this.plugin.options.right;
    this._ui.container.borderRadius = 3;
    this._ui.container.pointer.enabled = true;
    this._ui.container.drag.enabled = true;

    this._ui.header = this.plugin.create.textField();
    this._ui.container.addChild(this._ui.header);
    this._ui.header.width = "100%";
    this._ui.header.height = this._ui.header.lineHeight = 30;
    this._ui.header.top = 0;
    this._ui.header.value = this.plugin.options.title;
    this._ui.header.background = "#f7f7f7";
    this._ui.header.color = "#525252";
    this._ui.header.fontFamily = "Consolas Arial";
    this._ui.header.textAlign = "center";
    this._ui.header.value = "Chromakey authoring";

    this._ui.body = this.plugin.create.displayObjectContainer();
    this._ui.container.addChild(this._ui.body);
    this._ui.body.width = "100%";
    this._ui.body.height = this._ui.container.pixelHeight - this._ui.header.pixelHeight;
    this._ui.body.top = this._ui.header.height;
    this._ui.body.pointer.enabled = true;

    this._ui.datguiContainer = this.plugin.create.displayObjectContainer();
    this._ui.body.addChild(this._ui.datguiContainer);
    this._ui.datguiContainer.width = "100%";
    this._ui.datguiContainer.height = 140
    this._ui.datguiContainer.background = "black";
    this._ui.datguiContainer.pointer.enabled = true;

    this._ui.previewCanvas = this.plugin.create.canvas();
    this._ui.body.addChild(this._ui.previewCanvas);

    this._initDatGui();
}

/**
 * Destroy the GUI related stuff
 */
ForgePlugins.ChromaKey.prototype._destroyGUI = function()
{
    if (this._ui.datguiContainer !== null)
    {
        this._ui.datguiContainer.empty(true);
        this._ui.datguiContainer.destroy();
        this._ui.datguiContainer = null;
    }

    if (this._ui.header !== null)
    {
        this._ui.header.destroy();
        this._ui.header = null;
    }

    if (this._ui.previewChessboard !== null)
    {
        this._ui.previewChessboard.destroy();
        this._ui.previewChessboard = null;
    }

    if (this._ui.previewCanvas !== null)
    {
        this._ui.previewCanvas.destroy();
        this._ui.previewCanvas = null;
    }

    if (this._ui.body !== null)
    {
        this._ui.body.empty(true);
        this._ui.body.destroy();
        this._ui.body = null;
    }

    if (this._ui.container !== null)
    {
        this._ui.container.empty(true);
        this._ui.container.destroy();
        this._ui.container = null;
    }

    this._ui = null;
}

/**
 * Autoresize UI container
 * Called when showing or hiding preview elements
 * @private
 */
ForgePlugins.ChromaKey.prototype._autoResizeContainer = function()
{
    if (this._ui === null)
    {
        return;
    }

    this._ui.body.height = this._ui.datguiContainer.pixelHeight;
    if (this._authoring === true && this.hasPreview())
    {
        this._ui.body.height += this._ui.previewCanvas.pixelHeight;
    }
    this._ui.container.height = this._ui.header.pixelHeight + this._ui.body.pixelHeight;
}

/**
 * Preview canvas background image loaded event handler
 * @param {Event} event event object
 * @private
 */
ForgePlugins.ChromaKey.prototype._onChessboardImageLoaded = function(event)
{
    this._log("Preview canvas chessboard image loaded");
    var ctx = this._ui.previewCanvas.context2D;
    ctx.fillStyle = ctx.createPattern(event.emitter.element, 'repeat');

    this.updatePreview();
}

/**
 * Init dat.gui objects
 * @private
 */
ForgePlugins.ChromaKey.prototype._deinitDatGui = function()
{
    if (this._datGUI === null)
    {
        return;
    }

    // Clear listeners
    while (this._datGUI.__listening.length > 0) 
    {
        this._datGUI.__listening.pop();
    }

    // Clear controllers
    while (this._datGUI.__controllers.length > 0) 
    {
        var idx = this._datGUI.__controllers.length - 1;
        this._datGUI.remove(this._datGUI.__controllers[idx]);
    }

    this._datGUI = null;

    if (this._datGUIControls !== null)
    {
        this._datGUIControls.destroy();
        this._datGUIControls = null;
    }
}

/**
 * Init dat.gui objects
 * @private
 */
ForgePlugins.ChromaKey.prototype._initDatGui = function()
{
    this._datGUI = new dat.GUI(
    {
        width: this._ui.datguiContainer.pixelWidth,
        autoPlace: false
    });

    // Append dat.gui dom element to the plugin container and remove close button
    this._ui.datguiContainer.dom.appendChild(this._datGUI.domElement);
    var datCloseButton = document.querySelector(".dg .close-button");
    datCloseButton.parentNode.removeChild(datCloseButton);

    // Populate with settings
    this._datGUI.add(this._datGUIControls, "showPreview").name("Preview").listen();
    this._datGUI.add(this._datGUIControls, "bypass").name("Bypass").listen();
    this._datGUI.add(this._datGUIControls, "threshold", 0, 100).name("Threshold").listen();
    this._datGUI.add(this._datGUIControls, "smoothness", 0, 100).name("Smoothness").listen();
    this._datGUI.add(this._datGUIControls, "togglePlayPause").name("Play");

    // GUI defaults
    this._datGUIControls.showCanvas = true;
    this._datGUIControls.bypass = false;
}

/**
 * Setup canvas objects
 * @private
 */
ForgePlugins.ChromaKey.prototype._setupCanvasAuthoring = function(size)
{
    var videoRatio = size.width / size.height;

    var w, h;
    if (videoRatio >= 1)
    {
        w = this._ui.body.pixelWidth;
        h = w / videoRatio;
    }
    else
    {
        h = this._ui.body.pixelHeight;
        w = h * videoRatio;
    }

    this._ui.previewCanvas.width = Math.floor(w) + "px";
    this._ui.previewCanvas.height = Math.floor(h) + "px";
    this._ui.previewCanvas.top = this._ui.datguiContainer.height;

    this._log("Create preview background image");
    this._ui.previewChessboard = this.plugin.create.image(
    {
        key: "chessboard-background",
        url: "assets/chessboard.png",
        i18n: false
    });
    this._ui.previewChessboard.onLoadComplete.addOnce(this._onChessboardImageLoaded, this);
}

<|start_filename|>projects/vrrroom/plugins/GooeyMenu/i18n/fr-FR/locale.json<|end_filename|>
{
    "gooeymenu-item-0": "Changer la piste audio",
    "gooeymenu-item-1": "Afficher / masquer le classement",
    "gooeymenu-item-2": "Afficher / masquer la carte",
    "gooeymenu-item-3": "Afficher / masquer les stats",
    "gooeymenu-item-4": "Mode cinéma",
    "gooeymenu-item-5": "Retour accueil"
}

<|start_filename|>projects/vrrroom/plugins/Speedometer/src/Speedometer.js<|end_filename|>
var ForgePlugins = ForgePlugins || {};

/**
 * This plugin displays a simple speedometer on the screen, indicating the speed
 * of the vehicule the camera is in.
 */
ForgePlugins.Speedometer = function()
{
    // The reference to the video, for synchronization
    this._video = null;

    // Is the plugin ready for update
    this._ready = false;

    // Main container of the plugin
    this._container = null;

    // Label for the speed
    this._speedLabel = null;

    // Unit (kmh, mph...)
    this._unitLabel = null;

    // Timeline for sync
    this._timeline = null;

    // Current keyframe
    this._keyframe = null;
};

ForgePlugins.Speedometer.prototype = {

    /**
     * The boot function
     */
    boot: function()
    {
        // Setup the reference to the video
        this._setupVideo();

        // Create a timeline
        this._timeline = new FORGE.Timeline();

        // Create a div containing the informations
        this._container = this.plugin.create.displayObjectContainer();
        this._container.width = this.plugin.options.width;
        this._container.height = this.plugin.options.height;
        this._container.top = this.plugin.options.top;
        this._container.left = this.plugin.options.left;
        this._container.right = this.plugin.options.right;
        this._container.bottom = this.plugin.options.bottom;
        this._container.background = this.plugin.options.background;

        // Add it to the main container
        this.plugin.container.addChild(this._container);

        // Create the elements composing the speedometer (two textfield)
        this._createSpeedometer();

        // Get the keyframes for sync
        this._loadKeyframes();
    },

    /**
     * Get the video to sync with, and set it as a reference for
     * synchronization.
     */
    _setupVideo: function()
    {
        if (this.plugin.options.source === "media")
        {
            this._video = this.viewer.story.scene.media.displayObject;
        }
        else if (FORGE.UID.isTypeOf(this.plugin.options.source, "Plugin") === true)
        {
            var plugin = FORGE.UID.get(this.plugin.options.source);

            if (plugin.instanceReady === true)
            {
                this._video = plugin.instance.video;
            }
            else
            {
                if (plugin.onInstanceReady.has(this._setupVideo, this) === false)
                {
                    plugin.onInstanceReady.addOnce(this._setupVideo, this);
                }
            }
        }
    },

    /**
     * Remove the video reference and its event handlers
     */
    _clearVideo: function()
    {
        this._video = null;
    },

    /**
     * Create the two textfield containing informations about the speed
     */
    _createSpeedometer: function()
    {
        this._speedLabel = this.plugin.create.textField();
        this._speedLabel.value = "0";
        this._speedLabel.left = 15;
        this._speedLabel.verticalCenter = true;
        this._speedLabel.color = this.plugin.options.font.color;
        this._speedLabel.fontSize = this.plugin.options.font.size;
        this._speedLabel.fontFamily = this.plugin.options.font.family;
        this._container.addChild(this._speedLabel);

        this._unitLabel = this.plugin.create.textField();
        this._unitLabel.value = this.plugin.options.speedUnit;
        this._unitLabel.right = 10;
        this._unitLabel.verticalCenter = true;
        this._unitLabel.color = this.plugin.options.font.color;
        this._unitLabel.fontSize = 0.5 * this.plugin.options.font.size;
        this._unitLabel.fontFamily = this.plugin.options.font.family;
        this._container.addChild(this._unitLabel);
        this._unitLabel.width = this._unitLabel.width + 3; // Compensate weird autoWidth behavior
    },

    /**
     * Create the timeline given the keyframes
     * @param {Object} data - the data containing the keyframes
     */
    _createTimeline: function(data)
    {
        var keyframes = [];
        var keyframe;

        for (var i = 0, ii = data.length; i < ii; i++)
        {
            keyframe = new FORGE.Keyframe(data[i].ts, data[i].data);
            keyframes.push(keyframe);
        }

        this._timeline.keyframes = keyframes;

        this._ready = true;
        this.plugin.notifyInstanceReady();
    },

    /**
     * Load the keyframes from the json file, or directly from the json configuration.
     */
    _loadKeyframes: function()
    {
        if (typeof this.plugin.data.keyframes === "object")
        {
            this._createTimeline(this.plugin.data.keyframes);
        }
        else if (typeof this.plugin.data.keyframes)
        {
            this.viewer.load.json(this.plugin.uid + "-keyframes", this.plugin.data.keyframes, this._keyframesLoadCompleteHandler, this);
        }
    },

    /**
     * Keyframe load complete handler
     */
    _keyframesLoadCompleteHandler: function(event)
    {
        if (event.data !== null && typeof event.data.keyframes === "object")
        {
            this._createTimeline(event.data.keyframes);
        }
    },

    /**
     * Reset the entire plugin, on scene change. Reload everything, useful if
     * the driver is changing.
     */
    reset: function()
    {
        this._ready = false;

        this._clearVideo();
        this._setupVideo();

        this._loadKeyframes();
    },

    /**
     * Update if needed the displayed value, if the next jeyframe is attained.
     */
    update: function()
    {
        if (this._ready === false || this._video.playing === false || (this._video.duration - this._video.currentTime) <= 0)
        {
            return;
        }

        var kf = this._timeline.getKeyframes(this._video.currentTimeMS);
        if (kf !== null)
        {
            var keyframe = kf.previous;

            if (keyframe !== this._keyframe)
            {
                this._keyframe = keyframe;

                this._speedLabel.value = this._keyframe.data;
            }
        }
    },

    /**
     * Destroy routine
     */
    destroy: function()
    {
        this._video = null;
        this._container = null;
        this._speedLabel = null;
        this._unitLabel = null;
        this._timeline = null;
        this._keyframe = null;
    }
};

<|start_filename|>projects/vrrroom/plugins/GooeyMenu/i18n/en-US/locale.json<|end_filename|>
{
    "gooeymenu-item-0": "Change playlist track",
    "gooeymenu-item-1": "Show / hide the ranking",
    "gooeymenu-item-2": "Show / hide the map",
    "gooeymenu-item-3": "Show / hide the stats",
    "gooeymenu-item-4": "View cinema mode",
    "gooeymenu-item-5": "Back home"
}

<|start_filename|>projects/multiple-locales/i18n/en-US/locale.json<|end_filename|>
{
    "pano-welcome": "Welcome to the forest !",
    "pano-instructions": "Click and drag your mouse to move inside the panorama."
}

<|start_filename|>projects/paris26g/plugins/LookAt/src/LookAt.js<|end_filename|>
var ForgePlugins = ForgePlugins || {};

/**
 * This plugin displays thumbnails with camera look at actions.
 */
ForgePlugins.LookAt = function()
{
    // Canvas of the plugin
    this._canvas = null;

    // Loaded data
    this._data = null;

    // points of interest data
    this._poi = null;

    // poi counter
    this._count = 0;

    // images array
    this._images = null;

    // current destination
    this._destination = null;

    // current offset yaw
    this._offsetYaw = 0;

    // current offset pitch
    this._offsetPitch = 0;

    // current offset fov
    this._offsetFov = 0;

    // current factor for animation
    this._factor = 0;
};

ForgePlugins.LookAt.TIME_REFERENCE = 1500;

ForgePlugins.LookAt.prototype = {

    /**
     * The boot function
     */
    boot: function()
    {
        // Load the JSON data
        this._loadJsonData();
    },

    /**
     * The reset function
     */
    reset: function()
    {

    },

    /**
     * Load the data from the json file
     */
    _loadJsonData: function()
    {
        var json = this.plugin.data.json;

        if (typeof json === "string" && json !== "")
        {
            this.viewer.load.json(this.plugin.uid + "_json", json, this._jsonLoadComplete.bind(this), this);
        }
        else
        {
            this.plugin.warn("Plugin LookAt can't load json data: invalid URL!");
        }
    },

    /**
     * On JSON loaded, read it and parse it
     */
    _jsonLoadComplete: function(file)
    {
        this._data = file.data;

        this._prepareCanvas();
    },

    /**
     * Update the canvas rendering
     */
    update: function()
    {

    },

    /**
     * Prepare the canvas size
     */
    _prepareCanvas: function()
    {
        if (this._data === null)
        {
            return;
        }

        this._poi = this._data.poi;
        var poiLength = this._poi.length;
        this._count = poiLength;

        var rows = Math.ceil(Math.sqrt(poiLength));
        var columns = Math.floor(Math.sqrt(poiLength));

        var margin = this.plugin.options.container.margin;

        var hMax = rows * this.plugin.options.thumbnail.width + (rows - 1) * margin;
        var vMax = columns * this.plugin.options.thumbnail.height + (columns - 1) * margin;

        // Create the canvas
        this._canvas = this.plugin.create.canvas();
        this._canvas.width = hMax + margin * 2;
        this._canvas.height = vMax + margin * 2;

        this._canvas.top = this.plugin.options.top;
        this._canvas.left = this.plugin.options.left;
        this._canvas.right = this.plugin.options.right;
        this._canvas.bottom = this.plugin.options.bottom;

        this.plugin.container.addChild(this._canvas);

        this._images = [];

        // load images
        for(var i = 0; i < poiLength; i++)
        {
            // create an image element
            var img = document.createElement('img');
            img.crossOrigin = "anonymous";

            // use common loader as we need to count files
            img.onload = this._imageLoaded.bind(this);
            img.onerror = this._imageError.bind(this);
            img.onabort = this._imageError.bind(this);

            img.src = this._poi[i].img;

            // push image onto array in the same order as file names
            this._images.push(img);
        }

        // add click listener
        this._canvas.pointer.enabled = true;
        this._canvas.pointer.cursor = "pointer";
        this._canvas.pointer.onClick.add(this._canvasClick, this);
    },

    /**
     * Canvas click handler
     */
    _canvasClick: function(e)
    {
        var elem = this._canvas.dom,
        elemLeft = elem.offsetLeft,
        elemTop = elem.offsetTop;

        var x = e.data.center.x - elemLeft;
        var y = e.data.center.y - elemTop;

        var self = this;
        this._poi.forEach(function(element)
        {
            if (y > element.top && y < element.top + element.height && x > element.left && x < element.left + element.width)
            {
                self._lookAt(element);
            }
        });
    },

    /**
     * lookAt action for a given element
     */
    _lookAt: function(elem)
    {
        this._destination = elem;

        var camera = this.viewer.camera;

        // clean current animation
        camera.animation.stop();
        camera.animation.onComplete.remove(this._midAnimation, this);
        camera.animation.onComplete.remove(this._endAnimation, this);

        // delta yaw and delta pitch
        var dy = camera.yaw - elem.yaw;
        var dp = camera.pitch - elem.pitch;

        // don't move if too little
        if (Math.abs(dy) < 0.001 && Math.abs(dp) < 0.001)
        {
            return;
        }

        // compute a factor given the distance between the two hotspots
        this._factor = Math.log(Math.sqrt(dy * dy + dp * dp));
        this._factor = (this._factor < 2) ? 1 : this._factor / 2;

        // get mid yaw/pitch/fov
        this._offsetYaw = dy / (2 * this._factor);
        this._offsetPitch = dp / (2 * this._factor);
        this._offsetFov = this._factor > 1 ? Math.min(camera.fov, elem.fov) * this._factor * this._factor : (camera.fov + elem.fov) / 2;

        camera.animation.onComplete.addOnce(this._factor > 1 ? this._midAnimation : this._endAnimation, this);
        camera.lookAt(camera.yaw - this._offsetYaw, camera.pitch - this._offsetPitch, 0, this._offsetFov, ForgePlugins.LookAt.TIME_REFERENCE, true, FORGE.EasingType.SINE_IN);
    },

    _midAnimation: function()
    {
        var camera = this.viewer.camera;
        var elem = this._destination;
        var midTime = Math.abs(camera.yaw - elem.yaw - this._offsetYaw) / this._offsetYaw * ForgePlugins.LookAt.TIME_REFERENCE;

        camera.animation.onComplete.addOnce(this._endAnimation, this);
        camera.lookAt(elem.yaw + this._offsetYaw, elem.pitch + this._offsetPitch, 0, this._offsetFov, Math.abs(midTime) / this._factor, true, FORGE.EasingType.LINEAR);
    },

    _endAnimation: function()
    {
        var camera = this.viewer.camera;
        var elem = this._destination;
        camera.lookAt(elem.yaw, elem.pitch, 0, elem.fov, ForgePlugins.LookAt.TIME_REFERENCE, true, FORGE.EasingType.SINE_OUT);
    },

    /**
     * Image loaded handler
     */
    _imageLoaded: function(e)
    {
        // for each successful load we count down
        this._count--;
        //start when all images are loaded
        if (this._count === 0) this._drawThumbs();
    },

    /**
     * Image error/aborted handler
     */
    _imageError: function(e)
    {
        // for each error or aborted load we count down
        this._count--;
        //start when all images are requested
        if (this._count === 0) this._drawThumbs(false);
    },

    /**
     * Thumbnails drawing
     */
    _drawThumbs: function(status)
    {
        var ctx = this._canvas.context2D;

        try
        {
            ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);

            ctx.fillStyle = this.plugin.options.container.color;
            ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);

            var row = 0;
            var column = 0;

            var imgWidth = this.plugin.options.thumbnail.width;
            var imgHeight = this.plugin.options.thumbnail.height;
            var margin = this.plugin.options.container.margin;

            var imagesLength = this._images.length;
            for (var i = 0; i < imagesLength; i++)
            {
                var x = row * imgWidth + row * margin + margin;
                var y = column * imgHeight + column * margin + margin;

                if (status === false)
                {
                    // black box fallback when no image
                    ctx.fillStyle = "rgba(0, 0, 0, 1.0)";
                }
                else
                {
                    //draw background image
                    ctx.drawImage(this._images[i], x, y, imgWidth, imgHeight);

                    // draw a color box over the thumb
                    ctx.fillStyle = this.plugin.options.thumbnail.color;
                }

                ctx.fillRect(x, y, imgWidth, imgHeight);

                // set next position
                if (row >= 4) //Math.ceil(Math.sqrt(this._poi.length))
                {
                    column++;
                    row = 0;
                }
                else
                {
                    row++;
                }

                // set canvas coordinates to the poi object
                this._poi[i].width = imgWidth;
                this._poi[i].height = imgHeight;
                this._poi[i].top = y;
                this._poi[i].left = x;
            };
        }
        catch (e)
        {
            // waiting next frame to get the canvas not yet created
        }
    },

    /**
     * Destroy routine
     */
    destroy: function()
    {
        this._canvas.destroy();
        this._canvas = null;

        this._poi = null;
        this._images = null;
        this._data = null;
    }
};


<|start_filename|>projects/immersive-and-narrative/styles/style.css<|end_filename|>

@font-face
{
    font-family: dosislight;
    src: url("https://cdn.forgejs.org/samples/vrrroom/fonts/Dosis-Light.ttf");
}

@font-face
{
    font-family: dosisbold;
    src: url("https://cdn.forgejs.org/samples/vrrroom/fonts/Dosis-Bold.ttf");
}

<|start_filename|>index.html<|end_filename|>
<!DOCTYPE html>
<html lang="en_US">
<head>
    <title>ForgeJS Samples</title>
    <link rel="stylesheet" href="./css/bootstrap.min.css" media="screen,projection" charset="utf-8" />
</head>
<body>
    <div class="container">

        <h2 class="display-1">ForgeJS Features Samples</h2>

        <p class="lead">Click on a project link to try it! Learn from these samples.</p>

        <table class="table table-striped table-hover">
            <thead class="thead-inverse">
                <tr>
                    <th>name</th>
                    <th>description</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><a href="projects/simple-project/" target="_blank">Simple project</a></td>
                    <td>It is the simpliest possible project : a very tiny webpage, a tiny configuration, and only one image to display. This is an example of the minimum required to run a project.</td>
                </tr>
                <tr>
                    <td><a href="projects/littleplanet/" target="_blank">Littleplanet view</a></td>
                    <td>A simple project with a little planet view. This project uses the GoPro view that is between a rectilinear and a little planet effect depending on your zoom factor.</td>
                </tr>
                <tr>
                    <td><a href="projects/view-flat/" target="_blank">Flat view</a></td>
                    <td>This project demonstrates the flat view and its options.</td>
                </tr>
                <tr>
                    <td><a href="projects/multiple-scenes/" target="_blank">Multiple scenes</a></td>
                    <td>A simple example showing how to navigate through scenes.</td>
                </tr>
                <tr>
                    <td><a href="projects/video/" target="_blank">Simple video</a></td>
                    <td>A very simple example with a video playing.</td>
                </tr>
                <tr>
                    <td><a href="projects/media-grid/" target="_blank">Media grid</a></td>
                    <td>Demonstration of media grid.</td>
                </tr>
                <tr>
                    <td><a href="projects/multiresolution/" target="_blank">Multiresolution</a></td>
                    <td>An example to demonstrate the ForgeJS ability to handle any kind of panoramic-image size through multiresolution image processing.</td>
                </tr>
                <tr>
                    <td><a href="projects/multiple-media/" target="_blank">Multiple media</a></td>
                    <td>Many scenes, each with a different media type definition.</td>
                </tr>
                <tr>
                    <td><a href="projects/multiple-locales/" target="_blank">Multiple locales</a></td>
                    <td>Using the <em>LocaleSelector</em> plugin, it is possible to switch between the multiple locales present in this project.</td>
                </tr>
                <tr>
                    <td><a href="projects/change-view/" target="_blank">Changing the view</a></td>
                    <td>Change the view in realtime, going to Rectilinear to Little Planet, without stopping the playing video and without changing the orientation of the camera.</td>
                </tr>
                <tr>
                    <td><a href="projects/special-effects/" target="_blank">Special effects</a></td>
                    <td>Apply a special effect on a scene, and toggle it to view how it affects the scene.</td>
                </tr>
                <tr>
                    <td><a href="projects/audio-playlists/" target="_blank">Audio playlists</a></td>
                    <td>Using playlists, it's possible to apply a background sound track to each scene.</td>
                </tr>
                <tr>
                    <td><a href="projects/ambisonic/" target="_blank">Ambisonics</a></td>
                    <td>Using first-order ambisonics into a scene.</td>
                </tr>
                <tr>
                    <td><a href="projects/directors-cut/" target="_blank">Director's cut</a></td>
                    <td>Change the orientation of the camera in realtime according to waypoints.</td>
                </tr>
                <tr>
                    <td><a href="projects/scene-sync/" target="_blank">Scene synchronization</a></td>
                    <td>Navigate through videos and while keeping the video current time.</td>
                </tr>
                <tr>
                    <td><a href="projects/mpeg-dash/" target="_blank">MPEG-DASH videos</a></td>
                    <td>Video player based on MPEG-DASH streaming technology.</td>
                </tr>
                <tr>
                    <td><a href="projects/hotspots/" target="_blank">Hotspots</a></td>
                    <td>Add hotspots to a scene.</td>
                </tr>
                <tr>
                    <td><a href="projects/hotspots-actions/" target="_blank">Hotspots with actions</a></td>
                    <td>Add actions to hotspots.</td>
                </tr>
                <tr>
                    <td><a href="projects/hotspots-animation/" target="_blank">Moving hotspots</a></td>
                    <td>Apply animation transformations to hotspots.</td>
                </tr>
                <tr>
                    <td><a href="projects/hotspots-sounds/" target="_blank">Hotspots with spatialized sounds</a></td>
                    <td>Add hotspots with spatialized sounds to a scene.</td>
                </tr>
                <tr>
                    <td><a href="projects/hotspots-geometry/" target="_blank">Hotspots geometry</a></td>
                    <td>Add hotspots with 3D geometries such as a sphere or a box.</td>
                </tr>
                <tr>
                    <td><a href="projects/hotspots-scale/" target="_blank">Hotspots scale</a></td>
                    <td>Hotspots that are keeping the same aspect whatever the fov is.</td>
                </tr>
                <tr>
                    <td><a href="projects/hotspots-states/" target="_blank">Hotspots states</a></td>
                    <td>Hotspots states allows you to change the material on mouse over or setup a toggle system like checkboxes.</td>
                </tr>
                <tr>
                    <td><a href="projects/hotspots-states-sound/" target="_blank">Hotspots states sound</a></td>
                    <td>Hotspots states allows you to emit a sound on mouse over.</td>
                </tr>
                <tr>
                    <td><a href="projects/hotspots-states-animation/" target="_blank">Hotspots states animation</a></td>
                    <td>This sample show how to setup different animations for different states.</td>
                </tr>
                <tr>
                    <td><a href="projects/hotspots-sprite/" target="_blank">Hotspots animated sprite</a></td>
                    <td>Hotspots that have an animated sprite as texture.</td>
                </tr>
                <tr>
                    <td><a href="projects/hotspots-video/" target="_blank">Hotspots video</a></td>
                    <td>Hotspots that have a video as texture.</td>
                </tr>
                <tr>
                    <td><a href="projects/hotspots-dom/" target="_blank">Hotspots DOM</a></td>
                    <td>Hotspots that are not 3D objects but are DOM elements positionned in 2D space over the media.</td>
                </tr>
                <tr>
                    <td><a href="projects/controller-gamepad/" target="_blank">Gamepad controller</a></td>
                    <td>Sample to demonstrate and test the usage of gamepad controllers with ForgeJS. The plugin used to monitor the Gamepad only works on Chrome at the moment.</td>
                </tr>
                <tr>
                    <td><a href="projects/controller-pointer/" target="_blank">Pointer controller</a></td>
                    <td>Sample to demonstrate and test the settings of a pointer controller.</td>
                </tr>
                <tr>
                    <td><a href="projects/multiple-viewers/" target="_blank">Multiple Viewers</a></td>
                    <td>A test case for multiple FORGE.Viewer in the same page.</td>
                </tr>
                <tr>
                    <td><a href="projects/camera-limits/" target="_blank">Camera limitation</a></td>
                    <td>Apply direction and limits to the orientation and the field of view of the camera.</td>
                </tr>
                <tr>
                    <td><a href="projects/camera-parallax/" target="_blank">Camera parallax</a></td>
                    <td>Apply a camera parallax effect on the hotspots layer.</td>
                </tr>
                <tr>
                    <td><a href="projects/camera-events/" target="_blank">Camera events</a></td>
                    <td>This projects logs in the console the differents camera events available in the API.</td>
                </tr>
                <tr>
                    <td><a href="projects/media-video-events/" target="_blank">Media video events</a></td>
                    <td>Bind event handlers on a video media directly on the json configuration.</td>
                </tr>
                <tr>
                    <td><a href="projects/story-events/" target="_blank">Story events</a></td>
                    <td>Bind events handler to the story directly on the json configuration. Events are logged in the console in this sample.</td>
                </tr>
            </tbody>
        </table>

        <h2 class="display-1">ForgeJS Plugins Samples</h2>

        <p class="lead">Click on a sample link to try the plugin!</p>

        <table class="table table-striped table-hover">
            <thead class="thead-inverse">
                <tr>
                    <th>name</th>
                    <th>description</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><a href="projects/video-controls/" target="_blank">Video controls</a></td>
                    <td>Plugin to allow users to have full control of the video player through buttons and widgets.</td>
                </tr>
                <tr>
                    <td><a href="projects/googlemaps/" target="_blank">Google maps</a></td>
                    <td>Plugin that displays a GPS track on a Google map.</td>
                </tr>
                <tr>
                    <td><a href="projects/thumbnail-bar/" target="_blank">Thumbnail bar</a></td>
                    <td>A plugin which allow to add a bar at the bottom of the screen, containing thumbnails relative of each scene.</td>
                </tr>
                <tr>
                    <td><a href="projects/performance-monitor/" target="_blank">Performance Monitor</a></td>
                    <td>A plugin which allow to monitor performance: fps, ms, mb.</td>
                </tr>
                <tr>
                    <td><a href="projects/toolbox/" target="_blank">Toolbox</a></td>
                    <td>A plugin that displays a GUI to interact with camera values, the current scene or the view type.</td>
                </tr>
                <tr>
                    <td><a href="projects/editor/" target="_blank">Editor</a></td>
                    <td>The very first version of a hotspot editor plugin (WIP).</td>
                </tr>
                <tr>
                    <td><a href="projects/telemetry/" target="_blank">Telemetry</a></td>
                    <td>A project displaying multiple plugins, each being fed with data coming from the telemetry gathered by a GoPro camera. The plugins used in this project are speedometer, accelerometer, compass and altimeter. These plugins can be used as hotspots' texture or be displayed in screen space</td>
                </tr>
                <tr>
                    <td><a href="projects/share/" target="_blank">Share a view</a></td>
                    <td>A plugin allowing the user to share the current view type and camera orientation (yaw, pitch, roll and fov). The plugin keeps the url updated with those parameters.</td>
                </tr>
            </tbody>
        </table>

        <h2 class="display-1">ForgeJS Projects</h2>

        <p class="lead">These are bigger projects.</p>

        <table class="table table-striped table-hover">
            <thead class="thead-inverse">
                <tr>
                    <th>name</th>
                    <th>description</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><a href="projects/vrrroom/" target="_blank">Vrrroom</a></td>
                    <td>This is the project that has been presented at the IVRPA 2016 in Quebec.</td>
                </tr>
                <tr>
                    <td><a href="projects/immersive-and-narrative/" target="_blank">Combining immersive<br/>and narrative content</a></td>
                    <td>A sample highlighting the powerful storytelling capabilities of ForgeJS combining narrative and immersive scenes.</td>
                </tr>
                <tr>
                    <td><a href="projects/paris26g/" target="_blank">Paris 26 Gigapixels</a></td>
                    <td>A sample highlighting the power of the tiles management and look at navigation with ForgeJS.</td>
                </tr>
            </tbody>
        </table>

    </div>
</body>
</html>


<|start_filename|>projects/vrrroom/plugins/ChromaKey/src/ChromaKey.js<|end_filename|>
var ForgePlugins = ForgePlugins || {};

/**
 * ChromaKey plugin
 * Extract foreground with chromakey algorithm
 * Author / Provider modes to prepare settings and them use them
 *
 * @constructor ForgePlugins.ChromaKey
 */
ForgePlugins.ChromaKey = function()
{
    // Flags
    this._mediaReady = false;

    this._canvasReady = false;

    this._needsUpdate = false;

    this._authoring = false;

    // Input media
    this._mediaURL = null;

    this._media = null;

    this._mediaType = null;

    this._mediaLastFrameIndex = null;

    // Processing settings
    this._backgroundRGB = null;

    this._backgroundYCrCb = null;

    this._threshold = 0;

    this._smoothness = 0;

    this._backgroundSample = null;

    // Processing objects
    this._canvasSrc = null;

    this._canvasDst = null;

    // UI
    this._ui = null;

    // dat.gui
    this._datGUI = null;

    this._datGUIControls = null;

    // Worker
    this._workerState = -1; // State machine

    this._worker = null;

    this._onWorkerMessageBound = null;

    this._onWorkerErrorBound = null;
};

ForgePlugins.ChromaKey.WORKER_STATE = {
    INIT: -1, // worker is not ready
    IDLE: 0, // worker is ready, but nothing happens
    PROCESSING: 1 // worker is processing current frame, wait for response
};

ForgePlugins.ChromaKey.prototype = {

    /**
     * Boot sequence
     * Create UI object, set flags and get options from json config
     * Set authoring mode if asked by caller
     * Set input media
     */
    boot: function()
    {
        this._authoring = this.plugin.options.authoring || false;

        this._worker = new Worker(this.plugin.fullUrl + "src/ChromaKeyWorker.js");

        this._onWorkerMessageBound = this._onWorkerMessage.bind(this);
        this._worker.addEventListener("message", this._onWorkerMessageBound, true);

        this._onWorkerErrorBound = this._onWorkerError.bind(this);
        this._worker.addEventListener("error", this._onWorkerErrorBound, true);

        if (this._authoring)
        {
            this._datGUIControls = new ForgePlugins.ChromaKeySettings(this);
            this._initGUI();
        }

        // Apply mutable settings to dat structure if authoring mode
        if (typeof this.plugin.options.background !== "undefined")
        {
            if (this._authoring)
            {
                this._datGUIControls.background = this.plugin.options.background;
            }
            else
            {
                this.background = this.plugin.options.background;
            }
        }

        // Apply mutable settings to dat structure if authoring mode
        if (typeof this.plugin.options.backgroundSample !== "undefined")
        {
            this._backgroundSample = this.plugin.options.backgroundSample;
        }

        if (typeof this.plugin.options.threshold !== "undefined")
        {
            if (this._authoring)
            {
                this._datGUIControls.threshold = this.plugin.options.threshold;
            }
            else
            {
                this.threshold = this.plugin.options.threshold;
            }
        }

        if (typeof this.plugin.options.smoothness !== "undefined")
        {
            if (this._authoring)
            {
                this._datGUIControls.smoothness = this.plugin.options.smoothness;
            }
            else
            {
                this.smoothness = this.plugin.options.smoothness;
            }
        }

        if (typeof this.plugin.options.url !== "undefined")
        {
            this._setMedia(this.plugin.options.url);
        }

        this._workerState = ForgePlugins.ChromaKey.WORKER_STATE.IDLE;
    },

    /**
     * Destroy sequence
     */
    destroy: function()
    {
        this._mediaReady = false;
        this._needsUpdate = false;

        this._deinitDatGui();

        if (this._ui !== null)
        {
            this._destroyGUI();
        }

        if (this._canvasSrc !== null)
        {
            this._canvasSrc.destroy();
            this._canvasSrc = null;
        }

        if (this._canvasDst !== null)
        {
            this._canvasDst.destroy();
            this._canvasDst = null;
        }

        if (this._media !== null)
        {
            if (this._media.onEnded.has(this._onVideoEnded, this))
            {
                this._media.onEnded.remove(this._onVideoEnded, this);
            }
            this._media.destroy();
            this._media = null;
        }
        this._mediaURL = null;
        this._mediaType = null;

        this._worker.terminate();
        this._worker = null;
        this._onWorkerMessageBound = null;
        this._onWorkerErrorBound = null;

        this._backgroundRGB = null;
        this._backgroundYCrCb = null;
    },

    /**
     * Update routine
     */
    update: function()
    {
        if (this._workerState !== ForgePlugins.ChromaKey.WORKER_STATE.IDLE ||
            this._mediaReady === false ||
            this._canvasReady === false)
        {
            return;
        }

        // Force update if video has enough data for current frame or more
        if (this._mediaType === "video")
        {
            var video = this._media.videos[0].element;
            if (video.readyState >= video.HAVE_CURRENT_DATA)
            {
                var frameIndex = Math.floor(this._media.currentTime * this._media.data.framerate);
                if (frameIndex !== this._mediaLastFrameIndex)
                {
                    this._needsUpdate = true;
                    this._mediaLastFrameIndex = frameIndex;
                }
            }
        }

        if (this._needsUpdate === false)
        {
            return;
        }

        // Compute output frame
        this._computeFrame();

        // Refresh update flag
        this._needsUpdate = false;
    },

    /**
     * Placeholder, do nothing
     */
    reset: function() {},

    /**
     * Toggle play/pause
     */
    togglePlayPause: function()
    {
        if (this._mediaType === "image")
        {
            return;
        }

        if (this._media.playing)
        {
            this._media.pause();
        }
        else
        {
            this._media.play();
        }

        if (this._authoring === true)
        {
            this.togglePlayPauseAuthoring();
        }
    },

    /**
     * Internal log function
     * @param  {String} text text to log
     * @private
     */
    _log: function(text)
    {
        this.plugin.log("[" + this.plugin.instanceConfig.uid + "] " + text);
    },

    /**
     * Set media
     * @param {String} url media URL
     * @private
     */
    _setMedia: function(url)
    {
        this._log("Set media " + url);

        this._mediaURL = url;

        // Set media type
        this._mediaType = "image";
        var videoTypes = ["mp4", "webm", "ogg"];
        for (var i = 0, ii = videoTypes.length; i < ii; i++)
        {
            if (this._mediaURL.indexOf(videoTypes[i]) !== -1)
            {
                this._mediaType = "video";
                break;
            }
        }

        var mediaConfig = {
            key: this._uid,
            url: this._mediaURL
        };

        // Create media element
        if (this._mediaType === 'image')
        {
            this._media = this.plugin.create.image(mediaConfig);
            this._media.onLoadComplete.addOnce(this._onMediaLoadComplete, this);
        }
        else
        {
            this._media = this.plugin.create.video(this._uid, null, FORGE.VideoFormat.HTML5);
            this._media.load([this._mediaURL]);
            this._media.onLoadedMetaData.addOnce(this._onMediaLoadComplete, this);
            this._media.onEnded.add(this._onVideoEnded, this);
        }
    },

    /**
     * Media load complete event handler
     * @param {Event} event load complete event
     * @private
     */
    _onMediaLoadComplete: function(event)
    {
        this._setupCanvas();
        this._mediaReady = true;

        if (this.plugin.options.autoPlay === true)
        {
            this._start();
        }

        if (this._mediaType === "video")
        {
            if (this._media.data === null)
            {
                this._media.data = {};
            }

            this._media.data.framerate = this._media.data.framerate || 30;
        }

        this._needsUpdate = true;
    },

    /**
     * Video ended event handler
     * @param {Event} event event object
     * @private
     */
    _onVideoEnded: function(event)
    {
        this._media.currentTime = 0;
        this._media.play();
    },

    /**
     * Set background
     * @param {String} value background color string rgba(r,g,b)
     * @private
     */
    _setBackground: function(value)
    {
        this._backgroundRGB = FORGE.Color.fromRgbaString(value);
        this._backgroundYCrCb = FORGE.Color.rgbToYcbcr(this._backgroundRGB);
        this._needsUpdate = true;
    },

    /**
     * Set threshold
     * @param {Number} value treshold
     * @private
     */
    _setThreshold: function(value)
    {
        this._threshold = value;
        this._needsUpdate = true;
    },

    /**
     * Set alpha smoothness
     * @param {String} value alpha smoothness (percent)
     * @private
     */
    _setSmoothness: function(value)
    {
        this._smoothness = FORGE.Math.clamp(value, 0, 1);
        this._needsUpdate = true;
    },

    /**
     * get media size
     * @param {Object} object with width and height property
     * @private
     */
    _getMediaSize: function()
    {
        if (this._mediaType === 'video')
        {
            return {
                width: this._media.originalWidth,
                height: this._media.originalHeight
            }
        }

        return {
            width: this._media.width,
            height: this._media.height
        }
    },

    /**
     * Setup canvas objects
     * @private
     */
    _setupCanvas: function()
    {
        this._log("Setup canvas");
        var size = this._getMediaSize();

        this._canvasSrc = this.plugin.create.canvas();
        this._canvasDst = this.plugin.create.canvas();

        this._canvasSrc.width = this._canvasDst.width = size.width;
        this._canvasSrc.height = this._canvasDst.height = size.height;

        if (this._authoring === true)
        {
            this._setupCanvasAuthoring(size);
        }

        this._autoResizeContainer();

        this._computeFrame();
    },

    _onWorkerMessage: function(msg)
    {
        // this._log("Compute frame done");
        this._workerState = ForgePlugins.ChromaKey.WORKER_STATE.IDLE;

        if (msg.data.success === true)
        {
            if (this._canvasReady === false)
            {
                this._canvasReady = true;
                this.plugin.notifyInstanceReady();
            }
            this._canvasDst.context2D.putImageData(msg.data.frame, 0, 0);
            delete msg.data.frame;

            this.updatePreview();
        }
    },

    _onWorkerError: function(event)
    {
        this._workerState = ForgePlugins.ChromaKey.WORKER_STATE.IDLE;

        if (typeof event.message !== "undefined")
        {
            throw new Error(event.message + " (" + event.filename + ":" + event.lineno + ")");
        }
    },

    /**
     * Compute a frame
     * Get image data from source canvas
     * Put image data into dest canvas
     *
     * @todo: should use a web worker to lighten main thread
     * @private
     */
    _computeFrame: function()
    {
        this._log("Compute frame");

        var size = this._getMediaSize();
        var w = size.width;
        var h = size.height;

        // Prepare input frame
        var ctx = this._canvasSrc.context2D;
        ctx.clearRect(0, 0, w, h);
        ctx.drawImage(this._media.element, 0, 0, w, h);

        var frame = ctx.getImageData(0, 0, w, h);

        // Use sample 10x10 as background color
        if (this._backgroundSample !== null)
        {
            var idx = this._backgroundSample.x + this._backgroundSample.y * w;

            var r = frame.data[4 * idx + 0];
            var g = frame.data[4 * idx + 1];
            var b = frame.data[4 * idx + 2];
            var rgb = "rgb(" + r + "," + g + "," + b + ")";

            this._setBackground(rgb);
        }

        // Sending canvas data to the worker using a copy memory operation
        var workerParams = {
            action: "do",
            frame: frame,
            width: w,
            height: h,
            background: this._backgroundYCrCb,
            threshold: this._threshold,
            smoothness: this._smoothness
        }
        this._workerState = ForgePlugins.ChromaKey.WORKER_STATE.PROCESSING;
        this._worker.postMessage(workerParams);
    },

    /**
     * Start processing
     * @private
     */
    _start: function()
    {
        if (this._mediaReady === false)
        {
            return;
        }

        this.togglePlayPause();
    }
};

/**
 * Get and set media URL
 * @name ForgePlugins.ChromaKey#url
 * @type {String}
 */
Object.defineProperty(ForgePlugins.ChromaKey.prototype, "url",
{
    get: function()
    {
        return this._mediaURL;
    },

    set: function(value)
    {
        if (typeof value !== "string")
        {
            return;
        }

        this._setMedia(value);
    }
});

/**
 * Get and set background color (RGB)
 * @name ForgePlugins.ChromaKey#background
 * @type {String}
 */
Object.defineProperty(ForgePlugins.ChromaKey.prototype, "background",
{
    get: function()
    {
        return this._backgroundRGB;
    },

    set: function(value)
    {
        this._setBackground(value);
    }
});

/**
 * Get and set threshold
 * @name ForgePlugins.ChromaKey#threshold
 * @type {String}
 */
Object.defineProperty(ForgePlugins.ChromaKey.prototype, "threshold",
{
    get: function()
    {
        return this._threshold;
    },

    set: function(value)
    {
        this._setThreshold(value);
    }
});

/**
 * Get and set smoothness
 * @name ForgePlugins.ChromaKey#smoothness
 * @type {String}
 */
Object.defineProperty(ForgePlugins.ChromaKey.prototype, "smoothness",
{
    get: function()
    {
        return this._smoothness * 100;
    },

    set: function(value)
    {
        this._setSmoothness(value / 100);
    }
});

/**
 * Get provider
 * @name ForgePlugins.ChromaKey#provider
 * @type {FORGE.Canvas}
 */
Object.defineProperty(ForgePlugins.ChromaKey.prototype, "texture",
{
    get: function()
    {
        return this._canvasDst;
    }
});

<|start_filename|>projects/telemetry/src/TelemetryManager.js<|end_filename|>
 FORGE.TelemetryManager = function(viewer)
{
    this._viewer = viewer;

    this._modules = [];
};

FORGE.TelemetryManager.prototype.add = function(name, pluginUid, hotspotUid)
{
	var config = {name: name, plugin: pluginUid, hotspot: hotspotUid};
	var module = new FORGE.TelemetryModule(this._viewer, config);
    this._modules.push(module);
};

Object.defineProperty(FORGE.TelemetryManager.prototype, "modules",
{
    get: function()
    {
        return this._modules;
    }
});


<|start_filename|>projects/vrrroom/plugins/ChromaKey/src/ChromaKeyWorker.js<|end_filename|>
/**
 * ChromaKey worker
 * Run image processing routine to save CPU time in main thread
 */

function RGBToYCbCr(color)
{
    return {
        Y: 0 + 0.299 * color.r + 0.587 * color.g + 0.114 * color.b,
        Cr: 128 - 0.169 * color.r - 0.331 * color.g + 0.500 * color.b,
        Cb: 128 + 0.500 * color.r - 0.419 * color.g - 0.081 * color.b
    };
};

function YCbCrToRGB(color)
{
    color.Cr -= 128;
    color.Cb -= 128;

    return {
        r: 1.000 * color.Y + 0.000 * color.Cr + 1.400 * color.Cb,
        g: 1.000 * color.Y - 0.343 * color.Cr - 0.711 * color.Cb,
        b: 1.000 * color.Y + 1.765 * color.Cr + 0.000 * color.Cb
    };
};

function colorDesaturate(color, scale)
{
    var Cr = (color.Cr - 128) * scale + 128;
    var Cb = (color.Cb - 128) * scale + 128;

    return {
        Y: color.Y,
        Cr: Cr,
        Cb: Cb
    }
}

function colorDistance(a, b)
{
    return Math.sqrt((a.Cb - b.Cb) * (a.Cb - b.Cb) + (a.Cr - b.Cr) * (a.Cr - b.Cr));
};

function colorSubtractChroma(a, b)
{
    return {
        Y: a.Y,
        Cr: a.Cr - b.Cr,
        Cb: a.Cb - b.Cb
    }
};

function clamp(x, min, max)
{
    return Math.min(Math.max(x, min), max);
};

function smoothstep(edge0, edge1, x)
{
    x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
    return x * x * (3 - 2 * x);
};

function mix(a, b, mix)
{
    return a * mix + b * (1 - mix);
};

function computeFrame(frame, width, height, background, threshold, smoothness)
{
    self.log("Compute frame");
    var w = width,
        h = height;

    for (var y = 0; y < h; y++)
    {
        for (var x = 0; x < w; x++)
        {
            var index = (x + y * w);

            var foregroundRGB = {
                r: frame.data[index * 4 + 0],
                g: frame.data[index * 4 + 1],
                b: frame.data[index * 4 + 2]
            };
            var foregroundYCrCb = RGBToYCbCr(foregroundRGB);

            var distance = colorDistance(foregroundYCrCb, background);

            var low = 2.55 * threshold * (1 - smoothness);
            var high = 2.55 * threshold * (1 + smoothness);

            // Better option: apply smoothstep on the green channel and on transparency
            var foregroundRatio = smoothstep(low, high, distance); // 0 if key color detected

            // Desaturate 70% and mix depending on distance to background
            var desaturated = colorDesaturate(foregroundYCrCb, 0.7);
            var desaturatedRGB = YCbCrToRGB(desaturated);

            var mixFactor = clamp(distance / 128, 0, 1);
            frame.data[index * 4 + 0] = mix(frame.data[index * 4 + 0], desaturatedRGB.r, mixFactor);
            frame.data[index * 4 + 1] = mix(frame.data[index * 4 + 1], desaturatedRGB.g, mixFactor);
            frame.data[index * 4 + 2] = mix(frame.data[index * 4 + 2], desaturatedRGB.b, mixFactor);

            // Perserve red and blue, dissolve green channel
            // frame.data[index * 4 + 1] = mix(0, frame.data[index * 4 + 1], (1 - foregroundRatio));

            // Set alpha channel depending on foreground/background mix
            frame.data[index * 4 + 3] = 255 * foregroundRatio;
        }
    }

    return true;
};



self.log = function(text)
{
    // console.log("[ChromaKey Worker] " + text);
};

self.onmessage = function(msg)
{
    self.log("Command received from main script : " + msg.data.action);

    switch (msg.data.action)
    {
        case "do":
            var success = computeFrame(msg.data.frame, msg.data.width, msg.data.height, msg.data.background, msg.data.threshold, msg.data.smoothness);
            postMessage(
            {
                success: success,
                frame: msg.data.frame
            });
            break;

        default:
            break;
    }

    // Terminate the worker calling close() method
    // close();
};

<|start_filename|>projects/vrrroom/plugins/ChromaKey/src/ChromaKeySettings.js<|end_filename|>
var ForgePlugins = ForgePlugins || {};

/**
 * ChromaKey plugin settings
 * Interface used for dat.gui communication
 *
 * @constructor ForgePlugins.ChromaKeySettings
 * @param {ForgePlugins.ChromaKey} chromaKey - {@link ForgePlugins.ChromaKey} reference.
 */
ForgePlugins.ChromaKeySettings = function(chromaKey)
{
    this._chromaKey = chromaKey;
    this._bypass = false;

    this.togglePlayPause = function()
    {
        this._chromaKey.togglePlayPause();
    };
};

ForgePlugins.ChromaKeySettings.prototype.destroy = function()
{
    // Release weak reference on chromakey plugin
    this._chromaKey = null;
};

/**
 * Treshold property accessor
 */
Object.defineProperty(ForgePlugins.ChromaKeySettings.prototype, "threshold",
{
    set: function(value)
    {
        this._chromaKey.threshold = value;
    },

    get: function()
    {
        return this._chromaKey.threshold;
    }
});

/**
 * Smoothness property accessor
 */
Object.defineProperty(ForgePlugins.ChromaKeySettings.prototype, "smoothness",
{
    set: function(value)
    {
        this._chromaKey.smoothness = value;
    },

    get: function()
    {
        return this._chromaKey.smoothness;
    }
});

/**
 * Background property accessor
 */
Object.defineProperty(ForgePlugins.ChromaKeySettings.prototype, "background",
{
    set: function(value)
    {
        this._chromaKey.background = value;
    },

    get: function()
    {
        return this._chromaKey.background;
    }
});

/**
 * Show preview property accessor
 * Enable or disable the display of preview canvas
 */
Object.defineProperty(ForgePlugins.ChromaKeySettings.prototype, "showPreview",
{
    set: function(value)
    {
        if (value === true)
        {
            this._chromaKey.showPreview();
            this._chromaKey.updatePreview();
        }
        else
        {
            this._chromaKey.hidePreview();
        }
    },

    get: function()
    {
        return this._chromaKey.hasPreview();
    }
});

/**
 * Bypass property accessor
 * Turn on/off the processing to highlight current state
 */
Object.defineProperty(ForgePlugins.ChromaKeySettings.prototype, "bypass",
{
    set: function(value)
    {
        this._bypass = value;
        this._chromaKey.updatePreview();
    },

    get: function()
    {
        return this._bypass;
    }
});

<|start_filename|>projects/vrrroom/i18n/fr-FR/locale.json<|end_filename|>
{
    "i18n-storyName": "Vrrroom",
    "i18n-storySlug": "vrrroom",
    "i18n-storyDescription": "Vrrroom",

    "intro-text": "L’AVENTURE DE LA RÉALITÉ VIRTUELLE COMMENCE,<br>C’EST PARTI POUR LA COURSE !",
    "intro-btn": "i18n/fr-FR/start_fr.png"
}

<|start_filename|>projects/vrrroom/plugins/GooeyMenu/src/GooeyMenu.js<|end_filename|>
var ForgePlugins = ForgePlugins || {};

/**
 * A Gooey menu, inspired by the one created by <NAME> here
 * http://codepen.io/lbebber/pen/RNgBPP
 */
ForgePlugins.GooeyMenu = function()
{
    // The main container
    this._container = null;

    // The list of items in the menu
    this._items = [];

    // Number of items
    this._itemsCount = 0
};

ForgePlugins.GooeyMenu.prototype = {

    /**
     * The boot function
     */
    boot: function()
    {
        // Create a FORGE.DisplayObjectContainer instance
        this._container = this.plugin.create.displayObjectContainer();

        // Set properties to the div
        this._container.width = this.plugin.options.width;
        this._container.height = this.plugin.options.height;
        this._container.top = this.plugin.options.top;
        this._container.left = this.plugin.options.left;
        this._container.right = this.plugin.options.right;
        this._container.bottom = this.plugin.options.bottom;

        // Add it to the main container
        this.plugin.container.addChild(this._container);

        // Write the content of the menu
        var content = '';
        content += '<nav class="menu">';
        content += '    <input type="checkbox" href="#" class="menu-open" name="menu-open" id="menu-open"/>';
        content += '    <label class="menu-open-button" for="menu-open">';
        content += '        <span class="hamburger hamburger-1"></span>';
        content += '        <span class="hamburger hamburger-2"></span>';
        content += '        <span class="hamburger hamburger-3"></span>';
        content += '    </label>';

        this._itemsCount = this.plugin.options.items.length;

        for (var i = 0; i < this._itemsCount; i++)
        {
            content += '    <a class="menu-item menu-'
                    + this.plugin.options.items[i].id + ' '
                    + (this.plugin.options.items[i].ticked ? 'menu-item-active' : 'menu-item-inactive')
                    + (this.plugin.options.items[i].toggle ? ' menu-item-toggle' : '')
                    + '" id="menu-item-' + i + '"></a>';
        }

        content += '</nav>';

        // Set it to the DOM
        this._container.dom.innerHTML = content;

        // Configure each item and its action
        for (var i = 0; i < this._itemsCount; i++)
        {
            this._items[i] = {
                title: this.plugin.create.string('gooeymenu-item-' + i),
                dom: document.getElementById('menu-item-' + i)
            };

            this._items[i].dom.addEventListener('click', this._clickHandler.bind(this));
        }

        this._setLabelTitle();

        // Load the locale
        this.viewer.i18n.onLocaleChangeComplete.add(this._onLocaleChangeCompletehandler, this);
    },

    /**
     * Handle a click on an element of the menu
     */
    _clickHandler: function(event)
    {
        var item = event.target;

        // Change the visual
        if (item.classList.contains('menu-item-active') && item.classList.contains('menu-item-toggle'))
        {
            item.classList.remove('menu-item-active');
            item.classList.add('menu-item-inactive');
        }
        else if(item.classList.contains('menu-item-toggle'))
        {
            item.classList.remove('menu-item-inactive');
            item.classList.add('menu-item-active');
        }

        // Execute the action
        var id = item.id.charAt(item.id.length - 1);
        var action = this.plugin.options.items[id].click.action;
        var target = this.plugin.options.items[id].click.target;

        // Toggle the visibility of plugins on the scene
        if (action === "visibility")
        {
            for (var i = 0, ii = target.length; i < ii; i++)
            {
                this.viewer.plugins.get(target[i]).container.toggleVisibility();
            }
        }

        // Change the current scene
        else if (action === "scene")
        {
            if (target === "next")
            {
                this.viewer.story.nextScene();
            }
            else if (target === "previous")
            {
                this.viewer.story.previousScene();
            }
            else if (typeof target === "string")
            {
                this.viewer.story.scene = target
            }
            else if (Array.isArray(target))
            {
                for (var i = 0, ii = target.length; i < ii; i++)
                {
                    if (this.viewer.story.scene.uid === target[i][0])
                    {
                        this.viewer.story.scene = target[i][1];
                    }
                    else if (this.viewer.story.scene.uid === target[i][1])
                    {
                        this.viewer.story.scene = target[i][0];
                    }
                }
            }
        }

        // Change the current song in the playlist
        else if (action === "playlist")
        {
            if (target === "next")
            {
                this.viewer.playlists.nextTrack();
            }
            else if (target === "previous")
            {
                this.viewer.playlists.previousTrack();
            }
        }
    },

    /**
     * Handle the locale change
     */
    _onLocaleChangeCompletehandler: function(event)
    {
        this._setLabelTitle();
    },

    _setLabelTitle: function()
    {
        for (var i = 0; i < this._itemsCount; i++)
        {
            this._items[i].dom.title = this._items[i].title.value;
        }
    },

    /**
     * Placeholder, do nothing
     */
    reset: function() {},

    /**
     * Placeholder, do nothing
     */
    update: function() {},

    /**
     * Destroy the button.
     */
    destroy: function()
    {
        this.viewer.i18n.onLocaleChangeComplete.remove(this._onLocaleChangeCompletehandler, this);

        this._container = null;

        for (var i = 0; i < this._itemsCount; i++)
        {
            this._items[i].dom = null;
            this._items[i] = null;
        }

        this._items = null;
    }
};