<|start_filename|>src/graphics/program-lib/chunks/cubeMapRotate.frag<|end_filename|>
#ifdef CUBEMAP_ROTATION
uniform mat3 cubeMapRotationMatrix;
#endif

vec3 cubeMapRotate(vec3 refDir) {
#ifdef CUBEMAP_ROTATION
    return refDir * cubeMapRotationMatrix;
#else
    return refDir;
#endif
}


<|start_filename|>src/resources/render.js<|end_filename|>
import { Render } from '../scene/render.js';

// The scope of this function is the render asset
function onContainerAssetLoaded(containerAsset) {
    const renderAsset = this;
    if (!renderAsset.resource) return;

    const containerResource = containerAsset.resource;

    const render = containerResource.renders && containerResource.renders[renderAsset.data.renderIndex];
    if (render) {
        renderAsset.resource.meshes = render.resource.meshes;
    }
}

// The scope of this function is the render asset
function onContainerAssetAdded(containerAsset) {
    const renderAsset = this;

    renderAsset.registry.off('load:' + containerAsset.id, onContainerAssetLoaded, renderAsset);
    renderAsset.registry.on('load:' + containerAsset.id, onContainerAssetLoaded, renderAsset);
    renderAsset.registry.off('remove:' + containerAsset.id, onContainerAssetRemoved, renderAsset);
    renderAsset.registry.once('remove:' + containerAsset.id, onContainerAssetRemoved, renderAsset);

    if (!containerAsset.resource) {
        renderAsset.registry.load(containerAsset);
    } else {
        onContainerAssetLoaded.call(renderAsset, containerAsset);
    }
}

function onContainerAssetRemoved(containerAsset) {
    const renderAsset = this;

    renderAsset.registry.off('load:' + containerAsset.id, onContainerAssetLoaded, renderAsset);

    if (renderAsset.resource) {
        renderAsset.resource.destroy();
    }
}

/**
 * @class
 * @name RenderHandler
 * @implements {ResourceHandler}
 * @classdesc Resource handler used for loading {@link Render} resources.
 * @param {GraphicsDevice} device - The graphics device that will be rendering.
 * @param {StandardMaterial} defaultMaterial - The shared default material that is used in any place that a material is not specified.
 */
class RenderHandler {
    constructor(assets) {
        this._registry = assets;
    }

    load(url, callback, asset) {
    }

    open(url, data) {
        return new Render();
    }

    patch(asset, registry) {
        if (!asset.data.containerAsset)
            return;

        const containerAsset = registry.get(asset.data.containerAsset);
        if (!containerAsset) {
            registry.once('add:' + asset.data.containerAsset, onContainerAssetAdded, asset);
            return;
        }

        onContainerAssetAdded.call(asset, containerAsset);
    }
}

export { RenderHandler };


<|start_filename|>scripts/animation/tween.js<|end_filename|>
var Tween = pc.createScript('tween');

Tween.attributes.add('tweens', {
    type: 'json',
    schema: [
        {
            name: 'autoPlay',
            title: 'Autoplay',
            description: 'Play tween immediately.',
            type: 'boolean',
            default: false
        }, {
            name: 'event',
            title: 'Trigger Event',
            description: 'Play tween on the specified event name. This event must be fired on the global application object (e.g. this.app.fire(\'eventname\');).',
            type: 'string'
        }, {
            name: 'path',
            title: 'Path',
            description: 'The path from the entity to the property. e.g. \'light.color\', \'camera.fov\' or \'script.vehicle.speed\'.',
            type: 'string'
        }, {
            name: 'start',
            title: 'Start',
            type: 'vec4'
        }, {
            name: 'end',
            title: 'End',
            type: 'vec4'
        }, {
            name: 'easingFunction',
            title: 'Easing Function',
            description: 'The easing functions: Linear, Quadratic, Cubic, Quartic, Quintic, Sinusoidal, Exponential, Circular, Elastic, Back and Bounce.',
            type: 'number',
            enum: [
                { 'Linear': 0 },
                { 'Quadratic': 1 },
                { 'Cubic': 2 },
                { 'Quartic': 3 },
                { 'Quintic': 4 },
                { 'Sinusoidal': 5 },
                { 'Exponential': 6 },
                { 'Circular': 7 },
                { 'Elastic': 8 },
                { 'Back': 9 },
                { 'Bounce': 10 }
            ],
            default: 0
        }, {
            name: 'easingType',
            title: 'Easing Type',
            description: 'Whether to ease in, easy out or ease in and then out using the specified easing function. Note that for a Linear easing function, the easing type is ignored.',
            type: 'number',
            enum: [
                { 'In': 0 },
                { 'Out': 1 },
                { 'InOut': 2 }
            ],
            default: 0
        }, {
            name: 'delay',
            title: 'Delay',
            description: 'Time to wait in milliseconds after receiving the trigger event before executing the tween. Defaults to 0.',
            type: 'number',
            default: 0
        }, {
            name: 'duration',
            title: 'Duration',
            description: 'Time to execute the tween in milliseconds. Defaults to 1000.',
            type: 'number',
            default: 1000
        }, {
            name: 'repeat',
            title: 'Repeat',
            description: 'The number of times the tween should be repeated after the initial playback. -1 will repeat forever. Defaults to 0.',
            type: 'number',
            default: 0
        }, {
            name: 'repeatDelay',
            title: 'Repeat Delay',
            description: 'Time to wait in milliseconds before executing each repeat of the tween. Defaults to 0.',
            type: 'number',
            default: 0
        }, {
            name: 'yoyo',
            title: 'Yoyo',
            description: 'This function only has effect if used along with repeat. When active, the behaviour of the tween will be like a yoyo, i.e. it will bounce to and from the start and end values, instead of just repeating the same sequence from the beginning. Defaults to false.',
            type: 'boolean',
            default: false
        }, {
            name: 'startEvent',
            title: 'Start Event',
            description: 'Executed right before the tween starts animating, after any delay time specified by the delay method. This will be executed only once per tween, i.e. it will not be run when the tween is repeated via repeat(). It is great for synchronising to other events or triggering actions you want to happen when a tween starts.',
            type: 'string'
        }, {
            name: 'stopEvent',
            title: 'Stop Event',
            description: 'Executed when a tween is explicitly stopped via stop(), but not when it is completed normally.',
            type: 'string'
        }, {
            name: 'updateEvent',
            title: 'Update Event',
            description: 'Executed each time the tween is updated, after the values have been actually updated.',
            type: 'string'
        }, {
            name: 'completeEvent',
            title: 'Complete Event',
            description: 'Executed when a tween is finished normally (i.e. not stopped).',
            type: 'string'
        }, {
            name: 'repeatEvent',
            title: 'Repeat Event',
            description: 'Executed whenever a tween has just finished one repetition and will begin another.',
            type: 'string'
        }
    ],
    array: true
});

// initialize code called once per entity
Tween.prototype.initialize = function () {
    var app = this.app;
    var i;

    this.tweenInstances = [];
    this.tweenCallbacks = [];

    var makeStartCallback = function (i) {
        return function () {
            this.start(i);
        };
    };

    for (i = 0; i < this.tweens.length; i++) {
        var tween = this.tweens[i];
        if (tween.autoPlay) {
            this.start(i);
        }
        if (tween.event && tween.event.length > 0) {
            this.tweenCallbacks[i] = {
                event: tween.event,
                cb: makeStartCallback(i)
            };
            app.on(this.tweenCallbacks[i].event, this.tweenCallbacks[i].cb, this);
        }
    }

    // Resume all paused tweens if the script is enabled
    this.on('enable', function () {
        for (i = 0; i < this.tweens.length; i++) {
            if (this.tweenInstances[i] && this.tweenInstances[i].isPaused()) {
                if (this.tweenInstances[i].isPaused()) {
                    this.tweenInstances[i].resume();
                }
            }
        }
    });

    // Pause all playing tweens if the script is disabled
    this.on('disable', function () {
        for (i = 0; i < this.tweens.length; i++) {
            if (this.tweenInstances[i]) {
                if (this.tweenInstances[i].isPlaying()) {
                    this.tweenInstances[i].pause();
                }
            }
        }
    });

    this.on('attr', function (name, value, prev) {
        for (i = 0; i < this.tweenCallbacks.length; i++) {
            if (this.tweenCallbacks[i]) {
                app.off(this.tweenCallbacks[i].event, this.tweenCallbacks[i].cb, this);
                this.tweenCallbacks[i] = null;
            }
        }

        for (i = 0; i < this.tweens.length; i++) {
            var tween = this.tweens[i];
            if (tween.event.length > 0) {
                this.tweenCallbacks[i] = {
                    event: tween.event,
                    cb: makeStartCallback(i)
                };
                app.on(this.tweenCallbacks[i].event, this.tweenCallbacks[i].cb, this);
            }
        }
    });
};

Tween.prototype.start = function (idx) {
    var app = this.app;
    var tween = this.tweens[idx];

    var easingTypes = ['In', 'Out', 'InOut'];
    var easingFuncs = ['Linear', 'Quadratic', 'Cubic', 'Quartic', 'Quintic', 'Sinusoidal', 'Exponential', 'Circular', 'Elastic', 'Back', 'Bounce'];

    var easingFunc;
    if (tween.easingFunction === 0) {
        easingFunc = TWEEN.Easing[easingFuncs[tween.easingFunction]].None;
    } else {
        easingFunc = TWEEN.Easing[easingFuncs[tween.easingFunction]][easingTypes[tween.easingType]];
    }

    var tweenInstances = this.tweenInstances;
    if (tweenInstances[idx]) {
        tweenInstances[idx].stop();
    }

    var pathSegments = tween.path.split('.');
    var propertyOwner = this.entity;
    for (i = 0; i < pathSegments.length - 1; i++) {
        propertyOwner = propertyOwner[pathSegments[i]];
    }

    var propertyName = pathSegments[pathSegments.length - 1];
    var property = propertyOwner[propertyName];

    var startValue, endValue;
    var isNumber = typeof property === 'number';
    var start = tween.start;
    var end = tween.end;
    if (isNumber) {
        startValue = { x: start.x };
        endValue = { x: end.x };
    } else if (property instanceof pc.Vec2) {
        startValue = new pc.Vec2(start.x, start.y);
        endValue = new pc.Vec2(end.x, end.y);
    } else if (property instanceof pc.Vec3) {
        startValue = new pc.Vec3(start.x, start.y, start.z);
        endValue = new pc.Vec3(end.x, end.y, end.z);
    } else if (property instanceof pc.Vec4) {
        startValue = start.clone();
        endValue = end.clone();
    } else if (property instanceof pc.Color) {
        startValue = new pc.Color(start.x, start.y, start.z, start.w);
        endValue = new pc.Color(end.x, end.y, end.z, end.w);
    } else {
        console.error('ERROR: tween - specified property must be a number, vec2, vec3, vec4 or color');
        return;
    }

    var updateProperty = function (value) {
        // Update the tweened property. Transformation functions are special-cased here.
        switch (propertyName) {
            case 'eulerAngles':
                propertyOwner.setEulerAngles(value);
                break;
            case 'localEulerAngles':
                propertyOwner.setLocalEulerAngles(value);
                break;
            case 'localPosition':
                propertyOwner.setLocalPosition(value);
                break;
            case 'localScale':
                propertyOwner.setLocalScale(value);
                break;
            case 'position':
                propertyOwner.setPosition(value);
                break;
            default:
                propertyOwner[propertyName] = isNumber ? value.x : value;

                if (propertyOwner instanceof pc.Material) {
                    propertyOwner.update();
                }
                break;
        }
    };

    updateProperty(startValue);

    tweenInstances[idx] = new TWEEN.Tween(startValue)
        .to(endValue, tween.duration)
        .easing(easingFunc)
        .onStart(function (obj) {
            if (tween.startEvent !== '') {
                app.fire(tween.startEvent);
            }
        })
        .onStop(function (obj) {
            if (tween.stopEvent !== '') {
                app.fire(tween.stopEvent);
            }
            tweenInstances[idx] = null;
        })
        .onUpdate(function (obj) {
            updateProperty(obj);

            if (tween.updateEvent !== '') {
                app.fire(tween.updateEvent);
            }
        })
        .onComplete(function (obj) {
            if (tween.completeEvent !== '') {
                app.fire(tween.completeEvent);
            }
            tweenInstances[idx] = null;
        })
        .onRepeat(function (obj) {
            if (tween.repeatEvent !== '') {
                app.fire(tween.repeatEvent);
            }
        })
        .repeat(tween.repeat === -1 ? Infinity : tween.repeat)
        .repeatDelay(tween.repeatDelay)
        .yoyo(tween.yoyo)
        .delay(tween.delay)
        .start();
};

// We have to update the tween.js engine somewhere once a frame...
var app = pc.Application.getApplication();
if (app) {
    app.on('update', function (dt) {
        TWEEN.update();
    });
}


<|start_filename|>src/i18n/utils.js<|end_filename|>
import {
    DEFAULT_LOCALE,
    DEFAULT_LOCALE_FALLBACKS
} from './constants.js';

// Maps locale to function that returns the plural index
// based on the CLDR rules. See here for reference
// https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
// and http://unicode.org/reports/tr35/tr35-numbers.html#Operands .
// An initial set of locales is supported and we can keep adding more as we go.
var PLURALS = {};

// Helper function to define the plural function for an array of locales
function definePluralFn(locales, fn) {
    for (var i = 0, len = locales.length; i < len; i++) {
        PLURALS[locales[i]] = fn;
    }
}

// Gets the language portion form a locale
function getLang(locale) {
    var idx = locale.indexOf('-');
    if (idx !== -1) {
        return locale.substring(0, idx);
    }

    return locale;
}

// Replaces the language in the specified locale and returns the result
function replaceLang(locale, desiredLang) {
    var idx = locale.indexOf('-');
    if (idx !== -1) {
        return desiredLang + locale.substring(idx);
    }

    return desiredLang;
}

function findAvailableLocale(desiredLocale, availableLocales) {
    if (availableLocales[desiredLocale]) {
        return desiredLocale;
    }

    var fallback = DEFAULT_LOCALE_FALLBACKS[desiredLocale];
    if (fallback && availableLocales[fallback]) {
        return fallback;
    }

    var lang = getLang(desiredLocale);

    fallback = DEFAULT_LOCALE_FALLBACKS[lang];
    if (availableLocales[fallback]) {
        return fallback;
    }

    if (availableLocales[lang]) {
        return lang;
    }

    return DEFAULT_LOCALE;
}

// Only OTHER
definePluralFn([
    'ja',
    'ko',
    'th',
    'vi',
    'zh',
    'id'
], function (n) {
    return 0;
});

// ONE, OTHER
definePluralFn([
    'fa',
    'hi'
], function (n) {
    if (n >= 0 && n <= 1) {
        return 0; // one
    }

    return 1; // other
});

// from Unicode rules: i = 0..1
definePluralFn([
    'fr',
    'pt'
], function (n) {
    if (n >= 0 && n < 2) {
        return 0; // one
    }

    return 1; // other
});

// danish
definePluralFn([
    'da'
], function (n) {
    if (n === 1 || !Number.isInteger(n) && n >= 0 && n <= 1) {
        return 0; // one
    }

    return 1; // other
});

definePluralFn([
    'de',
    'en',
    'it',
    'el',
    'es',
    'tr',
    'fi',
    'sv',
    'nb',
    'no',
    'ur'
], function (n) {
    if (n === 1)  {
        return 0; // one
    }

    return 1; // other
});

// ONE, FEW, MANY, OTHER
definePluralFn([
    'ru',
    'uk'
], function (n) {
    if (Number.isInteger(n)) {
        var mod10 = n % 10;
        var mod100 = n % 100;

        if (mod10 === 1 && mod100 !== 11) {
            return 0; // one
        } else if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) {
            return 1; // few
        } else if (mod10 === 0 || mod10 >= 5 && mod10 <= 9 || mod100 >= 11 && mod100 <= 14) {
            return 2; // many
        }
    }

    return 3; // other
});

// polish
definePluralFn([
    'pl'
], function (n) {
    if (Number.isInteger(n)) {
        if (n === 1) {
            return 0; // one
        }
        var mod10 = n % 10;
        var mod100 = n % 100;

        if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) {
            return 1; // few
        } else if (mod10 >= 0 && mod10 <= 1 || mod10 >= 5 && mod10 <= 9 || mod100 >= 12 && mod100 <= 14) {
            return 2; // many
        }
    }

    return 3; // other
});

// ZERO, ONE, TWO, FEW, MANY, OTHER
definePluralFn([
    'ar'
], function (n) {
    if (n === 0)  {
        return 0; // zero
    } else if (n === 1) {
        return 1; // one
    } else if (n === 2) {
        return 2; // two
    }

    if (Number.isInteger(n)) {
        var mod100 = n % 100;
        if (mod100 >= 3 && mod100 <= 10) {
            return 3; // few
        } else if (mod100 >= 11 && mod100 <= 99) {
            return 4; // many
        }
    }

    return 5; // other
});

var DEFAULT_PLURAL_FN = PLURALS[getLang(DEFAULT_LOCALE)];

// Gets the function that converts to plural for a language
function getPluralFn(lang) {
    return PLURALS[lang] || DEFAULT_PLURAL_FN;
}

export {
    replaceLang,
    getLang,
    getPluralFn,
    findAvailableLocale
};


<|start_filename|>src/xr/xr-dom-overlay.js<|end_filename|>
import { platform } from "../core/platform.js";

/**
 * @class
 * @name XrDomOverlay
 * @classdesc DOM Overlay provides the ability to use DOM elements as an overlay in a WebXR AR session. It requires that the root DOM element is provided for session start. That way, input source select events are first tested against DOM Elements and then propagated down to the XR Session. If this propagation is not desirable, use the `beforexrselect` event on a DOM element and the `preventDefault` function to stop propagation.
 * @description DOM Overlay provides the ability to use DOM elements as an overlay in a WebXR AR session. It requires that the root DOM element is provided for session start. That way, input source select events are first tested against DOM Elements and then propagated down to the XR Session. If this propagation is not desirable, use the `beforexrselect` event on a DOM element and the `preventDefault` function to stop propagation.
 * @hideconstructor
 * @param {XrManager} manager - WebXR Manager.
 * @property {boolean} supported True if DOM Overlay is supported.
 * @property {boolean} available True if DOM Overlay is available. It can only be available if it is supported, during a valid WebXR session and if a valid root element is provided.
 * @property {string|null} state State of the DOM Overlay, which defines how the root DOM element is rendered. Possible options:
 *
 * * screen: Screen - indicates that the DOM element is covering whole physical screen, matching XR viewports.
 * * floating: Floating - indicates that the underlying platform renders the DOM element as floating in space, which can move during the WebXR session or allow the application to move the element.
 * * head-locked: Head Locked - indicates that the DOM element follows the user's head movement consistently, appearing similar to a helmet heads-up display.
 *
 * @example
 * app.xr.domOverlay.root = element;
 * app.xr.start(camera, pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR);
 * @example
 * // Disable input source firing `select` event when some descendant element of DOM overlay root is touched/clicked.
 * // This is useful when the user interacts with UI elements and there should not be `select` events behind UI.
 * someElement.addEventListener('beforexrselect', function (evt) {
 *     evt.preventDefault();
 * });
 */
class XrDomOverlay {
    constructor(manager) {
        this._manager = manager;
        this._supported = platform.browser && !!window.XRDOMOverlayState;
        this._root = null;
    }

    get supported() {
        return this._supported;
    }

    get available() {
        return this._supported && this._manager.active && this._manager._session.domOverlayState !== null;
    }

    get state() {
        if (! this._supported || ! this._manager.active || ! this._manager._session.domOverlayState)
            return null;

        return this._manager._session.domOverlayState.type;
    }

    /**
     * @name XrDomOverlay#root
     * @type {object|null}
     * @description The DOM element to be used as the root for DOM Overlay. Can be changed only outside of an active WebXR session.
     * @example
     * app.xr.domOverlay.root = element;
     * app.xr.start(camera, pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR);
     */
    set root(value) {
        if (! this._supported || this._manager.active)
            return;

        this._root = value;
    }

    get root() {
        return this._root;
    }
}

export { XrDomOverlay };


<|start_filename|>src/graphics/program-lib/chunks/lightmapDir.frag<|end_filename|>
uniform sampler2D texture_lightMap;
uniform sampler2D texture_dirLightMap;

void addLightMap() {
    vec3 color = $texture2DSAMPLE(texture_lightMap, $UV).$CH;
    vec3 dir = texture2D(texture_dirLightMap, $UV).xyz;
    if (dot(dir, vec3(1.0)) < 0.00001) {
        dDiffuseLight += color;
    } else {
        dLightDirNormW = normalize(dir * 2.0 - vec3(1.0));

        float vlight = saturate(dot(dLightDirNormW, -dVertexNormalW));
        float flight = saturate(dot(dLightDirNormW, -dNormalW));
        float nlight = (flight / max(vlight, 0.01)) * 0.5;

        dDiffuseLight += color * nlight * 2.0;
    }

    dSpecularLight += color * getLightSpecular();
}


<|start_filename|>src/framework/components/rigid-body/data.js<|end_filename|>
/**
 * @private
 * @class
 * @name RigidBodyComponentData
 * @classdesc Contains data for the RigidBodyComponent.
 * @description Create a new data structure for a RigidBodyComponent.
 */
class RigidBodyComponentData {
    constructor() {
        this.enabled = true;
    }
}

export { RigidBodyComponentData };


<|start_filename|>src/graphics/program-lib/chunks/base.vert<|end_filename|>
attribute vec3 vertex_position;
attribute vec3 vertex_normal;
attribute vec4 vertex_tangent;
attribute vec2 vertex_texCoord0;
attribute vec2 vertex_texCoord1;
attribute vec4 vertex_color;

uniform mat4 matrix_viewProjection;
uniform mat4 matrix_model;
uniform mat3 matrix_normal;

vec3 dPositionW;
mat4 dModelMatrix;
mat3 dNormalMatrix;


<|start_filename|>src/graphics/program-lib/chunks/ambientPrefilteredCube.frag<|end_filename|>
#ifndef PMREM4
#define PMREM4
uniform samplerCube texture_prefilteredCubeMap4;
#endif

void addAmbient() {
    vec3 fixedReflDir = fixSeamsStatic(cubeMapRotate(dNormalW), 1.0 - 1.0 / 4.0);
#ifndef RIGHT_HANDED_CUBEMAP
    fixedReflDir.x *= -1.0;
#endif
    dDiffuseLight += processEnvironment($DECODE(textureCube(texture_prefilteredCubeMap4, fixedReflDir)).rgb);
}


<|start_filename|>src/scene/skin.js<|end_filename|>
/**
 * @class
 * @name Skin
 * @classdesc A skin contains data about the bones in a hierarchy that drive a skinned mesh animation.
 * Specifically, the skin stores the bone name and inverse bind matrix and for each bone.
 * Inverse bind matrices are instrumental in the mathematics of vertex skinning.
 * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this skin.
 * @param {Mat4[]} ibp - The array of inverse bind matrices.
 * @param {string[]} boneNames - The array of bone names for the bones referenced by this skin.
 */

class Skin {
    constructor(graphicsDevice, ibp, boneNames) {
        // Constant between clones
        this.device = graphicsDevice;
        this.inverseBindPose = ibp;
        this.boneNames = boneNames;
    }
}

export { Skin };


<|start_filename|>src/graphics/program-lib/chunks/startNineSliced.frag<|end_filename|>
    nineSlicedUv = vUv0;
    nineSlicedUv.y = 1.0 - nineSlicedUv.y;
    


<|start_filename|>src/graphics/program-lib/chunks/screenDepth.frag<|end_filename|>
uniform highp sampler2D uDepthMap;

#ifndef SCREENSIZE
#define SCREENSIZE
uniform vec4 uScreenSize;
#endif

#ifndef VIEWMATRIX
#define VIEWMATRIX
uniform mat4 matrix_view;
#endif

#ifndef CAMERAPLANES
#define CAMERAPLANES
uniform vec4 camera_params; // 1 / camera_far,      camera_far,     (1 - f / n) / 2,        (1 + f / n) / 2
#endif

#ifdef GL2
float linearizeDepth(float z) {
    z = z * 2.0 - 1.0;
    return 1.0 / (camera_params.z * z + camera_params.w);
}
#else
#ifndef UNPACKFLOAT
#define UNPACKFLOAT
float unpackFloat(vec4 rgbaDepth) {
    const vec4 bitShift = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0);
    return dot(rgbaDepth, bitShift);
}
#endif
#endif

// Retrieves rendered linear camera depth by UV
float getLinearScreenDepth(vec2 uv) {
    #ifdef GL2
    return linearizeDepth(texture2D(uDepthMap, uv).r) * camera_params.y;
    #else
    return unpackFloat(texture2D(uDepthMap, uv)) * camera_params.y;
    #endif
}

#ifndef VERTEXSHADER
// Retrieves rendered linear camera depth under the current pixel
float getLinearScreenDepth() {
    vec2 uv = gl_FragCoord.xy * uScreenSize.zw;
    return getLinearScreenDepth(uv);
}
#endif

// Generates linear camera depth for the given world position
float getLinearDepth(vec3 pos) {
    return -(matrix_view * vec4(pos, 1.0)).z;
}


<|start_filename|>src/scene/render.js<|end_filename|>
import { EventHandler } from '../core/event-handler.js';

/**
 * @event
 * @private
 * @name Render#set:meshes
 * @description Fired when the meshes are set
 * @param {Mesh[]} meshes - The meshes
 */

/**
 * @class
 * @private
 * @name Render
 * @augments EventHandler
 * @classdesc A render contains an array of meshes that are referenced by a single hierarchy node in a GLB model, and are accessible using {@link ContainerResource#renders} property. The render is the resource of a Render Asset.
 * @description Create a new Render. These are usually created by the GLB loader and not created by hand.
 * @property {Mesh[]} meshes The meshes that the render contains
 */
class Render extends EventHandler {
    constructor() {
        super();

        // meshes are reference counted, and this class owns the references and is responsible
        // for releasing the meshes when they are no longer referenced
        this._meshes = null;
    }

    destroy() {
        this.meshes = null;
    }

    // decrement references to meshes, destroy the ones with zero references
    decRefMeshes() {
        if (this._meshes) {
            const count = this._meshes.length;
            for (let i = 0; i < count; i++) {
                const mesh = this._meshes[i];
                if (mesh) {
                    mesh.decRefCount();
                    if (mesh.getRefCount() < 1) {
                        mesh.destroy();
                        this._meshes[i] = null;
                    }
                }
            }
        }
    }

    // increments ref count on all meshes
    incRefMeshes() {
        if (this._meshes) {
            const count = this._meshes.length;
            for (let i = 0; i < count; i++) {
                if (this._meshes[i]) {
                    this._meshes[i].incRefCount();
                }
            }
        }
    }

    get meshes() {
        return this._meshes;
    }

    set meshes(value) {

        // decrement references on the existing meshes
        this.decRefMeshes();

        // assign new meshes
        this._meshes = value;
        this.incRefMeshes();

        this.fire('set:meshes', value);
    }
}

export { Render };


<|start_filename|>src/framework/components/joint/constants.js<|end_filename|>
/**
 * @private
 * @constant
 * @type {string}
 * @name MOTION_FREE
 * @description Specified degree of freedom has free movement.
 */
export const MOTION_FREE = 'free';
/**
 * @private
 * @constant
 * @type {string}
 * @name MOTION_LIMITED
 * @description Specified degree of freedom has limited movement.
 */
export const MOTION_LIMITED = 'limited';
/**
 * @private
 * @constant
 * @type {string}
 * @name MOTION_LOCKED
 * @description Specified degree of freedom is locked and allows no movement.
 */
export const MOTION_LOCKED = 'locked';


<|start_filename|>src/input/constants.js<|end_filename|>
export const ACTION_MOUSE = 'mouse';
export const ACTION_KEYBOARD = 'keyboard';
export const ACTION_GAMEPAD = 'gamepad';

export const AXIS_MOUSE_X = 'mousex';
export const AXIS_MOUSE_Y = 'mousey';
export const AXIS_PAD_L_X = 'padlx';
export const AXIS_PAD_L_Y = 'padly';
export const AXIS_PAD_R_X = 'padrx';
export const AXIS_PAD_R_Y = 'padry';
export const AXIS_KEY = 'key';

/**
 * @constant
 * @type {string}
 * @name EVENT_KEYDOWN
 * @description Name of event fired when a key is pressed.
 */
export const EVENT_KEYDOWN = 'keydown';
/**
 * @constant
 * @type {string}
 * @name EVENT_KEYUP
 * @description Name of event fired when a key is released.
 */
export const EVENT_KEYUP = 'keyup';

/**
 * @constant
 * @type {string}
 * @name EVENT_MOUSEDOWN
 * @description Name of event fired when a mouse button is pressed.
 */
export const EVENT_MOUSEDOWN = "mousedown";
/**
 * @constant
 * @type {string}
 * @name EVENT_MOUSEMOVE
 * @description Name of event fired when the mouse is moved.
 */
export const EVENT_MOUSEMOVE = "mousemove";
/**
 * @constant
 * @type {string}
 * @name EVENT_MOUSEUP
 * @description Name of event fired when a mouse button is released.
 */
export const EVENT_MOUSEUP = "mouseup";
/**
 * @constant
 * @type {string}
 * @name EVENT_MOUSEWHEEL
 * @description Name of event fired when the mouse wheel is rotated.
 */
export const EVENT_MOUSEWHEEL = "mousewheel";

/**
 * @constant
 * @type {string}
 * @name EVENT_TOUCHSTART
 * @description Name of event fired when a new touch occurs. For example, a finger is placed on the device.
 */
export const EVENT_TOUCHSTART = 'touchstart';
/**
 * @constant
 * @type {string}
 * @name EVENT_TOUCHEND
 * @description Name of event fired when touch ends. For example, a finger is lifted off the device.
 */
export const EVENT_TOUCHEND = 'touchend';
/**
 * @constant
 * @type {string}
 * @name EVENT_TOUCHMOVE
 * @description Name of event fired when a touch moves.
 */
export const EVENT_TOUCHMOVE = 'touchmove';
/**
 * @constant
 * @type {string}
 * @name EVENT_TOUCHCANCEL
 * @description Name of event fired when a touch point is interrupted in some way.
 * The exact reasons for canceling a touch can vary from device to device.
 * For example, a modal alert pops up during the interaction; the touch point leaves the document area,
 * or there are more touch points than the device supports, in which case the earliest touch point is canceled.
 */
export const EVENT_TOUCHCANCEL = 'touchcancel';

/**
 * @constant
 * @type {string}
 * @name EVENT_SELECT
 * @description Name of event fired when a new xr select occurs. For example, primary trigger was pressed.
 */
export const EVENT_SELECT = 'select';
/**
 * @constant
 * @type {string}
 * @name EVENT_SELECTSTART
 * @description Name of event fired when a new xr select starts. For example, primary trigger is now pressed.
 */
export const EVENT_SELECTSTART = 'selectstart';
/**
 * @constant
 * @type {string}
 * @name EVENT_SELECTEND
 * @description Name of event fired when xr select ends. For example, a primary trigger is now released.
 */
export const EVENT_SELECTEND = 'selectend';

/**
 * @constant
 * @type {number}
 * @name KEY_BACKSPACE
 */
export const KEY_BACKSPACE = 8;
/**
 * @constant
 * @type {number}
 * @name KEY_TAB
 */
export const KEY_TAB = 9;
/**
 * @constant
 * @type {number}
 * @name KEY_RETURN
 */
export const KEY_RETURN = 13;
/**
 * @constant
 * @type {number}
 * @name KEY_ENTER
 */
export const KEY_ENTER = 13;
/**
 * @constant
 * @type {number}
 * @name KEY_SHIFT
 */
export const KEY_SHIFT = 16;
/**
 * @constant
 * @type {number}
 * @name KEY_CONTROL
 */
export const KEY_CONTROL = 17;
/**
 * @constant
 * @type {number}
 * @name KEY_ALT
 */
export const KEY_ALT = 18;
/**
 * @constant
 * @type {number}
 * @name KEY_PAUSE
 */
export const KEY_PAUSE = 19;
/**
 * @constant
 * @type {number}
 * @name KEY_CAPS_LOCK
 */
export const KEY_CAPS_LOCK = 20;
/**
 * @constant
 * @type {number}
 * @name KEY_ESCAPE
 */
export const KEY_ESCAPE = 27;
/**
 * @constant
 * @type {number}
 * @name KEY_SPACE
 */
export const KEY_SPACE = 32;
/**
 * @constant
 * @type {number}
 * @name KEY_PAGE_UP
 */
export const KEY_PAGE_UP = 33;
/**
 * @constant
 * @type {number}
 * @name KEY_PAGE_DOWN
 */
export const KEY_PAGE_DOWN = 34;
/**
 * @constant
 * @type {number}
 * @name KEY_END
 */
export const KEY_END = 35;
/**
 * @constant
 * @type {number}
 * @name KEY_HOME
 */
export const KEY_HOME = 36;
/**
 * @constant
 * @type {number}
 * @name KEY_LEFT
 */
export const KEY_LEFT = 37;
/**
 * @constant
 * @type {number}
 * @name KEY_UP
 */
export const KEY_UP = 38;
/**
 * @constant
 * @type {number}
 * @name KEY_RIGHT
 */
export const KEY_RIGHT = 39;
/**
 * @constant
 * @type {number}
 * @name KEY_DOWN
 */
export const KEY_DOWN = 40;
/**
 * @constant
 * @type {number}
 * @name KEY_PRINT_SCREEN
 */
export const KEY_PRINT_SCREEN = 44;
/**
 * @constant
 * @type {number}
 * @name KEY_INSERT
 */
export const KEY_INSERT = 45;
/**
 * @constant
 * @type {number}
 * @name KEY_DELETE
 */
export const KEY_DELETE = 46;
/**
 * @constant
 * @type {number}
 * @name KEY_0
 */
export const KEY_0 = 48;
/**
 * @constant
 * @type {number}
 * @name KEY_1
 */
export const KEY_1 = 49;
/**
 * @constant
 * @type {number}
 * @name KEY_2
 */
export const KEY_2 = 50;
/**
 * @constant
 * @type {number}
 * @name KEY_3
 */
export const KEY_3 = 51;
/**
 * @constant
 * @type {number}
 * @name KEY_4
 */
export const KEY_4 = 52;
/**
 * @constant
 * @type {number}
 * @name KEY_5
 */
export const KEY_5 = 53;
/**
 * @constant
 * @type {number}
 * @name KEY_6
 */
export const KEY_6 = 54;
/**
 * @constant
 * @type {number}
 * @name KEY_7
 */
export const KEY_7 = 55;
/**
 * @constant
 * @type {number}
 * @name KEY_8
 */
export const KEY_8 = 56;
/**
 * @constant
 * @type {number}
 * @name KEY_9
 */
export const KEY_9 = 57;

/**
 * @constant
 * @type {number}
 * @name KEY_SEMICOLON
 */
export const KEY_SEMICOLON = 59;
/**
 * @constant
 * @type {number}
 * @name KEY_EQUAL
 */
export const KEY_EQUAL = 61;

/**
 * @constant
 * @type {number}
 * @name KEY_A
 */
export const KEY_A = 65;
/**
 * @constant
 * @type {number}
 * @name KEY_B
 */
export const KEY_B = 66;
/**
 * @constant
 * @type {number}
 * @name KEY_C
 */
export const KEY_C = 67;
/**
 * @constant
 * @type {number}
 * @name KEY_D
 */
export const KEY_D = 68;
/**
 * @constant
 * @type {number}
 * @name KEY_E
 */
export const KEY_E = 69;
/**
 * @constant
 * @type {number}
 * @name KEY_F
 */
export const KEY_F = 70;
/**
 * @constant
 * @type {number}
 * @name KEY_G
 */
export const KEY_G = 71;
/**
 * @constant
 * @type {number}
 * @name KEY_H
 */
export const KEY_H = 72;
/**
 * @constant
 * @type {number}
 * @name KEY_I
 */
export const KEY_I = 73;
/**
 * @constant
 * @type {number}
 * @name KEY_J
 */
export const KEY_J = 74;
/**
 * @constant
 * @type {number}
 * @name KEY_K
 */
export const KEY_K = 75;
/**
 * @constant
 * @type {number}
 * @name KEY_L
 */
export const KEY_L = 76;
/**
 * @constant
 * @type {number}
 * @name KEY_M
 */
export const KEY_M = 77;
/**
 * @constant
 * @type {number}
 * @name KEY_N
 */
export const KEY_N = 78;
/**
 * @constant
 * @type {number}
 * @name KEY_O
 */
export const KEY_O = 79;
/**
 * @constant
 * @type {number}
 * @name KEY_P
 */
export const KEY_P = 80;
/**
 * @constant
 * @type {number}
 * @name KEY_Q
 */
export const KEY_Q = 81;
/**
 * @constant
 * @type {number}
 * @name KEY_R
 */
export const KEY_R = 82;
/**
 * @constant
 * @type {number}
 * @name KEY_S
 */
export const KEY_S = 83;
/**
 * @constant
 * @type {number}
 * @name KEY_T
 */
export const KEY_T = 84;
/**
 * @constant
 * @type {number}
 * @name KEY_U
 */
export const KEY_U = 85;
/**
 * @constant
 * @type {number}
 * @name KEY_V
 */
export const KEY_V = 86;
/**
 * @constant
 * @type {number}
 * @name KEY_W
 */
export const KEY_W = 87;
/**
 * @constant
 * @type {number}
 * @name KEY_X
 */
export const KEY_X = 88;
/**
 * @constant
 * @type {number}
 * @name KEY_Y
 */
export const KEY_Y = 89;
/**
 * @constant
 * @type {number}
 * @name KEY_Z
 */
export const KEY_Z = 90;

/**
 * @constant
 * @type {number}
 * @name KEY_WINDOWS
 */
export const KEY_WINDOWS = 91;

/**
 * @constant
 * @type {number}
 * @name KEY_CONTEXT_MENU
 */
export const KEY_CONTEXT_MENU = 93;

/**
 * @constant
 * @type {number}
 * @name KEY_NUMPAD_0
 */
export const KEY_NUMPAD_0 = 96;
/**
 * @constant
 * @type {number}
 * @name KEY_NUMPAD_1
 */
export const KEY_NUMPAD_1 = 97;
/**
 * @constant
 * @type {number}
 * @name KEY_NUMPAD_2
 */
export const KEY_NUMPAD_2 = 98;
/**
 * @constant
 * @type {number}
 * @name KEY_NUMPAD_3
 */
export const KEY_NUMPAD_3 = 99;
/**
 * @constant
 * @type {number}
 * @name KEY_NUMPAD_4
 */
export const KEY_NUMPAD_4 = 100;
/**
 * @constant
 * @type {number}
 * @name KEY_NUMPAD_5
 */
export const KEY_NUMPAD_5 = 101;
/**
 * @constant
 * @type {number}
 * @name KEY_NUMPAD_6
 */
export const KEY_NUMPAD_6 = 102;
/**
 * @constant
 * @type {number}
 * @name KEY_NUMPAD_7
 */
export const KEY_NUMPAD_7 = 103;
/**
 * @constant
 * @type {number}
 * @name KEY_NUMPAD_8
 */
export const KEY_NUMPAD_8 = 104;
/**
 * @constant
 * @type {number}
 * @name KEY_NUMPAD_9
 */
export const KEY_NUMPAD_9 = 105;

/**
 * @constant
 * @type {number}
 * @name KEY_MULTIPLY
 */
export const KEY_MULTIPLY = 106;
/**
 * @constant
 * @type {number}
 * @name KEY_ADD
 */
export const KEY_ADD = 107;
/**
 * @constant
 * @type {number}
 * @name KEY_SEPARATOR
 */
export const KEY_SEPARATOR = 108;
/**
 * @constant
 * @type {number}
 * @name KEY_SUBTRACT
 */
export const KEY_SUBTRACT = 109;
/**
 * @constant
 * @type {number}
 * @name KEY_DECIMAL
 */
export const KEY_DECIMAL = 110;
/**
 * @constant
 * @type {number}
 * @name KEY_DIVIDE
 */
export const KEY_DIVIDE = 111;

/**
 * @constant
 * @type {number}
 * @name KEY_F1
 */
export const KEY_F1 = 112;
/**
 * @constant
 * @type {number}
 * @name KEY_F2
 */
export const KEY_F2 = 113;
/**
 * @constant
 * @type {number}
 * @name KEY_F3
 */
export const KEY_F3 = 114;
/**
 * @constant
 * @type {number}
 * @name KEY_F4
 */
export const KEY_F4 = 115;
/**
 * @constant
 * @type {number}
 * @name KEY_F5
 */
export const KEY_F5 = 116;
/**
 * @constant
 * @type {number}
 * @name KEY_F6
 */
export const KEY_F6 = 117;
/**
 * @constant
 * @type {number}
 * @name KEY_F7
 */
export const KEY_F7 = 118;
/**
 * @constant
 * @type {number}
 * @name KEY_F8
 */
export const KEY_F8 = 119;
/**
 * @constant
 * @type {number}
 * @name KEY_F9
 */
export const KEY_F9 = 120;
/**
 * @constant
 * @type {number}
 * @name KEY_F10
 */
export const KEY_F10 = 121;
/**
 * @constant
 * @type {number}
 * @name KEY_F11
 */
export const KEY_F11 = 122;
/**
 * @constant
 * @type {number}
 * @name KEY_F12
 */
export const KEY_F12 = 123;

/**
 * @constant
 * @type {number}
 * @name KEY_COMMA
 */
export const KEY_COMMA = 188;
/**
 * @constant
 * @type {number}
 * @name KEY_PERIOD
 */
export const KEY_PERIOD = 190;
/**
 * @constant
 * @type {number}
 * @name KEY_SLASH
 */
export const KEY_SLASH = 191;
/**
 * @constant
 * @type {number}
 * @name KEY_OPEN_BRACKET
 */
export const KEY_OPEN_BRACKET = 219;
/**
 * @constant
 * @type {number}
 * @name KEY_BACK_SLASH
 */
export const KEY_BACK_SLASH = 220;
/**
 * @constant
 * @type {number}
 * @name KEY_CLOSE_BRACKET
 */
export const KEY_CLOSE_BRACKET = 221;

/**
 * @constant
 * @type {number}
 * @name KEY_META
 */
export const KEY_META = 224;

/**
 * @constant
 * @type {number}
 * @name MOUSEBUTTON_NONE
 * @description No mouse buttons pressed.
 */
export const MOUSEBUTTON_NONE = -1;
/**
 * @constant
 * @type {number}
 * @name MOUSEBUTTON_LEFT
 * @description The left mouse button.
 */
export const MOUSEBUTTON_LEFT = 0;
/**
 * @constant
 * @type {number}
 * @name MOUSEBUTTON_MIDDLE
 * @description The middle mouse button.
 */
export const MOUSEBUTTON_MIDDLE = 1;
/**
 * @constant
 * @type {number}
 * @name MOUSEBUTTON_RIGHT
 * @description The right mouse button.
 */
export const MOUSEBUTTON_RIGHT = 2;

/**
 * @constant
 * @type {number}
 * @name PAD_1
 * @description Index for pad 1.
 */
export const PAD_1 = 0;
/**
 * @constant
 * @type {number}
 * @name PAD_2
 * @description Index for pad 2.
 */
export const PAD_2 = 1;
/**
 * @constant
 * @type {number}
 * @name PAD_3
 * @description Index for pad 3.
 */
export const PAD_3 = 2;
/**
 * @constant
 * @type {number}
 * @name PAD_4
 * @description Index for pad 4.
 */
export const PAD_4 = 3;

/**
 * @constant
 * @type {number}
 * @name PAD_FACE_1
 * @description The first face button, from bottom going clockwise.
 */
export const PAD_FACE_1 = 0;
/**
 * @constant
 * @type {number}
 * @name PAD_FACE_2
 * @description The second face button, from bottom going clockwise.
 */
export const PAD_FACE_2 = 1;
/**
 * @constant
 * @type {number}
 * @name PAD_FACE_3
 * @description The third face button, from bottom going clockwise.
 */
export const PAD_FACE_3 = 2;
/**
 * @constant
 * @type {number}
 * @name PAD_FACE_4
 * @description The fourth face button, from bottom going clockwise.
 */
export const PAD_FACE_4 = 3;

/**
 * @constant
 * @type {number}
 * @name PAD_L_SHOULDER_1
 * @description The first shoulder button on the left.
 */
export const PAD_L_SHOULDER_1 = 4;
/**
 * @constant
 * @type {number}
 * @name PAD_R_SHOULDER_1
 * @description The first shoulder button on the right.
 */
export const PAD_R_SHOULDER_1 = 5;
/**
 * @constant
 * @type {number}
 * @name PAD_L_SHOULDER_2
 * @description The second shoulder button on the left.
 */
export const PAD_L_SHOULDER_2 = 6;
/**
 * @constant
 * @type {number}
 * @name PAD_R_SHOULDER_2
 * @description The second shoulder button on the right.
 */
export const PAD_R_SHOULDER_2 = 7;

/**
 * @constant
 * @type {number}
 * @name PAD_SELECT
 * @description The select button.
 */
export const PAD_SELECT = 8;
/**
 * @constant
 * @type {number}
 * @name PAD_START
 * @description The start button.
 */
export const PAD_START = 9;

/**
 * @constant
 * @type {number}
 * @name PAD_L_STICK_BUTTON
 * @description The button when depressing the left analogue stick.
 */
export const PAD_L_STICK_BUTTON = 10;
/**
 * @constant
 * @type {number}
 * @name PAD_R_STICK_BUTTON
 * @description The button when depressing the right analogue stick.
 */
export const PAD_R_STICK_BUTTON = 11;

/**
 * @constant
 * @type {number}
 * @name PAD_UP
 * @description Direction pad up.
 */
export const PAD_UP = 12;
/**
 * @constant
 * @type {number}
 * @name PAD_DOWN
 * @description Direction pad down.
 */
export const PAD_DOWN = 13;
/**
 * @constant
 * @type {number}
 * @name PAD_LEFT
 * @description Direction pad left.
 */
export const PAD_LEFT = 14;
/**
 * @constant
 * @type {number}
 * @name PAD_RIGHT
 * @description Direction pad right.
 */
export const PAD_RIGHT = 15;

/**
 * @constant
 * @type {number}
 * @name PAD_VENDOR
 * @description Vendor specific button.
 */
export const PAD_VENDOR = 16;

/**
 * @constant
 * @type {number}
 * @name PAD_L_STICK_X
 * @description Horizontal axis on the left analogue stick.
 */
export const PAD_L_STICK_X = 0;
/**
 * @constant
 * @type {number}
 * @name PAD_L_STICK_Y
 * @description Vertical axis on the left analogue stick.
 */
export const PAD_L_STICK_Y = 1;
/**
 * @constant
 * @type {number}
 * @name PAD_R_STICK_X
 * @description Horizontal axis on the right analogue stick.
 */
export const PAD_R_STICK_X = 2;
/**
 * @constant
 * @type {number}
 * @name PAD_R_STICK_Y
 * @description Vertical axis on the right analogue stick.
 */
export const PAD_R_STICK_Y = 3;


<|start_filename|>tests/math/test_vec4.js<|end_filename|>
describe("pc.Vec4", function () {
    it("x", function() {
        var v1 = new pc.Vec4(2, 4, 6, 8);
        equal(2, v1.x);
    });

    it("y", function() {
        var v1 = new pc.Vec4(2, 4, 6, 8);
        equal(4, v1.y);
    });

    it("z", function() {
        var v1 = new pc.Vec4(2, 4, 6, 8);
        equal(6, v1.z);
    });

    it("w", function() {
        var v1 = new pc.Vec4(2, 4, 6, 8);
        equal(8, v1.w);
    });

    it("add", function() {
        var v1 = new pc.Vec4(2, 4, 6, 8);
        var v2 = new pc.Vec4(1, 2, 3, 4);

        v1.add(v2);

        equal(3, v1.x);
        equal(6, v1.y);
        equal(9, v1.z);
        equal(12, v1.w);
    });

    it("add2", function() {
        var v1 = new pc.Vec4(2, 4, 6, 8);
        var v2 = new pc.Vec4(1, 2, 3, 4);
        var r = new pc.Vec4();

        r.add2(v1, v2);

        equal(3, r.x);
        equal(6, r.y);
        equal(9, r.z);
        equal(12, r.w);
    });

    it("addScalar", function() {
        var v = new pc.Vec4(2, 4, 6, 8);

        v.addScalar(2);

        equal(4, v.x);
        equal(6, v.y);
        equal(8, v.z);
        equal(10, v.w);
    });

    it("clone", function () {
        var v1 = new pc.Vec4();
        var v2 = v1.clone();

        ok(typeof v2 === "object");

        equal(v1.x, v2.x);
        equal(v1.y, v2.y);
        equal(v1.z, v2.z);
        equal(v1.w, v2.w);
    });

    it("copy", function () {
        var v1 = new pc.Vec4(2, 4, 6, 8);
        var v2 = new pc.Vec4();

        v2.copy(v1);

        equal(2, v2.x);
        equal(4, v2.y);
        equal(6, v2.z);
        equal(8, v2.w);
    });

    it("constructor: no args", function () {
        var v = new pc.Vec4();

        equal(v.x, 0);
        equal(v.y, 0);
        equal(v.z, 0);
        equal(v.w, 0);
    });


    it("constructor: args", function() {
        var v = new pc.Vec4(1, 2, 3, 4);

        equal(1, v.x);
        equal(2, v.y);
        equal(3, v.z);
        equal(4, v.w);
    });

    it("div", function() {
        var v1 = new pc.Vec4(9, 16, 25, 36);
        var v2 = new pc.Vec4(3, 4, 5, 6);
        v1.div(v2);

        equal(3, v1.x);
        equal(4, v1.y);
        equal(5, v1.z);
        equal(6, v1.w);
    });

    it("div2", function() {
        var v1 = new pc.Vec4(9, 16, 25, 36);
        var v2 = new pc.Vec4(3, 4, 5, 6);
        var r = new pc.Vec4();
        r.div2(v1, v2);

        equal(3, r.x);
        equal(4, r.y);
        equal(5, r.z);
        equal(6, r.w);
    });

    it("divScalar", function() {
        var v = new pc.Vec4(3, 6, 9, 12);
        v.divScalar(3);

        equal(1, v.x);
        equal(2, v.y);
        equal(3, v.z);
        equal(4, v.w);
    });

    it("dot", function() {
        var v1 = new pc.Vec4(1, 2, 3, 4);
        var v2 = new pc.Vec4(5, 6, 7, 8);

        var r = v1.dot(v2);

        equal(r, 70);
    });

    it("equals", function() {
        var v1 = new pc.Vec4(1, 2, 3, 4);
        var v2 = new pc.Vec4(5, 6, 7, 8);

        var e = v1.equals(v2);
        equal(false, e);

        var e = v1.equals(v1);
        equal(true, e);
    });

    it("length", function() {
        var v = new pc.Vec4(0, 3, 4, 0);
        var l = v.length();
        equal(5, l);
    });

    it("lengthSq", function() {
        var v = new pc.Vec4(0, 3, 4, 0);
        var l = v.lengthSq();
        equal(25, l);
    });

    it("lerp", function() {
        var v1 = new pc.Vec4(1, 2, 3, 4);
        var v2 = new pc.Vec4(5, 6, 7, 8);
        var r = new pc.Vec4();

        r.lerp(v1, v2, 0);

        equal(v1.x, r.x);
        equal(v1.y, r.y);
        equal(v1.z, r.z);
        equal(v1.w, r.w);

        r.lerp(v1, v2, 1);

        equal(v2.x, r.x);
        equal(v2.y, r.y);
        equal(v2.z, r.z);
        equal(v2.w, r.w);
    });

    it("mul", function() {
        var v1 = new pc.Vec4(1, 2, 3, 4);
        var v2 = new pc.Vec4(5, 6, 7, 8);

        v1.mul(v2);

        equal(5, v1.x);
        equal(12, v1.y);
        equal(21, v1.z);
        equal(32, v1.w);
    });

    it("mul2", function() {
        var v1 = new pc.Vec4(1, 2, 3, 4);
        var v2 = new pc.Vec4(5, 6, 7, 8);
        var r = new pc.Vec4();

        r.mul2(v1, v2);

        equal(5, r.x);
        equal(12, r.y);
        equal(21, r.z);
        equal(32, r.w);
    });

    it("mulScalar", function() {
        var v = new pc.Vec4(1, 2, 3, 4);
        v.mulScalar(2);

        equal(2, v.x);
        equal(4, v.y);
        equal(6, v.z);
        equal(8, v.w);
    });

    it("normalize", function(){
        var x = new pc.Vec4(10, 0, 0, 0);
        var y = new pc.Vec4(0, 10, 0, 0);
        var z = new pc.Vec4(0, 0, 10, 0);
        var w = new pc.Vec4(0, 0, 0, 10);

        x.normalize()
        equal(1, x.x);
        equal(0, x.y);
        equal(0, x.z);
        equal(0, x.w);

        y.normalize()
        equal(0, y.x);
        equal(1, y.y);
        equal(0, y.z);
        equal(0, y.w);

        z.normalize()
        equal(0, z.x);
        equal(0, z.y);
        equal(1, z.z);
        equal(0, z.w);

        w.normalize()
        equal(0, w.x);
        equal(0, w.y);
        equal(0, w.z);
        equal(1, w.w);
    });

    it("set", function() {
        var v1 = new pc.Vec4();

        v1.set(2, 4, 6, 8);

        equal(2, v1.x);
        equal(4, v1.y);
        equal(6, v1.z);
        equal(8, v1.w);
    });

    it("sub", function() {
        var v1 = new pc.Vec4(1, 2, 3, 4);
        var v2 = new pc.Vec4(5, 6, 7, 8);

        v1.sub(v2);

        equal(-4, v1.x);
        equal(-4, v1.y);
        equal(-4, v1.z);
        equal(-4, v1.w);
    });

    it("sub2", function() {
        var v1 = new pc.Vec4(1, 2, 3, 4);
        var v2 = new pc.Vec4(5, 6, 7, 8);
        var r = new pc.Vec4();

        r.sub2(v1, v2);

        equal(-4, r.x);
        equal(-4, r.y);
        equal(-4, r.z);
        equal(-4, r.w);
    });

    it("subScalar", function() {
        var v = new pc.Vec4(2, 4, 6, 8);

        v.subScalar(2);

        equal(0, v.x);
        equal(2, v.y);
        equal(4, v.z);
        equal(6, v.w);
    });

    it("toString", function() {
        var v1 = new pc.Vec4(2, 4, 6, 8);

        var s = v1.toString();

        equal(s, '[2, 4, 6, 8]');
    });

    it("floor", function () {
        var v = new pc.Vec4(1.2, 2.6, 3.8, 4.4);
        v.floor();

        equal(1, v.x);
        equal(2, v.y);
        equal(3, v.z);
        equal(4, v.w);
    });

    it("ceil", function () {
        var v = new pc.Vec4(1.2, 2.6, 3.8, 4.4);
        v.ceil();

        equal(2, v.x);
        equal(3, v.y);
        equal(4, v.z);
        equal(5, v.w);
    });

    it("round", function () {
        var v = new pc.Vec4(1.2, 2.6, 3.8, 4.4);
        v.round();

        equal(1, v.x);
        equal(3, v.y);
        equal(4, v.z);
        equal(4, v.w);
    });

    it("min", function () {
        var v = new pc.Vec4(1, 2, 3, 4);
        var c = new pc.Vec4(3, 0, 2, 6);
        v.min(c);

        equal(1, v.x);
        equal(0, v.y);
        equal(2, v.z);
        equal(4, v.w);
    });

    it("max", function () {
        var v = new pc.Vec4(1, 2, 3, 4);
        var c = new pc.Vec4(3, 0, 2, 6);
        v.max(c);

        equal(3, v.x);
        equal(2, v.y);
        equal(3, v.z);
        equal(6, v.w);
    });

});



<|start_filename|>src/core/debug.js<|end_filename|>
let table = null;
let row = null;
let title = null;
let field = null;

function init() {
    table = document.createElement('table');
    row = document.createElement('tr');
    title = document.createElement('td');
    field = document.createElement('td');

    table.style.cssText = 'position:absolute;font-family:sans-serif;font-size:12px;color:#cccccc';
    table.style.top = '0px';
    table.style.left = '0px';
    table.style.border = 'thin solid #cccccc';

    document.body.appendChild(table);
}

/**
 * @private
 * @name debug
 * @namespace
 */
const debug = {
    /**
     * @private
     * @function
     * @name debug.display
     * @description Display an object and its data in a table on the page.
     * @param {object} data - The object to display.
     */
    display: function (data) {
        if (!table) {
            init();
        }

        table.innerHTML = '';
        for (const key in data) {
            const r = row.cloneNode();
            const t = title.cloneNode();
            const f = field.cloneNode();

            t.textContent = key;
            f.textContent = data[key];

            r.appendChild(t);
            r.appendChild(f);
            table.appendChild(r);
        }
    }
};

export { debug };


<|start_filename|>src/framework/scene-registry-item.js<|end_filename|>
/**
 * @class
 * @name SceneRegistryItem
 * @description Item to be stored in the {@link SceneRegistry}.
 * @param {string} name - The name of the scene.
 * @param {string} url - The url of the scene file.
 * @property {string} name - The name of the scene.
 * @property {string} url - The url of the scene file.
 * @property {boolean} loaded - Returns true if the scene data is still being loaded.
 */
class SceneRegistryItem {
    constructor(name, url) {
        this.name = name;
        this.url = url;
        this.data = null;
        this._loading = false;
        this._onLoadedCallbacks = [];
    }

    get loaded() {
        return !!this.data;
    }

    get loading() {
        return this._loading;
    }
}

export { SceneRegistryItem };


<|start_filename|>src/graphics/program-lib/chunks/particle_normalMap.frag<|end_filename|>
    vec3 normalMap = normalize(texture2D(normalMap, vec2(texCoordsAlphaLife.x, 1.0 - texCoordsAlphaLife.y)).xyz * 2.0 - 1.0);
    vec3 normal = ParticleMat * normalMap;


<|start_filename|>src/scene/picker.js<|end_filename|>
import { Color } from "../math/color.js";

import { ADDRESS_CLAMP_TO_EDGE, CLEARFLAG_DEPTH, FILTER_NEAREST, PIXELFORMAT_R8_G8_B8_A8 } from '../graphics/constants.js';
import { GraphicsDevice } from '../graphics/graphics-device.js';
import { RenderTarget } from '../graphics/render-target.js';
import { Texture } from '../graphics/texture.js';

import { SHADER_PICK, SORTMODE_NONE } from './constants.js';
import { Camera } from './camera.js';
import { Command } from './mesh-instance.js';
import { Layer } from './layer.js';
import { LayerComposition } from './composition/layer-composition.js';

import { getApplication } from '../framework/globals.js';
import { Entity } from '../framework/entity.js';

let _deviceDeprecationWarning = false;
let _getSelectionDeprecationWarning = false;
let _prepareDeprecationWarning = false;
const tempSet = new Set();

const clearDepthOptions = {
    depth: 1.0,
    flags: CLEARFLAG_DEPTH
};

/**
 * @class
 * @name Picker
 * @classdesc Picker object used to select mesh instances from screen coordinates.
 * @description Create a new instance of a Picker object.
 * @param {Application} app - The application managing this picker instance.
 * @param {number} width - The width of the pick buffer in pixels.
 * @param {number} height - The height of the pick buffer in pixels.
 * @property {number} width Width of the pick buffer in pixels (read-only).
 * @property {number} height Height of the pick buffer in pixels (read-only).
 * @property {RenderTarget} renderTarget The render target used by the picker internally (read-only).
 */
class Picker {
    constructor(app, width, height) {
        if (app instanceof GraphicsDevice) {
            app = getApplication();
            if (!_deviceDeprecationWarning) {
                _deviceDeprecationWarning = true;
                // #if _DEBUG
                console.warn("pc.Picker now takes pc.Application as first argument. Passing pc.GraphicsDevice is deprecated.");
                // #endif
            }
        }

        this.app = app;
        this.device = app.graphicsDevice;

        // uniform for the mesh index encoded into rgba
        this.pickColor = new Float32Array(4);
        this.pickColor[3] = 1;

        // mapping table from ids to meshInstances
        this.mapping = [];

        // create layer composition with the layer and camera
        this.cameraEntity = null;
        this.layer = null;
        this.layerComp = null;
        this.initLayerComposition();

        // internal render target
        this._renderTarget = null;

        // clear command user to simulate layer clearing, required due to storing meshes from multiple layers on a singe layer
        const device = this.device;
        this.clearDepthCommand = new Command(0, 0, function () {
            device.clear(clearDepthOptions);
        });

        this.width = 0;
        this.height = 0;
        this.resize(width, height);
    }

    /**
     * @function
     * @name Picker#getSelection
     * @description Return the list of mesh instances selected by the specified rectangle in the
     * previously prepared pick buffer.The rectangle using top-left coordinate system.
     * @param {number} x - The left edge of the rectangle.
     * @param {number} y - The top edge of the rectangle.
     * @param {number} [width] - The width of the rectangle.
     * @param {number} [height] - The height of the rectangle.
     * @returns {MeshInstance[]} An array of mesh instances that are in the selection.
     * @example
     * // Get the selection at the point (10,20)
     * var selection = picker.getSelection(10, 20);
     * @example
     * // Get all models in rectangle with corners at (10,20) and (20,40)
     * var selection = picker.getSelection(10, 20, 10, 20);
     */
    getSelection(x, y, width, height) {
        const device = this.device;

        if (typeof x === 'object') {
            // #if _DEBUG
            if (!_prepareDeprecationWarning) {
                _prepareDeprecationWarning = true;
                console.warn("Picker.getSelection:param 'rect' is deprecated, use 'x, y, width, height' instead.");
            }
            // #endif

            const rect = x;
            x = rect.x;
            y = rect.y;
            width = rect.width;
            height = rect.height;
        } else {
            y = this.renderTarget.height - (y + (height || 1));
        }

        // make sure we have nice numbers to work with
        x = Math.floor(x);
        y = Math.floor(y);
        width = Math.floor(Math.max(width || 1, 1));
        height = Math.floor(Math.max(height || 1, 1));

        // backup active render target
        const origRenderTarget = device.renderTarget;

        // Ready the device for rendering to the pick buffer
        device.setRenderTarget(this.renderTarget);
        device.updateBegin();

        const pixels = new Uint8Array(4 * width * height);
        device.readPixels(x, y, width, height, pixels);

        device.updateEnd();

        // Restore render target
        device.setRenderTarget(origRenderTarget);

        const mapping = this.mapping;
        for (let i = 0; i < width * height; i++) {
            const r = pixels[4 * i + 0];
            const g = pixels[4 * i + 1];
            const b = pixels[4 * i + 2];
            const index = r << 16 | g << 8 | b;

            // White is 'no selection'
            if (index !== 0xffffff) {
                tempSet.add(mapping[index]);
            }
        }

        // return the content of the set as an array
        const selection = [];
        tempSet.forEach((meshInstance) => selection.push(meshInstance));
        tempSet.clear();

        return selection;
    }

    allocateRenderTarget() {

        const colorBuffer = new Texture(this.device, {
            format: PIXELFORMAT_R8_G8_B8_A8,
            width: this.width,
            height: this.height,
            mipmaps: false,
            minFilter: FILTER_NEAREST,
            magFilter: FILTER_NEAREST,
            addressU: ADDRESS_CLAMP_TO_EDGE,
            addressV: ADDRESS_CLAMP_TO_EDGE
        });
        colorBuffer.name = 'pick';

        this.renderTarget = new RenderTarget({
            colorBuffer: colorBuffer,
            depth: true
        });
    }

    releaseRenderTarget() {

        // unset it from the camera
        this.cameraEntity.camera.renderTarget = null;

        if (this._renderTarget) {
            this._renderTarget._colorBuffer.destroy();
            this._renderTarget.destroy();
            this._renderTarget = null;
        }
    }

    initLayerComposition() {

        const device = this.device;
        const self = this;
        const pickColorId = device.scope.resolve('uColor');

        // camera
        this.cameraEntity = new Entity();
        this.cameraEntity.addComponent("camera");

        // layer all meshes rendered for picking at added to
        this.layer = new Layer({
            name: "Picker",
            shaderPass: SHADER_PICK,
            opaqueSortMode: SORTMODE_NONE,

            // executes just before the mesh is rendered. And index encoded in rgb is assigned to it
            onDrawCall: function (meshInstance, index) {
                self.pickColor[0] = ((index >> 16) & 0xff) / 255;
                self.pickColor[1] = ((index >> 8) & 0xff) / 255;
                self.pickColor[2] = (index & 0xff) / 255;
                pickColorId.setValue(self.pickColor);
                device.setBlending(false);

                // keep the index -> meshInstance index mapping
                self.mapping[index] = meshInstance;
            }
        });
        this.layer.addCamera(this.cameraEntity.camera);

        // composition
        this.layerComp = new LayerComposition("picker");
        this.layerComp.pushOpaque(this.layer);
    }

    /**
     * @function
     * @name Picker#prepare
     * @description Primes the pick buffer with a rendering of the specified models from the point of view
     * of the supplied camera. Once the pick buffer has been prepared, {@link Picker#getSelection} can be
     * called multiple times on the same picker object. Therefore, if the models or camera do not change
     * in any way, {@link Picker#prepare} does not need to be called again.
     * @param {CameraComponent} camera - The camera component used to render the scene.
     * @param {Scene} scene - The scene containing the pickable mesh instances.
     * @param {Layer[]} [layers] - Layers from which objects will be picked. If not supplied, all layers of the specified camera will be used.
     */
    prepare(camera, scene, layers) {

        // handle deprecated arguments
        if (camera instanceof Camera) {
            // #if _DEBUG
            if (!_getSelectionDeprecationWarning) {
                _getSelectionDeprecationWarning = true;
                console.warn("pc.Picker#prepare now takes pc.CameraComponent as first argument. Passing pc.Camera is deprecated.");
            }
            // #endif

            // Get the camera component
            camera = camera.node.camera;
        }

        if (layers instanceof Layer) {
            layers = [layers];
        }

        // populate the layer with meshes and depth clear commands
        this.layer.clearMeshInstances();
        const destMeshInstances = this.layer.opaqueMeshInstances;

        // source mesh instances
        const srcLayers = scene.layers.layerList;
        const subLayerEnabled = scene.layers.subLayerEnabled;
        const isTransparent = scene.layers.subLayerList;

        for (let i = 0; i < srcLayers.length; i++) {
            const srcLayer = srcLayers[i];

            // skip the layer if it does not match the provided ones
            if (layers && layers.indexOf(srcLayer) < 0) {
                continue;
            }

            if (srcLayer.enabled && subLayerEnabled[i]) {

                // if the layer is rendered by the camera
                const layerCamId = srcLayer.cameras.indexOf(camera);
                if (layerCamId >= 0) {

                    // if the layer clears the depth, add command to clear it
                    if (srcLayer._clearDepthBuffer) {
                        destMeshInstances.push(this.clearDepthCommand);
                    }

                    // copy all pickable mesh instances
                    const meshInstances = isTransparent[i] ? srcLayer.instances.transparentMeshInstances : srcLayer.instances.opaqueMeshInstances;
                    for (let j = 0; j < meshInstances.length; j++) {
                        const meshInstance = meshInstances[j];
                        if (meshInstance.pick) {
                            destMeshInstances.push(meshInstance);
                        }
                    }
                }
            }
        }

        // make the render target the right size
        if (!this.renderTarget || (this.width !== this.renderTarget.width || this.height !== this.renderTarget.height)) {
            this.releaseRenderTarget();
            this.allocateRenderTarget();
        }

        // prepare the rendering camera
        this.updateCamera(camera);

        // clear registered meshes mapping
        this.mapping.length = 0;

        // render
        this.app.renderer.renderComposition(this.layerComp);
    }

    updateCamera(srcCamera) {

        // copy transform
        this.cameraEntity.copy(srcCamera.entity);
        this.cameraEntity.name = "PickerCamera";

        // copy camera component properties - which overwrites few properties we change to what is needed later
        const destCamera = this.cameraEntity.camera;
        destCamera.copy(srcCamera);

        // set up clears
        destCamera.clearColorBuffer = true;
        destCamera.clearDepthBuffer = true;
        destCamera.clearStencilBuffer = true;
        destCamera.clearColor = Color.WHITE;

        // render target
        destCamera.renderTarget = this.renderTarget;

        // layers
        this.layer.clearCameras();
        this.layer.addCamera(destCamera);
        destCamera.layers = [this.layer.id];
    }

    /**
     * @function
     * @name Picker#resize
     * @description Sets the resolution of the pick buffer. The pick buffer resolution does not need
     * to match the resolution of the corresponding frame buffer use for general rendering of the
     * 3D scene. However, the lower the resolution of the pick buffer, the less accurate the selection
     * results returned by {@link Picker#getSelection}. On the other hand, smaller pick buffers will
     * yield greater performance, so there is a trade off.
     * @param {number} width - The width of the pick buffer in pixels.
     * @param {number} height - The height of the pick buffer in pixels.
     */
    resize(width, height) {
        this.width = Math.floor(width);
        this.height = Math.floor(height);
    }
}

export { Picker };


<|start_filename|>tsd.js<|end_filename|>
const fs = require('fs');

const path = './build/playcanvas.d.ts';

// replace declare with export and add 'export as namespace pc' in the end
let ts = (fs.readFileSync(path, 'utf8')).toString();
ts = ts.replace(/^declare /gm, 'export ') + 'export as namespace pc;\n';
fs.writeFileSync(path, ts);


<|start_filename|>src/framework/components/element/system.js<|end_filename|>
import { Color } from '../../../math/color.js';
import { Vec2 } from '../../../math/vec2.js';
import { Vec4 } from '../../../math/vec4.js';

import {
    PIXELFORMAT_R8_G8_B8_A8
} from '../../../graphics/constants.js';
import { Texture } from '../../../graphics/texture.js';

import { BLEND_PREMULTIPLIED, SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED } from '../../../scene/constants.js';
import { StandardMaterial } from '../../../scene/materials/standard-material.js';

import { Component } from '../component.js';
import { ComponentSystem } from '../system.js';

import { ELEMENTTYPE_IMAGE, ELEMENTTYPE_TEXT } from './constants.js';
import { ElementComponent } from './component.js';
import { ElementComponentData } from './data.js';

const _schema = ['enabled'];

/**
 * @class
 * @name ElementComponentSystem
 * @augments ComponentSystem
 * @classdesc Manages creation of {@link ElementComponent}s.
 * @param {Application} app - The application.
 */
class ElementComponentSystem extends ComponentSystem {
    constructor(app) {
        super(app);

        this.id = 'element';

        this.ComponentType = ElementComponent;
        this.DataType = ElementComponentData;

        this.schema = _schema;
        this._unicodeConverter = null;
        this._rtlReorder = null;

        // default texture - make white so we can tint it with emissive color
        this._defaultTexture = new Texture(app.graphicsDevice, { width: 1, height: 1, format: PIXELFORMAT_R8_G8_B8_A8 });
        this._defaultTexture.name = 'element-system';
        var pixels = this._defaultTexture.lock();
        var pixelData = new Uint8Array(4);
        pixelData[0] = 255.0;
        pixelData[1] = 255.0;
        pixelData[2] = 255.0;
        pixelData[3] = 255.0;
        pixels.set(pixelData);
        this._defaultTexture.unlock();

        // image element materials created on demand by getImageElementMaterial()
        this.defaultImageMaterial = null;
        this.defaultImage9SlicedMaterial = null;
        this.defaultImage9TiledMaterial = null;
        this.defaultImageMaskMaterial = null;
        this.defaultImage9SlicedMaskMaterial = null;
        this.defaultImage9TiledMaskMaterial = null;
        this.defaultScreenSpaceImageMaterial = null;
        this.defaultScreenSpaceImage9SlicedMaterial = null;
        this.defaultScreenSpaceImage9TiledMaterial = null;
        this.defaultScreenSpaceImageMask9SlicedMaterial = null;
        this.defaultScreenSpaceImageMask9TiledMaterial = null;
        this.defaultScreenSpaceImageMaskMaterial = null;

        // text element materials created on demand by getTextElementMaterial()
        this.defaultTextMaterial = null;
        this.defaultBitmapTextMaterial = null;
        this.defaultScreenSpaceTextMaterial = null;
        this.defaultScreenSpaceBitmapTextMaterial = null;

        this.defaultImageMaterials = [];

        this.on('beforeremove', this.onRemoveComponent, this);
    }

    destroy() {
        this._defaultTexture.destroy();
    }

    initializeComponentData(component, data, properties) {
        component._beingInitialized = true;

        if (data.anchor !== undefined) {
            if (data.anchor instanceof Vec4) {
                component.anchor.copy(data.anchor);
            } else {
                component.anchor.set(data.anchor[0], data.anchor[1], data.anchor[2], data.anchor[3]);
            }
        }

        if (data.pivot !== undefined) {
            if (data.pivot instanceof Vec2) {
                component.pivot.copy(data.pivot);
            } else {
                component.pivot.set(data.pivot[0], data.pivot[1]);
            }
        }

        var splitHorAnchors = Math.abs(component.anchor.x - component.anchor.z) > 0.001;
        var splitVerAnchors = Math.abs(component.anchor.y - component.anchor.w) > 0.001;
        var _marginChange = false;
        var color;

        if (data.margin !== undefined) {
            if (data.margin instanceof Vec4) {
                component.margin.copy(data.margin);
            } else {
                component._margin.set(data.margin[0], data.margin[1], data.margin[2], data.margin[3]);
            }

            _marginChange = true;
        }

        if (data.left !== undefined) {
            component._margin.x = data.left;
            _marginChange = true;
        }
        if (data.bottom !== undefined) {
            component._margin.y = data.bottom;
            _marginChange = true;
        }
        if (data.right !== undefined) {
            component._margin.z = data.right;
            _marginChange = true;
        }
        if (data.top !== undefined) {
            component._margin.w = data.top;
            _marginChange = true;
        }
        if (_marginChange) {
            // force update
            component.margin = component._margin;
        }

        var shouldForceSetAnchor = false;

        if (data.width !== undefined && !splitHorAnchors) {
            // force update
            component.width = data.width;
        } else if (splitHorAnchors) {
            shouldForceSetAnchor = true;
        }
        if (data.height !== undefined && !splitVerAnchors) {
            // force update
            component.height = data.height;
        } else if (splitVerAnchors) {
            shouldForceSetAnchor = true;
        }

        if (shouldForceSetAnchor) {
            /* eslint-disable no-self-assign */
            // force update
            component.anchor = component.anchor;
            /* eslint-enable no-self-assign */
        }

        if (data.enabled !== undefined) {
            component.enabled = data.enabled;
        }

        if (data.useInput !== undefined) {
            component.useInput = data.useInput;
        }

        component.batchGroupId = data.batchGroupId === undefined || data.batchGroupId === null ? -1 : data.batchGroupId;

        if (data.layers && Array.isArray(data.layers)) {
            component.layers = data.layers.slice(0);
        }

        if (data.type !== undefined) {
            component.type = data.type;
        }

        if (component.type === ELEMENTTYPE_IMAGE) {
            if (data.rect !== undefined) {
                component.rect = data.rect;
            }
            if (data.color !== undefined) {
                color = data.color;
                if (! (color instanceof Color)) {
                    color = new Color(data.color[0], data.color[1], data.color[2]);
                }
                component.color = color;
            }

            if (data.opacity !== undefined) component.opacity = data.opacity;
            if (data.textureAsset !== undefined) component.textureAsset = data.textureAsset;
            if (data.texture) component.texture = data.texture;
            if (data.spriteAsset !== undefined) component.spriteAsset = data.spriteAsset;
            if (data.sprite) component.sprite = data.sprite;
            if (data.spriteFrame !== undefined) component.spriteFrame = data.spriteFrame;
            if (data.pixelsPerUnit !== undefined && data.pixelsPerUnit !== null) component.pixelsPerUnit = data.pixelsPerUnit;
            if (data.materialAsset !== undefined) component.materialAsset = data.materialAsset;
            if (data.material) component.material = data.material;

            if (data.mask !== undefined) {
                component.mask = data.mask;
            }
        } else if (component.type === ELEMENTTYPE_TEXT) {
            if (data.autoWidth !== undefined) component.autoWidth = data.autoWidth;
            if (data.autoHeight !== undefined) component.autoHeight = data.autoHeight;
            if (data.rtlReorder !== undefined) component.rtlReorder = data.rtlReorder;
            if (data.unicodeConverter !== undefined) component.unicodeConverter = data.unicodeConverter;
            if (data.text !== null && data.text !== undefined) {
                component.text = data.text;
            } else if (data.key !== null && data.key !== undefined) {
                component.key = data.key;
            }
            if (data.color !== undefined) {
                color = data.color;
                if (! (color instanceof Color)) {
                    color = new Color(color[0], color[1], color[2]);
                }
                component.color = color;
            }
            if (data.opacity !== undefined) {
                component.opacity = data.opacity;
            }
            if (data.spacing !== undefined) component.spacing = data.spacing;
            if (data.fontSize !== undefined) {
                component.fontSize = data.fontSize;
                if (!data.lineHeight) component.lineHeight = data.fontSize;
            }
            if (data.lineHeight !== undefined) component.lineHeight = data.lineHeight;
            if (data.maxLines !== undefined) component.maxLines = data.maxLines;
            if (data.wrapLines !== undefined) component.wrapLines = data.wrapLines;
            if (data.minFontSize !== undefined) component.minFontSize = data.minFontSize;
            if (data.maxFontSize !== undefined) component.maxFontSize = data.maxFontSize;
            if (data.autoFitWidth) component.autoFitWidth = data.autoFitWidth;
            if (data.autoFitHeight) component.autoFitHeight = data.autoFitHeight;
            if (data.fontAsset !== undefined) component.fontAsset = data.fontAsset;
            if (data.font !== undefined) component.font = data.font;
            if (data.alignment !== undefined) component.alignment = data.alignment;
            if (data.outlineColor !== undefined) component.outlineColor = data.outlineColor;
            if (data.outlineThickness !== undefined) component.outlineThickness = data.outlineThickness;
            if (data.shadowColor !== undefined) component.shadowColor = data.shadowColor;
            if (data.shadowOffset !== undefined) component.shadowOffset = data.shadowOffset;
            if (data.enableMarkup !== undefined) component.enableMarkup = data.enableMarkup;
        }
        // OTHERWISE: group

        // find screen
        // do this here not in constructor so that component is added to the entity
        var result = component._parseUpToScreen();
        if (result.screen) {
            component._updateScreen(result.screen);
        }

        super.initializeComponentData(component, data, properties);

        component._beingInitialized = false;

        if (component.type === ELEMENTTYPE_IMAGE && component._image._meshDirty) {
            component._image._updateMesh(component._image.mesh);
        }
    }

    onRemoveComponent(entity, component) {
        component.onRemove();
    }

    cloneComponent(entity, clone) {
        var source = entity.element;

        var data = {
            enabled: source.enabled,
            width: source.width,
            height: source.height,
            anchor: source.anchor.clone(),
            pivot: source.pivot.clone(),
            margin: source.margin.clone(),
            alignment: source.alignment && source.alignment.clone() || source.alignment,
            autoWidth: source.autoWidth,
            autoHeight: source.autoHeight,
            type: source.type,
            rect: source.rect && source.rect.clone() || source.rect,
            rtlReorder: source.rtlReorder,
            unicodeConverter: source.unicodeConverter,
            materialAsset: source.materialAsset,
            material: source.material,
            color: source.color && source.color.clone() || source.color,
            opacity: source.opacity,
            textureAsset: source.textureAsset,
            texture: source.texture,
            spriteAsset: source.spriteAsset,
            sprite: source.sprite,
            spriteFrame: source.spriteFrame,
            pixelsPerUnit: source.pixelsPerUnit,
            spacing: source.spacing,
            lineHeight: source.lineHeight,
            wrapLines: source.wrapLines,
            layers: source.layers,
            fontSize: source.fontSize,
            minFontSize: source.minFontSize,
            maxFontSize: source.maxFontSize,
            autoFitWidth: source.autoFitWidth,
            autoFitHeight: source.autoFitHeight,
            maxLines: source.maxLines,
            fontAsset: source.fontAsset,
            font: source.font,
            useInput: source.useInput,
            batchGroupId: source.batchGroupId,
            mask: source.mask,
            outlineColor: source.outlineColor && source.outlineColor.clone() || source.outlineColor,
            outlineThickness: source.outlineThickness,
            shadowColor: source.shadowColor && source.shadowColor.clone() || source.shadowColor,
            shadowOffset: source.shadowOffset && source.shadowOffset.clone() || source.shadowOffset,
            enableMarkup: source.enableMarkup
        };

        if (source.key !== undefined && source.key !== null) {
            data.key = source.key;
        } else {
            data.text = source.text;
        }

        return this.addComponent(clone, data);
    }

    getTextElementMaterial(screenSpace, msdf) {
        if (screenSpace) {
            if (msdf) {
                if (!this.defaultScreenSpaceTextMaterial) {
                    this.defaultScreenSpaceTextMaterial = new StandardMaterial();
                    this.defaultScreenSpaceTextMaterial.name = "defaultScreenSpaceTextMaterial";
                    this.defaultScreenSpaceTextMaterial.msdfMap = this._defaultTexture;
                    this.defaultScreenSpaceTextMaterial.useLighting = false;
                    this.defaultScreenSpaceTextMaterial.useGammaTonemap = false;
                    this.defaultScreenSpaceTextMaterial.useFog = false;
                    this.defaultScreenSpaceTextMaterial.useSkybox = false;
                    this.defaultScreenSpaceTextMaterial.diffuse.set(0, 0, 0); // black diffuse color to prevent ambient light being included
                    this.defaultScreenSpaceTextMaterial.emissive.set(1, 1, 1);
                    this.defaultScreenSpaceTextMaterial.opacity = 0.5;
                    this.defaultScreenSpaceTextMaterial.blendType = BLEND_PREMULTIPLIED;
                    this.defaultScreenSpaceTextMaterial.depthWrite = false;
                    this.defaultScreenSpaceTextMaterial.depthTest = false;
                    this.defaultScreenSpaceTextMaterial.emissiveVertexColor = true;
                    this.defaultScreenSpaceTextMaterial.update();
                }
                return this.defaultScreenSpaceTextMaterial;
            }
            if (!this.defaultScreenSpaceBitmapTextMaterial) {
                this.defaultScreenSpaceBitmapTextMaterial = new StandardMaterial();
                this.defaultScreenSpaceBitmapTextMaterial.name = "defaultScreenSpaceBitmapTextMaterial";
                this.defaultScreenSpaceBitmapTextMaterial.emissive.set(0.5, 0.5, 0.5); // set to non-(1,1,1) so that tint is actually applied
                this.defaultScreenSpaceBitmapTextMaterial.emissiveMap = this._defaultTexture;
                this.defaultScreenSpaceBitmapTextMaterial.emissiveTint = true;
                this.defaultScreenSpaceBitmapTextMaterial.opacity = 0.5;
                this.defaultScreenSpaceBitmapTextMaterial.opacityMap = this._defaultTexture;
                this.defaultScreenSpaceBitmapTextMaterial.opacityMapChannel = 'a';
                this.defaultScreenSpaceBitmapTextMaterial.useLighting = false;
                this.defaultScreenSpaceBitmapTextMaterial.useGammaTonemap = false;
                this.defaultScreenSpaceBitmapTextMaterial.useFog = false;
                this.defaultScreenSpaceBitmapTextMaterial.useSkybox = false;
                this.defaultScreenSpaceBitmapTextMaterial.diffuse.set(0, 0, 0); // black diffuse color to prevent ambient light being included
                this.defaultScreenSpaceBitmapTextMaterial.blendType = BLEND_PREMULTIPLIED;
                this.defaultScreenSpaceBitmapTextMaterial.depthWrite = false;
                this.defaultScreenSpaceBitmapTextMaterial.depthTest = false;
                this.defaultScreenSpaceBitmapTextMaterial.emissiveVertexColor = true;
                this.defaultScreenSpaceBitmapTextMaterial.update();
            }
            return this.defaultScreenSpaceBitmapTextMaterial;

        }
        if (msdf) {
            if (!this.defaultTextMaterial) {
                this.defaultTextMaterial = new StandardMaterial();
                this.defaultTextMaterial.name = "defaultTextMaterial";
                this.defaultTextMaterial.msdfMap = this._defaultTexture;
                this.defaultTextMaterial.useLighting = false;
                this.defaultTextMaterial.useGammaTonemap = false;
                this.defaultTextMaterial.useFog = false;
                this.defaultTextMaterial.useSkybox = false;
                this.defaultTextMaterial.diffuse.set(0, 0, 0); // black diffuse color to prevent ambient light being included
                this.defaultTextMaterial.emissive.set(1, 1, 1);
                this.defaultTextMaterial.opacity = 0.5;
                this.defaultTextMaterial.blendType = BLEND_PREMULTIPLIED;
                this.defaultTextMaterial.depthWrite = false;
                this.defaultTextMaterial.emissiveVertexColor = true;
                this.defaultTextMaterial.update();
            }
            return this.defaultTextMaterial;
        }
        if (!this.defaultBitmapTextMaterial) {
            this.defaultBitmapTextMaterial = new StandardMaterial();
            this.defaultBitmapTextMaterial.name = "defaultBitmapTextMaterial";
            this.defaultBitmapTextMaterial.emissive.set(0.5, 0.5, 0.5);  // set to non-(1,1,1) so that tint is actually applied
            this.defaultBitmapTextMaterial.emissiveTint = true;
            this.defaultBitmapTextMaterial.emissiveMap = this._defaultTexture;
            this.defaultBitmapTextMaterial.opacity = 0.5;
            this.defaultBitmapTextMaterial.opacityMap = this._defaultTexture;
            this.defaultBitmapTextMaterial.opacityMapChannel = 'a';
            this.defaultBitmapTextMaterial.useLighting = false;
            this.defaultBitmapTextMaterial.useGammaTonemap = false;
            this.defaultBitmapTextMaterial.useFog = false;
            this.defaultBitmapTextMaterial.useSkybox = false;
            this.defaultBitmapTextMaterial.diffuse.set(0, 0, 0); // black diffuse color to prevent ambient light being included
            this.defaultBitmapTextMaterial.blendType = BLEND_PREMULTIPLIED;
            this.defaultBitmapTextMaterial.depthWrite = false;
            this.defaultBitmapTextMaterial.emissiveVertexColor = true;
            this.defaultBitmapTextMaterial.update();
        }
        return this.defaultBitmapTextMaterial;
    }

    _createBaseImageMaterial() {
        var material = new StandardMaterial();

        material.diffuse.set(0, 0, 0); // black diffuse color to prevent ambient light being included
        material.emissive.set(0.5, 0.5, 0.5); // use non-white to compile shader correctly
        material.emissiveMap = this._defaultTexture;
        material.emissiveTint = true;
        material.opacityMap = this._defaultTexture;
        material.opacityMapChannel = "a";
        material.opacityTint = true;
        material.opacity = 0; // use non-1 opacity to compile shader correctly
        material.useLighting = false;
        material.useGammaTonemap = false;
        material.useFog = false;
        material.useSkybox = false;
        material.blendType = BLEND_PREMULTIPLIED;
        material.depthWrite = false;

        return material;
    }

    getImageElementMaterial(screenSpace, mask, nineSliced, nineSliceTiled) {
        /* eslint-disable no-else-return */
        if (screenSpace) {
            if (mask) {
                if (nineSliced) {
                    if (!this.defaultScreenSpaceImageMask9SlicedMaterial) {
                        this.defaultScreenSpaceImageMask9SlicedMaterial = this._createBaseImageMaterial();
                        this.defaultScreenSpaceImageMask9SlicedMaterial.name = "defaultScreenSpaceImageMask9SlicedMaterial";
                        this.defaultScreenSpaceImageMask9SlicedMaterial.nineSlicedMode = SPRITE_RENDERMODE_SLICED;
                        this.defaultScreenSpaceImageMask9SlicedMaterial.depthTest = false;
                        this.defaultScreenSpaceImageMask9SlicedMaterial.alphaTest = 1;
                        this.defaultScreenSpaceImageMask9SlicedMaterial.redWrite = false;
                        this.defaultScreenSpaceImageMask9SlicedMaterial.greenWrite = false;
                        this.defaultScreenSpaceImageMask9SlicedMaterial.blueWrite = false;
                        this.defaultScreenSpaceImageMask9SlicedMaterial.alphaWrite = false;
                        this.defaultScreenSpaceImageMask9SlicedMaterial.update();

                        this.defaultImageMaterials.push(this.defaultScreenSpaceImageMask9SlicedMaterial);
                    }
                    return this.defaultScreenSpaceImageMask9SlicedMaterial;
                } else if (nineSliceTiled) {
                    if (!this.defaultScreenSpaceImageMask9TiledMaterial) {
                        this.defaultScreenSpaceImageMask9TiledMaterial = this.defaultScreenSpaceImage9TiledMaterial.clone();
                        this.defaultScreenSpaceImageMask9TiledMaterial.name = "defaultScreenSpaceImageMask9TiledMaterial";
                        this.defaultScreenSpaceImageMask9TiledMaterial.nineSlicedMode = SPRITE_RENDERMODE_TILED;
                        this.defaultScreenSpaceImageMask9TiledMaterial.depthTest = false;
                        this.defaultScreenSpaceImageMask9TiledMaterial.alphaTest = 1;
                        this.defaultScreenSpaceImageMask9TiledMaterial.redWrite = false;
                        this.defaultScreenSpaceImageMask9TiledMaterial.greenWrite = false;
                        this.defaultScreenSpaceImageMask9TiledMaterial.blueWrite = false;
                        this.defaultScreenSpaceImageMask9TiledMaterial.alphaWrite = false;
                        this.defaultScreenSpaceImageMask9TiledMaterial.update();

                        this.defaultImageMaterials.push(this.defaultScreenSpaceImageMask9TiledMaterial);
                    }
                    return this.defaultScreenSpaceImageMask9TiledMaterial;
                } else {
                    if (!this.defaultScreenSpaceImageMaskMaterial) {
                        this.defaultScreenSpaceImageMaskMaterial = this._createBaseImageMaterial();
                        this.defaultScreenSpaceImageMaskMaterial.name = "defaultScreenSpaceImageMaskMaterial";
                        this.defaultScreenSpaceImageMaskMaterial.depthTest = false;
                        this.defaultScreenSpaceImageMaskMaterial.alphaTest = 1;
                        this.defaultScreenSpaceImageMaskMaterial.redWrite = false;
                        this.defaultScreenSpaceImageMaskMaterial.greenWrite = false;
                        this.defaultScreenSpaceImageMaskMaterial.blueWrite = false;
                        this.defaultScreenSpaceImageMaskMaterial.alphaWrite = false;
                        this.defaultScreenSpaceImageMaskMaterial.update();

                        this.defaultImageMaterials.push(this.defaultScreenSpaceImageMaskMaterial);
                    }
                    return this.defaultScreenSpaceImageMaskMaterial;
                }
            } else {
                if (nineSliced) {
                    if (!this.defaultScreenSpaceImage9SlicedMaterial) {
                        this.defaultScreenSpaceImage9SlicedMaterial = this._createBaseImageMaterial();
                        this.defaultScreenSpaceImage9SlicedMaterial.name = "defaultScreenSpaceImage9SlicedMaterial";
                        this.defaultScreenSpaceImage9SlicedMaterial.nineSlicedMode = SPRITE_RENDERMODE_SLICED;
                        this.defaultScreenSpaceImage9SlicedMaterial.depthTest = false;
                        this.defaultScreenSpaceImage9SlicedMaterial.update();

                        this.defaultImageMaterials.push(this.defaultScreenSpaceImage9SlicedMaterial);
                    }
                    return this.defaultScreenSpaceImage9SlicedMaterial;
                } else if (nineSliceTiled) {
                    if (!this.defaultScreenSpaceImage9TiledMaterial) {
                        this.defaultScreenSpaceImage9TiledMaterial = this._createBaseImageMaterial();
                        this.defaultScreenSpaceImage9TiledMaterial.name = "defaultScreenSpaceImage9TiledMaterial";
                        this.defaultScreenSpaceImage9TiledMaterial.nineSlicedMode = SPRITE_RENDERMODE_TILED;
                        this.defaultScreenSpaceImage9TiledMaterial.depthTest = false;
                        this.defaultScreenSpaceImage9TiledMaterial.update();

                        this.defaultImageMaterials.push(this.defaultScreenSpaceImage9TiledMaterial);
                    }

                    return this.defaultScreenSpaceImage9TiledMaterial;
                } else {
                    if (!this.defaultScreenSpaceImageMaterial) {
                        this.defaultScreenSpaceImageMaterial = this._createBaseImageMaterial();
                        this.defaultScreenSpaceImageMaterial.name = "defaultScreenSpaceImageMaterial";
                        this.defaultScreenSpaceImageMaterial.depthTest = false;
                        this.defaultScreenSpaceImageMaterial.update();

                        this.defaultImageMaterials.push(this.defaultScreenSpaceImageMaterial);
                    }
                    return this.defaultScreenSpaceImageMaterial;
                }
            }
        } else {
            if (mask) {
                if (nineSliced) {
                    if (!this.defaultImage9SlicedMaskMaterial) {
                        this.defaultImage9SlicedMaskMaterial = this._createBaseImageMaterial();
                        this.defaultImage9SlicedMaskMaterial.name = "defaultImage9SlicedMaskMaterial";
                        this.defaultImage9SlicedMaskMaterial.nineSlicedMode = SPRITE_RENDERMODE_SLICED;
                        this.defaultImage9SlicedMaskMaterial.alphaTest = 1;
                        this.defaultImage9SlicedMaskMaterial.redWrite = false;
                        this.defaultImage9SlicedMaskMaterial.greenWrite = false;
                        this.defaultImage9SlicedMaskMaterial.blueWrite = false;
                        this.defaultImage9SlicedMaskMaterial.alphaWrite = false;
                        this.defaultImage9SlicedMaskMaterial.update();

                        this.defaultImageMaterials.push(this.defaultImage9SlicedMaskMaterial);
                    }
                    return this.defaultImage9SlicedMaskMaterial;
                } else if (nineSliceTiled) {
                    if (!this.defaultImage9TiledMaskMaterial) {
                        this.defaultImage9TiledMaskMaterial = this._createBaseImageMaterial();
                        this.defaultImage9TiledMaskMaterial.name = "defaultImage9TiledMaskMaterial";
                        this.defaultImage9TiledMaskMaterial.nineSlicedMode = SPRITE_RENDERMODE_TILED;
                        this.defaultImage9TiledMaskMaterial.alphaTest = 1;
                        this.defaultImage9TiledMaskMaterial.redWrite = false;
                        this.defaultImage9TiledMaskMaterial.greenWrite = false;
                        this.defaultImage9TiledMaskMaterial.blueWrite = false;
                        this.defaultImage9TiledMaskMaterial.alphaWrite = false;
                        this.defaultImage9TiledMaskMaterial.update();

                        this.defaultImageMaterials.push(this.defaultImage9TiledMaskMaterial);
                    }
                    return this.defaultImage9TiledMaskMaterial;
                } else {
                    if (!this.defaultImageMaskMaterial) {
                        this.defaultImageMaskMaterial = this._createBaseImageMaterial();
                        this.defaultImageMaskMaterial.name = "defaultImageMaskMaterial";
                        this.defaultImageMaskMaterial.alphaTest = 1;
                        this.defaultImageMaskMaterial.redWrite = false;
                        this.defaultImageMaskMaterial.greenWrite = false;
                        this.defaultImageMaskMaterial.blueWrite = false;
                        this.defaultImageMaskMaterial.alphaWrite = false;
                        this.defaultImageMaskMaterial.update();

                        this.defaultImageMaterials.push(this.defaultImageMaskMaterial);
                    }
                    return this.defaultImageMaskMaterial;
                }
            } else {
                if (nineSliced) {
                    if (!this.defaultImage9SlicedMaterial) {
                        this.defaultImage9SlicedMaterial = this._createBaseImageMaterial();
                        this.defaultImage9SlicedMaterial.name = "defaultImage9SlicedMaterial";
                        this.defaultImage9SlicedMaterial.nineSlicedMode = SPRITE_RENDERMODE_SLICED;
                        this.defaultImage9SlicedMaterial.update();

                        this.defaultImageMaterials.push(this.defaultImage9SlicedMaterial);
                    }
                    return this.defaultImage9SlicedMaterial;
                } else if (nineSliceTiled) {
                    if (!this.defaultImage9TiledMaterial) {
                        this.defaultImage9TiledMaterial = this._createBaseImageMaterial();
                        this.defaultImage9TiledMaterial.name = "defaultImage9TiledMaterial";
                        this.defaultImage9TiledMaterial.nineSlicedMode = SPRITE_RENDERMODE_TILED;
                        this.defaultImage9TiledMaterial.update();

                        this.defaultImageMaterials.push(this.defaultImage9TiledMaterial);
                    }
                    return this.defaultImage9TiledMaterial;
                } else {
                    if (!this.defaultImageMaterial) {
                        this.defaultImageMaterial = this._createBaseImageMaterial();
                        this.defaultImageMaterial.name = "defaultImageMaterial";
                        this.defaultImageMaterial.update();

                        this.defaultImageMaterials.push(this.defaultImageMaterial);
                    }
                    return this.defaultImageMaterial;
                }
            }
        }
        /* eslint-enable no-else-return */
    }

    registerUnicodeConverter(func) {
        this._unicodeConverter = func;
    }

    registerRtlReorder(func) {
        this._rtlReorder = func;
    }

    getUnicodeConverter() {
        return this._unicodeConverter;
    }

    getRtlReorder() {
        return this._rtlReorder;
    }
}

Component._buildAccessors(ElementComponent.prototype, _schema);

export { ElementComponentSystem };


<|start_filename|>src/framework/components/script/component.js<|end_filename|>
import { SortedLoopArray } from '../../../core/sorted-loop-array.js';

import { ScriptAttributes } from '../../../script/script-attributes.js';

import { Component } from '../component.js';
import { Entity } from '../../entity.js';

/**
 * @component
 * @class
 * @name ScriptComponent
 * @augments Component
 * @classdesc The ScriptComponent allows you to extend the functionality of an Entity by attaching your own Script Types defined in JavaScript files
 * to be executed with access to the Entity. For more details on scripting see <a href="//developer.playcanvas.com/user-manual/scripting/">Scripting</a>.
 * @param {ScriptComponentSystem} system - The ComponentSystem that created this Component.
 * @param {Entity} entity - The Entity that this Component is attached to.
 * @property {ScriptType[]} scripts An array of all script instances attached to an entity. This Array shall not be modified by developer.
 */
class ScriptComponent extends Component {
    constructor(system, entity) {
        super(system, entity);

        // holds all script instances for this component
        this._scripts = [];
        // holds all script instances with an update method
        this._updateList = new SortedLoopArray({ sortBy: '__executionOrder' });
        // holds all script instances with a postUpdate method
        this._postUpdateList = new SortedLoopArray({ sortBy: '__executionOrder' });

        this._scriptsIndex = {};
        this._destroyedScripts = [];
        this._destroyed = false;
        this._scriptsData = null;
        this._oldState = true;

        // override default 'enabled' property of base pc.Component
        // because this is faster
        this._enabled = true;

        // whether this component is currently being enabled
        this._beingEnabled = false;
        // if true then we are currently looping through
        // script instances. This is used to prevent a scripts array
        // from being modified while a loop is being executed
        this._isLoopingThroughScripts = false;

        // the order that this component will be updated
        // by the script system. This is set by the system itself.
        this._executionOrder = -1;

        this.on('set_enabled', this._onSetEnabled, this);
    }

    static scriptMethods = {
        initialize: 'initialize',
        postInitialize: 'postInitialize',
        update: 'update',
        postUpdate: 'postUpdate',
        swap: 'swap'
    };

    /**
     * @event
     * @name ScriptComponent#enable
     * @description Fired when Component becomes enabled
     * Note: this event does not take in account entity or any of its parent enabled state.
     * @example
     * entity.script.on('enable', function () {
     *     // component is enabled
     * });
     */

    /**
     * @event
     * @name ScriptComponent#disable
     * @description Fired when Component becomes disabled
     * Note: this event does not take in account entity or any of its parent enabled state.
     * @example
     * entity.script.on('disable', function () {
     *     // component is disabled
     * });
     */

    /**
     * @event
     * @name ScriptComponent#state
     * @description Fired when Component changes state to enabled or disabled
     * Note: this event does not take in account entity or any of its parent enabled state.
     * @param {boolean} enabled - True if now enabled, False if disabled.
     * @example
     * entity.script.on('state', function (enabled) {
     *     // component changed state
     * });
     */

    /**
     * @event
     * @name ScriptComponent#remove
     * @description Fired when Component is removed from entity.
     * @example
     * entity.script.on('remove', function () {
     *     // entity has no more script component
     * });
     */

    /**
     * @event
     * @name ScriptComponent#create
     * @description Fired when a script instance is created and attached to component.
     * @param {string} name - The name of the Script Type.
     * @param {ScriptType} scriptInstance - The instance of the {@link ScriptType} that has been created.
     * @example
     * entity.script.on('create', function (name, scriptInstance) {
     *     // new script instance added to component
     * });
     */

    /**
     * @event
     * @name ScriptComponent#create:[name]
     * @description Fired when a script instance is created and attached to component.
     * @param {ScriptType} scriptInstance - The instance of the {@link ScriptType} that has been created.
     * @example
     * entity.script.on('create:playerController', function (scriptInstance) {
     *     // new script instance 'playerController' is added to component
     * });
     */

    /**
     * @event
     * @name ScriptComponent#destroy
     * @description Fired when a script instance is destroyed and removed from component.
     * @param {string} name - The name of the Script Type.
     * @param {ScriptType} scriptInstance - The instance of the {@link ScriptType} that has been destroyed.
     * @example
     * entity.script.on('destroy', function (name, scriptInstance) {
     *     // script instance has been destroyed and removed from component
     * });
     */

    /**
     * @event
     * @name ScriptComponent#destroy:[name]
     * @description Fired when a script instance is destroyed and removed from component.
     * @param {ScriptType} scriptInstance - The instance of the {@link ScriptType} that has been destroyed.
     * @example
     * entity.script.on('destroy:playerController', function (scriptInstance) {
     *     // script instance 'playerController' has been destroyed and removed from component
     * });
     */

    /**
     * @event
     * @name ScriptComponent#move
     * @description Fired when a script instance is moved in component.
     * @param {string} name - The name of the Script Type.
     * @param {ScriptType} scriptInstance - The instance of the {@link ScriptType} that has been moved.
     * @param {number} ind - New position index.
     * @param {number} indOld - Old position index.
     * @example
     * entity.script.on('move', function (name, scriptInstance, ind, indOld) {
     *     // script instance has been moved in component
     * });
     */

    /**
     * @event
     * @name ScriptComponent#move:[name]
     * @description Fired when a script instance is moved in component.
     * @param {ScriptType} scriptInstance - The instance of the {@link ScriptType} that has been moved.
     * @param {number} ind - New position index.
     * @param {number} indOld - Old position index.
     * @example
     * entity.script.on('move:playerController', function (scriptInstance, ind, indOld) {
     *     // script instance 'playerController' has been moved in component
     * });
     */

    /**
     * @event
     * @name ScriptComponent#error
     * @description Fired when a script instance had an exception.
     * @param {ScriptType} scriptInstance - The instance of the {@link ScriptType} that raised the exception.
     * @param {Error} err - Native JS Error object with details of an error.
     * @param {string} method - The method of the script instance that the exception originated from.
     * @example
     * entity.script.on('error', function (scriptInstance, err, method) {
     *     // script instance caught an exception
     * });
     */

    onEnable() {
        this._beingEnabled = true;
        this._checkState();

        if (!this.entity._beingEnabled) {
            this.onPostStateChange();
        }

        this._beingEnabled = false;
    }

    onDisable() {
        this._checkState();
    }

    onPostStateChange() {
        var script;

        var wasLooping = this._beginLooping();

        for (var i = 0, len = this.scripts.length; i < len; i++) {
            script = this.scripts[i];

            if (script._initialized && !script._postInitialized && script.enabled) {
                script._postInitialized = true;

                if (script.postInitialize)
                    this._scriptMethod(script, ScriptComponent.scriptMethods.postInitialize);
            }
        }

        this._endLooping(wasLooping);
    }

    // Sets isLoopingThroughScripts to false and returns
    // its previous value
    _beginLooping() {
        var looping = this._isLoopingThroughScripts;
        this._isLoopingThroughScripts = true;
        return looping;
    }

    // Restores isLoopingThroughScripts to the specified parameter
    // If all loops are over then remove destroyed scripts form the _scripts array
    _endLooping(wasLoopingBefore) {
        this._isLoopingThroughScripts = wasLoopingBefore;
        if (!this._isLoopingThroughScripts) {
            this._removeDestroyedScripts();
        }
    }

    // We also need this handler because it is fired
    // when value === old instead of onEnable and onDisable
    // which are only fired when value !== old
    _onSetEnabled(prop, old, value) {
        this._beingEnabled = true;
        this._checkState();
        this._beingEnabled = false;
    }

    _checkState() {
        var state = this.enabled && this.entity.enabled;
        if (state === this._oldState)
            return;

        this._oldState = state;

        this.fire(state ? 'enable' : 'disable');
        this.fire('state', state);

        if (state) {
            this.system._addComponentToEnabled(this);
        } else {
            this.system._removeComponentFromEnabled(this);
        }

        var wasLooping = this._beginLooping();

        var script;
        for (var i = 0, len = this.scripts.length; i < len; i++) {
            script = this.scripts[i];
            script.enabled = script._enabled;
        }

        this._endLooping(wasLooping);
    }

    _onBeforeRemove() {
        this.fire('remove');

        var wasLooping = this._beginLooping();

        // destroy all scripts
        for (var i = 0; i < this.scripts.length; i++) {
            var script = this.scripts[i];
            if (!script) continue;

            this.destroy(script.__scriptType.__name);
        }

        this._endLooping(wasLooping);
    }

    _removeDestroyedScripts() {
        var len = this._destroyedScripts.length;
        if (!len) return;

        var i;
        for (i = 0; i < len; i++) {
            var script = this._destroyedScripts[i];
            this._removeScriptInstance(script);
        }

        this._destroyedScripts.length = 0;

        // update execution order for scripts
        this._resetExecutionOrder(0, this._scripts.length);
    }

    _onInitializeAttributes() {
        for (var i = 0, len = this.scripts.length; i < len; i++)
            this.scripts[i].__initializeAttributes();
    }

    _scriptMethod(script, method, arg) {
        // #if _DEBUG
        try {
        // #endif
            script[method](arg);
        // #if _DEBUG
        } catch (ex) {
            // disable script if it fails to call method
            script.enabled = false;

            if (!script._callbacks || !script._callbacks.error) {
                console.warn('unhandled exception while calling "' + method + '" for "' + script.__scriptType.__name + '" script: ', ex);
                console.error(ex);
            }

            script.fire('error', ex, method);
            this.fire('error', script, ex, method);
        }
        // #endif
    }

    _onInitialize() {
        var script, scripts = this._scripts;

        var wasLooping = this._beginLooping();

        for (var i = 0, len = scripts.length; i < len; i++) {
            script = scripts[i];
            if (!script._initialized && script.enabled) {
                script._initialized = true;
                if (script.initialize)
                    this._scriptMethod(script, ScriptComponent.scriptMethods.initialize);
            }
        }

        this._endLooping(wasLooping);
    }

    _onPostInitialize() {
        this.onPostStateChange();
    }

    _onUpdate(dt) {
        var self = this;
        var list = self._updateList;
        if (!list.length) return;

        var script;

        var wasLooping = self._beginLooping();

        for (list.loopIndex = 0; list.loopIndex < list.length; list.loopIndex++) {
            script = list.items[list.loopIndex];
            if (script.enabled) {
                self._scriptMethod(script, ScriptComponent.scriptMethods.update, dt);
            }
        }

        self._endLooping(wasLooping);
    }

    _onPostUpdate(dt) {
        var self = this;
        var list = self._postUpdateList;
        if (!list.length) return;

        var wasLooping = self._beginLooping();

        var script;

        for (list.loopIndex = 0; list.loopIndex < list.length; list.loopIndex++) {
            script = list.items[list.loopIndex];
            if (script.enabled) {
                self._scriptMethod(script, ScriptComponent.scriptMethods.postUpdate, dt);
            }
        }

        self._endLooping(wasLooping);
    }

    /**
     * @private
     * @function
     * @description Inserts script instance into the scripts array at the specified index. Also inserts the script
     * into the update list if it has an update method and the post update list if it has a postUpdate method.
     * @param {object} scriptInstance - The script instance.
     * @param {number} index - The index where to insert the script at. If -1 then append it at the end.
     * @param {number} scriptsLength - The length of the scripts array.
     */
    _insertScriptInstance(scriptInstance, index, scriptsLength) {
        if (index === -1) {
            // append script at the end and set execution order
            this._scripts.push(scriptInstance);
            scriptInstance.__executionOrder = scriptsLength;

            // append script to the update list if it has an update method
            if (scriptInstance.update) {
                this._updateList.append(scriptInstance);
            }

            // add script to the postUpdate list if it has a postUpdate method
            if (scriptInstance.postUpdate) {
                this._postUpdateList.append(scriptInstance);
            }
        } else {
            // insert script at index and set execution order
            this._scripts.splice(index, 0, scriptInstance);
            scriptInstance.__executionOrder = index;

            // now we also need to update the execution order of all
            // the script instances that come after this script
            this._resetExecutionOrder(index + 1, scriptsLength + 1);

            // insert script to the update list if it has an update method
            // in the right order
            if (scriptInstance.update) {
                this._updateList.insert(scriptInstance);
            }

            // insert script to the postUpdate list if it has a postUpdate method
            // in the right order
            if (scriptInstance.postUpdate) {
                this._postUpdateList.insert(scriptInstance);
            }
        }
    }

    _removeScriptInstance(scriptInstance) {
        var idx = this._scripts.indexOf(scriptInstance);
        if (idx === -1) return idx;

        this._scripts.splice(idx, 1);

        if (scriptInstance.update) {
            this._updateList.remove(scriptInstance);
        }

        if (scriptInstance.postUpdate) {
            this._postUpdateList.remove(scriptInstance);
        }

        return idx;
    }

    _resetExecutionOrder(startIndex, scriptsLength) {
        for (var i = startIndex; i < scriptsLength; i++) {
            this._scripts[i].__executionOrder = i;
        }
    }

    _resolveEntityScriptAttribute(attribute, attributeName, oldValue, useGuid, newAttributes, duplicatedIdsMap) {
        if (attribute.array) {
            // handle entity array attribute
            var len = oldValue.length;
            if (! len) {
                return;
            }

            var newGuidArray = oldValue.slice();
            for (var i = 0; i < len; i++) {
                var guid = newGuidArray[i] instanceof Entity ? newGuidArray[i].getGuid() : newGuidArray[i];
                if (duplicatedIdsMap[guid]) {
                    newGuidArray[i] = useGuid ? duplicatedIdsMap[guid].getGuid() : duplicatedIdsMap[guid];
                }
            }

            newAttributes[attributeName] = newGuidArray;
        } else {
            // handle regular entity attribute
            if (oldValue instanceof Entity) {
                oldValue = oldValue.getGuid();
            } else if (typeof oldValue !== 'string') {
                return;
            }

            if (duplicatedIdsMap[oldValue]) {
                newAttributes[attributeName] = duplicatedIdsMap[oldValue];
            }
        }
    }

    /* eslint-disable jsdoc/no-undefined-types */
    /**
     * @function
     * @name ScriptComponent#has
     * @description Detect if script is attached to an entity.
     * @param {string|Class<ScriptType>} nameOrType - The name or type of {@link ScriptType}.
     * @returns {boolean} If script is attached to an entity.
     * @example
     * if (entity.script.has('playerController')) {
     *     // entity has script
     * }
     */
    /* eslint-enable jsdoc/no-undefined-types */
    has(nameOrType) {
        if (typeof nameOrType === 'string') {
            return !!this._scriptsIndex[nameOrType];
        }

        if (!nameOrType) return false;
        var scriptType = nameOrType;
        var scriptName = scriptType.__name;
        var scriptData = this._scriptsIndex[scriptName];
        var scriptInstance = scriptData && scriptData.instance;
        return scriptInstance instanceof scriptType; // will return false if scriptInstance undefined
    }

    /* eslint-disable jsdoc/no-undefined-types */
    /**
     * @function
     * @name ScriptComponent#get
     * @description Get a script instance (if attached).
     * @param {string|Class<ScriptType>} nameOrType - The name or type of {@link ScriptType}.
     * @returns {ScriptType|null} If script is attached, the instance is returned. Otherwise null is returned.
     * @example
     * var controller = entity.script.get('playerController');
     */
    /* eslint-enable jsdoc/no-undefined-types */
    get(nameOrType) {
        if (typeof nameOrType === 'string') {
            var data = this._scriptsIndex[nameOrType];
            return data ? data.instance : null;
        }

        if (!nameOrType) return null;
        var scriptType = nameOrType;
        var scriptName = scriptType.__name;
        var scriptData = this._scriptsIndex[scriptName];
        var scriptInstance = scriptData && scriptData.instance;
        return scriptInstance instanceof scriptType ? scriptInstance : null;
    }

    /* eslint-disable jsdoc/no-undefined-types */
    /**
     * @function
     * @name ScriptComponent#create
     * @description Create a script instance and attach to an entity script component.
     * @param {string|Class<ScriptType>} nameOrType - The name or type of {@link ScriptType}.
     * @param {object} [args] - Object with arguments for a script.
     * @param {boolean} [args.enabled] - If script instance is enabled after creation. Defaults to true.
     * @param {object} [args.attributes] - Object with values for attributes (if any), where key is name of an attribute.
     * @param {boolean} [args.preloading] - If script instance is created during preload. If true, script and attributes must be initialized manually. Defaults to false.
     * @param {number} [args.ind] - The index where to insert the script instance at. Defaults to -1, which means append it at the end.
     * @returns {ScriptType} Returns an instance of a {@link ScriptType} if successfully attached to an entity,
     * or null if it failed because a script with a same name has already been added
     * or if the {@link ScriptType} cannot be found by name in the {@link ScriptRegistry}.
     * @example
     * entity.script.create('playerController', {
     *     attributes: {
     *         speed: 4
     *     }
     * });
     */
    /* eslint-enable jsdoc/no-undefined-types */
    create(nameOrType, args = {}) {
        var self = this;

        var scriptType = nameOrType;
        var scriptName = nameOrType;

        // shorthand using script name
        if (typeof scriptType === 'string') {
            scriptType = this.system.app.scripts.get(scriptType);
        } else if (scriptType) {
            scriptName = scriptType.__name;
        }

        if (scriptType) {
            if (!this._scriptsIndex[scriptName] || !this._scriptsIndex[scriptName].instance) {
                // create script instance
                var scriptInstance = new scriptType({
                    app: this.system.app,
                    entity: this.entity,
                    enabled: args.hasOwnProperty('enabled') ? args.enabled : true,
                    attributes: args.attributes
                });

                var len = this._scripts.length;
                var ind = -1;
                if (typeof args.ind === 'number' && args.ind !== -1 && len > args.ind)
                    ind = args.ind;

                this._insertScriptInstance(scriptInstance, ind, len);

                this._scriptsIndex[scriptName] = {
                    instance: scriptInstance,
                    onSwap: function () {
                        self.swap(scriptName);
                    }
                };

                this[scriptName] = scriptInstance;

                if (!args.preloading)
                    scriptInstance.__initializeAttributes();

                this.fire('create', scriptName, scriptInstance);
                this.fire('create:' + scriptName, scriptInstance);

                this.system.app.scripts.on('swap:' + scriptName, this._scriptsIndex[scriptName].onSwap);

                if (!args.preloading) {

                    if (scriptInstance.enabled && !scriptInstance._initialized) {
                        scriptInstance._initialized = true;

                        if (scriptInstance.initialize)
                            this._scriptMethod(scriptInstance, ScriptComponent.scriptMethods.initialize);
                    }

                    if (scriptInstance.enabled && !scriptInstance._postInitialized) {
                        scriptInstance._postInitialized = true;
                        if (scriptInstance.postInitialize)
                            this._scriptMethod(scriptInstance, ScriptComponent.scriptMethods.postInitialize);
                    }
                }


                return scriptInstance;
            }

            console.warn('script \'' + scriptName + '\' is already added to entity \'' + this.entity.name + '\'');
        } else {
            this._scriptsIndex[scriptName] = {
                awaiting: true,
                ind: this._scripts.length
            };

            console.warn('script \'' + scriptName + '\' is not found, awaiting it to be added to registry');
        }

        return null;
    }

    /* eslint-disable jsdoc/no-undefined-types */
    /**
     * @function
     * @name ScriptComponent#destroy
     * @description Destroy the script instance that is attached to an entity.
     * @param {string|Class<ScriptType>} nameOrType - The name or type of {@link ScriptType}.
     * @returns {boolean} If it was successfully destroyed.
     * @example
     * entity.script.destroy('playerController');
     */
    /* eslint-enable jsdoc/no-undefined-types */
    destroy(nameOrType) {
        var scriptName = nameOrType;
        var scriptType = nameOrType;

        // shorthand using script name
        if (typeof scriptType === 'string') {
            scriptType = this.system.app.scripts.get(scriptType);
        } else if (scriptType) {
            scriptName = scriptType.__name;
        }

        var scriptData = this._scriptsIndex[scriptName];
        delete this._scriptsIndex[scriptName];
        if (!scriptData) return false;

        var scriptInstance = scriptData.instance;
        if (scriptInstance && !scriptInstance._destroyed) {
            scriptInstance.enabled = false;
            scriptInstance._destroyed = true;

            // if we are not currently looping through our scripts
            // then it's safe to remove the script
            if (!this._isLoopingThroughScripts) {
                var ind = this._removeScriptInstance(scriptInstance);
                if (ind >= 0) {
                    this._resetExecutionOrder(ind, this._scripts.length);
                }
            } else {
                // otherwise push the script in _destroyedScripts and
                // remove it from _scripts when the loop is over
                this._destroyedScripts.push(scriptInstance);
            }
        }

        // remove swap event
        this.system.app.scripts.off('swap:' + scriptName, scriptData.onSwap);

        delete this[scriptName];

        this.fire('destroy', scriptName, scriptInstance || null);
        this.fire('destroy:' + scriptName, scriptInstance || null);

        if (scriptInstance)
            scriptInstance.fire('destroy');

        return true;
    }

    /* eslint-disable jsdoc/no-undefined-types */
    /**
     * @private
     * @function
     * @name ScriptComponent#swap
     * @description Swap the script instance.
     * @param {string|Class<ScriptType>} nameOrType - The name or type of {@link ScriptType}.
     * @returns {boolean} If it was successfully swapped.
     */
    /* eslint-enable jsdoc/no-undefined-types */
    swap(nameOrType) {
        var scriptName = nameOrType;
        var scriptType = nameOrType;

        // shorthand using script name
        if (typeof scriptType === 'string') {
            scriptType = this.system.app.scripts.get(scriptType);
        } else if (scriptType) {
            scriptName = scriptType.__name;
        }

        var old = this._scriptsIndex[scriptName];
        if (!old || !old.instance) return false;

        var scriptInstanceOld = old.instance;
        var ind = this._scripts.indexOf(scriptInstanceOld);

        var scriptInstance = new scriptType({
            app: this.system.app,
            entity: this.entity,
            enabled: scriptInstanceOld.enabled,
            attributes: scriptInstanceOld.__attributes
        });

        if (!scriptInstance.swap)
            return false;

        scriptInstance.__initializeAttributes();

        // add to component
        this._scripts[ind] = scriptInstance;
        this._scriptsIndex[scriptName].instance = scriptInstance;
        this[scriptName] = scriptInstance;

        // set execution order and make sure we update
        // our update and postUpdate lists
        scriptInstance.__executionOrder = ind;
        if (scriptInstanceOld.update) {
            this._updateList.remove(scriptInstanceOld);
        }
        if (scriptInstanceOld.postUpdate) {
            this._postUpdateList.remove(scriptInstanceOld);
        }

        if (scriptInstance.update) {
            this._updateList.insert(scriptInstance);
        }
        if (scriptInstance.postUpdate) {
            this._postUpdateList.insert(scriptInstance);
        }

        this._scriptMethod(scriptInstance, ScriptComponent.scriptMethods.swap, scriptInstanceOld);

        this.fire('swap', scriptName, scriptInstance);
        this.fire('swap:' + scriptName, scriptInstance);

        return true;
    }

    /**
     * @function
     * @private
     * @name ScriptComponent#resolveDuplicatedEntityReferenceProperties
     * @description When an entity is cloned and it has entity script attributes that point
     * to other entities in the same subtree that is cloned, then we want the new script attributes to point
     * at the cloned entities. This method remaps the script attributes for this entity and it assumes that this
     * entity is the result of the clone operation.
     * @param {ScriptComponent} oldScriptComponent - The source script component that belongs to the entity that was being cloned.
     * @param {object} duplicatedIdsMap - A dictionary with guid-entity values that contains the entities that were cloned.
     */
    resolveDuplicatedEntityReferenceProperties(oldScriptComponent, duplicatedIdsMap) {
        var newScriptComponent = this.entity.script;
        var i, j;

        // for each script in the old component
        for (var scriptName in oldScriptComponent._scriptsIndex) {
            // get the script type from the script registry
            var scriptType = this.system.app.scripts.get(scriptName);
            if (! scriptType) {
                continue;
            }

            // get the script from the component's index
            var script = oldScriptComponent._scriptsIndex[scriptName];
            if (! script || ! script.instance) {
                continue;
            }

            // if __attributesRaw exists then it means that the new entity
            // has not yet initialized its attributes so put the new guid in there,
            // otherwise it means that the attributes have already been initialized
            // so convert the new guid to an entity
            // and put it in the new attributes
            var newAttributesRaw = newScriptComponent[scriptName].__attributesRaw;
            var newAttributes = newScriptComponent[scriptName].__attributes;
            if (! newAttributesRaw && ! newAttributes) {
                continue;
            }

            // if we are using attributesRaw then use the guid otherwise use the entity
            var useGuid = !!newAttributesRaw;

            // get the old script attributes from the instance
            var oldAttributes = script.instance.__attributes;
            for (var attributeName in oldAttributes) {
                if (! oldAttributes[attributeName]) {
                    continue;
                }

                // get the attribute definition from the script type
                var attribute = scriptType.attributes.get(attributeName);
                if (!attribute) {
                    continue;
                }

                if (attribute.type === 'entity') {
                    // entity attributes
                    this._resolveEntityScriptAttribute(
                        attribute,
                        attributeName,
                        oldAttributes[attributeName],
                        useGuid,
                        newAttributesRaw || newAttributes,
                        duplicatedIdsMap
                    );
                } else if (attribute.type === 'json' && Array.isArray(attribute.schema)) {
                    // json attributes
                    var oldValue = oldAttributes[attributeName];
                    var newJsonValue = (newAttributesRaw ? newAttributesRaw[attributeName] : newAttributes[attributeName]);

                    for (i = 0; i < attribute.schema.length; i++) {
                        var field = attribute.schema[i];
                        if (field.type !== 'entity') {
                            continue;
                        }

                        if (attribute.array) {
                            for (j = 0; j < oldValue.length; j++) {
                                this._resolveEntityScriptAttribute(
                                    field,
                                    field.name,
                                    oldValue[j][field.name],
                                    useGuid,
                                    newJsonValue[j],
                                    duplicatedIdsMap
                                );
                            }

                        } else {
                            this._resolveEntityScriptAttribute(
                                field,
                                field.name,
                                oldValue[field.name],
                                useGuid,
                                newJsonValue,
                                duplicatedIdsMap
                            );
                        }
                    }
                }
            }
        }
    }


    /* eslint-disable jsdoc/no-undefined-types */
    /**
     * @function
     * @name ScriptComponent#move
     * @description Move script instance to different position to alter update order of scripts within entity.
     * @param {string|Class<ScriptType>} nameOrType - The name or type of {@link ScriptType}.
     * @param {number} ind - New position index.
     * @returns {boolean} If it was successfully moved.
     * @example
     * entity.script.move('playerController', 0);
     */
    /* eslint-enable jsdoc/no-undefined-types */
    move(nameOrType, ind) {
        var len = this._scripts.length;
        if (ind >= len || ind < 0)
            return false;

        var scriptType = nameOrType;
        var scriptName = nameOrType;

        if (typeof scriptName !== 'string') {
            scriptName = nameOrType.__name;
        } else {
            scriptType = null;
        }

        var scriptData = this._scriptsIndex[scriptName];
        if (!scriptData || !scriptData.instance)
            return false;

        // if script type specified, make sure instance of said type
        var scriptInstance = scriptData.instance;
        if (scriptType && !(scriptInstance instanceof scriptType))
            return false;

        var indOld = this._scripts.indexOf(scriptInstance);
        if (indOld === -1 || indOld === ind)
            return false;

        // move script to another position
        this._scripts.splice(ind, 0, this._scripts.splice(indOld, 1)[0]);

        // reset execution order for scripts and re-sort update and postUpdate lists
        this._resetExecutionOrder(0, len);
        this._updateList.sort();
        this._postUpdateList.sort();

        this.fire('move', scriptName, scriptInstance, ind, indOld);
        this.fire('move:' + scriptName, scriptInstance, ind, indOld);

        return true;
    }

    get enabled() {
        return this._enabled;
    }

    set enabled(value) {
        var oldValue = this._enabled;
        this._enabled = value;
        this.fire('set', 'enabled', oldValue, value);
    }

    get scripts() {
        return this._scripts;
    }

    set scripts(value) {
        this._scriptsData = value;

        for (var key in value) {
            if (!value.hasOwnProperty(key))
                continue;

            var script = this._scriptsIndex[key];
            if (script) {
                // existing script

                // enabled
                if (typeof value[key].enabled === 'boolean')
                    script.enabled = !!value[key].enabled;

                // attributes
                if (typeof value[key].attributes === 'object') {
                    for (var attr in value[key].attributes) {
                        if (ScriptAttributes.reservedNames.has(attr))
                            continue;

                        if (!script.__attributes.hasOwnProperty(attr)) {
                            // new attribute
                            var scriptType = this.system.app.scripts.get(key);
                            if (scriptType)
                                scriptType.attributes.add(attr, { });
                        }

                        // update attribute
                        script[attr] = value[key].attributes[attr];
                    }
                }
            } else {
                // TODO scripts2
                // new script
                console.log(this.order);
            }
        }
    }
}

export { ScriptComponent };


<|start_filename|>src/framework/components/scroll-view/constants.js<|end_filename|>
/**
 * @constant
 * @type {number}
 * @name SCROLL_MODE_CLAMP
 * @description Content does not scroll any further than its bounds.
 */
export const SCROLL_MODE_CLAMP = 0;
/**
 * @constant
 * @type {number}
 * @name SCROLL_MODE_BOUNCE
 * @description Content scrolls past its bounds and then gently bounces back.
 */
export const SCROLL_MODE_BOUNCE = 1;
/**
 * @constant
 * @type {number}
 * @name SCROLL_MODE_INFINITE
 * @description Content can scroll forever.
 */
export const SCROLL_MODE_INFINITE = 2;

/**
 * @constant
 * @type {number}
 * @name SCROLLBAR_VISIBILITY_SHOW_ALWAYS
 * @description The scrollbar will be visible all the time.
 */
export const SCROLLBAR_VISIBILITY_SHOW_ALWAYS = 0;
/**
 * @constant
 * @type {number}
 * @name SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED
 * @description The scrollbar will be visible only when content exceeds the size of the viewport.
 */
export const SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED = 1;


<|start_filename|>src/framework/components/sprite/constants.js<|end_filename|>
/**
 * @constant
 * @type {string}
 * @name SPRITETYPE_SIMPLE
 * @description A {@link SpriteComponent} that displays a single frame from a sprite asset.
 */
export const SPRITETYPE_SIMPLE = 'simple';

/**
 * @constant
 * @type {string}
 * @name SPRITETYPE_ANIMATED
 * @description A {@link SpriteComponent} that renders sprite animations.
 */
export const SPRITETYPE_ANIMATED = 'animated';


<|start_filename|>src/asset/constants.js<|end_filename|>
export const ABSOLUTE_URL = new RegExp(
    '^' + // beginning of the url
    '\\s*' +  // ignore leading spaces (some browsers trim the url automatically, but we can't assume that)
    '(?:' +  // beginning of a non-captured regex group
        // `{protocol}://`
        '(?:' +  // beginning of protocol scheme (non-captured regex group)
            '[a-z]+[a-z0-9\\-\\+\\.]*' + // protocol scheme must (RFC 3986) consist of "a letter and followed by any combination of letters, digits, plus ("+"), period ("."), or hyphen ("-")."
            ':' + // protocol scheme must end with colon character
        ')?' + // end of optional scheme group, the group is optional since the string may be a protocol-relative absolute URL
        '//' + // an absolute url must always begin with two forward slash characters (ignoring any leading spaces and protocol scheme)

        '|' + // or another option(s):

        // Data URL (RFC 2397), simplified
        'data:' +

        // Blob data
        '|blob:' +
    ')',
    'i' // non case-sensitive flag
);

/**
 * @constant
 * @type {string}
 * @name ASSET_ANIMATION
 * @description Asset type name for animation.
 */
export const ASSET_ANIMATION = 'animation';
/**
 * @constant
 * @type {string}
 * @name ASSET_AUDIO
 * @description Asset type name for audio.
 */
export const ASSET_AUDIO = 'audio';
/**
 * @constant
 * @type {string}
 * @name ASSET_IMAGE
 * @description Asset type name for image.
 */
export const ASSET_IMAGE = 'image';
/**
 * @constant
 * @type {string}
 * @name ASSET_JSON
 * @description Asset type name for json.
 */
export const ASSET_JSON = 'json';
/**
 * @constant
 * @type {string}
 * @name ASSET_MODEL
 * @description Asset type name for model.
 */
export const ASSET_MODEL = 'model';
/**
 * @constant
 * @type {string}
 * @name ASSET_MATERIAL
 * @description Asset type name for material.
 */
export const ASSET_MATERIAL = 'material';
/**
 * @constant
 * @type {string}
 * @name ASSET_TEXT
 * @description Asset type name for text.
 */
export const ASSET_TEXT = 'text';
/**
 * @constant
 * @type {string}
 * @name ASSET_TEXTURE
 * @description Asset type name for texture.
 */
export const ASSET_TEXTURE = 'texture';
/**
 * @constant
 * @type {string}
 * @name ASSET_CUBEMAP
 * @description Asset type name for cubemap.
 */
export const ASSET_CUBEMAP = 'cubemap';
/**
 * @constant
 * @type {string}
 * @name ASSET_SHADER
 * @description Asset type name for shader.
 */
export const ASSET_SHADER = 'shader';
/**
 * @constant
 * @type {string}
 * @name ASSET_CSS
 * @description Asset type name for CSS.
 */
export const ASSET_CSS = 'css';
/**
 * @constant
 * @type {string}
 * @name ASSET_HTML
 * @description Asset type name for HTML.
 */
export const ASSET_HTML = 'html';
/**
 * @constant
 * @type {string}
 * @name ASSET_SCRIPT
 * @description Asset type name for script.
 */
export const ASSET_SCRIPT = 'script';
/**
 * @constant
 * @type {string}
 * @name ASSET_CONTAINER
 * @description Asset type name for a container.
 */
export const ASSET_CONTAINER = 'container';


<|start_filename|>src/graphics/program-lib/chunks/shadowCascades.frag<|end_filename|>
const float maxCascades = 4.0;

// shadow matrix for selected cascade
mat4 cascadeShadowMat;

// function which selects a shadow projection matrix based on cascade distances 
void getShadowCascadeMatrix(mat4 shadowMatrixPalette[4], float shadowCascadeDistances[4], float shadowCascadeCount) {

    // depth in 0 .. far plane range
    float depth = 1.0 / gl_FragCoord.w;

    // find cascade index based on the depth (loop as there is no per component vec compare operator in webgl)
    float cascadeIndex = 0.0;
    for (float i = 0.0; i < maxCascades; i++) {
        if (depth < shadowCascadeDistances[int(i)]) {
            cascadeIndex = i;
            break;
        }
    }

    // limit to actual number of used cascades
    cascadeIndex = min(cascadeIndex, shadowCascadeCount - 1.0);

    // pick shadow matrix
    #ifdef GL2
        cascadeShadowMat = shadowMatrixPalette[int(cascadeIndex)];
    #else
        // webgl 1 does not allow non-cost index array lookup
        if (cascadeIndex == 0.0) {
            cascadeShadowMat = shadowMatrixPalette[0];
        }
        else if (cascadeIndex == 1.0) {
            cascadeShadowMat = shadowMatrixPalette[1];
        }
        else if (cascadeIndex == 2.0) {
            cascadeShadowMat = shadowMatrixPalette[2];
        }
        else {
            cascadeShadowMat = shadowMatrixPalette[3];
        }
    #endif
}

void fadeShadow(float shadowCascadeDistances[4]) {                  

    // if the pixel is past the shadow distance, remove shadow
    // this enforces straight line instead of corner of shadow which moves when camera rotates  
    float depth = 1.0 / gl_FragCoord.w;
    if (depth > shadowCascadeDistances[int(maxCascades - 1.0)]) {
        dShadowCoord.z = -9999999.0;
    }
}


<|start_filename|>src/graphics/program-lib/chunks/skyboxHDR.frag<|end_filename|>
varying vec3 vViewDir;

uniform samplerCube texture_cubeMap;

#ifdef CUBEMAP_ROTATION
uniform mat3 cubeMapRotationMatrix;
#endif

void main(void) {
#ifdef CUBEMAP_ROTATION
    vec3 dir=vViewDir * cubeMapRotationMatrix;
#else
    vec3 dir=vViewDir;
#endif
#ifndef RIGHT_HANDED_CUBEMAP
    dir.x *= -1.0;
#endif
    vec3 color = processEnvironment($textureCubeSAMPLE(texture_cubeMap, fixSeamsStatic(dir, $FIXCONST)).rgb);
    color = toneMap(color);
    color = gammaCorrectOutput(color);
    gl_FragColor = vec4(color, 1.0);
}


<|start_filename|>src/math/constants.js<|end_filename|>
/**
 * @constant
 * @type {number}
 * @name CURVE_LINEAR
 * @description A linear interpolation scheme.
 */
export const CURVE_LINEAR = 0;
/**
 * @constant
 * @type {number}
 * @name CURVE_SMOOTHSTEP
 * @description A smooth step interpolation scheme.
 */
export const CURVE_SMOOTHSTEP = 1;
/**
 * @deprecated
 * @constant
 * @type {number}
 * @name CURVE_CATMULL
 * @description A Catmull-Rom spline interpolation scheme. This interpolation scheme is deprecated. Use CURVE_SPLINE instead.
 */
export const CURVE_CATMULL = 2;
/**
 * @deprecated
 * @constant
 * @type {number}
 * @name CURVE_CARDINAL
 * @description A cardinal spline interpolation scheme. This interpolation scheme is deprecated. Use CURVE_SPLINE instead.
 */
export const CURVE_CARDINAL = 3;
/**
 * @constant
 * @type {number}
 * @name CURVE_SPLINE
 * @description Cardinal spline interpolation scheme. For Catmull-Rom, specify curve tension 0.5.
 */
export const CURVE_SPLINE = 4;
/**
 * @constant
 * @type {number}
 * @name CURVE_STEP
 * @description A stepped interpolator, free from the shackles of blending.
 */
export const CURVE_STEP = 5;


<|start_filename|>tests/framework/scene-registry/test_scene_registry.js<|end_filename|>
describe("pc.SceneRegistry", function () {
    var app;

    beforeEach(function () {
        app = new pc.Application(document.createElement("canvas"));
        app.systems.add(new pc.DummyComponentSystem(app));
    });

    afterEach(function () {
        app.destroy();
    });

    it("new registry is empty", function () {
        var registry = new pc.SceneRegistry();

        expect(registry.list().length).to.equal(0);
    });

    it("add", function () {
        var registry = new pc.SceneRegistry();

        registry.add("New Scene", "/test.json");

        expect(registry.list().length).to.equal(1);
    });

    it("find", function () {
        var registry = new pc.SceneRegistry();
        registry.add("New Scene", "/test.json");

        var result = registry.find("New Scene");

        expect(result.name).to.equal("New Scene");
        expect(result.url).to.equal("/test.json");
    });

    it("remove", function () {
        var registry = new pc.SceneRegistry();
        registry.add("New Scene", "/test.json");

        registry.remove("New Scene");

        expect(registry.list().length).to.equal(0);
        expect(registry.find("New Scene")).to.equal(null);
    });

    it("add multiple", function () {
        var registry = new pc.SceneRegistry();
        registry.add("New Scene 1", "/test1.json");
        registry.add("New Scene 2", "/test2.json");
        registry.add("New Scene 3", "/test3.json");

        expect(registry.list().length).to.equal(3);
        expect(registry.list()[0].url).to.equal('/test1.json');
        expect(registry.list()[1].url).to.equal('/test2.json');
        expect(registry.list()[2].url).to.equal('/test3.json');

        expect(registry.find("New Scene 1").url).to.equal("/test1.json");
        expect(registry.find("New Scene 2").url).to.equal("/test2.json");
        expect(registry.find("New Scene 3").url).to.equal("/test3.json");
    });

    it("remove middle value", function () {
        var registry = new pc.SceneRegistry();
        registry.add("New Scene 1", "/test1.json");
        registry.add("New Scene 2", "/test2.json");
        registry.add("New Scene 3", "/test3.json");

        registry.remove("New Scene 2");

        expect(registry.list().length).to.equal(2);
        expect(registry.list()[0].url).to.equal('/test1.json');
        expect(registry.list()[1].url).to.equal('/test3.json');

        expect(registry.find("New Scene 1").url).to.equal("/test1.json");
        expect(registry.find("New Scene 3").url).to.equal("/test3.json");
    });

    it("url index", function () {
        var registry = new pc.SceneRegistry();
        registry.add("New Scene 1", "/test1.json");

        var result = registry.findByUrl('/test1.json');
        expect(result.name).to.equal("New Scene 1");
        expect(result.url).to.equal("/test1.json");

    });


    it("remove middle, url index", function () {
        var registry = new pc.SceneRegistry();
        registry.add("New Scene 1", "/test1.json");
        registry.add("New Scene 2", "/test2.json");
        registry.add("New Scene 3", "/test3.json");

        registry.remove("New Scene 2");

        expect(registry.findByUrl("/test1.json").name).to.equal("New Scene 1");
        expect(registry.findByUrl("/test2.json")).to.equal(null);
        expect(registry.findByUrl("/test3.json").name).to.equal("New Scene 3");
    });

    var promisedLoadSceneData = function (registry, sceneItemOrUrl) {
        return new Promise(function (resolve, reject) {
            registry.loadSceneData(sceneItemOrUrl, function (err, sceneItem) {
                if (err) {
                    reject(err);
                }

                resolve(sceneItem);
            });
        });
    };

    it("load and cache, check data is valid, unload data, check data is removed with SceneItem", async function () {
        var registry = new pc.SceneRegistry(app);
        registry.add("New Scene 1", "base/tests/test-assets/scenes/test1.json");

        var sceneItem = registry.find("New Scene 1");
        await promisedLoadSceneData(registry, sceneItem);

        expect(sceneItem).to.exist;
        expect(sceneItem.data).to.exist;
        expect(sceneItem._loading).to.equal(false);

        registry.unloadSceneData(sceneItem);
        expect(sceneItem.data).to.null;
        expect(sceneItem._loading).to.equal(false);
    });

    it("load and cache, check data is valid, unload data, check data is removed with Urls", async function () {
        var registry = new pc.SceneRegistry(app);
        var sceneUrl = "base/tests/test-assets/scenes/test1.json";
        registry.add("New Scene 1", sceneUrl);

        var sceneItem = await promisedLoadSceneData(registry, sceneUrl);
        expect(sceneItem).to.exist;
        expect(sceneItem.data).to.exist;
        expect(sceneItem._loading).to.equal(false);

        registry.unloadSceneData(sceneUrl);
        expect(sceneItem.data).to.null;
        expect(sceneItem._loading).to.equal(false);
    });
});


<|start_filename|>src/framework/components/collision/component.js<|end_filename|>
import { Asset } from '../../../asset/asset.js';

import { Component } from '../component.js';

/**
 * @component
 * @class
 * @name CollisionComponent
 * @augments Component
 * @classdesc A collision volume. Use this in conjunction with a {@link RigidBodyComponent}
 * to make a collision volume that can be simulated using the physics engine.
 *
 * If the {@link Entity} does not have a {@link RigidBodyComponent} then this collision
 * volume will act as a trigger volume. When an entity with a dynamic or kinematic body enters
 * or leaves an entity with a trigger volume, both entities will receive trigger events.
 *
 * The following table shows all the events that can be fired between two Entities:
 *
 * |                                       | Rigid Body (Static)                                                   | Rigid Body (Dynamic or Kinematic)                                     | Trigger Volume                                      |
 * | ------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------- |
 * | **Rigid Body (Static)**               |                                                                       | <ul><li>contact</li><li>collisionstart</li><li>collisionend</li></ul> |                                                     |
 * | **Rigid Body (Dynamic or Kinematic)** | <ul><li>contact</li><li>collisionstart</li><li>collisionend</li></ul> | <ul><li>contact</li><li>collisionstart</li><li>collisionend</li></ul> | <ul><li>triggerenter</li><li>triggerleave</li></ul> |
 * | **Trigger Volume**                    |                                                                       | <ul><li>triggerenter</li><li>triggerleave</li></ul>                   |                                                     |
 *
 * @description Create a new CollisionComponent.
 * @param {CollisionComponentSystem} system - The ComponentSystem that created this Component.
 * @param {Entity} entity - The Entity that this Component is attached to.
 * @property {string} type The type of the collision volume. Can be:
 *
 * * "box": A box-shaped collision volume.
 * * "capsule": A capsule-shaped collision volume.
 * * "compound": A compound shape. Any descendant entities with a collision component
 * of type box, capsule, cone, cylinder or sphere will be combined into a single, rigid
 * shape.
 * * "cone": A cone-shaped collision volume.
 * * "cylinder": A cylinder-shaped collision volume.
 * * "mesh": A collision volume that uses a model asset as its shape.
 * * "sphere": A sphere-shaped collision volume.
 *
 * Defaults to "box".
 * @property {Vec3} halfExtents The half-extents of the box-shaped collision volume in the
 * x, y and z axes. Defaults to [0.5, 0.5, 0.5].
 * @property {number} radius The radius of the sphere, capsule, cylinder or cone-shaped collision
 * volumes. Defaults to 0.5.
 * @property {number} axis The local space axis with which the capsule, cylinder or cone-shaped
 * collision volume's length is aligned. 0 for X, 1 for Y and 2 for Z. Defaults to 1 (Y-axis).
 * @property {number} height The total height of the capsule, cylinder or cone-shaped collision
 * volume from tip to tip. Defaults to 2.
 * @property {Asset|number} asset The asset for the model of the mesh collision volume - can also be
 * an asset id. Defaults to null.
 * @property {Asset|number} renderAsset The render asset of the mesh collision volume - can also be
 * an asset id. Defaults to null. If not set then the asset property will be checked instead.
 * @property {Model} model The model that is added to the scene graph for the mesh collision
 * volume.
 */
class CollisionComponent extends Component {
    constructor(system, entity) {
        super(system, entity);

        this._compoundParent = null;

        this.entity.on('insert', this._onInsert, this);

        this.on('set_type', this.onSetType, this);
        this.on('set_halfExtents', this.onSetHalfExtents, this);
        this.on('set_radius', this.onSetRadius, this);
        this.on('set_height', this.onSetHeight, this);
        this.on('set_axis', this.onSetAxis, this);
        this.on("set_asset", this.onSetAsset, this);
        this.on("set_renderAsset", this.onSetRenderAsset, this);
        this.on("set_model", this.onSetModel, this);
        this.on("set_render", this.onSetRender, this);
    }

    // Events Documentation
    /**
     * @event
     * @name CollisionComponent#contact
     * @description The 'contact' event is fired when a contact occurs between two rigid bodies.
     * @param {ContactResult} result - Details of the contact between the two rigid bodies.
     */

    /**
     * @event
     * @name CollisionComponent#collisionstart
     * @description The 'collisionstart' event is fired when two rigid bodies start touching.
     * @param {ContactResult} result - Details of the contact between the two Entities.
     */

    /**
     * @event
     * @name CollisionComponent#collisionend
     * @description The 'collisionend' event is fired two rigid-bodies stop touching.
     * @param {Entity} other - The {@link Entity} that stopped touching this collision volume.
     */

    /**
     * @event
     * @name CollisionComponent#triggerenter
     * @description The 'triggerenter' event is fired when a rigid body enters a trigger volume.
     * a {@link RigidBodyComponent} attached.
     * @param {Entity} other - The {@link Entity} that entered this collision volume.
     */

    /**
     * @event
     * @name CollisionComponent#triggerleave
     * @description The 'triggerleave' event is fired when a rigid body exits a trigger volume.
     * a {@link RigidBodyComponent} attached.
     * @param {Entity} other - The {@link Entity} that exited this collision volume.
     */

    onSetType(name, oldValue, newValue) {
        if (oldValue !== newValue) {
            this.system.changeType(this, oldValue, newValue);
        }
    }

    onSetHalfExtents(name, oldValue, newValue) {
        var t = this.data.type;
        if (this.data.initialized && t === 'box') {
            this.system.recreatePhysicalShapes(this);
        }
    }

    onSetRadius(name, oldValue, newValue) {
        var t = this.data.type;
        if (this.data.initialized && (t === 'sphere' || t === 'capsule' || t === 'cylinder' || t === 'cone')) {
            this.system.recreatePhysicalShapes(this);
        }
    }

    onSetHeight(name, oldValue, newValue) {
        var t = this.data.type;
        if (this.data.initialized && (t === 'capsule' || t === 'cylinder' || t === 'cone')) {
            this.system.recreatePhysicalShapes(this);
        }
    }

    onSetAxis(name, oldValue, newValue) {
        var t = this.data.type;
        if (this.data.initialized && (t === 'capsule' || t === 'cylinder' || t === 'cone')) {
            this.system.recreatePhysicalShapes(this);
        }
    }

    onSetAsset(name, oldValue, newValue) {
        var asset;
        var assets = this.system.app.assets;

        if (oldValue) {
            // Remove old listeners
            asset = assets.get(oldValue);
            if (asset) {
                asset.off('remove', this.onAssetRemoved, this);
            }
        }

        if (newValue) {
            if (newValue instanceof Asset) {
                this.data.asset = newValue.id;
            }

            asset = assets.get(this.data.asset);
            if (asset) {
                // make sure we don't subscribe twice
                asset.off('remove', this.onAssetRemoved, this);
                asset.on('remove', this.onAssetRemoved, this);
            }
        }

        if (this.data.initialized && this.data.type === 'mesh') {
            if (!newValue) {
                // if asset is null set model to null
                // so that it's going to be removed from the simulation
                this.data.model = null;
            }
            this.system.recreatePhysicalShapes(this);
        }
    }

    onSetRenderAsset(name, oldValue, newValue) {
        var asset;
        var assets = this.system.app.assets;

        if (oldValue) {
            // Remove old listeners
            asset = assets.get(oldValue);
            if (asset) {
                asset.off('remove', this.onRenderAssetRemoved, this);
            }
        }

        if (newValue) {
            if (newValue instanceof Asset) {
                this.data.renderAsset = newValue.id;
            }

            asset = assets.get(this.data.renderAsset);
            if (asset) {
                // make sure we don't subscribe twice
                asset.off('remove', this.onRenderAssetRemoved, this);
                asset.on('remove', this.onRenderAssetRemoved, this);
            }
        }

        if (this.data.initialized && this.data.type === 'mesh') {
            if (!newValue) {
                // if render asset is null set render to null
                // so that it's going to be removed from the simulation
                this.data.render = null;
            }
            this.system.recreatePhysicalShapes(this);
        }
    }

    onSetModel(name, oldValue, newValue) {
        if (this.data.initialized && this.data.type === 'mesh') {
            // recreate physical shapes skipping loading the model
            // from the 'asset' as the model passed in newValue might
            // have been created procedurally
            this.system.implementations.mesh.doRecreatePhysicalShape(this);
        }
    }

    onSetRender(name, oldValue, newValue) {
        this.onSetModel(name, oldValue, newValue);
    }

    onAssetRemoved(asset) {
        asset.off('remove', this.onAssetRemoved, this);
        if (this.data.asset === asset.id) {
            this.asset = null;
        }
    }

    onRenderAssetRemoved(asset) {
        asset.off('remove', this.onRenderAssetRemoved, this);
        if (this.data.renderAsset === asset.id) {
            this.renderAsset = null;
        }
    }

    _getCompoundChildShapeIndex(shape) {
        var compound = this.data.shape;
        var shapes = compound.getNumChildShapes();

        for (var i = 0; i < shapes; i++) {
            var childShape = compound.getChildShape(i);
            if (childShape.ptr === shape.ptr) {
                return i;
            }
        }

        return null;
    }

    _onInsert(parent) {
        // TODO
        // if is child of compound shape
        // and there is no change of compoundParent, then update child transform
        // once updateChildTransform is exposed in ammo.js

        if (typeof Ammo === 'undefined')
            return;

        if (this._compoundParent) {
            this.system.recreatePhysicalShapes(this);
        } else if (! this.entity.rigidbody) {
            var ancestor = this.entity.parent;
            while (ancestor) {
                if (ancestor.collision && ancestor.collision.type === 'compound') {
                    if (ancestor.collision.shape.getNumChildShapes() === 0) {
                        this.system.recreatePhysicalShapes(ancestor.collision);
                    } else {
                        this.system.recreatePhysicalShapes(this);
                    }
                    break;
                }
                ancestor = ancestor.parent;
            }
        }
    }

    _updateCompound() {
        var entity = this.entity;
        if (entity._dirtyWorld) {
            var dirty = entity._dirtyLocal;
            var parent = entity;
            while (parent && !dirty) {
                if (parent.collision && parent.collision === this._compoundParent)
                    break;

                if (parent._dirtyLocal)
                    dirty = true;

                parent = parent.parent;
            }

            if (dirty) {
                entity.forEach(this.system.implementations.compound._updateEachDescendantTransform, entity);

                var bodyComponent = this._compoundParent.entity.rigidbody;
                if (bodyComponent)
                    bodyComponent.activate();
            }
        }
    }

    onEnable() {
        if (this.data.type === 'mesh' && (this.data.asset || this.data.renderAsset) && this.data.initialized) {
            var asset = this.system.app.assets.get(this.data.asset || this.data.renderAsset);
            // recreate the collision shape if the model asset is not loaded
            // or the shape does not exist
            if (asset && (!asset.resource || !this.data.shape)) {
                this.system.recreatePhysicalShapes(this);
                return;
            }
        }

        if (this.entity.rigidbody) {
            if (this.entity.rigidbody.enabled) {
                this.entity.rigidbody.enableSimulation();
            }
        } else if (this._compoundParent && this !== this._compoundParent) {
            if (this._compoundParent.shape.getNumChildShapes() === 0) {
                this.system.recreatePhysicalShapes(this._compoundParent);
            } else {
                var transform = this.system._getNodeTransform(this.entity, this._compoundParent.entity);
                this._compoundParent.shape.addChildShape(transform, this.data.shape);
                Ammo.destroy(transform);

                if (this._compoundParent.entity.rigidbody)
                    this._compoundParent.entity.rigidbody.activate();
            }
        } else if (this.entity.trigger) {
            this.entity.trigger.enable();
        }
    }

    onDisable() {
        if (this.entity.rigidbody) {
            this.entity.rigidbody.disableSimulation();
        } else if (this._compoundParent && this !== this._compoundParent) {
            if (! this._compoundParent.entity._destroying) {
                this.system._removeCompoundChild(this._compoundParent, this.data.shape);

                if (this._compoundParent.entity.rigidbody)
                    this._compoundParent.entity.rigidbody.activate();
            }
        } else if (this.entity.trigger) {
            this.entity.trigger.disable();
        }
    }

    onBeforeRemove() {
        if (this.asset) {
            this.asset = null;
        }
        if (this.renderAsset) {
            this.renderAsset = null;
        }

        this.entity.off('insert', this._onInsert, this);

        this.off();
    }
}

export { CollisionComponent };


<|start_filename|>src/anim/evaluator/anim-events.js<|end_filename|>
/**
 * @class
 * @name AnimEvents
 * @classdesc AnimEvents stores a sorted array of animation events which should fire sequentially during the playback of an pc.AnimTrack.
 * @description Create a new collection of animation events.
 * - @param {object[]} events An array of animation events.
 * @example
 * const events = new pc.AnimEvents([
 *     {
 *         name: 'my_event',
 *         time: 1.3, // given in seconds
 *         // any additional properties added are optional and will be available in the EventHandler callback's event object
 *         myProperty: 'test',
 *         myOtherProperty: true
 *     }
 * ]);
 * animTrack.events = events;
 */
class AnimEvents {
    constructor(events) {
        this._events = [...events];
        this._events.sort((a, b) => a.time - b.time);
    }

    get events() {
        return this._events;
    }
}

export { AnimEvents };


<|start_filename|>src/framework/components/layout-group/constants.js<|end_filename|>
/**
 * @constant
 * @type {number}
 * @name FITTING_NONE
 * @description Disable all fitting logic.
 */
export const FITTING_NONE = 0;
/**
 * @constant
 * @type {number}
 * @name FITTING_STRETCH
 * @description Stretch child elements to fit the parent container.
 */
export const FITTING_STRETCH = 1;
/**
 * @constant
 * @type {number}
 * @name FITTING_SHRINK
 * @description Shrink child elements to fit the parent container.
 */
export const FITTING_SHRINK = 2;
/**
 * @constant
 * @type {number}
 * @name FITTING_BOTH
 * @description Apply both STRETCH and SHRINK fitting logic where applicable.
 */
export const FITTING_BOTH = 3;


<|start_filename|>src/script/script.js<|end_filename|>
import { EventHandler } from '../core/event-handler.js';

import { ScriptHandler } from '../resources/script.js';

import { script } from '../framework/script.js';
import { Application } from '../framework/application.js';

import { ScriptAttributes } from './script-attributes.js';
import { ScriptType } from './script-type.js';

const reservedScriptNames = new Set([
    'system', 'entity', 'create', 'destroy', 'swap', 'move',
    'scripts', '_scripts', '_scriptsIndex', '_scriptsData',
    'enabled', '_oldState', 'onEnable', 'onDisable', 'onPostStateChange',
    '_onSetEnabled', '_checkState', '_onBeforeRemove',
    '_onInitializeAttributes', '_onInitialize', '_onPostInitialize',
    '_onUpdate', '_onPostUpdate',
    '_callbacks', 'has', 'get', 'on', 'off', 'fire', 'once', 'hasEvent'
]);

/* eslint-disable jsdoc/no-undefined-types */
/**
 * @static
 * @function
 * @name createScript
 * @description Create and register a new {@link ScriptType}.
 * It returns new class type (constructor function), which is auto-registered to {@link ScriptRegistry} using it's name.
 * This is the main interface to create Script Types, to define custom logic using JavaScript, that is used to create interaction for entities.
 * @param {string} name - Unique Name of a Script Type.
 * If a Script Type with the same name has already been registered and the new one has a `swap` method defined in its prototype,
 * then it will perform hot swapping of existing Script Instances on entities using this new Script Type.
 * Note: There is a reserved list of names that cannot be used, such as list below as well as some starting from `_` (underscore):
 * system, entity, create, destroy, swap, move, scripts, onEnable, onDisable, onPostStateChange, has, on, off, fire, once, hasEvent.
 * @param {Application} [app] - Optional application handler, to choose which {@link ScriptRegistry} to add a script to.
 * By default it will use `Application.getApplication()` to get current {@link Application}.
 * @returns {Class<ScriptType>} A class type (constructor function) that inherits {@link ScriptType},
 * which the developer is meant to further extend by adding attributes and prototype methods.
 * @example
 * var Turning = pc.createScript('turn');
 *
 * // define `speed` attribute that is available in Editor UI
 * Turning.attributes.add('speed', {
 *     type: 'number',
 *     default: 180,
 *     placeholder: 'deg/s'
 * });
 *
 * // runs every tick
 * Turning.prototype.update = function (dt) {
 *     this.entity.rotate(0, this.speed * dt, 0);
 * };
 */
/* eslint-enable jsdoc/no-undefined-types */
function createScript(name, app) {
    if (script.legacy) {
        // #if _DEBUG
        console.error("This project is using the legacy script system. You cannot call pc.createScript(). See: http://developer.playcanvas.com/en/user-manual/scripting/legacy/");
        // #endif
        return null;
    }

    if (reservedScriptNames.has(name))
        throw new Error('script name: \'' + name + '\' is reserved, please change script name');

    var scriptType = function (args) {
        EventHandler.prototype.initEventHandler.call(this);
        ScriptType.prototype.initScriptType.call(this, args);
    };

    scriptType.prototype = Object.create(ScriptType.prototype);
    scriptType.prototype.constructor = scriptType;

    scriptType.extend = ScriptType.extend;
    scriptType.attributes = new ScriptAttributes(scriptType);

    registerScript(scriptType, name, app);
    return scriptType;
}

// Editor uses this - migrate to ScriptAttributes.reservedNames and delete this
var reservedAttributes = {};
ScriptAttributes.reservedNames.forEach((value, value2, set) => {
    reservedAttributes[value] = 1;
});
createScript.reservedAttributes = reservedAttributes;


/* eslint-disable jsdoc/no-undefined-types */
/* eslint-disable jsdoc/check-examples */
/**
 * @static
 * @function
 * @name registerScript
 * @description Register a existing class type as a Script Type to {@link ScriptRegistry}.
 * Useful when defining a ES6 script class that extends {@link ScriptType} (see example).
 * @param {Class<ScriptType>} script - The existing class type (constructor function) to be registered as a Script Type.
 * Class must extend {@link ScriptType} (see example). Please note: A class created using {@link createScript} is auto-registered,
 * and should therefore not be pass into {@link registerScript} (which would result in swapping out all related script instances).
 * @param {string} [name] - Optional unique name of the Script Type. By default it will use the same name as the existing class.
 * If a Script Type with the same name has already been registered and the new one has a `swap` method defined in its prototype,
 * then it will perform hot swapping of existing Script Instances on entities using this new Script Type.
 * Note: There is a reserved list of names that cannot be used, such as list below as well as some starting from `_` (underscore):
 * system, entity, create, destroy, swap, move, scripts, onEnable, onDisable, onPostStateChange, has, on, off, fire, once, hasEvent.
 * @param {Application} [app] - Optional application handler, to choose which {@link ScriptRegistry} to register the script type to.
 * By default it will use `Application.getApplication()` to get current {@link Application}.
 * @example
 * // define a ES6 script class
 * class PlayerController extends pc.ScriptType {
 *
 *     initialize() {
 *         // called once on initialize
 *     }
 *
 *     update(dt) {
 *         // called each tick
 *     }
 * }
 *
 * // register the class as a script
 * pc.registerScript(PlayerController);
 *
 * // declare script attributes (Must be after pc.registerScript())
 * PlayerController.attributes.add('attribute1', {type: 'number'});
 */
/* eslint-enable jsdoc/check-examples */
/* eslint-enable jsdoc/no-undefined-types */
function registerScript(script, name, app) {
    if (script.legacy) {
        // #if _DEBUG
        console.error("This project is using the legacy script system. You cannot call pc.registerScript(). See: http://developer.playcanvas.com/en/user-manual/scripting/legacy/");
        // #endif
        return;
    }

    if (typeof script !== 'function')
        throw new Error('script class: \'' + script + '\' must be a constructor function (i.e. class).');

    if (!(script.prototype instanceof ScriptType))
        throw new Error('script class: \'' + ScriptType.__getScriptName(script) + '\' does not extend pc.ScriptType.');

    name = name || script.__name || ScriptType.__getScriptName(script);

    if (reservedScriptNames.has(name))
        throw new Error('script name: \'' + name + '\' is reserved, please change script name');

    script.__name = name;

    // add to scripts registry
    var registry = app ? app.scripts : Application.getApplication().scripts;
    registry.add(script);

    ScriptHandler._push(script);
}

export { createScript, registerScript };


<|start_filename|>src/shape/frustum.js<|end_filename|>
import { Vec3 } from "../math/vec3";
import { PROJECTION_PERSPECTIVE } from "../scene/constants";

const _frustumPoints = [new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3()];

/**
 * @class
 * @name Frustum
 * @classdesc A frustum is a shape that defines the viewing space of a camera. It can be
 * used to determine visibility of points and bounding spheres. Typically, you would not
 * create a Frustum shape directly, but instead query {@link CameraComponent#frustum}.
 * @description Creates a new frustum shape.
 * @example
 * var frustum = new pc.Frustum();
 */
class Frustum {
    constructor() {
        this.planes = [];
        for (let i = 0; i < 6; i++)
            this.planes[i] = [];
    }

    /**
     * @function
     * @name Frustum#setFromMat4
     * @description Updates the frustum shape based on the supplied 4x4 matrix.
     * @param {Mat4} matrix - The matrix describing the shape of the frustum.
     * @example
     * // Create a perspective projection matrix
     * var projMat = pc.Mat4();
     * projMat.setPerspective(45, 16 / 9, 1, 1000);
     *
     * // Create a frustum shape that is represented by the matrix
     * var frustum = new pc.Frustum();
     * frustum.setFromMat4(projMat);
     */
    setFromMat4(matrix) {
        const vpm = matrix.data;

        let plane;
        const planes = this.planes;

        // Extract the numbers for the RIGHT plane
        plane = planes[0];
        plane[0] = vpm[3] - vpm[0];
        plane[1] = vpm[7] - vpm[4];
        plane[2] = vpm[11] - vpm[8];
        plane[3] = vpm[15] - vpm[12];
        // Normalize the result
        let t = Math.sqrt(plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]);
        plane[0] /= t;
        plane[1] /= t;
        plane[2] /= t;
        plane[3] /= t;

        // Extract the numbers for the LEFT plane
        plane = planes[1];
        plane[0] = vpm[3] + vpm[0];
        plane[1] = vpm[7] + vpm[4];
        plane[2] = vpm[11] + vpm[8];
        plane[3] = vpm[15] + vpm[12];
        // Normalize the result
        t = Math.sqrt(plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]);
        plane[0] /= t;
        plane[1] /= t;
        plane[2] /= t;
        plane[3] /= t;

        // Extract the BOTTOM plane
        plane = planes[2];
        plane[0] = vpm[3] + vpm[1];
        plane[1] = vpm[7] + vpm[5];
        plane[2] = vpm[11] + vpm[9];
        plane[3] = vpm[15] + vpm[13];
        // Normalize the result
        t = Math.sqrt(plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]);
        plane[0] /= t;
        plane[1] /= t;
        plane[2] /= t;
        plane[3] /= t;

        // Extract the TOP plane
        plane = planes[3];
        plane[0] = vpm[3] - vpm[1];
        plane[1] = vpm[7] - vpm[5];
        plane[2] = vpm[11] - vpm[9];
        plane[3] = vpm[15] - vpm[13];
        // Normalize the result
        t = Math.sqrt(plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]);
        plane[0] /= t;
        plane[1] /= t;
        plane[2] /= t;
        plane[3] /= t;

        // Extract the FAR plane
        plane = planes[4];
        plane[0] = vpm[3] - vpm[2];
        plane[1] = vpm[7] - vpm[6];
        plane[2] = vpm[11] - vpm[10];
        plane[3] = vpm[15] - vpm[14];
        // Normalize the result
        t = Math.sqrt(plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]);
        plane[0] /= t;
        plane[1] /= t;
        plane[2] /= t;
        plane[3] /= t;

        // Extract the NEAR plane
        plane = planes[5];
        plane[0] = vpm[3] + vpm[2];
        plane[1] = vpm[7] + vpm[6];
        plane[2] = vpm[11] + vpm[10];
        plane[3] = vpm[15] + vpm[14];
        // Normalize the result
        t = Math.sqrt(plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]);
        plane[0] /= t;
        plane[1] /= t;
        plane[2] /= t;
        plane[3] /= t;
    }

    /**
     * @function
     * @name Frustum#containsPoint
     * @description Tests whether a point is inside the frustum. Note that points lying in a frustum plane are
     * considered to be outside the frustum.
     * @param {Vec3} point - The point to test.
     * @returns {boolean} True if the point is inside the frustum, false otherwise.
     */
    containsPoint(point) {
        let p, plane;
        for (p = 0; p < 6; p++) {
            plane = this.planes[p];
            if (plane[0] * point.x + plane[1] * point.y + plane[2] * point.z + plane[3] <= 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * @function
     * @name Frustum#containsSphere
     * @description Tests whether a bounding sphere intersects the frustum. If the sphere is outside the frustum,
     * zero is returned. If the sphere intersects the frustum, 1 is returned. If the sphere is completely inside
     * the frustum, 2 is returned. Note that a sphere touching a frustum plane from the outside is considered to
     * be outside the frustum.
     * @param {BoundingSphere} sphere - The sphere to test.
     * @returns {number} 0 if the bounding sphere is outside the frustum, 1 if it intersects the frustum and 2 if
     * it is contained by the frustum.
     */
    containsSphere(sphere) {
        let c = 0;
        let d;
        let p;

        const sr = sphere.radius;
        const sc = sphere.center;
        const scx = sc.x;
        const scy = sc.y;
        const scz = sc.z;
        const planes = this.planes;
        let plane;

        for (p = 0; p < 6; p++) {
            plane = planes[p];
            d = plane[0] * scx + plane[1] * scy + plane[2] * scz + plane[3];
            if (d <= -sr)
                return 0;
            if (d > sr)
                c++;
        }

        return (c === 6) ? 2 : 1;
    }

    // returns an array of corners of the frustum of the camera, using specified near and far distance
    // the points are in the local coordinate system of the camera (object space)
    static getPoints(camera, near, far) {

        near = near || camera._nearClip;
        far = far || camera._farClip;

        const fov = camera._fov * Math.PI / 180.0;
        let y = camera._projection === PROJECTION_PERSPECTIVE ? Math.tan(fov / 2.0) * near : camera._orthoHeight;
        let x = y * camera._aspectRatio;

        const points = _frustumPoints;
        points[0].x = x;
        points[0].y = -y;
        points[0].z = -near;
        points[1].x = x;
        points[1].y = y;
        points[1].z = -near;
        points[2].x = -x;
        points[2].y = y;
        points[2].z = -near;
        points[3].x = -x;
        points[3].y = -y;
        points[3].z = -near;

        if (camera._projection === PROJECTION_PERSPECTIVE) {
            y = Math.tan(fov / 2.0) * far;
            x = y * camera._aspectRatio;
        }
        points[4].x = x;
        points[4].y = -y;
        points[4].z = -far;
        points[5].x = x;
        points[5].y = y;
        points[5].z = -far;
        points[6].x = -x;
        points[6].y = y;
        points[6].z = -far;
        points[7].x = -x;
        points[7].y = -y;
        points[7].z = -far;

        return points;
    }
}

export { Frustum };


<|start_filename|>src/scene/skin-instance-cache.js<|end_filename|>
import { RefCountedObject } from '../core/ref-counted-object.js';
import { SkinInstance } from './skin-instance.js';

// Class used as an entry in the ref-counted skin instance cache
class SkinInstanceCachedObject extends RefCountedObject {
    constructor(skin, skinInstance) {
        super();
        this.skin = skin;
        this.skinInstance = skinInstance;
    }
}

// Pure static class, implementing the cache of skin instances used by render component.
class SkinInstanceCache {
    // map of SkinInstances allowing those to be shared between
    // (specifically a single glb with multiple render components)
    // It maps a rootBone to an array of SkinInstanceCachedObject
    // this allows us to find if a skin instance already exists for a rootbone, and a specific skin
    static _skinInstanceCache = new Map();

    // #if _DEBUG
    // function that logs out the state of the skin instances cache
    static logCachedSkinInstances() {
        console.log("CachedSkinInstances");
        SkinInstanceCache._skinInstanceCache.forEach(function (array, rootBone) {
            console.log(`${rootBone.name}: Array(${array.length})`);
            for (let i = 0; i < array.length; i++) {
                console.log(`  ${i}: RefCount ${array[i].getRefCount()}`);
            }
        });
    }
    // #endif

    // returns cached or creates a skin instance for the skin and a rootBone, to be used by render component
    // on the specified entity
    static createCachedSkinedInstance(skin, rootBone, entity) {

        // try and get skin instance from the cache
        let skinInst = SkinInstanceCache.getCachedSkinInstance(skin, rootBone);

        // don't have skin instance for this skin
        if (!skinInst) {

            skinInst = new SkinInstance(skin);
            skinInst.resolve(rootBone, entity);

            // add it to the cache
            SkinInstanceCache.addCachedSkinInstance(skin, rootBone, skinInst);
        }

        return skinInst;
    }

    // returns already created skin instance from skin, for use on the rootBone
    // ref count of existing skinInstance is increased
    static getCachedSkinInstance(skin, rootBone) {

        let skinInstance = null;

        // get an array of cached object for the rootBone
        const cachedObjArray = SkinInstanceCache._skinInstanceCache.get(rootBone);
        if (cachedObjArray) {

            // find matching skin
            const cachedObj = cachedObjArray.find((element) => element.skin === skin);
            if (cachedObj) {
                cachedObj.incRefCount();
                skinInstance = cachedObj.skinInstance;
            }
        }

        return skinInstance;
    }

    // adds skin instance to the cache, and increases ref count on it
    static addCachedSkinInstance(skin, rootBone, skinInstance) {

        // get an array for the rootBone
        let cachedObjArray = SkinInstanceCache._skinInstanceCache.get(rootBone);
        if (!cachedObjArray) {
            cachedObjArray = [];
            SkinInstanceCache._skinInstanceCache.set(rootBone, cachedObjArray);
        }

        // find entry for the skin
        let cachedObj = cachedObjArray.find((element) => element.skin === skin);
        if (!cachedObj) {
            cachedObj = new SkinInstanceCachedObject(skin, skinInstance);
            cachedObjArray.push(cachedObj);
        }

        cachedObj.incRefCount();
    }

    // removes skin instance from the cache. This decreases ref count, and when that reaches 0 it gets destroyed
    static removeCachedSkinInstance(skinInstance) {

        if (skinInstance) {
            const rootBone = skinInstance.rootBone;
            if (rootBone) {

                // an array for boot bone
                const cachedObjArray = SkinInstanceCache._skinInstanceCache.get(rootBone);
                if (cachedObjArray) {

                    // actual skin instance
                    const cachedObjIndex = cachedObjArray.findIndex((element) => element.skinInstance === skinInstance);
                    if (cachedObjIndex >= 0) {

                        // dec ref on the object
                        const cachedObj = cachedObjArray[cachedObjIndex];
                        cachedObj.decRefCount();

                        // last reference, needs to be destroyed
                        if (cachedObj.getRefCount() === 0) {
                            cachedObjArray.splice(cachedObjIndex, 1);

                            // if the array is empty
                            if (!cachedObjArray.length) {
                                SkinInstanceCache._skinInstanceCache.delete(rootBone);
                            }

                            // destroy the skin instance
                            if (skinInstance) {
                                skinInstance.destroy();
                                cachedObj.skinInstance = null;
                            }
                        }
                    }
                }
            }
        }
    }
}

export { SkinInstanceCache };


<|start_filename|>extras/mini-stats/cpu-timer.js<|end_filename|>
class CpuTimer {
    constructor(app) {
        this._frameIndex = 0;
        this._frameTimings = [];
        this._timings = [];
        this._prevTimings = [];
        this.unitsName = "ms";
        this.decimalPlaces = 1;

        this.enabled = true;

        app.on('frameupdate', this.begin.bind(this, 'update'));
        app.on('framerender', this.mark.bind(this, 'render'));
        app.on('frameend', this.mark.bind(this, 'other'));
    }

    // mark the beginning of the frame
    begin(name) {
        if (!this.enabled) {
            return;
        }

        // end previous frame timings
        if (this._frameIndex < this._frameTimings.length) {
            this._frameTimings.splice(this._frameIndex);
        }
        var tmp = this._prevTimings;
        this._prevTimings = this._timings;
        this._timings = this._frameTimings;
        this._frameTimings = tmp;
        this._frameIndex = 0;

        this.mark(name);
    }

    // mark
    mark(name) {
        if (!this.enabled) {
            return;
        }

        var timestamp = pc.now();

        // end previous mark
        var prev;
        if (this._frameIndex > 0) {
            prev = this._frameTimings[this._frameIndex - 1];
            prev[1] = timestamp - prev[1];
        } else if (this._timings.length > 0) {
            prev = this._timings[this._timings.length - 1];
            prev[1] = timestamp - prev[1];
        }

        if (this._frameIndex >= this._frameTimings.length) {
            this._frameTimings.push([name, timestamp]);
        } else {
            var timing = this._frameTimings[this._frameIndex];
            timing[0] = name;
            timing[1] = timestamp;
        }
        this._frameIndex++;
    }

    get timings() {
        // remove the last time point from the list (which is the time spent outside
        // of playcanvas)
        return this._timings.slice(0, -1).map(function (v) {
            return v[1];
        });
    }
}

export { CpuTimer };


<|start_filename|>src/scene/composition/layer-composition.js<|end_filename|>
import { EventHandler } from '../../core/event-handler.js';
import { set } from '../../core/set-utils.js';

import { Vec3 } from '../../math/vec3.js';

import {
    LAYERID_DEPTH,
    BLEND_NONE,
    COMPUPDATED_BLEND, COMPUPDATED_CAMERAS, COMPUPDATED_INSTANCES, COMPUPDATED_LIGHTS,
    LIGHTTYPE_DIRECTIONAL, LIGHTTYPE_OMNI, LIGHTTYPE_SPOT
} from '../constants.js';

import { RenderAction } from '../render-action.js';
import { WorldClusters } from '../world-clusters.js';
import { LightCompositionData } from './light-composition-data.js';

import { getApplication } from '../../framework/globals.js';

const tempSet = new Set();
const tempClusterArray = [];

/**
 * @class
 * @name LayerComposition
 * @augments EventHandler
 * @classdesc Layer Composition is a collection of {@link Layer} that is fed to {@link Scene#layers} to define rendering order.
 * @description Create a new layer composition.
 * @param {GraphicsDevice} [graphicsDevice] - The graphics device used to manage this layer composition. If it is not provided, a device is obtained
 * from the {@link Application}.
 * @param {string} [name] - Optional non-unique name of the layer composition. Defaults to "Untitled" if not specified.
 * @property {Layer[]} layerList A read-only array of {@link Layer} sorted in the order they will be rendered.
 * @property {boolean[]} subLayerList A read-only array of boolean values, matching {@link Layer#layerList}.
 * True means only semi-transparent objects are rendered, and false means opaque.
 * @property {boolean[]} subLayerEnabled A read-only array of boolean values, matching {@link Layer#layerList}.
 * True means the layer is rendered, false means it's skipped.
 * @property {CameraComponent[]} cameras A read-only array of {@link CameraComponent} that can be used during rendering. e.g. Inside
 * {@link Layer#onPreCull}, {@link Layer#onPostCull}, {@link Layer#onPreRender}, {@link Layer#onPostRender}.
 */
// Composition can hold only 2 sublayers of each layer
class LayerComposition extends EventHandler {
    // temporary way to enable clustered lighting, will be removed later when this becomes default
    static clusteredLightingEnabled = false;

    constructor(graphicsDevice, name = "Untitled") {
        super();

        if (typeof graphicsDevice === 'string') {
            // handle previous constructor parameters: (name)
            graphicsDevice = null;
            name = graphicsDevice;
        }
        this.device = graphicsDevice || getApplication()?.graphicsDevice;
        this.name = name;

        // enable logging
        this.logRenderActions = false;

        // all layers added into the composition
        this.layerList = [];

        this.subLayerList = [];
        this.subLayerEnabled = []; // more granular control on top of layer.enabled (ANDed)
        this._opaqueOrder = {};
        this._transparentOrder = {};

        this._dirty = false;
        this._dirtyBlend = false;
        this._dirtyLights = false;
        this._dirtyCameras = false;

        // all unique meshInstances from all layers, stored both as an array, and also a set for fast search
        this._meshInstances = [];
        this._meshInstancesSet = new Set();

        // an array of all unique lights from all layers
        this._lights = [];

        // a map of Light to index in _lights for fast lookup
        this._lightsMap = new Map();

        // each entry in _lights has entry of type LightCompositionData here at the same index,
        // storing shadow casters and additional composition related data for the light
        this._lightCompositionData = [];

        // _lights split into arrays per type of light, indexed by LIGHTTYPE_*** constants
        this._splitLights = [[], [], []];

        // array of unique cameras from all layers (CameraComponent type)
        this.cameras = [];

        // the actual rendering sequence, generated based on layers and cameras
        this._renderActions = [];

        // all currently created light clusters, that need to be updated before rendering
        this._worldClusters = [];

        // empty cluster with no lights
        this._emptyWorldClusters = null;

        // clustered lighting parameters
        this._clusteredLightingCells = new Vec3(10, 3, 10);
        this._clusteredLightingMaxLights = 64;
    }

    destroy() {

        // empty light cluster
        if (this._emptyWorldClusters) {
            this._emptyWorldClusters.destroy();
            this._emptyWorldClusters = null;
        }

        // all other clusters
        this._worldClusters.forEach((cluster) => cluster.destroy());
        this._worldClusters = null;
    }

    get clusteredLightingCells() {
        return this._clusteredLightingCells;
    }

    set clusteredLightingCells(value) {
        if (!this._clusteredLightingCells.equals(value)) {
            this._clusteredLightingCells.copy(value);
            this.updateWorldClusters();
        }
    }

    get clusteredLightingMaxLights() {
        return this._clusteredLightingMaxLights;
    }

    set clusteredLightingMaxLights(value) {
        if (this._clusteredLightingMaxLights !== value) {
            this._clusteredLightingMaxLights = value;
            this.updateWorldClusters();
        }
    }

    // update clusters with parameter changes
    updateWorldClusters() {
        this._worldClusters.forEach((cluster) => {
            cluster.cells = this._clusteredLightingCells;
            cluster.maxCellLightCount = this._clusteredLightingMaxLights;
        });
    }

    // returns an empty light cluster object to be used when no lights are used
    get emptyWorldClusters() {
        if (!this._emptyWorldClusters) {

            // create cluster structure with no lights
            this._emptyWorldClusters = new WorldClusters(this.device, new Vec3(1, 1, 1), 4);
            this._emptyWorldClusters.name = "ClusterEmpty";

            // update it once to avoid doing it each frame
            this._emptyWorldClusters.update([], false);
        }

        return this._emptyWorldClusters;
    }

    // function which splits list of lights on a a target object into separate lists of lights based on light type
    _splitLightsArray(target) {
        var light;
        var lights = target._lights;
        target._splitLights[LIGHTTYPE_DIRECTIONAL].length = 0;
        target._splitLights[LIGHTTYPE_OMNI].length = 0;
        target._splitLights[LIGHTTYPE_SPOT].length = 0;

        for (var i = 0; i < lights.length; i++) {
            light = lights[i];
            if (light.enabled) {
                target._splitLights[light._type].push(light);
            }
        }
    }

    _update() {
        var i, j;
        var layer;
        var len = this.layerList.length;
        var result = 0;

        // if composition dirty flags are not set, test if layers are marked dirty
        if (!this._dirty || !this._dirtyLights || !this._dirtyCameras) {
            for (i = 0; i < len; i++) {
                layer = this.layerList[i];
                if (layer._dirty) {
                    this._dirty = true;
                }
                if (layer._dirtyLights) {
                    this._dirtyLights = true;
                }
                if (layer._dirtyCameras) {
                    this._dirtyCameras = true;
                }
            }
        }

        // function adds unique meshInstances from src array into destArray. A destSet is a Set containing already
        // existing meshInstances  to accelerate the removal of duplicates
        // returns true if any of the materials on these meshInstances has _dirtyBlend set
        function addUniqueMeshInstance(destArray, destSet, srcArray) {
            var meshInst, material, dirtyBlend = false;
            var srcLen = srcArray.length;
            for (var s = 0; s < srcLen; s++) {
                meshInst = srcArray[s];

                if (!destSet.has(meshInst)) {
                    destSet.add(meshInst);
                    destArray.push(meshInst);

                    material = meshInst.material;
                    if (material && material._dirtyBlend) {
                        dirtyBlend = true;
                        material._dirtyBlend = false;
                    }
                }
            }
            return dirtyBlend;
        }

        // rebuild this._meshInstances array - add all unique meshInstances from all layers to it
        // also set this._dirtyBlend to true if material of any meshInstance has _dirtyBlend set, and clear those flags on materials
        if (this._dirty) {
            result |= COMPUPDATED_INSTANCES;
            this._meshInstances.length = 0;
            this._meshInstancesSet.clear();

            for (i = 0; i < len; i++) {
                layer = this.layerList[i];
                if (!layer.passThrough) {

                    // add meshInstances from both opaque and transparent lists
                    this._dirtyBlend = addUniqueMeshInstance(this._meshInstances, this._meshInstancesSet, layer.opaqueMeshInstances) || this._dirtyBlend;
                    this._dirtyBlend = addUniqueMeshInstance(this._meshInstances, this._meshInstancesSet, layer.transparentMeshInstances) || this._dirtyBlend;
                }

                layer._dirty = false;
            }

            this._dirty = false;
        }

        // funtion moves transparent or opaque meshes based on moveTransparent from src to dest array
        function moveByBlendType(dest, src, moveTransparent) {
            var material, isTransparent;
            for (var s = 0; s < src.length;) {

                material = src[s].material;
                isTransparent = material && material.blendType !== BLEND_NONE;

                if (isTransparent === moveTransparent) {

                    // add it to dest
                    dest.push(src[s]);

                    // remove it from src
                    src[s] = src[src.length - 1];
                    src.length--;

                } else {

                    // just skip it
                    s++;
                }
            }
        }

        // for each layer, split its meshInstances to either opaque or transparent array based on material blend type
        if (this._dirtyBlend) {
            result |= COMPUPDATED_BLEND;

            for (i = 0; i < len; i++) {
                layer = this.layerList[i];
                if (!layer.passThrough) {

                    // move any opaque meshInstances from transparentMeshInstances to opaqueMeshInstances
                    moveByBlendType(layer.opaqueMeshInstances, layer.transparentMeshInstances, false);

                    // move any transparent meshInstances from opaqueMeshInstances to transparentMeshInstances
                    moveByBlendType(layer.transparentMeshInstances, layer.opaqueMeshInstances, true);
                }
            }
            this._dirtyBlend = false;
        }

        if (this._dirtyLights) {
            result |= COMPUPDATED_LIGHTS;
            this._dirtyLights = false;

            this.updateLights();
        }

        // if meshes OR lights changed, rebuild shadow casters
        if (result) {
            this.updateShadowCasters();
        }

        var camera, index, cameraIndex;
        if (this._dirtyCameras || (result & COMPUPDATED_LIGHTS)) {

            this._dirtyCameras = false;
            result |= COMPUPDATED_CAMERAS;

            // walk the layers and build an array of unique cameras from all layers
            this.cameras.length = 0;
            for (i = 0; i < len; i++) {
                layer = this.layerList[i];
                layer._dirtyCameras = false;

                // for all cameras in the layer
                for (j = 0; j < layer.cameras.length; j++) {
                    camera = layer.cameras[j];
                    index = this.cameras.indexOf(camera);
                    if (index < 0) {
                        this.cameras.push(camera);
                    }
                }
            }

            // sort cameras by priority
            if (this.cameras.length > 1) {
                this.cameras.sort((a, b) => a.priority - b.priority);
            }

            // collect a list of layers this camera renders
            const cameraLayers = [];

            // render in order of cameras sorted by priority
            var renderActionCount = 0;
            for (i = 0; i < this.cameras.length; i++) {
                camera = this.cameras[i];
                cameraLayers.length = 0;

                // first render action for this camera
                let cameraFirstRenderAction = true;
                const cameraFirstRenderActionIndex = renderActionCount;

                // last render action for the camera
                let lastRenderAction = null;

                // true if post processing stop layer was found for the camera
                let postProcessMarked = false;

                // walk all global sorted list of layers (sublayers) to check if camera renders it
                // this adds both opaque and transparent sublayers if camera renders the layer
                for (j = 0; j < len; j++) {

                    layer = this.layerList[j];
                    if (layer) {

                        // if layer needs to be rendered
                        if (layer.cameras.length > 0) {

                            // if the camera renders this layer
                            if (camera.layers.indexOf(layer.id) >= 0) {

                                cameraLayers.push(layer);

                                // if this layer is the stop layer for postprocessing
                                if (!postProcessMarked && layer.id === camera.disablePostEffectsLayer) {
                                    postProcessMarked = true;

                                    // the previously added render action is the last post-processed layer
                                    if (lastRenderAction) {

                                        // mark it to trigger postprocessing callback
                                        lastRenderAction.triggerPostprocess = true;
                                    }
                                }

                                // camera index in the layer array
                                cameraIndex = layer.cameras.indexOf(camera);
                                if (cameraIndex >= 0) {

                                    // add render action to describe rendering step
                                    lastRenderAction = this.addRenderAction(this._renderActions, renderActionCount, layer, j, cameraIndex,
                                                                            cameraFirstRenderAction, postProcessMarked);
                                    renderActionCount++;
                                    cameraFirstRenderAction = false;
                                }
                            }
                        }
                    }
                }

                // based on all layers this camera renders, prepare a list of directional lights the camera needs to render shadow for
                // and set these up on the first render action for the camera. Only do it if camera renders any layers.
                if (cameraFirstRenderActionIndex < renderActionCount) {
                    this._renderActions[cameraFirstRenderActionIndex].collectDirectionalLights(cameraLayers, this._splitLights[LIGHTTYPE_DIRECTIONAL], this._lights);
                }

                // if no render action for this camera was marked for end of posprocessing, mark last one
                if (!postProcessMarked && lastRenderAction) {
                    lastRenderAction.triggerPostprocess = true;
                }

                // handle camera stacking if this render action has postprocessing enabled
                if (camera.renderTarget && camera.postEffectsEnabled) {
                    // process previous render actions starting with previous camera
                    this.propagateRenderTarget(cameraFirstRenderActionIndex - 1, camera);
                }
            }

            this._renderActions.length = renderActionCount;

            // prepare clustered lighting for render actions
            if (LayerComposition.clusteredLightingEnabled) {
                this.allocateLightClusters();
            }
        }

        if ((result & COMPUPDATED_LIGHTS) || (result & COMPUPDATED_CAMERAS)) {
            this._logRenderActions();
        }

        return result;
    }

    updateShadowCasters() {

        // _lightCompositionData already has the right size, just clean up shadow casters
        const lightCount = this._lights.length;
        for (let i = 0; i < lightCount; i++) {
            this._lightCompositionData[i].clearShadowCasters();
        }

        // for each layer
        const len = this.layerList.length;
        for (let i = 0; i < len; i++) {
            const layer = this.layerList[i];

            // layer can be in the list two times (opaque, transp), add casters only one time
            if (!tempSet.has(layer)) {
                tempSet.add(layer);

                // for each light of a layer
                const lights = layer._lights;
                for (let j = 0; j < lights.length; j++) {

                    // only need casters when casting shadows
                    if (lights[j].castShadows) {

                        // find its index in global light list, and get shadow casters for it
                        const lightIndex = this._lightsMap.get(lights[j]);
                        const lightCompData = this._lightCompositionData[lightIndex];

                        // add unique meshes from the layer to casters
                        lightCompData.addShadowCasters(layer.shadowCasters);
                    }
                }
            }
        }

        tempSet.clear();
    }

    updateLights() {

        // build a list and map of all unique lights from all layers
        this._lights.length = 0;
        this._lightsMap.clear();

        const count = this.layerList.length;
        for (let i = 0; i < count; i++) {
            const layer = this.layerList[i];

            // layer can be in the list two times (opaque, transp), process it only one time
            if (!tempSet.has(layer)) {
                tempSet.add(layer);

                const lights = layer._lights;
                for (let j = 0; j < lights.length; j++) {
                    const light = lights[j];

                    // add new light
                    let lightIndex = this._lightsMap.get(light);
                    if (lightIndex === undefined) {
                        lightIndex = this._lights.length;
                        this._lightsMap.set(light, lightIndex);
                        this._lights.push(light);

                        // make sure the light has composition data allocated
                        let lightCompData = this._lightCompositionData[lightIndex];
                        if (!lightCompData) {
                            lightCompData = new LightCompositionData();
                            this._lightCompositionData[lightIndex] = lightCompData;
                        }
                    }
                }
            }

            // split layer lights lists by type
            this._splitLightsArray(layer);
            layer._dirtyLights = false;
        }

        tempSet.clear();

        // split light list by type
        this._splitLightsArray(this);

        // adjust _lightCompositionData to the right size, matching number of lights
        const lightCount = this._lights.length;
        this._lightCompositionData.length = lightCount;
    }

    // find existing light cluster that is compatible with specified layer
    findCompatibleCluster(layer, renderActionCount) {

        // check already set up render actions
        for (let i = 0; i < renderActionCount; i++) {
            const ra = this._renderActions[i];
            const raLayer = this.layerList[ra.layerIndex];

            // if layer is the same (but different sublayer), cluster can be used directly as lights are the same
            if (layer === raLayer) {
                return ra.lightClusters;
            }

            if (ra.lightClusters) {
                // if the layer has exactly the same set of lights, use the same cluster
                if (set.equals(layer._clusteredLightsSet, raLayer._clusteredLightsSet)) {
                    return ra.lightClusters;
                }
            }
        }

        // no match
        return null;
    }

    // assign light clusters to render actions that need it
    allocateLightClusters() {

        // reuse previously allocated clusters
        tempClusterArray.push(...this._worldClusters);

        // start with no clusters
        this._worldClusters.length = 0;

        // process all render actions
        const count = this._renderActions.length;
        for (let i = 0; i < count; i++) {
            const ra = this._renderActions[i];
            const layer = this.layerList[ra.layerIndex];

            // if the layer has lights used by clusters
            if (layer._clusteredLightsSet.size) {

                // and if the layer has meshes
                const transparent = this.subLayerList[ra.layerIndex];
                const meshInstances = transparent ? layer.transparentMeshInstances : layer.opaqueMeshInstances;
                if (meshInstances.length) {

                    // reuse cluster that was already set up and is compatible
                    let clusters = this.findCompatibleCluster(layer, i);
                    if (!clusters) {

                        // use already allocated cluster from before
                        if (tempClusterArray.length) {
                            clusters = tempClusterArray.pop();
                        }

                        // create new cluster
                        if (!clusters) {
                            clusters = new WorldClusters(this.device, this._clusteredLightingCells, this._clusteredLightingMaxLights);
                        }

                        clusters.name = "Cluster-" + this._worldClusters.length;
                        this._worldClusters.push(clusters);
                    }

                    ra.lightClusters = clusters;
                }
            }

            // no clustered lights, use the cluster with no lights
            if (!ra.lightClusters) {
                ra.lightClusters = this.emptyWorldClusters;
            }
        }

        // delete leftovers
        tempClusterArray.forEach((item) => item.destroy());
        tempClusterArray.length = 0;
    }

    // function adds new render action to a list, while trying to limit allocation and reuse already allocated objects
    addRenderAction(renderActions, renderActionIndex, layer, layerIndex, cameraIndex, cameraFirstRenderAction, postProcessMarked) {

        // try and reuse object, otherwise allocate new
        let renderAction = renderActions[renderActionIndex];
        if (!renderAction) {
            renderAction = renderActions[renderActionIndex] = new RenderAction();
        }

        // render target from the camera takes precedence over the render target from the layer
        let rt = layer.renderTarget;
        const camera = layer.cameras[cameraIndex];
        if (camera && camera.renderTarget) {
            if (layer.id !== LAYERID_DEPTH) {   // ignore depth layer
                rt = camera.renderTarget;
            }
        }

        // was camera and render target combo used already
        let used = false;
        for (let i = renderActionIndex - 1; i >= 0; i--) {
            if (renderActions[i].camera === camera && renderActions[i].renderTarget === rt) {
                used = true;
                break;
            }
        }

        // clear flags - use camera clear flags in the first render action for each camera,
        // or when render target (from layer) was not yet cleared by this camera
        const needsClear = cameraFirstRenderAction || !used;
        let clearColor = needsClear ? camera.clearColorBuffer : false;
        let clearDepth = needsClear ? camera.clearDepthBuffer : false;
        let clearStencil = needsClear ? camera.clearStencilBuffer : false;

        // clear buffers if requested by the layer
        clearColor |= layer.clearColorBuffer;
        clearDepth |= layer.clearDepthBuffer;
        clearStencil |= layer.clearStencilBuffer;

        // for cameras with post processing enabled, on layers after post processing has been applied already (so UI and similar),
        // don't render them to render target anymore
        if (postProcessMarked && camera.postEffectsEnabled) {
            rt = null;
        }

        // store the properties - write all as we reuse previously allocated class instances
        renderAction.reset();
        renderAction.triggerPostprocess = false;
        renderAction.layerIndex = layerIndex;
        renderAction.cameraIndex = cameraIndex;
        renderAction.camera = camera;
        renderAction.renderTarget = rt;
        renderAction.clearColor = clearColor;
        renderAction.clearDepth = clearDepth;
        renderAction.clearStencil = clearStencil;
        renderAction.firstCameraUse = cameraFirstRenderAction;

        return renderAction;
    }

    // executes when post-processing camera's render actions were created to propage rendering to
    // render targets to previous camera as needed
    propagateRenderTarget(startIndex, fromCamera) {

        for (let a = startIndex; a >= 0; a--) {

            const ra = this._renderActions[a];
            const layer = this.layerList[ra.layerIndex];

            // if we hit render action with a render target (other than depth layer), that marks the end of camera stack
            // TODO: refactor this as part of depth layer refactoring
            if (ra.renderTarget && layer.id !== LAYERID_DEPTH) {
                break;
            }

            // skip over depth layer
            if (layer.id === LAYERID_DEPTH) {
                continue;
            }

            // camera stack ends when viewport or scissor of the camera changes
            const thisCamera = ra?.camera.camera;
            if (thisCamera) {
                if (!fromCamera.camera.rect.equals(thisCamera.rect) || !fromCamera.camera.scissorRect.equals(thisCamera.scissorRect)) {
                    break;
                }
            }

            // render it to render target
            ra.renderTarget = fromCamera.renderTarget;
        }
    }

    // logs render action and their properties
    _logRenderActions() {

        // #if _DEBUG
        if (this.logRenderActions) {
            console.log("Render Actions for composition: " + this.name);
            for (let i = 0; i < this._renderActions.length; i++) {
                const ra = this._renderActions[i];
                const layerIndex = ra.layerIndex;
                const layer = this.layerList[layerIndex];
                const enabled = layer.enabled && this.subLayerEnabled[layerIndex];
                const transparent = this.subLayerList[layerIndex];
                const camera = layer.cameras[ra.cameraIndex];
                const dirLightCount = ra.directionalLights.length;
                const clear = (ra.clearColor ? "Color " : "..... ") + (ra.clearDepth ? "Depth " : "..... ") + (ra.clearStencil ? "Stencil" : ".......");

                console.log(i +
                    (" Cam: " + (camera ? camera.entity.name : "-")).padEnd(22, " ") +
                    (" Lay: " + layer.name).padEnd(22, " ") +
                    (transparent ? " TRANSP" : " OPAQUE") +
                    (enabled ? " ENABLED " : " DISABLED") +
                    " Meshes: ", (transparent ? layer.transparentMeshInstances.length : layer.opaqueMeshInstances.length) +
                    (" RT: " + (ra.renderTarget ? ra.renderTarget.name : "-")).padEnd(30, " ") +
                    " Clear: " + clear +
                    " Lights: (" + layer._clusteredLightsSet.size + "/" + layer._lightsSet.size + ")" +
                    " " + (ra.lightClusters !== this._emptyWorldClusters ? (ra.lightClusters.name) : "").padEnd(10, " ") +
                    (ra.firstCameraUse ? " CAM-FIRST" : "") +
                    (ra.triggerPostprocess ? " POSTPROCESS" : "") +
                    (dirLightCount ? (" DirLights: " + dirLightCount) : "")
                );
            }
        }
        // #endif
    }

    _isLayerAdded(layer) {
        if (this.layerList.indexOf(layer) >= 0) {
            // #if _DEBUG
            console.error("Layer is already added.");
            // #endif
            return true;
        }
        return false;
    }

    _isSublayerAdded(layer, transparent) {
        for (var i = 0; i < this.layerList.length; i++) {
            if (this.layerList[i] === layer && this.subLayerList[i] === transparent) {
                // #if _DEBUG
                console.error("Sublayer is already added.");
                // #endif
                return true;
            }
        }
        return false;
    }

    // Whole layer API

    /**
     * @function
     * @name LayerComposition#push
     * @description Adds a layer (both opaque and semi-transparent parts) to the end of the {@link Layer#layerList}.
     * @param {Layer} layer - A {@link Layer} to add.
     */
    push(layer) {
        // add both opaque and transparent to the end of the array
        if (this._isLayerAdded(layer)) return;
        this.layerList.push(layer);
        this.layerList.push(layer);
        this._opaqueOrder[layer.id] = this.subLayerList.push(false) - 1;
        this._transparentOrder[layer.id] = this.subLayerList.push(true) - 1;
        this.subLayerEnabled.push(true);
        this.subLayerEnabled.push(true);
        this._dirty = true;
        this._dirtyLights = true;
        this._dirtyCameras = true;
        this.fire("add", layer);
    }

    /**
     * @function
     * @name LayerComposition#insert
     * @description Inserts a layer (both opaque and semi-transparent parts) at the chosen index in the {@link Layer#layerList}.
     * @param {Layer} layer - A {@link Layer} to add.
     * @param {number} index - Insertion position.
     */
    insert(layer, index) {
        // insert both opaque and transparent at the index
        if (this._isLayerAdded(layer)) return;
        this.layerList.splice(index, 0,    layer,  layer);
        this.subLayerList.splice(index, 0, false,  true);

        var count = this.layerList.length;
        this._updateOpaqueOrder(index, count - 1);
        this._updateTransparentOrder(index, count - 1);
        this.subLayerEnabled.splice(index, 0, true,  true);
        this._dirty = true;
        this._dirtyLights = true;
        this._dirtyCameras = true;
        this.fire("add", layer);
    }

    /**
     * @function
     * @name LayerComposition#remove
     * @description Removes a layer (both opaque and semi-transparent parts) from {@link Layer#layerList}.
     * @param {Layer} layer - A {@link Layer} to remove.
     */
    remove(layer) {
        // remove all occurrences of a layer
        var id = this.layerList.indexOf(layer);

        delete this._opaqueOrder[id];
        delete this._transparentOrder[id];

        while (id >= 0) {
            this.layerList.splice(id, 1);
            this.subLayerList.splice(id, 1);
            this.subLayerEnabled.splice(id, 1);
            id = this.layerList.indexOf(layer);
            this._dirty = true;
            this._dirtyLights = true;
            this._dirtyCameras = true;
            this.fire("remove", layer);
        }

        // update both orders
        var count = this.layerList.length;
        this._updateOpaqueOrder(0, count - 1);
        this._updateTransparentOrder(0, count - 1);
    }

    // Sublayer API

    /**
     * @function
     * @name LayerComposition#pushOpaque
     * @description Adds part of the layer with opaque (non semi-transparent) objects to the end of the {@link Layer#layerList}.
     * @param {Layer} layer - A {@link Layer} to add.
     */
    pushOpaque(layer) {
        // add opaque to the end of the array
        if (this._isSublayerAdded(layer, false)) return;
        this.layerList.push(layer);
        this._opaqueOrder[layer.id] = this.subLayerList.push(false) - 1;
        this.subLayerEnabled.push(true);
        this._dirty = true;
        this._dirtyLights = true;
        this._dirtyCameras = true;
        this.fire("add", layer);
    }

    /**
     * @function
     * @name LayerComposition#insertOpaque
     * @description Inserts an opaque part of the layer (non semi-transparent mesh instances) at the chosen index in the {@link Layer#layerList}.
     * @param {Layer} layer - A {@link Layer} to add.
     * @param {number} index - Insertion position.
     */
    insertOpaque(layer, index) {
        // insert opaque at index
        if (this._isSublayerAdded(layer, false)) return;
        this.layerList.splice(index, 0,    layer);
        this.subLayerList.splice(index, 0, false);

        var count = this.subLayerList.length;
        this._updateOpaqueOrder(index, count - 1);

        this.subLayerEnabled.splice(index, 0, true);
        this._dirty = true;
        this._dirtyLights = true;
        this._dirtyCameras = true;
        this.fire("add", layer);
    }

    /**
     * @function
     * @name LayerComposition#removeOpaque
     * @description Removes an opaque part of the layer (non semi-transparent mesh instances) from {@link Layer#layerList}.
     * @param {Layer} layer - A {@link Layer} to remove.
     */
    removeOpaque(layer) {
        // remove opaque occurrences of a layer
        for (var i = 0, len = this.layerList.length; i < len; i++) {
            if (this.layerList[i] === layer && !this.subLayerList[i]) {
                this.layerList.splice(i, 1);
                this.subLayerList.splice(i, 1);

                len--;
                this._updateOpaqueOrder(i, len - 1);

                this.subLayerEnabled.splice(i, 1);
                this._dirty = true;
                this._dirtyLights = true;
                this._dirtyCameras = true;
                if (this.layerList.indexOf(layer) < 0) {
                    this.fire("remove", layer); // no sublayers left
                }
                return;
            }
        }
    }

    /**
     * @function
     * @name LayerComposition#pushTransparent
     * @description Adds part of the layer with semi-transparent objects to the end of the {@link Layer#layerList}.
     * @param {Layer} layer - A {@link Layer} to add.
     */
    pushTransparent(layer) {
        // add transparent to the end of the array
        if (this._isSublayerAdded(layer, true)) return;
        this.layerList.push(layer);
        this._transparentOrder[layer.id] = this.subLayerList.push(true) - 1;
        this.subLayerEnabled.push(true);
        this._dirty = true;
        this._dirtyLights = true;
        this._dirtyCameras = true;
        this.fire("add", layer);
    }

    /**
     * @function
     * @name LayerComposition#insertTransparent
     * @description Inserts a semi-transparent part of the layer at the chosen index in the {@link Layer#layerList}.
     * @param {Layer} layer - A {@link Layer} to add.
     * @param {number} index - Insertion position.
     */
    insertTransparent(layer, index) {
        // insert transparent at index
        if (this._isSublayerAdded(layer, true)) return;
        this.layerList.splice(index, 0,    layer);
        this.subLayerList.splice(index, 0, true);

        var count = this.subLayerList.length;
        this._updateTransparentOrder(index, count - 1);

        this.subLayerEnabled.splice(index, 0, true);
        this._dirty = true;
        this._dirtyLights = true;
        this._dirtyCameras = true;
        this.fire("add", layer);
    }

    /**
     * @function
     * @name LayerComposition#removeTransparent
     * @description Removes a transparent part of the layer from {@link Layer#layerList}.
     * @param {Layer} layer - A {@link Layer} to remove.
     */
    removeTransparent(layer) {
        // remove transparent occurrences of a layer
        for (var i = 0, len = this.layerList.length; i < len; i++) {
            if (this.layerList[i] === layer && this.subLayerList[i]) {
                this.layerList.splice(i, 1);
                this.subLayerList.splice(i, 1);

                len--;
                this._updateTransparentOrder(i, len - 1);

                this.subLayerEnabled.splice(i, 1);
                this._dirty = true;
                this._dirtyLights = true;
                this._dirtyCameras = true;
                if (this.layerList.indexOf(layer) < 0) {
                    this.fire("remove", layer); // no sublayers left
                }
                return;
            }
        }
    }

    _getSublayerIndex(layer, transparent) {
        // find sublayer index in the composition array
        var id = this.layerList.indexOf(layer);
        if (id < 0) return -1;

        if (this.subLayerList[id] !== transparent) {
            id = this.layerList.indexOf(layer, id + 1);
            if (id < 0) return -1;
            if (this.subLayerList[id] !== transparent) {
                return -1;
            }
        }
        return id;
    }

    /**
     * @function
     * @name LayerComposition#getOpaqueIndex
     * @description Gets index of the opaque part of the supplied layer in the {@link Layer#layerList}.
     * @param {Layer} layer - A {@link Layer} to find index of.
     * @returns {number} The index of the opaque part of the specified layer.
     */
    getOpaqueIndex(layer) {
        return this._getSublayerIndex(layer, false);
    }

    /**
     * @function
     * @name LayerComposition#getTransparentIndex
     * @description Gets index of the semi-transparent part of the supplied layer in the {@link Layer#layerList}.
     * @param {Layer} layer - A {@link Layer} to find index of.
     * @returns {number} The index of the semi-transparent part of the specified layer.
     */
    getTransparentIndex(layer) {
        return this._getSublayerIndex(layer, true);
    }

    /**
     * @function
     * @name LayerComposition#getLayerById
     * @description Finds a layer inside this composition by its ID. Null is returned, if nothing is found.
     * @param {number} id - An ID of the layer to find.
     * @returns {Layer} The layer corresponding to the specified ID. Returns null if layer is not found.
     */
    getLayerById(id) {
        for (var i = 0; i < this.layerList.length; i++) {
            if (this.layerList[i].id === id) return this.layerList[i];
        }
        return null;
    }

    /**
     * @function
     * @name LayerComposition#getLayerByName
     * @description Finds a layer inside this composition by its name. Null is returned, if nothing is found.
     * @param {string} name - The name of the layer to find.
     * @returns {Layer} The layer corresponding to the specified name. Returns null if layer is not found.
     */
    getLayerByName(name) {
        for (var i = 0; i < this.layerList.length; i++) {
            if (this.layerList[i].name === name) return this.layerList[i];
        }
        return null;
    }

    _updateOpaqueOrder(startIndex, endIndex) {
        for (var i = startIndex; i <= endIndex; i++) {
            if (this.subLayerList[i] === false) {
                this._opaqueOrder[this.layerList[i].id] = i;
            }
        }
    }

    _updateTransparentOrder(startIndex, endIndex) {
        for (var i = startIndex; i <= endIndex; i++) {
            if (this.subLayerList[i] === true) {
                this._transparentOrder[this.layerList[i].id] = i;
            }
        }
    }

    // Used to determine which array of layers has any sublayer that is
    // on top of all the sublayers in the other array. The order is a dictionary
    // of <layerId, index>.
    _sortLayersDescending(layersA, layersB, order) {
        var i = 0;
        var len = 0;
        var id = 0;
        var topLayerA = -1;
        var topLayerB = -1;

        // search for which layer is on top in layersA
        for (i = 0, len = layersA.length; i < len; i++) {
            id = layersA[i];
            if (order.hasOwnProperty(id)) {
                topLayerA = Math.max(topLayerA, order[id]);
            }
        }

        // search for which layer is on top in layersB
        for (i = 0, len = layersB.length; i < len; i++) {
            id = layersB[i];
            if (order.hasOwnProperty(id)) {
                topLayerB = Math.max(topLayerB, order[id]);
            }
        }

        // if the layers of layersA or layersB do not exist at all
        // in the composition then return early with the other.
        if (topLayerA === -1 && topLayerB !== -1) {
            return 1;
        } else if (topLayerB === -1 && topLayerA !== -1) {
            return -1;
        }

        // sort in descending order since we want
        // the higher order to be first
        return topLayerB - topLayerA;
    }

    /**
     * @private
     * @function
     * @name LayerComposition#sortTransparentLayers
     * @description Used to determine which array of layers has any transparent sublayer that is on top of all the transparent sublayers in the other array.
     * @param {number[]} layersA - IDs of layers.
     * @param {number[]} layersB - IDs of layers.
     * @returns {number} Returns a negative number if any of the transparent sublayers in layersA is on top of all the transparent sublayers in layersB,
     * or a positive number if any of the transparent sublayers in layersB is on top of all the transparent sublayers in layersA, or 0 otherwise.
     */
    sortTransparentLayers(layersA, layersB) {
        return this._sortLayersDescending(layersA, layersB, this._transparentOrder);
    }

    /**
     * @private
     * @function
     * @name LayerComposition#sortOpaqueLayers
     * @description Used to determine which array of layers has any opaque sublayer that is on top of all the opaque sublayers in the other array.
     * @param {number[]} layersA - IDs of layers.
     * @param {number[]} layersB - IDs of layers.
     * @returns {number} Returns a negative number if any of the opaque sublayers in layersA is on top of all the opaque sublayers in layersB,
     * or a positive number if any of the opaque sublayers in layersB is on top of all the opaque sublayers in layersA, or 0 otherwise.
     */
    sortOpaqueLayers(layersA, layersB) {
        return this._sortLayersDescending(layersA, layersB, this._opaqueOrder);
    }
}

export { LayerComposition };


<|start_filename|>src/graphics/program-lib/chunks/cubeMapProjectNone.frag<|end_filename|>
vec3 cubeMapProject(vec3 dir) {
    return cubeMapRotate(dir);
}


<|start_filename|>src/framework/components/screen/constants.js<|end_filename|>
/**
 * @constant
 * @type {string}
 * @name SCALEMODE_NONE
 * @description Always use the application's resolution as the resolution for the {@link ScreenComponent}.
 */
export const SCALEMODE_NONE = "none";
/**
 * @constant
 * @type {string}
 * @name SCALEMODE_BLEND
 * @description Scale the {@link ScreenComponent} when the application's resolution is different than the ScreenComponent's referenceResolution.
 */
export const SCALEMODE_BLEND = "blend";


<|start_filename|>tests/framework/components/element/test_element.js<|end_filename|>
describe("pc.ElementComponent", function() {
    var app;

    beforeEach(function () {
        app = new pc.Application(document.createElement("canvas"));
    });

    afterEach(function () {
        app.destroy();
    });

    it('Create default element component', function () {
        var e = new pc.Entity();
        e.addComponent("element");

        expect(e.element.alignment).to.equal(null);
        expect(e.element.anchor.x).to.equal(0);
        expect(e.element.anchor.y).to.equal(0);
        expect(e.element.anchor.z).to.equal(0);
        expect(e.element.anchor.w).to.equal(0);
        expect(e.element.autoFitHeight).to.equal(null);
        expect(e.element.autoFitWidth).to.equal(null);
        expect(e.element.autoHeight).to.equal(null);
        expect(e.element.autoWidth).to.equal(null);
        expect(e.element.batchGroupId).to.equal(-1);
        expect(e.element.bottom).to.equal(0);
        expect(e.element.calculatedHeight).to.equal(32);
        expect(e.element.calculatedWidth).to.equal(32);
        expect(e.element.canvasCorners[0].x).to.equal(0);
        expect(e.element.canvasCorners[0].y).to.equal(0);
        expect(e.element.canvasCorners[1].x).to.equal(0);
        expect(e.element.canvasCorners[1].y).to.equal(0);
        expect(e.element.canvasCorners[2].x).to.equal(0);
        expect(e.element.canvasCorners[2].y).to.equal(0);
        expect(e.element.canvasCorners[3].x).to.equal(0);
        expect(e.element.canvasCorners[3].y).to.equal(0);
        expect(e.element.color).to.equal(null);
        expect(e.element.drawOrder).to.equal(0);
        expect(e.element.enableMarkup).to.equal(null);
        expect(e.element.font).to.equal(null);
        expect(e.element.fontAsset).to.equal(null);
        expect(e.element.fontSize).to.equal(null);
        expect(e.element.height).to.equal(32);
        expect(e.element.layers).to.contain(pc.LAYERID_UI);
        expect(e.element.left).to.equal(0);
        expect(e.element.lineHeight).to.equal(null);
        expect(e.element.margin.x).to.equal(0);
        expect(e.element.margin.y).to.equal(0);
        expect(e.element.margin.z).to.equal(-32);
        expect(e.element.margin.w).to.equal(-32);
        expect(e.element.mask).to.equal(null);
        expect(e.element.material).to.equal(null);
        expect(e.element.materialAsset).to.equal(null);
        expect(e.element.maxFontSize).to.equal(null);
        expect(e.element.maxLines).to.equal(null);
        expect(e.element.minFontSize).to.equal(null);
        expect(e.element.opacity).to.equal(null);
        expect(e.element.outlineColor).to.equal(null);
        expect(e.element.outlineThickness).to.equal(null);
        expect(e.element.pivot.x).to.equal(0);
        expect(e.element.pivot.y).to.equal(0);
        expect(e.element.pixelsPerUnit).to.equal(null);
        expect(e.element.rangeEnd).to.equal(null);
        expect(e.element.rangeStart).to.equal(null);
        expect(e.element.rect).to.equal(null);
        expect(e.element.right).to.equal(-32);
        expect(e.element.rtlReorder).to.equal(null);
        expect(e.element.screen).to.equal(null);
        expect(e.element.screenCorners[0].x).to.equal(0);
        expect(e.element.screenCorners[0].y).to.equal(0);
        expect(e.element.screenCorners[0].z).to.equal(0);
        expect(e.element.screenCorners[1].x).to.equal(0);
        expect(e.element.screenCorners[1].y).to.equal(0);
        expect(e.element.screenCorners[1].z).to.equal(0);
        expect(e.element.screenCorners[2].x).to.equal(0);
        expect(e.element.screenCorners[2].y).to.equal(0);
        expect(e.element.screenCorners[2].z).to.equal(0);
        expect(e.element.screenCorners[3].x).to.equal(0);
        expect(e.element.screenCorners[3].y).to.equal(0);
        expect(e.element.screenCorners[3].z).to.equal(0);
        expect(e.element.shadowColor).to.equal(null);
        expect(e.element.shadowOffset).to.equal(null);
        expect(e.element.spacing).to.equal(null);
        expect(e.element.sprite).to.equal(null);
        expect(e.element.spriteAsset).to.equal(null);
        expect(e.element.spriteFrame).to.equal(null);
        expect(e.element.text).to.equal(null);
        expect(e.element.textHeight).to.equal(0);
        expect(e.element.textWidth).to.equal(0);
        expect(e.element.texture).to.equal(null);
        expect(e.element.textureAsset).to.equal(null);
        expect(e.element.top).to.equal(-32);
        expect(e.element.type).to.equal('group');
        expect(e.element.unicodeConverter).to.equal(null);
        expect(e.element.useInput).to.equal(false);
        expect(e.element.width).to.equal(32);
        expect(e.element.worldCorners[0].x).to.equal(0);
        expect(e.element.worldCorners[0].y).to.equal(0);
        expect(e.element.worldCorners[0].z).to.equal(0);
        expect(e.element.worldCorners[1].x).to.equal(32);
        expect(e.element.worldCorners[1].y).to.equal(0);
        expect(e.element.worldCorners[1].z).to.equal(0);
        expect(e.element.worldCorners[2].x).to.equal(32);
        expect(e.element.worldCorners[2].y).to.equal(32);
        expect(e.element.worldCorners[2].z).to.equal(0);
        expect(e.element.worldCorners[3].x).to.equal(0);
        expect(e.element.worldCorners[3].y).to.equal(32);
        expect(e.element.worldCorners[3].z).to.equal(0);
        expect(e.element.wrapLines).to.equal(null);
    });

    it('screen component unbound on reset', function () {
        var screen = new pc.Entity();
        screen.addComponent('screen');
        app.root.addChild(screen);

        var e = new pc.Entity();
        e.addComponent('element');

        screen.addChild(e);

        expect(screen.screen._elements).to.include(e.element);

        e.reparent(app.root);

        expect(screen.screen._elements).to.not.include(e.element);
    });

    it('screen component unbound on destroy', function () {
        var screen = new pc.Entity();
        screen.addComponent('screen');
        app.root.addChild(screen);

        var e = new pc.Entity();
        e.addComponent('element');

        screen.addChild(e);

        expect(screen.screen._elements).to.include(e.element);

        e.destroy();

        expect(screen.screen._elements).to.not.include(e.element);
    });
});
