API Docs for:

File: /home/lindsay/xeolabs/xeogl-next/xeogl/src/models/model.js

/**
 A **Model** is a {{#crossLink "Group"}}{{/crossLink}} of {{#crossLink "Component"}}Components{{/crossLink}}.

 Model is an abstract base class that's subclassed by (at least):

 * {{#crossLink "GLTFModel"}}{{/crossLink}}, which loads its components from glTF files.
 * {{#crossLink "OBJModel"}}{{/crossLink}}, which loads its components from .OBJ and .MTL files.
 * {{#crossLink "STLModel"}}{{/crossLink}}, which loads its components from .STL files.
 * {{#crossLink "SceneJSModel"}}{{/crossLink}}, which loads its components from SceneJS scene definitions.
 * {{#crossLink "BuildableModel"}}{{/crossLink}}, which provides a fluent API for building its components.


 @class Model
 @module xeogl
 @submodule models
 @constructor
 @param [owner] {Component} Owner component. When destroyed, the owner will destroy this component as well. Creates this component within the default {{#crossLink "Scene"}}{{/crossLink}} when omitted.
 @param [cfg] {*} Configs
 @param [cfg.id] {String} Optional ID, unique among all components in the parent scene, generated automatically when omitted.
 @param [cfg.meta] {String:Object} Optional map of user-defined metadata.
 @param [cfg.entityType] {String} Optional entity classification when using within a semantic data model. See the {{#crossLink "Object"}}{{/crossLink}} documentation for usage.
 @param [cfg.parent] {Object} The parent.
 @param [cfg.position=[0,0,0]] {Float32Array} Local 3D position.
 @param [cfg.scale=[1,1,1]] {Float32Array} Local scale.
 @param [cfg.rotation=[0,0,0]] {Float32Array} Local rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.
 @param [cfg.matrix=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1] {Float32Array} Local modelling transform matrix. Overrides the position, scale and rotation parameters.
 @param [cfg.visible=true] {Boolean}        Indicates if visible.
 @param [cfg.culled=false] {Boolean}        Indicates if culled from view.
 @param [cfg.pickable=true] {Boolean}       Indicates if pickable.
 @param [cfg.clippable=true] {Boolean}      Indicates if clippable.
 @param [cfg.collidable=true] {Boolean}     Indicates if included in boundary calculations.
 @param [cfg.castShadow=true] {Boolean}     Indicates if casting shadows.
 @param [cfg.receiveShadow=true] {Boolean}  Indicates if receiving shadows.
 @param [cfg.outlined=false] {Boolean}      Indicates if outline is rendered.
 @param [cfg.ghosted=false] {Boolean}       Indicates if rendered as ghosted.
 @param [cfg.highlighted=false] {Boolean}   Indicates if rendered as highlighted.
 @param [cfg.selected=false] {Boolean}      Indicates if rendered as selected.
 @param [cfg.edges=false] {Boolean}         Indicates if edges are emphasized.
 @param [cfg.aabbVisible=false] {Boolean}   Indicates if axis-aligned World-space bounding box is visible.
 @param [cfg.obbVisible=false] {Boolean}    Indicates if oriented World-space bounding box is visible.
 @param [cfg.colorize=[1.0,1.0,1.0]] {Float32Array}  RGB colorize color, multiplies by the rendered fragment colors.
 @param [cfg.opacity=1.0] {Number} Opacity factor, multiplies by the rendered fragment alpha.

 @extends Group
 */
import {core} from "../core.js";
import {utils} from '../utils.js';
import {Group} from "../objects/group.js";
import {componentClasses} from "./../componentClasses.js";

const type = "xeogl.Model";

class Model extends Group {

    /**
     JavaScript class name for this Component.

     For example: "xeogl.AmbientLight", "xeogl.MetallicMaterial" etc.

     @property type
     @type String
     @final
     */
    get type() {
        return type;
    }

    init(cfg) {

        /**
         All contained {{#crossLink "Components"}}{{/crossLink}}, mapped to their IDs.

         @property components
         @type {{String:Component}}
         */
        this.components = {};

        /**
         Number of contained {{#crossLink "Components"}}{{/crossLink}}.

         @property numComponents
         @type Number
         */
        this.numComponents = 0;

        /**
         A map of maps; for each contained {{#crossLink "Component"}}{{/crossLink}} type,
         a map to IDs to {{#crossLink "Component"}}{{/crossLink}} instances, eg.

         ````
         "xeogl.Geometry": {
                "alpha": <xeogl.Geometry>,
                "beta": <xeogl.Geometry>
              },
         "xeogl.Rotate": {
                "charlie": <xeogl.Rotate>,
                "delta": <xeogl.Rotate>,
                "echo": <xeogl.Rotate>,
              },
         //...
         ````

         @property types
         @type {String:{String:xeogl.Component}}
         */
        this.types = {};

        /**
         All contained {{#crossLink "Object"}}Objects{{/crossLink}}, mapped to their IDs.

         @property objects
         @final
         @type {{String:Object}}
         */
        this.objects = {};

        /**
         {{#crossLink "Object"}}Objects{{/crossLink}} in this Model that have GUIDs, mapped to their GUIDs.

         Each Object is registered in this map when its {{#crossLink "Object/guid:property"}}{{/crossLink}} is
         assigned a value.

         @property guidObjects
         @final
         @type {{String:Object}}
         */
        this.guidObjects = {};

        /**
         All contained {{#crossLink "Mesh"}}Meshes{{/crossLink}}, mapped to their IDs.

         @property meshes
         @final
         @type {String:xeogl.Mesh}
         */
        this.meshes = {};

        /**
         {{#crossLink "Object"}}Objects{{/crossLink}} in this Model that have entity types, mapped to their IDs.

         Each Object is registered in this map when its {{#crossLink "Object/entityType:property"}}{{/crossLink}} is
         set to value.

         @property entities
         @final
         @type {{String:Object}}
         */
        this.entities = {};

        /**
         For each entity type, a map of IDs to {{#crossLink "Object"}}Objects{{/crossLink}} of that entity type.

         Each Object is registered in this map when its {{#crossLink "Object/entityType:property"}}{{/crossLink}} is
         assigned a value.

         @property entityTypes
         @final
         @type {String:{String:xeogl.Component}}
         */
        this.entityTypes = {};

        /**
         Lazy-regenerated ID lists.
         */
        this._objectGUIDs = null;
        this._entityIds = null;

        // xeogl.Model overrides xeogl.Group / xeogl.Object state properties, (eg. visible, ghosted etc)
        // and those redefined properties are being set here through the super constructor.

        super.init(cfg); // Call xeogl.Group._init()

        this.scene._modelCreated(this);
    }

    _addComponent(component) {
        let componentId;
        let types;
        if (utils.isNumeric(component) || utils.isString(component)) { // Component ID
            component = this.scene.components[component];
            if (!component) {
                this.warn("Component not found: " + utils.inQuotes(component));
                return;
            }
        } else if (utils.isObject(component)) { // Component config
            const type = component.type || "xeogl.Component";
            if (!core.isComponentType(type)) {
                this.error("Not a xeogl component type: " + type);
                return;
            }
            component = new window[type](this.scene, component);
        }
        if (component.scene !== this.scene) { // Component in wrong Scene
            this.error("Attempted to add component from different xeogl.Scene: " + utils.inQuotes(component.id));
            return;
        }
        if (this.components[component.id]) { // Component already in this Model
            return;
        }
        if (component.model && component.model.id !== this.id) { // Component in other Model
            component.model._removeComponent(component); // Transferring to this Model
        }
        this.components[component.id] = component;
        types = this.types[component.type];
        if (!types) {
            types = this.types[component.type] = {};
        }
        types[component.id] = component;
        if (component.isType("xeogl.Object")) {
            const object = component;
            this.objects[object.id] = object;
            if (object.entityType) {
                this.entities[object.id] = object;
                let objectsOfType = this.entityTypes[object.entityType];
                if (!objectsOfType) {
                    objectsOfType = {};
                    this.entityTypes[object.entityType] = objectsOfType;
                }
                objectsOfType[object.id] = object;
                this._entityIds = null; // Lazy regenerate
                this._entityTypeIds = null; // Lazy regenerate
            }
            if (object.guid) {
                this.guidObjects[object.id] = object;
                this._objectGUIDs = null; // To lazy-rebuild
            }
            if (component.isType("xeogl.Mesh")) {
                this.meshes[component.id] = component;
            }
        }
        this.numComponents++;
        component._addedToModel(this);
        return component;
    }

    _removeComponent(component) {
        const id = component.id;
        delete this.components[id];
        delete this.meshes[id];
        delete this.objects[id];
        if (component.entityType) {
            delete this.entities[id];
            const objectsOfType = this.entityTypes[component.entityType];
            if (objectsOfType) {
                delete objectsOfType[id];
            }
            this._entityIds = null; // Lazy regenerate
            this._entityTypeIds = null; // Lazy regenerate
        }
        if (component.guid) {
            delete this.guidObjects[component.guid];
            this._objectGUIDs = null; // To lazy-rebuild
        }
    }

    /**
     Destroys all {{#crossLink "Component"}}Components{{/crossLink}} in this Model.
     @method clear
     */
    clear() {
        // For efficiency, destroy Meshes first to avoid
        // xeogl's automatic default component substitutions
        for (var id in this.meshes) {
            if (this.meshes.hasOwnProperty(id)) {
                this.meshes[id].destroy();
            }
        }
        for (var id in this.components) {
            if (this.components.hasOwnProperty(id)) {
                this.components[id].destroy(); // Groups in this Model will remove themselves when they're destroyed
            }
        }
        this.components = {};
        this.numComponents = 0;
        this.types = {};
        this.objects = {};
        this.meshes = {};
        this.entities = {};
    }

    /**
     Convenience array of entity type IDs in {{#crossLink "Model/entityTypes:property"}}{{/crossLink}}.
     @property entityTypeIds
     @final
     @type {Array of String}
     */
    get objectGUIDs() {
        if (!this._objectGUIDs) {
            this._objectGUIDs = Object.keys(this.guidObjects);
        }
        return this._objectGUIDs;
    }

    /**
     Convenience array of entity type IDs in {{#crossLink "Model/entityTypes:property"}}{{/crossLink}}.
     @property entityTypeIds
     @final
     @type {Array of String}
     */
    get entityTypeIds() {
        if (!this._entityTypeIds) {
            this._entityTypeIds = Object.keys(this.entityTypes);
        }
        return this._entityTypeIds;
    }

    /**
     Convenience array of IDs in {{#crossLink "Model/entities:property"}}{{/crossLink}}.
     @property entityIds
     @final
     @type {Array of String}
     */
    get entityIds() {
        if (!this._entityIds) {
            this._entityIds = Object.keys(this.entities);
        }
        return this._entityIds;
    }

    /**
     * @deprecated
     */
    destroyAll() {
        this.clear();
    }

    destroy() {
        super.destroy();
        this.clear();
        this.scene._modelDestroyed(this);
    }
}

componentClasses[type] = Model;

export{Model};