/**
An **Object** is a visible 3D element within a {{#crossLink "Viewer"}}{{/crossLink}}.
## Overview
TODO
## Example
In the example below we'll create three Objects, each with a unique ID and a modelling transform.
<iframe style="width: 600px; height: 400px" src="../../examples/object_Object.html"></iframe>
````Javascript
// Create a Viewer
var viewer = new BIMSURFER.Viewer({ element: "myDiv" });
// Create a Camera
var camera = new BIMSURFER.Camera(viewer, {
eye: [10, 10, -10]
});
// Create a CameraControl to control our Camera with mouse and keyboard
var cameraControl = new BIMSURFER.CameraControl(viewer, {
camera: camera
});
// Create a Geometry
var geometry = new BIMSURFER.TeapotGeometry(viewer, {
id: "myGeometry"
});
// Create first Object
// Use the Geometry
var object21 = new BIMSURFER.Object(viewer, {
id: "myObject1",
type: "IfcCovering",
geometries: [ geometry ],
matrix: BIMSURFER.math.translationMat4v([-4, 0,0])
});
// Create second Object
// Reuse the Geometry
var object2 = new BIMSURFER.Object(viewer, {
id: "myObject2",
type: "IfcFlowTerminal",
geometries: [ geometry ],
matrix: BIMSURFER.math.translationMat4v([4, 0,0])
});
````
We can then find the objects in the {{#crossLink "Viewer"}}{{/crossLink}} by ID:
````javascript
var foo = viewer.components["myObject1"];
````
or by IFC type:
````javascript
// Get all Objects of the given IFC type
var wallObjects = viewer.components["IfcWall"];
// Get our "foo" object from those
var foo = wallObjects["moObject1"];
````
@class Object
@module BIMSURFER
@submodule objects
@constructor
@param [viewer] {Viewer} Parent {{#crossLink "Viewer"}}{{/crossLink}}.
@param [cfg] {*} Configs
@param [cfg.id] {String} Optional ID, unique among all components in the parent viewer, generated automatically when omitted.
@param [cfg.meta] {String:Object} Optional map of user-defined metadata to attach to this Object.
@param [cfg.type] {String} The IFC type of this Object.
@param [cfg.color] {Array of Number} The color of this Object, defaults to the color of the specified IFC type.
@param [cfg.geometries] {Array of Geometry} The {{#crossLink "Geometry"}}{{/crossLink}} to render for this Object.
@param [cfg.clipping=true] {Boolean} Whether this Object is clipped by {{#crossLink "Clips"}}{{/crossLink}}.
@param [cfg.transparent=false] {Boolean} Whether this Object is transparent or not.
@param [cfg.opacity=1] {Number} Scalar in range 0-1 that controls opacity, where 0 is completely transparent and 1 is completely opaque.
Only applies while this Object's {{#crossLink "Object/transparent:property"}}transparent{{/crossLink}} equals ````true````.
@param [cfg.highlight=false] {Boolean} Whether this Object is highlighted or not.
@param [cfg.xray=false] {Boolean} Whether this Object is highlighted or not.
@param [cfg.matrix=[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]] {Array of Number} Transform matrix - a one-dimensional, sixteen element array of elements, an identity matrix by default.
@extends Component
*/
(function () {
"use strict";
BIMSURFER.Object = BIMSURFER.Component.extend({
/**
JavaScript class name for this Component.
@property className
@type String
@final
*/
className: "BIMSURFER.Object",
_init: function (cfg) {
this._color = [1, 1, 1, 1];
this._opacity = 1.0;
var contentNode = this.viewer.scene.getNode("contentRoot");
this._rootNode = contentNode.addNode();
this._enableNode = this._rootNode.addNode({
type: "enable"
});
this._flagsNode = this._enableNode.addNode({
type: "flags",
flags: {
backfaces: false
}
});
this._materialNode = this._flagsNode.addNode({
type: "material",
specularColor: { r: 1, g: 1, b: 1 }
});
this._matrixNode = this._materialNode.addNode({
type: "matrix"
});
this._nameNode = this._matrixNode.addNode({
type: "name",
name: this.id
});
this._geometryNodes = [];
if (cfg.geometries) {
// Use the given Geometry components
var geometries = cfg.geometries;
var geometry;
for (var i = 0, len = geometries.length; i < len; i++) {
geometry = geometries[i];
if (BIMSURFER._isString(geometry)) {
// Geometry is the ID of a BIMSURFER.Geometry within the Viewer
geometry = this.viewer.components[geometry];
if (!geometry) {
this.error("geometry[" + i + "] not found in viewer");
continue;
}
if (!geometry.coreId) {
this.error("geometry[" + i + "] is not a BIMSURFER.Geometry");
continue;
}
} else {
// Geometry is an instance of a BIMSURFER.Geometry within the Viewer
if (!geometry.coreId) {
this.error("geometry[" + i + "] is not a BIMSURFER.Geometry");
continue;
}
if (geometry.viewer.id != this.viewer.id) {
this.error("geometry[" + i + "] is not within the same Viewer");
continue;
}
}
this._geometryNodes.push(
this._nameNode.addNode({
type: "geometry",
coreId: geometry.coreId
}));
}
} else {
// Use the Viewer's default box-shaped BIMSURFER.Geometry,
// creating that first if needed
var geometry = this.viewer.components["geometry.default"];
if (!geometry) {
geometry = new BIMSURFER.Geometry(this.viewer, {
id: "geometry.default"
});
}
this._geometryNodes.push(
this._nameNode.addNode({
type: "geometry",
coreId: geometry.coreId
}));
}
this._initBoundary();
this.type = cfg.type;
if (this.type) {
if (cfg.color) {
this.color = cfg.color;
} else {
var color;
var materials = BIMSURFER.constants.materials;
if (!materials) {
this.warn("Property expected in BIMSURFER.constants: materials");
} else {
color = materials[this.type];
if (!color) {
this.log("Material not found for type: ", this.type);
color = materials["DEFAULT"];
}
if (!color) {
this.log("Default material not found for type: ", this.type);
}
}
this.color = color || [ 0.8470588235, 0.427450980392, 0, 1.0];
}
} else {
this.color = cfg.color;
}
this.transparent = cfg.transparent;
this.opacity = cfg.opacity;
this.xray = cfg.xray;
this.highlight = cfg.highlight;
this.matrix = cfg.matrix;
this.label = cfg.label;
this.active = cfg.active !== false;
},
_initBoundary: function () {
var i, len;
// Initial inside-out boundary, ready to expand to fit geometry or sub-objects
this._modelBoundary = {
xmin: 1000000.0,
ymin: 1000000.0,
zmin: 1000000.0,
xmax: -1000000.0,
ymax: -1000000.0,
zmax: -1000000.0
};
var geometry;
for (i = 0, len = this._geometryNodes.length; i < len; i++) {
geometry = this._geometryNodes[i];
this._expandBoundaryByBoundary(this._modelBoundary, geometry.getBoundary());
}
this._modelCenter = [
(this._modelBoundary.xmax + this._modelBoundary.xmin) * 0.5,
(this._modelBoundary.ymax + this._modelBoundary.ymin) * 0.5,
(this._modelBoundary.zmax + this._modelBoundary.zmin) * 0.5
];
this._modelBoundaryVerts = this._boundaryToVerts(this._modelBoundary);
this._center = [0, 0, 0];
this._boundary = null;
},
_expandBoundaryByBoundary: function (a, b) {
if (a.xmin > b.xmin) {
a.xmin = b.xmin;
}
if (a.ymin > b.ymin) {
a.ymin = b.ymin;
}
if (a.zmin > b.zmin) {
a.zmin = b.zmin;
}
if (a.xmax < b.xmax) {
a.xmax = b.xmax;
}
if (a.ymax < b.ymax) {
a.ymax = b.ymax;
}
if (a.zmax < b.zmax) {
a.zmax = b.zmax;
}
},
_boundaryToVerts: function (boundary) {
return [
[boundary.xmin, boundary.ymin, boundary.zmin],
[boundary.xmax, boundary.ymin, boundary.zmin],
[boundary.xmax, boundary.ymax, boundary.zmin],
[boundary.xmin, boundary.ymax, boundary.zmin],
[boundary.xmin, boundary.ymin, boundary.zmax],
[boundary.xmax, boundary.ymin, boundary.zmax],
[boundary.xmax, boundary.ymax, boundary.zmax],
[boundary.xmin, boundary.ymax, boundary.zmax]
];
},
_vertsToBoundary: function (verts) {
var xmin = 100000;
var ymin = 100000;
var zmin = 100000;
var xmax = -100000;
var ymax = -100000;
var zmax = -100000;
var x, y, z;
for (var i = 0, len = verts.length; i < len; i++) {
x = verts[i][0];
y = verts[i][1];
z = verts[i][2];
if (x === undefined || x === null ||
y === undefined || y === null ||
z === undefined || z === null) {
continue;
}
if (x < xmin) {
xmin = x;
}
if (y < ymin) {
ymin = y;
}
if (z < zmin) {
zmin = z;
}
if (x > xmax) {
xmax = x;
}
if (y > ymax) {
ymax = y;
}
if (z > zmax) {
zmax = z;
}
}
return { xmin: xmin, ymin: ymin, zmin: zmin, xmax: xmax, ymax: ymax, zmax: zmax };
},
_props: {
/**
* Whether this Object is active or not.
*
* Fires an {{#crossLink "Object/active:event"}}{{/crossLink}} event on change.
*
* @property active
* @type Boolean
*/
active: {
set: function (value) {
value = !!value;
if (this._active === value) {
return;
}
this._enableNode.setEnabled(value);
if (this.label) {
this.label.active = value;
}
/**
* Fired whenever this Object's {{#crossLink "Object/active:property"}}{{/crossLink}} property changes.
* @event active
* @param value The property's new value
*/
this.fire('active', this._active = value);
},
get: function () {
return this._active;
}
},
/**
Whether this Object is transparent.
@property transparent
@default false
@type Boolean
*/
transparent: {
set: function (value) {
value = !!value;
if (this._transparent === value) {
return;
}
this._transparent = value;
this._flagsNode.setTransparent(this._transparent || this._xray);
this._materialNode.setAlpha(this._xray ? 0.7 : (this._transparent ? this._opacity : 1.0));
},
get: function () {
return this._transparent;
}
},
/**
Whether this Object is highlighted.
@property highlighted
@default false
@type Boolean
*/
highlight: {
set: function (value) {
if (this._highlighted === value) {
return;
}
this._highlighted = value;
if (value) {
this._desaturated = false;
}
this._materialNode.setColor(
this._highlighted
? { r: 0.7, g: 0.7, b: 0.3 }
: { r: this._color[0], g: this._color[1], b: this._color[2] });
},
get: function () {
return this._highlighted;
}
},
/**
Whether this Object is desaturated.
@property desaturated
@default false
@type Boolean
*/
desaturate: {
set: function (value) {
if (this._desaturated === value) {
return;
}
this._desaturated = value;
if (value) {
this._highlighted = false;
}
this._materialNode.setColor(
this._desaturated
? { r: 0.4, g: 0.4, b: 0.4 }
: { r: this._color[0], g: this._color[1], b: this._color[2] });
},
get: function () {
return this._desaturated;
}
},
/**
Whether this Object is X-rayed
@property xray
@default false
@type Boolean
*/
xray: {
set: function (value) {
value = !!value;
if (this._xray === value) {
return;
}
this._xray = value;
this._flagsNode.setTransparent(this._transparent || this._xray);
this._materialNode.setAlpha(this._xray ? 0.7 : (this._transparent ? this._opacity : 1.0));
},
get: function () {
return this._xray;
}
},
/**
The color of this Object.
@property color
@default [1.0, 1.0, 1.0]
@type Array(Number)
*/
color: {
set: function (value) {
if (!value) {
var materials = BIMSURFER.constants.materials;
if (materials) {
value = materials["DEFAULT"];
}
}
this._color = value || [ 0.8470588235, 0.427450980392, 0, 1.0];
this._materialNode.setColor(
this._highlighted
? { r: 0.7, g: 0.7, b: 0.3 }
: { r: this._color[0], g: this._color[1], b: this._color[2] });
},
get: function () {
return this._color;
}
},
/**
Factor in the range [0..1] indicating how transparent this Object is.
A value of 0.0 indicates fully transparent, 1.0 is fully opaque.
This Object will appear transparent only if {{#crossLink "Object/transparent:property"}}{{/crossLink}} is also
set to **true**.
@property opacity
@default 1.0
@type Number
*/
opacity: {
set: function (value) {
this._opacity = value !== null && value !== undefined ? value : 0.4;
this._materialNode.setAlpha(this._xray ? 0.7 : (this._transparent ? this._opacity : 1.0));
},
get: function () {
return this._opacity;
}
},
/**
* This Object's transformation matrix.
*
* @property matrix
* @default [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
* @type {Array of Number}
*/
matrix: {
set: function (value) {
value = value || [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ];
this._matrixNode.setElements(value);
this._boundary = null;
this.fire('matrix', this._matrix = value);
},
get: function () {
return this._matrix;
}
},
/**
* The World-space boundary of this Object.
*
* @property boundary
* @type {*}
*/
boundary: {
get: function () {
if (!this._boundary) {
this._boundary = this._vertsToBoundary(
BIMSURFER.math.transformPoints3(this._matrix, this._modelBoundaryVerts));
this._center[0] = (this._boundary.xmax + this._boundary.xmin) * 0.5;
this._center[1] = (this._boundary.ymax + this._boundary.ymin) * 0.5;
this._center[2] = (this._boundary.zmax + this._boundary.zmin) * 0.5;
}
return this._boundary;
}
},
/**
* The World-space center of this Object.
*
* @property center
* @type {Array of Number}
*/
center: {
get: function () {
if (!this._boundary) {
this._boundary = this._vertsToBoundary(
BIMSURFER.math.transformPoints3(this._matrix, this._modelBoundaryVerts));
this._center[0] = (this._boundary.xmax + this._boundary.xmin) * 0.5;
this._center[1] = (this._boundary.ymax + this._boundary.ymin) * 0.5;
this._center[2] = (this._boundary.zmax + this._boundary.zmin) * 0.5;
}
return this._center;
}
},
/**
* Indicates if this Object shows a debug {{#crossLink "Label"}}{{/crossLink}}.
*
* @property label
* @type Boolean
*/
label: {
set: function (value) {
value = !!value;
if (!!this._label === value) {
return;
}
if (value) {
if (this._label) {
} else {
this._label = new BIMSURFER.Label(viewer, {
object: this,
text: "<b>" + this.className + "<hr style=\"height=1px; background: darkgray; border: 0;\"></b>" + (this.type ? ("type='" + this.type + "'<br>") : "") + "id='" + this.id + "'",
pos: [0, 0, 0]
});
}
} else {
this._label.destroy();
this._label = null;
}
},
get: function () {
return !!this._label;
}
}
},
_destroy: function () {
this._rootNode.destroy();
if (this.label) {
this.label.destroy();
}
}
});
})();