/**
An **OBJModel** is a {{#crossLink "Model"}}{{/crossLink}} that loads itself from OBJ and MTL files.
<a href="../../examples/#importing_obj_conferenceRoom"><img src="../../../assets/images/screenshots/OBJModel.png"></img></a>
## Overview
* Begins loading as soon as you set its {{#crossLink "OBJModel/src:property"}}{{/crossLink}} property to the location of an OBJ file.
* Once loaded, contains an {{#crossLink "Mesh"}}{{/crossLink}} for each object. The {{#crossLink "Mesh"}}Meshes{{/crossLink}} can then be independently shown, hidden, colored, transformed etc.
* Set {{#crossLink "OBJModel/src:property"}}{{/crossLink}} to a new file path at any time, to clear the OBJModel and load components from the new file.
OBJModel inherits these capabilities from its {{#crossLink "Group"}}{{/crossLink}} base class:
* Allows you to access and manipulate the {{#crossLink "Meshes"}}{{/crossLink}} within it.
* Can be transformed as a unit within World-space.
* Can be a child within a parent {{#crossLink "Group"}}{{/crossLink}}.
* Provides its World-space axis-aligned and object-aligned boundaries.
## Examples
* [Basic example](../../examples/#importing_obj_people)
* [Models within an object hierarchy](../../examples/#objects_hierarchy_models)
## Usage
Let's load the conference room model (shown in the screenshot above):
````javascript
var confRoom = new xeogl.OBJModel({
id: "confRoom",
src: "models/obj/conference/conference.obj"
});
````
Bind a callback to fire when the model has loaded:
````javascript
confRoom.on("loaded", function() {
// OBJModel has loaded!
});
````
That fires immediately if the OBJModel already happens to be loaded. You can also bind a callback to fire if loading fails:
````javascript
confRoom.on("error", function(msg) {
// Error occurred
});
````
To switch to a different OBJ file, simply update {{#crossLink "OBJModel/src:property"}}{{/crossLink}}:
````javascript
confRoom.src = "models/obj/female02/female02.obj";
````
### Fitting to view
````javascript
var cameraFlight = new xeogl.CameraFlightAnimation();
cameraFlight.flyTo(confRoom);
````
### Accessing components
Let's make everything transparent, except for the conference table and chairs:
````javascript
for (var id in confRoom.meshes) {
var mesh = confRoom.meshes[id];
switch (id) {
case "confRoom#mesh31":
case "confRoom#mesh29":
case "confRoom#mesh30":
break;
default: // Not a chair mesh
mesh.material.alpha = 0.5;
mesh.material.blendMode = "blend"
}
}
````
Note the format of the {{#crossLink "Mesh"}}{{/crossLink}} IDs - an OBJModel prefixes its own ID to the IDs of its components:
````<OBJModel ID>#<OBJ object/group ID>````
**Transforms**
An OBJModel lets us transform its Meshes as a group:
```` Javascript
var model = new xeogl.OBJModel({
src: "models/obj/conference/conference.obj"
position: [-35, 0, 0],
rotation: [0, 45, 0],
scale: [0.5, 0.5, 0.5]
});
model.position = [-20, 0, 0];
````
Let's move the white table top upwards:
````javascript
var tableTop = confRoom.meshes["confRoom#mesh29"];
tableTop.position = [0, 150, 0];
````
## Examples
* [Conference room model](../../examples/#importing_obj_conferenceRoom)
* [Two character models](../../examples/#importing_obj_people)
@class OBJModel
@module xeogl
@submodule models
@constructor
@param [scene] {Scene} Parent {{#crossLink "Scene"}}Scene{{/crossLink}} - creates this OBJModel in the default
{{#crossLink "Scene"}}Scene{{/crossLink}} when omitted.
@param [cfg] {*} Configs
@param [cfg.id] {String} Optional ID, unique among all components in the parent {{#crossLink "Scene"}}Scene{{/crossLink}},
generated automatically when omitted.
@param [cfg.entityType] {String} Optional entity classification when using within a semantic data model. See the {{#crossLink "Object"}}{{/crossLink}} documentation for usage.
@param [cfg.meta] {String:Object} Optional map of user-defined metadata to attach to this OBJModel.
@param [cfg.src] {String} Path to an OBJ file. You can set this to a new file path at any time, which will cause the
OBJModel to load components from the new file (after first destroying any components loaded from a previous file path).
@param [cfg.quantizeGeometry=true] {Boolean} When true, quantizes geometry to reduce memory and GPU bus usage.
@param [cfg.combineGeometry=true] {Boolean} When true, combines geometry vertex buffers to improve rendering performance.
@param [cfg.ghosted=false] {Boolean} When true, sets all the OBJModel's Meshes initially ghosted.
@param [cfg.highlighted=false] {Boolean} When true, sets all the OBJModel's Meshes initially highlighted.
@param [cfg.outline=false] {Boolean} When true, sets all the OBJModel's Meshes initially outlined.
@param [cfg.edgeThreshold=2] {Number} When ghosting, this is the threshold angle between normals of adjacent triangles, below which their shared wireframe edge is not drawn.
@param [cfg.transform] {Number|String|Transform} A Local-to-World-space (modelling) {{#crossLink "Transform"}}{{/crossLink}} to attach to this OBJModel.
Must be within the same {{#crossLink "Scene"}}{{/crossLink}} as this STLModel. Internally, the given
{{#crossLink "Transform"}}{{/crossLink}} will be inserted above each top-most {{#crossLink "Transform"}}Transform{{/crossLink}}
that the STLModel attaches to its {{#crossLink "Mesh"}}Meshes{{/crossLink}}.
@param [cfg.splitMeshes=true] {Boolean} When true, creates a separate {{#crossLink "Mesh"}}{{/crossLink}} for each group of faces that share the same vertex colors. Only works with binary STL.|
@param [cfg.position=[0,0,0]] {Float32Array} The STLModel's local 3D position.
@param [cfg.scale=[1,1,1]] {Float32Array} The STLModel's local scale.
@param [cfg.rotation=[0,0,0]] {Float32Array} The STLModel's local rotation, as Euler angles given in degrees.
@param [cfg.matrix=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1] {Float32Array} The STLModel's local transform matrix. Overrides the position, scale and rotation parameters.
@extends Model
*/
{
xeogl.OBJModel = class xeoglOBJModel extends xeogl.Model {
init(cfg) {
super.init(cfg);
this._src = null;
this.src = cfg.src;
}
/**
Path to a Wavefront OBJ file.
You can set this to a new file path at any time, which will cause the OBJModel to load components from
the new file (after first destroying any components loaded from a previous file path).
Also loads materials from any MTL files referenced in the OBJ.
Fires a {{#crossLink "OBJModel/src:event"}}{{/crossLink}} event on change.
@property src
@type String
*/
set src(value) {
if (!value) {
return;
}
if (!xeogl._isString(value)) {
this.error("Value for 'src' should be a string");
return;
}
if (value === this._src) { // Already loaded this OBJModel
/**
Fired whenever this OBJModel has finished loading components from the OBJ file
specified by {{#crossLink "OBJModel/src:property"}}{{/crossLink}}.
@event loaded
*/
this.fire("loaded", true, true);
return;
}
this.destroyAll();
this._src = value;
xeogl.OBJModel.load(this, this._src);
/**
Fired whenever this OBJModel's {{#crossLink "OBJModel/src:property"}}{{/crossLink}} property changes.
@event src
@param value The property's new value
*/
this.fire("src", this._src);
}
get src() {
return this._src;
}
/**
* Loads OBJ and MTL from file(s) into a {{#crossLink "Model"}}{{/crossLink}}.
*
* @method load
* @static
* @param {Model} model Model to load into.
* @param {String} src Path to OBJ file.
* @param {Function} [ok] Completion callback.
*/
static load(model, src, ok) {
var spinner = model.scene.canvas.spinner;
spinner.processes++;
loadOBJ(model, src, function (state) {
loadMTLs(model, state, function () {
createMeshes(model, state);
spinner.processes--;
xeogl.scheduleTask(function () {
model.fire("loaded", true);
});
if (ok) {
ok();
}
});
});
}
/**
* Parses OBJ and MTL text strings into a {{#crossLink "Model"}}{{/crossLink}}.
*
* @method parse
* @static
* @param {Model} model Model to load into.
* @param {String} objText OBJ text string.
* @param {String} [mtlText] MTL text string.
* @param {String} [basePath] Base path for external resources.
*/
static parse(model, objText, mtlText, basePath) {
if (!objText) {
this.warn("load() param expected: objText");
return;
}
var state = parseOBJ(model, objText, null);
if (mtlText) {
parseMTL(model, mtlText, basePath);
}
createMeshes(model, state);
model.src = null;
model.fire("loaded", true, true);
}
};
//--------------------------------------------------------------------------------------------
// Loads OBJ
//
// Parses OBJ into an intermediate state object. The object will contain geometry data
// and material IDs from which meshes can be created later. The object will also
// contain a list of filenames of the MTL files referenced by the OBJ, is any.
//
// Originally based on the THREE.js OBJ and MTL loaders:
//
// https://github.com/mrdoob/three.js/blob/dev/examples/js/loaders/OBJLoader.js
// https://github.com/mrdoob/three.js/blob/dev/examples/js/loaders/MTLLoader.js
//--------------------------------------------------------------------------------------------
var loadOBJ = function (model, url, ok) {
loadFile(url, function (text) {
var state = parseOBJ(model, text, url);
ok(state);
},
function (error) {
model.error(error);
});
};
var parseOBJ = (function () {
const regexp = {
// v float float float
vertex_pattern: /^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/,
// vn float float float
normal_pattern: /^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/,
// vt float float
uv_pattern: /^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/,
// f vertex vertex vertex
face_vertex: /^f\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)(?:\s+(-?\d+))?/,
// f vertex/uv vertex/uv vertex/uv
face_vertex_uv: /^f\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+))?/,
// f vertex/uv/normal vertex/uv/normal vertex/uv/normal
face_vertex_uv_normal: /^f\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+)\/(-?\d+))?/,
// f vertex//normal vertex//normal vertex//normal
face_vertex_normal: /^f\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)(?:\s+(-?\d+)\/\/(-?\d+))?/,
// o object_name | g group_name
object_pattern: /^[og]\s*(.+)?/,
// s boolean
smoothing_pattern: /^s\s+(\d+|on|off)/,
// mtllib file_reference
material_library_pattern: /^mtllib /,
// usemtl material_name
material_use_pattern: /^usemtl /
};
return function (model, text, url) {
url = url || ""
var state = {
src: url,
basePath: getBasePath(url),
objects: [],
object: {},
positions: [],
normals: [],
uv: [],
materialLibraries: {}
};
startObject(state, "", false);
// Parts of this parser logic are derived from the THREE.js OBJ loader:
// https://github.com/mrdoob/three.js/blob/dev/examples/js/loaders/OBJLoader.js
if (text.indexOf('\r\n') !== -1) {
// This is faster than String.split with regex that splits on both
text = text.replace('\r\n', '\n');
}
var lines = text.split('\n');
var line = '', lineFirstChar = '', lineSecondChar = '';
var lineLength = 0;
var result = [];
// Faster to just trim left side of the line. Use if available.
var trimLeft = ( typeof ''.trimLeft === 'function' );
for (var i = 0, l = lines.length; i < l; i++) {
line = lines[i];
line = trimLeft ? line.trimLeft() : line.trim();
lineLength = line.length;
if (lineLength === 0) {
continue;
}
lineFirstChar = line.charAt(0);
if (lineFirstChar === '#') {
continue;
}
if (lineFirstChar === 'v') {
lineSecondChar = line.charAt(1);
if (lineSecondChar === ' ' && ( result = regexp.vertex_pattern.exec(line) ) !== null) {
// 0 1 2 3
// ['v 1.0 2.0 3.0', '1.0', '2.0', '3.0']
state.positions.push(
parseFloat(result[1]),
parseFloat(result[2]),
parseFloat(result[3])
);
} else if (lineSecondChar === 'n' && ( result = regexp.normal_pattern.exec(line) ) !== null) {
// 0 1 2 3
// ['vn 1.0 2.0 3.0', '1.0', '2.0', '3.0']
state.normals.push(
parseFloat(result[1]),
parseFloat(result[2]),
parseFloat(result[3])
);
} else if (lineSecondChar === 't' && ( result = regexp.uv_pattern.exec(line) ) !== null) {
// 0 1 2
// ['vt 0.1 0.2', '0.1', '0.2']
state.uv.push(
parseFloat(result[1]),
parseFloat(result[2])
);
} else {
model.error('Unexpected vertex/normal/uv line: \'' + line + '\'');
return;
}
} else if (lineFirstChar === 'f') {
if (( result = regexp.face_vertex_uv_normal.exec(line) ) !== null) {
// f vertex/uv/normal vertex/uv/normal vertex/uv/normal
// 0 1 2 3 4 5 6 7 8 9 10 11 12
// ['f 1/1/1 2/2/2 3/3/3', '1', '1', '1', '2', '2', '2', '3', '3', '3', undefined, undefined, undefined]
addFace(state,
result[1], result[4], result[7], result[10],
result[2], result[5], result[8], result[11],
result[3], result[6], result[9], result[12]
);
} else if (( result = regexp.face_vertex_uv.exec(line) ) !== null) {
// f vertex/uv vertex/uv vertex/uv
// 0 1 2 3 4 5 6 7 8
// ['f 1/1 2/2 3/3', '1', '1', '2', '2', '3', '3', undefined, undefined]
addFace(state,
result[1], result[3], result[5], result[7],
result[2], result[4], result[6], result[8]
);
} else if (( result = regexp.face_vertex_normal.exec(line) ) !== null) {
// f vertex//normal vertex//normal vertex//normal
// 0 1 2 3 4 5 6 7 8
// ['f 1//1 2//2 3//3', '1', '1', '2', '2', '3', '3', undefined, undefined]
addFace(state,
result[1], result[3], result[5], result[7],
undefined, undefined, undefined, undefined,
result[2], result[4], result[6], result[8]
);
} else if (( result = regexp.face_vertex.exec(line) ) !== null) {
// f vertex vertex vertex
// 0 1 2 3 4
// ['f 1 2 3', '1', '2', '3', undefined]
addFace(state, result[1], result[2], result[3], result[4]);
} else {
model.error('Unexpected face line: \'' + line + '\'');
return;
}
} else if (lineFirstChar === 'l') {
var lineParts = line.substring(1).trim().split(' ');
var lineVertices = [], lineUVs = [];
if (line.indexOf('/') === -1) {
lineVertices = lineParts;
} else {
for (var li = 0, llen = lineParts.length; li < llen; li++) {
var parts = lineParts[li].split('/');
if (parts[0] !== '') {
lineVertices.push(parts[0]);
}
if (parts[1] !== '') {
lineUVs.push(parts[1]);
}
}
}
addLineGeometry(state, lineVertices, lineUVs);
} else if (( result = regexp.object_pattern.exec(line) ) !== null) {
// o object_name
// or
// g group_name
var id = result[0].substr(1).trim();
startObject(state, id, true);
} else if (regexp.material_use_pattern.test(line)) {
// material
var id = line.substring(7).trim();
state.object.material.id = id;
} else if (regexp.material_library_pattern.test(line)) {
// mtl file
state.materialLibraries[line.substring(7).trim()] = true;
} else if (( result = regexp.smoothing_pattern.exec(line) ) !== null) {
// smooth shading
var value = result[1].trim().toLowerCase();
state.object.material.smooth = ( value === '1' || value === 'on' );
} else {
// Handle null terminated files without exception
if (line === '\0') {
continue;
}
model.error('Unexpected line: \'' + line + '\'');
return;
}
}
return state;
};
function getBasePath(src) {
var n = src.lastIndexOf('/');
return (n === -1) ? src : src.substring(0, n + 1);
}
function startObject(state, id, fromDeclaration) {
if (state.object && state.object.fromDeclaration === false) {
state.object.id = id;
state.object.fromDeclaration = ( fromDeclaration !== false );
return;
}
state.object = {
id: id || '',
geometry: {
positions: [],
normals: [],
uv: []
},
material: {
id: '',
smooth: true
},
fromDeclaration: ( fromDeclaration !== false )
};
state.objects.push(state.object);
}
function parseVertexIndex(value, len) {
var index = parseInt(value, 10);
return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
}
function parseNormalIndex(value, len) {
var index = parseInt(value, 10);
return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
}
function parseUVIndex(value, len) {
var index = parseInt(value, 10);
return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
}
function addVertex(state, a, b, c) {
var src = state.positions;
var dst = state.object.geometry.positions;
dst.push(src[a + 0]);
dst.push(src[a + 1]);
dst.push(src[a + 2]);
dst.push(src[b + 0]);
dst.push(src[b + 1]);
dst.push(src[b + 2]);
dst.push(src[c + 0]);
dst.push(src[c + 1]);
dst.push(src[c + 2]);
}
function addVertexLine(state, a) {
var src = state.positions;
var dst = state.object.geometry.positions;
dst.push(src[a + 0]);
dst.push(src[a + 1]);
dst.push(src[a + 2]);
}
function addNormal(state, a, b, c) {
var src = state.normals;
var dst = state.object.geometry.normals;
dst.push(src[a + 0]);
dst.push(src[a + 1]);
dst.push(src[a + 2]);
dst.push(src[b + 0]);
dst.push(src[b + 1]);
dst.push(src[b + 2]);
dst.push(src[c + 0]);
dst.push(src[c + 1]);
dst.push(src[c + 2]);
}
function addUV(state, a, b, c) {
var src = state.uv;
var dst = state.object.geometry.uv;
dst.push(src[a + 0]);
dst.push(src[a + 1]);
dst.push(src[b + 0]);
dst.push(src[b + 1]);
dst.push(src[c + 0]);
dst.push(src[c + 1]);
}
function addUVLine(state, a) {
var src = state.uv;
var dst = state.object.geometry.uv;
dst.push(src[a + 0]);
dst.push(src[a + 1]);
}
function addFace(state, a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd) {
var vLen = state.positions.length;
var ia = parseVertexIndex(a, vLen);
var ib = parseVertexIndex(b, vLen);
var ic = parseVertexIndex(c, vLen);
var id;
if (d === undefined) {
addVertex(state, ia, ib, ic);
} else {
id = parseVertexIndex(d, vLen);
addVertex(state, ia, ib, id);
addVertex(state, ib, ic, id);
}
if (ua !== undefined) {
var uvLen = state.uv.length;
ia = parseUVIndex(ua, uvLen);
ib = parseUVIndex(ub, uvLen);
ic = parseUVIndex(uc, uvLen);
if (d === undefined) {
addUV(state, ia, ib, ic);
} else {
id = parseUVIndex(ud, uvLen);
addUV(state, ia, ib, id);
addUV(state, ib, ic, id);
}
}
if (na !== undefined) {
// Normals are many times the same. If so, skip function call and parseInt.
var nLen = state.normals.length;
ia = parseNormalIndex(na, nLen);
ib = na === nb ? ia : parseNormalIndex(nb, nLen);
ic = na === nc ? ia : parseNormalIndex(nc, nLen);
if (d === undefined) {
addNormal(state, ia, ib, ic);
} else {
id = parseNormalIndex(nd, nLen);
addNormal(state, ia, ib, id);
addNormal(state, ib, ic, id);
}
}
}
function addLineGeometry(state, positions, uv) {
state.object.geometry.type = 'Line';
var vLen = state.positions.length;
var uvLen = state.uv.length;
for (var vi = 0, l = positions.length; vi < l; vi++) {
addVertexLine(state, parseVertexIndex(positions[vi], vLen));
}
for (var uvi = 0, uvl = uv.length; uvi < uvl; uvi++) {
addUVLine(state, parseUVIndex(uv[uvi], uvLen));
}
}
})();
//--------------------------------------------------------------------------------------------
// Loads MTL files listed in parsed state
//--------------------------------------------------------------------------------------------
function loadMTLs(model, state, ok) {
var basePath = state.basePath;
var srcList = Object.keys(state.materialLibraries);
var numToLoad = srcList.length;
for (var i = 0, len = numToLoad; i < len; i++) {
loadMTL(model, basePath, basePath + srcList[i], function () {
if (--numToLoad === 0) {
ok();
}
});
}
}
//--------------------------------------------------------------------------------------------
// Loads an MTL file
//--------------------------------------------------------------------------------------------
var loadMTL = function (model, basePath, src, ok) {
loadFile(src, function (text) {
parseMTL(model, text, basePath);
ok();
},
function (error) {
model.error(error);
ok();
});
};
var parseMTL = (function () {
var delimiter_pattern = /\s+/;
return function (model, mtlText, basePath) {
var lines = mtlText.split('\n');
var materialCfg = {
id: "Default"
};
var needCreate = false;
var line;
var pos;
var key;
var value;
var alpha;
basePath = basePath || "";
for (var i = 0; i < lines.length; i++) {
line = lines[i].trim();
if (line.length === 0 || line.charAt(0) === '#') { // Blank line or comment ignore
continue;
}
pos = line.indexOf(' ');
key = ( pos >= 0 ) ? line.substring(0, pos) : line;
key = key.toLowerCase();
value = ( pos >= 0 ) ? line.substring(pos + 1) : '';
value = value.trim();
switch (key.toLowerCase()) {
case "newmtl": // New material
//if (needCreate) {
createMaterial(model, materialCfg);
//}
materialCfg = {
id: value
};
needCreate = true;
break;
case 'ka':
materialCfg.ambient = parseRGB(value);
break;
case 'kd':
materialCfg.diffuse = parseRGB(value);
break;
case 'ks':
materialCfg.specular = parseRGB(value);
break;
case 'map_kd':
if (!materialCfg.diffuseMap) {
materialCfg.diffuseMap = createTexture(model, basePath, value, "sRGB");
}
break;
case 'map_ks':
if (!materialCfg.specularMap) {
materialCfg.specularMap = createTexture(model, basePath, value, "linear");
}
break;
case 'map_bump':
case 'bump':
if (!materialCfg.normalMap) {
materialCfg.normalMap = createTexture(model, basePath, value);
}
break;
case 'ns':
materialCfg.shininess = parseFloat(value);
break;
case 'd':
alpha = parseFloat(value);
if (alpha < 1) {
materialCfg.alpha = alpha;
materialCfg.alphaMode = "blend";
}
break;
case 'tr':
alpha = parseFloat(value);
if (alpha > 0) {
materialCfg.alpha = 1 - alpha;
materialCfg.alphaMode = "blend";
}
break;
default:
// model.error("Unrecognized token: " + key);
}
}
if (needCreate) {
createMaterial(model, materialCfg);
}
};
function createTexture(model, basePath, value, encoding) {
var textureCfg = {};
var items = value.split(/\s+/);
var pos = items.indexOf('-bm');
if (pos >= 0) {
//matParams.bumpScale = parseFloat(items[pos + 1]);
items.splice(pos, 2);
}
pos = items.indexOf('-s');
if (pos >= 0) {
textureCfg.scale = [parseFloat(items[pos + 1]), parseFloat(items[pos + 2])];
items.splice(pos, 4); // we expect 3 parameters here!
}
pos = items.indexOf('-o');
if (pos >= 0) {
textureCfg.translate = [parseFloat(items[pos + 1]), parseFloat(items[pos + 2])];
items.splice(pos, 4); // we expect 3 parameters here!
}
textureCfg.src = basePath + items.join(' ').trim();
textureCfg.flipY = true;
textureCfg.encoding = encoding || "linear";
//textureCfg.wrapS = self.wrap;
//textureCfg.wrapT = self.wrap;
var texture = new xeogl.Texture(model, textureCfg);
model._addComponent(texture);
return texture.id;
}
function createMaterial(model, materialCfg) {
model._addComponent(new xeogl.PhongMaterial(model, materialCfg));
}
function parseRGB(value) {
var ss = value.split(delimiter_pattern, 3);
return [parseFloat(ss[0]), parseFloat(ss[1]), parseFloat(ss[2])];
}
})();
//--------------------------------------------------------------------------------------------
// Creates meshes from parsed state
//--------------------------------------------------------------------------------------------
var createMeshes = (function () {
return function (model, state) {
for (var j = 0, k = state.objects.length; j < k; j++) {
var object = state.objects[j];
var geometry = object.geometry;
var isLine = ( geometry.type === 'Line' );
if (geometry.positions.length === 0) {
// Skip o/g line declarations that did not follow with any faces
continue;
}
var geometryCfg = {
primitive: "triangles"
};
geometryCfg.positions = geometry.positions;
if (geometry.normals.length > 0) {
geometryCfg.normals = geometry.normals;
} else {
geometryCfg.autoVertexNormals = true;
}
if (geometry.uv.length > 0) {
geometryCfg.uv = geometry.uv;
}
var indices = new Array(geometryCfg.positions.length / 3); // Triangle soup
for (var idx = 0; idx < indices.length; idx++) {
indices[idx] = idx;
}
geometryCfg.indices = indices;
var xeoGeometry = new xeogl.Geometry(model, geometryCfg);
model._addComponent(xeoGeometry);
var materialId = object.material.id;
var material;
if (materialId && materialId !== "") {
material = model.scene.components[materialId];
if (!material) {
model.error("Material not found: " + materialId);
}
} else {
material = new xeogl.PhongMaterial(model, {
//emissive: [0.6, 0.6, 0.0],
diffuse: [0.6, 0.6, 0.6],
backfaces: true
});
model._addComponent(material);
}
// material.emissive = [Math.random(), Math.random(), Math.random()];
var mesh = new xeogl.Mesh(model, {
id: model.id + "#" + object.id,
geometry: xeoGeometry,
material: material,
pickable: true
});
model.addChild(mesh);
model._addComponent(mesh);
}
};
})();
function loadFile(url, ok, err) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.addEventListener('load', function (event) {
var response = event.target.response;
if (this.status === 200) {
if (ok) {
ok(response);
}
} else if (this.status === 0) {
// Some browsers return HTTP Status 0 when using non-http protocol
// e.g. 'file://' or 'data://'. Handle as success.
console.warn('loadFile: HTTP Status 0 received.');
if (ok) {
ok(response);
}
} else {
if (err) {
err(event);
}
}
}, false);
request.addEventListener('error', function (event) {
if (err) {
err(event);
}
}, false);
request.send(null);
}
}