d4l3k/WebSync

View on GitHub
assets/lib/MTL.js

Summary

Maintainability
F
4 days
Test Coverage
/**
 * Loads a Wavefront .mtl file specifying materials
 *
 * @author angelxuanchang
 */

THREE.MTLLoader = function(baseUrl, options, crossOrigin) {

    this.baseUrl = baseUrl;
    this.options = options;
    this.crossOrigin = crossOrigin;

};

THREE.MTLLoader.prototype = {

    constructor: THREE.MTLLoader,

    load: function(url, onLoad, onProgress, onError) {

        var scope = this;

        var loader = new THREE.XHRLoader();
        loader.setCrossOrigin(this.crossOrigin);
        loader.load(url, function(text) {

            onLoad(scope.parse(text));

        });

    },

    /**
     * Parses loaded MTL file
     * @param text - Content of MTL file
     * @return {THREE.MTLLoader.MaterialCreator}
     */
    parse: function(text) {

        var lines = text.split("\n");
        var info = {};
        var delimiter_pattern = /\s+/;
        var materialsInfo = {};

        for (var i = 0; i < lines.length; i++) {

            var line = lines[i];
            line = line.trim();

            if (line.length === 0 || line.charAt(0) === '#') {

                // Blank line or comment ignore
                continue;

            }

            var pos = line.indexOf(' ');

            var key = (pos >= 0) ? line.substring(0, pos) : line;
            key = key.toLowerCase();

            var value = (pos >= 0) ? line.substring(pos + 1) : "";
            value = value.trim();

            if (key === "newmtl") {

                // New material

                info = {
                    name: value
                };
                materialsInfo[value] = info;

            } else if (info) {

                if (key === "ka" || key === "kd" || key === "ks") {

                    var ss = value.split(delimiter_pattern, 3);
                    info[key] = [parseFloat(ss[0]), parseFloat(ss[1]), parseFloat(ss[2])];

                } else {

                    info[key] = value;

                }

            }

        }

        var materialCreator = new THREE.MTLLoader.MaterialCreator(this.baseUrl, this.options);
        materialCreator.setMaterials(materialsInfo);
        return materialCreator;

    }

};

/**
 * Create a new THREE-MTLLoader.MaterialCreator
 * @param baseUrl - Url relative to which textures are loaded
 * @param options - Set of options on how to construct the materials
 *                  side: Which side to apply the material
 *                        THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
 *                  wrap: What type of wrapping to apply for textures
 *                        THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
 *                  normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
 *                                Default: false, assumed to be already normalized
 *                  ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
 *                                  Default: false
 *                  invertTransparency: If transparency need to be inverted (inversion is needed if d = 0 is fully opaque)
 *                                      Default: false (d = 1 is fully opaque)
 * @constructor
 */

THREE.MTLLoader.MaterialCreator = function(baseUrl, options) {

    this.baseUrl = baseUrl;
    this.options = options;
    this.materialsInfo = {};
    this.materials = {};
    this.materialsArray = [];
    this.nameLookup = {};

    this.side = (this.options && this.options.side) ? this.options.side : THREE.FrontSide;
    this.wrap = (this.options && this.options.wrap) ? this.options.wrap : THREE.RepeatWrapping;

};

THREE.MTLLoader.MaterialCreator.prototype = {

    constructor: THREE.MTLLoader.MaterialCreator,

    setMaterials: function(materialsInfo) {

        this.materialsInfo = this.convert(materialsInfo);
        this.materials = {};
        this.materialsArray = [];
        this.nameLookup = {};

    },

    convert: function(materialsInfo) {

        if (!this.options) return materialsInfo;

        var converted = {};

        for (var mn in materialsInfo) {

            // Convert materials info into normalized form based on options

            var mat = materialsInfo[mn];

            var covmat = {};

            converted[mn] = covmat;

            for (var prop in mat) {

                var save = true;
                var value = mat[prop];
                var lprop = prop.toLowerCase();

                switch (lprop) {

                    case 'kd':
                    case 'ka':
                    case 'ks':

                        // Diffuse color (color under white light) using RGB values

                        if (this.options && this.options.normalizeRGB) {

                            value = [value[0] / 255, value[1] / 255, value[2] / 255];

                        }

                        if (this.options && this.options.ignoreZeroRGBs) {

                            if (value[0] === 0 && value[1] === 0 && value[1] === 0) {

                                // ignore

                                save = false;

                            }
                        }

                        break;

                    case 'd':

                        // According to MTL format (http://paulbourke.net/dataformats/mtl/):
                        //   d is dissolve for current material
                        //   factor of 1.0 is fully opaque, a factor of 0 is fully dissolved (completely transparent)

                        if (this.options && this.options.invertTransparency) {

                            value = 1 - value;

                        }

                        break;

                    default:

                        break;
                }

                if (save) {

                    covmat[lprop] = value;

                }

            }

        }

        return converted;

    },

    preload: function() {

        for (var mn in this.materialsInfo) {

            this.create(mn);

        }

    },

    getIndex: function(materialName) {

        return this.nameLookup[materialName];

    },

    getAsArray: function() {

        var index = 0;

        for (var mn in this.materialsInfo) {

            this.materialsArray[index] = this.create(mn);
            this.nameLookup[mn] = index;
            index++;

        }

        return this.materialsArray;

    },

    create: function(materialName) {

        if (this.materials[materialName] === undefined) {

            this.createMaterial_(materialName);

        }

        return this.materials[materialName];

    },

    createMaterial_: function(materialName) {

        // Create material

        var mat = this.materialsInfo[materialName];
        var params = {

            name: materialName,
            side: this.side

        };

        for (var prop in mat) {

            var value = mat[prop];

            switch (prop.toLowerCase()) {

                // Ns is material specular exponent

                case 'kd':

                    // Diffuse color (color under white light) using RGB values

                    params['diffuse'] = new THREE.Color().fromArray(value);

                    break;

                case 'ka':

                    // Ambient color (color under shadow) using RGB values

                    params['ambient'] = new THREE.Color().fromArray(value);

                    break;

                case 'ks':

                    // Specular color (color when light is reflected from shiny surface) using RGB values
                    params['specular'] = new THREE.Color().fromArray(value);

                    break;

                case 'map_kd':

                    // Diffuse texture map

                    params['map'] = this.loadTexture(this.baseUrl + value);
                    params['map'].wrapS = this.wrap;
                    params['map'].wrapT = this.wrap;

                    break;

                case 'ns':

                    // The specular exponent (defines the focus of the specular highlight)
                    // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.

                    params['shininess'] = value;

                    break;

                case 'd':

                    // According to MTL format (http://paulbourke.net/dataformats/mtl/):
                    //   d is dissolve for current material
                    //   factor of 1.0 is fully opaque, a factor of 0 is fully dissolved (completely transparent)

                    if (value < 1) {

                        params['transparent'] = true;
                        params['opacity'] = value;

                    }

                    break;

                default:
                    break;

            }

        }

        if (params['diffuse']) {

            if (!params['ambient']) params['ambient'] = params['diffuse'];
            params['color'] = params['diffuse'];

        }

        this.materials[materialName] = new THREE.MeshPhongMaterial(params);
        return this.materials[materialName];

    },


    loadTexture: function(url, mapping, onLoad, onError) {

        var isCompressed = /\.dds$/i.test(url);

        if (isCompressed) {

            var texture = THREE.ImageUtils.loadCompressedTexture(url, mapping, onLoad, onError);

        } else {

            var image = new Image();
            var texture = new THREE.Texture(image, mapping);

            var loader = new THREE.ImageLoader();
            loader.crossOrigin = this.crossOrigin;
            loader.load(url, function(image) {

                texture.image = THREE.MTLLoader.ensurePowerOfTwo_(image);
                texture.needsUpdate = true;

                if (onLoad) onLoad(texture);

            });

        }

        return texture;

    }

};

THREE.MTLLoader.ensurePowerOfTwo_ = function(image) {

    if (!THREE.Math.isPowerOfTwo(image.width) || !THREE.Math.isPowerOfTwo(image.height)) {

        var canvas = document.createElement("canvas");
        canvas.width = THREE.MTLLoader.nextHighestPowerOfTwo_(image.width);
        canvas.height = THREE.MTLLoader.nextHighestPowerOfTwo_(image.height);

        var ctx = canvas.getContext("2d");
        ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height);
        return canvas;

    }

    return image;

};

THREE.MTLLoader.nextHighestPowerOfTwo_ = function(x) {

    --x;

    for (var i = 1; i < 32; i <<= 1) {

        x = x | x >> i;

    }

    return x + 1;

};

THREE.EventDispatcher.prototype.apply(THREE.MTLLoader.prototype);
/**
 * Loads a Wavefront .obj file with materials
 *
 * @author mrdoob / http://mrdoob.com/
 * @author angelxuanchang
 */

THREE.OBJMTLLoader = function() {};

THREE.OBJMTLLoader.prototype = {

    constructor: THREE.OBJMTLLoader,

    load: function(url, mtlurl, onLoad, onProgress, onError) {

        var scope = this;

        var mtlLoader = new THREE.MTLLoader(url.substr(0, url.lastIndexOf("/") + 1));
        mtlLoader.load(mtlurl, function(materials) {

            var materialsCreator = materials;
            materialsCreator.preload();

            var loader = new THREE.XHRLoader(scope.manager);
            loader.setCrossOrigin(this.crossOrigin);
            loader.load(url, function(text) {

                var object = scope.parse(text);

                object.traverse(function(object) {

                    if (object instanceof THREE.Mesh) {

                        if (object.material.name) {

                            var material = materialsCreator.create(object.material.name);

                            if (material) object.material = material;

                        }

                    }

                });

                onLoad(object);

            });

        });

    },

    /**
     * Parses loaded .obj file
     * @param data - content of .obj file
     * @param mtllibCallback - callback to handle mtllib declaration (optional)
     * @return {THREE.Object3D} - Object3D (with default material)
     */

    parse: function(data, mtllibCallback) {

        function vector(x, y, z) {

            return new THREE.Vector3(x, y, z);

        }

        function uv(u, v) {

            return new THREE.Vector2(u, v);

        }

        function face3(a, b, c, normals) {

            return new THREE.Face3(a, b, c, normals);

        }

        var face_offset = 0;

        function meshN(meshName, materialName) {

            if (vertices.length > 0) {

                geometry.vertices = vertices;

                geometry.mergeVertices();
                geometry.computeCentroids();
                geometry.computeFaceNormals();
                geometry.computeBoundingSphere();

                object.add(mesh);

                geometry = new THREE.Geometry();
                mesh = new THREE.Mesh(geometry, material);
                verticesCount = 0;

            }

            if (meshName !== undefined) mesh.name = meshName;

            if (materialName !== undefined) {

                material = new THREE.MeshLambertMaterial();
                material.name = materialName;

                mesh.material = material;

            }

        }

        var group = new THREE.Object3D();
        var object = group;

        var geometry = new THREE.Geometry();
        var material = new THREE.MeshLambertMaterial();
        var mesh = new THREE.Mesh(geometry, material);

        var vertices = [];
        var verticesCount = 0;
        var normals = [];
        var uvs = [];

        function add_face(a, b, c, normals_inds) {

            if (normals_inds === undefined) {

                geometry.faces.push(face3(
                    parseInt(a) - (face_offset + 1),
                    parseInt(b) - (face_offset + 1),
                    parseInt(c) - (face_offset + 1)
                ));

            } else {

                geometry.faces.push(face3(
                    parseInt(a) - (face_offset + 1),
                    parseInt(b) - (face_offset + 1),
                    parseInt(c) - (face_offset + 1), [
                        normals[parseInt(normals_inds[0]) - 1].clone(),
                        normals[parseInt(normals_inds[1]) - 1].clone(),
                        normals[parseInt(normals_inds[2]) - 1].clone()
                    ]
                ));

            }

        }

        function add_uvs(a, b, c) {

            geometry.faceVertexUvs[0].push([
                uvs[parseInt(a) - 1].clone(),
                uvs[parseInt(b) - 1].clone(),
                uvs[parseInt(c) - 1].clone()
            ]);

        }

        function handle_face_line(faces, uvs, normals_inds) {

            if (faces[3] === undefined) {

                add_face(faces[0], faces[1], faces[2], normals_inds);

                if (!(uvs === undefined) && uvs.length > 0) {
                    add_uvs(uvs[0], uvs[1], uvs[2]);
                }

            } else {

                if (!(normals_inds === undefined) && normals_inds.length > 0) {

                    add_face(faces[0], faces[1], faces[3], [normals_inds[0], normals_inds[1], normals_inds[3]]);
                    add_face(faces[1], faces[2], faces[3], [normals_inds[1], normals_inds[2], normals_inds[3]]);

                } else {

                    add_face(faces[0], faces[1], faces[3]);
                    add_face(faces[1], faces[2], faces[3]);

                }

                if (!(uvs === undefined) && uvs.length > 0) {

                    add_uvs(uvs[0], uvs[1], uvs[3]);
                    add_uvs(uvs[1], uvs[2], uvs[3]);

                }

            }

        }


        // v float float float

        var vertex_pattern = /v( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/;

        // vn float float float

        var normal_pattern = /vn( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/;

        // vt float float

        var uv_pattern = /vt( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/;

        // f vertex vertex vertex ...

        var face_pattern1 = /f( +\d+)( +\d+)( +\d+)( +\d+)?/;

        // f vertex/uv vertex/uv vertex/uv ...

        var face_pattern2 = /f( +(\d+)\/(\d+))( +(\d+)\/(\d+))( +(\d+)\/(\d+))( +(\d+)\/(\d+))?/;

        // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ...

        var face_pattern3 = /f( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))?/;

        // f vertex//normal vertex//normal vertex//normal ... 

        var face_pattern4 = /f( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))?/

        //

        var lines = data.split("\n");

        for (var i = 0; i < lines.length; i++) {

            var line = lines[i];
            line = line.trim();

            var result;

            if (line.length === 0 || line.charAt(0) === '#') {

                continue;

            } else if ((result = vertex_pattern.exec(line)) !== null) {

                // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"]

                vertices.push(vector(
                    parseFloat(result[1]),
                    parseFloat(result[2]),
                    parseFloat(result[3])
                ));

            } else if ((result = normal_pattern.exec(line)) !== null) {

                // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"]

                normals.push(vector(
                    parseFloat(result[1]),
                    parseFloat(result[2]),
                    parseFloat(result[3])
                ));

            } else if ((result = uv_pattern.exec(line)) !== null) {

                // ["vt 0.1 0.2", "0.1", "0.2"]

                uvs.push(uv(
                    parseFloat(result[1]),
                    parseFloat(result[2])
                ));

            } else if ((result = face_pattern1.exec(line)) !== null) {

                // ["f 1 2 3", "1", "2", "3", undefined]

                handle_face_line([result[1], result[2], result[3], result[4]]);

            } else if ((result = face_pattern2.exec(line)) !== null) {

                // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined]

                handle_face_line(
                    [result[2], result[5], result[8], result[11]], //faces
                    [result[3], result[6], result[9], result[12]] //uv
                );

            } else if ((result = face_pattern3.exec(line)) !== null) {

                // ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined]

                handle_face_line(
                    [result[2], result[6], result[10], result[14]], //faces
                    [result[3], result[7], result[11], result[15]], //uv
                    [result[4], result[8], result[12], result[16]] //normal
                );

            } else if ((result = face_pattern4.exec(line)) !== null) {

                // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined]

                handle_face_line(
                    [result[2], result[5], result[8], result[11]], //faces
                    [], //uv
                    [result[3], result[6], result[9], result[12]] //normal
                );

            } else if (/^o /.test(line)) {

                // object

                meshN();
                face_offset = face_offset + vertices.length;
                vertices = [];
                object = new THREE.Object3D();
                object.name = line.substring(2).trim();
                group.add(object);

            } else if (/^g /.test(line)) {

                // group

                meshN(line.substring(2).trim(), undefined);

            } else if (/^usemtl /.test(line)) {

                // material

                meshN(undefined, line.substring(7).trim());

            } else if (/^mtllib /.test(line)) {

                // mtl file

                if (mtllibCallback) {

                    var mtlfile = line.substring(7);
                    mtlfile = mtlfile.trim();
                    mtllibCallback(mtlfile);

                }

            } else if (/^s /.test(line)) {

                // Smooth shading

            } else {

                console.log("THREE.OBJMTLLoader: Unhandled line " + line);

            }

        }

        //Add last object
        meshN(undefined, undefined);

        return group;

    }

};

THREE.EventDispatcher.prototype.apply(THREE.OBJMTLLoader.prototype);