/**
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};