55 WebGL加载三维模型

案例查看地址:点击这里

迄今为止,示例程序都是在代码中显式定位三维模型的顶点坐标,并保存在Float32Array类型的数组中。然而,大部分三维程序都是从模型文件中读取三维模型的顶点坐标和颜色数据,而模型文件是有三维建模软件生成的。

程序需要从模型文件中读取数据,并保存在之前使用的那些数组和缓冲区中。具体地,程序需要:

(1)准备Float32Array类型的数组vertices,从文件中读取模型的顶点坐标数据并保存到其中。

(2)准备Float32Array类型的数组 colors,从文件中读取模型的顶点颜色数据并保存到其中。

(3)准备Float32Array类型的数组 normals, 从文件中读取模型的顶点法线数据并保存到其中。

(4)准备 Uint15Array(或Uint8Array)类型的数组 indices, 从文件中读取顶点索引数据并保存在其中,顶点索引数据定义了组成整个模型的三角形序列。

(5)将前4步获取的数据写入缓冲区中,调用gl.drawElements()以绘制出整个立方体。

为了能将模型数据读入相应的数组中,并在第5步绘制它,我们需要理解obj文件格式中每一行的含义。


# Blender v2.60 (sub 0) OBJ File: ''
# www.blender.org
mtllib cube.mtl
o Cube
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -1.000000
v 1.000000 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
usemtl Material
f 1 2 3 4
f 5 8 7 6
f 2 6 7 3
f 3 7 8 4
f 5 1 4 8
usemtl Material.001
f 1 5 6 2

OBJ文件格式

OBJ格式的文件由若干个部分组成,包括顶点坐标部分、表面定义部分、材质定义部分等。每个部分定义了多个顶点、法线、表面等等。

(1)以井号(#)开头的行表示注释,上面1行和2行就是软件根据自身版本创建出来的注释。

(2)第3行引用了一个外部材质文件。OBJ格式将模型的材质信息存储在外部的MTL格式的文件中。

mtllib < 外部材质文件名 >这里,外部材质文件是cube.mtl。

(3)第4行按照如下格式指定了模型的名称:

o < 模型名称 > 示例程序中没有用到这条信息

(4)第5行到第12行按照如下格式定义了顶点的坐标,其中w是可选的,如果没有就默认为1.0 。

v x y z [w]本例中的模型时一个标准的立方体,共有8个顶点。

(5)第13行到第20行先指定了某个材质,然后列举了使用这个材质的表面。第13行指定了材质名称,该材质被定义在第3行引用的MTL文件中。

usemtl < 材质名 >

(6)接下来的第14行到第18行定义了使用这个材质的表面。每个表面是由顶点、纹理坐标和法线的索引序列定义的。

f v1 v2 v3 v4 ···

其中v1、v2、v3、v4是之前定义的顶点的索引值。注意,这里顶点的索引值从1开始,而不是从0开始。本例为了简单,没有包含法线,如果包含了法线向量,就需要遵照如下格式:

f v1//vn1 v2//vn2 v3//vn3 ···

其中,vn1、vn2等式法线向量的索引值,也是从1开始。

(7)第19行到第20行定义了使用了另一个材质的表面,即橘黄色的表面。


MTL 文件格式

# Blender MTL File: ''
# Material Count: 2
newmtl Material
Ka 0.000000 0.000000 0.000000
Kd 1.000000 0.000000 0.000000
Ks 0.000000 0.000000 0.000000
Ns 96.078431
Ni 1.000000
d 1.000000
illum 0
newmtl Material.001
Ka 0.000000 0.000000 0.000000
Kd 1.000000 0.450000 0.000000
Ks 0.000000 0.000000 0.000000
Ns 96.078431
Ni 1.000000
d 1.000000
illum 0
(1)第1行和第2行是注释。

(2)第3行使用newmtl定义一个新材质,格式如下:

newmtl < 材质名 >

材质名被OBJ文件引用,如材质Material就在cube.obj的第13行被引用了。

(3)第4行到第6行,分别使用Ka、Kd和Ks定义了表面的环境色、漫射色和高光色。颜色使用RGB格式定义,每个分量值的区间为[0.0,1.0],本例只用到漫反射,也就是物体表面本来的颜色。我们不用管其他两个颜色的含义。

(4)第7行使用Ns指定了高光色的权重,第8行用Ni指定了表面光学密度,第9行使用d指定了透明度,第10行用illum指定了光照模型。本例没有用到这些信息。

(5)第11行到第18行以同样的方法定义了另一种材质Material.001 。

理解了OBJ文件格式和Mtl文件格式,就可以从文件中读取模型各表面顶点坐标、颜色、法向量和索引信息,组织成数组写入缓冲区对象,调用drawElements()以绘制出模型。这里,OBJ对象没有定义法线方向,但我们可以根据顶点坐标,通过叉乘操作计算出法线方向。

案例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Title</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }

        #canvas {
            margin: 0;
            display: block;
        }
    </style>
</head>
<body οnlοad="main()">
<canvas id="canvas" height="800" width="800"></canvas>
</body>
<script src="lib/webgl-utils.js"></script>
<script src="lib/webgl-debug.js"></script>
<script src="lib/cuon-utils.js"></script>
<script src="lib/cuon-matrix.js"></script>
<script>
    //设置WebGL全屏显示
    var canvas = document.getElementById("canvas");
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    //顶点着色器
    var vertexShaderSource = "" +
        "attribute vec4 a_Position;\n" +
        "attribute vec4 a_Color;\n" +
        "attribute vec4 a_Normal;\n" +
        "uniform mat4 u_MvpMatrix;\n" +
        "uniform mat4 u_NormalMatrix;\n" +
        "varying vec4 v_Color;\n" +
        "void main(){\n" +
        "   vec3 lightDirection = vec3(-0.35, 0.35, 0.87);\n" +
        "   gl_Position = u_MvpMatrix * a_Position;\n" +
        "   vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\n" +
        "   float nDotL = max(dot(normal, lightDirection), 0.0);\n" +
        "   v_Color = vec4(a_Color.rgb * nDotL, a_Color.a);\n" +
        "}\n";

    //片元着色器
    var fragmentShaderSource = "" +
        "#ifdef GL_ES\n" +
        "precision mediump float;\n" +
        "#endif\n" +
        "varying vec4 v_Color;\n" +
        "void main(){\n" +
        "   gl_FragColor = v_Color;\n" +
        "}\n";

    function main() {
        var canvas = document.getElementById("canvas");

        var gl = getWebGLContext(canvas);

        if(!gl){
            console.log("无法获取WebGL的上下文");
            return;
        }

        //初始化着色器
        if(!initShaders(gl, vertexShaderSource, fragmentShaderSource)){
            console.log("无法初始化片元着色器");
            return;
        }

        //设置背景色和隐藏面消除
        gl.clearColor(0.2, 0.2, 0.2, 1.0);
        gl.enable(gl.DEPTH_TEST);

        //获取着色器相关的attribute和uniform变量
        var program = gl.program;
        program.a_Position = gl.getAttribLocation(program, "a_Position");
        program.a_Color = gl.getAttribLocation(program, "a_Color");
        program.a_Normal = gl.getAttribLocation(program, "a_Normal");
        program.u_MvpMatrix = gl.getUniformLocation(program, "u_MvpMatrix");
        program.u_NormalMatrix = gl.getUniformLocation(program, "u_NormalMatrix");

        if(program.a_Position < 0 || program.a_Color < 0 || program.a_Normal < 0 || !program.u_MvpMatrix || !program.u_NormalMatrix){
            console.log("无法获取到attribute和uniform相关变量");
            return;
        }

        //为顶点坐标、颜色和法向量准备空白缓冲区对象
        var model = initVertexBuffer(gl, program);
        if(!model){
            console.log("无法准备空白缓冲区");
            return;
        }

        //计算视点投影矩阵
        var viewProjectMatrix = new Matrix4();
        viewProjectMatrix.setPerspective(30.0, canvas.width/canvas.height, 1.0, 5000.0);
        viewProjectMatrix.lookAt(0.0, 500.0, 200.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

        //读取OBJ文件
        readOBJFile("resources/cube.obj", gl, model, 60, true);

        var currentAngle = 0.0; //当前模型的旋转角度
        var tick = function () {
            currentAngle = animate(currentAngle); //更新角度
            draw(gl, program, currentAngle, viewProjectMatrix, model);
            requestAnimationFrame(tick);
        };

        tick();
    }

    //声明变换矩阵(模型矩阵,模型视图投影矩阵,正常矩阵)
    var g_modelMatrix = new Matrix4();
    var g_mvpMatrix = new Matrix4();
    var g_normalMatrix = new Matrix4();

    //绘制当前的模型
    function draw(gl, program, angle, viewProjectMatrix, model) {
        //判断obj文件和mtl文件都已经解析完成
        if(g_objDoc !== null && g_objDoc.isMTLComplete()){
            g_drawingInfo = onReadComplete(gl, model, g_objDoc);
            g_objDoc = null;
        }

        //判断模型数据是否解析完成
        if(!g_drawingInfo) return;

        gl.clear(gl.COLOR_BUFFER_BIT, gl.DEPTH_BUFFER_BIT);

        //设置模型旋转
        g_modelMatrix.setRotate(angle, 1.0, 0.0, 0.0);
        g_modelMatrix.rotate(angle, 0.0, 1.0, 0.0);
        g_modelMatrix.rotate(angle, 0.0, 0.0, 1.0);

        //计算正常的变换矩阵并将值赋值给u_NormalMatrix
        g_normalMatrix.setInverseOf(g_modelMatrix);
        g_normalMatrix.transpose();
        gl.uniformMatrix4fv(program.u_NormalMatrix, false, g_normalMatrix.elements);

        //计算模型视图投影矩阵
        g_mvpMatrix.set(viewProjectMatrix);
        g_mvpMatrix.multiply(g_modelMatrix);
        gl.uniformMatrix4fv(program.u_MvpMatrix, false, g_mvpMatrix.elements);

        //绘制
        gl.drawElements(gl.TRIANGLES, g_drawingInfo.indices.length, gl.UNSIGNED_SHORT, 0);
    }

    //创建缓冲区对象并初始化配置
    function initVertexBuffer(gl, program) {
        var obj = new Object();
        obj.vertexBuffer = createEmptyArrayBuffer(gl, program.a_Position, 3, gl.FLOAT);
        obj.normalBuffer = createEmptyArrayBuffer(gl, program.a_Normal, 3, gl.FLOAT);
        obj.colorBuffer = createEmptyArrayBuffer(gl, program.a_Color, 4, gl.FLOAT);
        obj.indexBuffer = gl.createBuffer();
        if(!obj.vertexBuffer || !obj.normalBuffer || !obj.colorBuffer || !obj.indexBuffer){
            return null;
        }

        gl.bindBuffer(gl.ARRAY_BUFFER, null);

        return obj;
    }

    //创建一个缓冲区对象,将其分配给属性变量,并启用赋值
    function createEmptyArrayBuffer(gl, a_attribute, num, type) {
        var buffer = gl.createBuffer();
        if(!buffer){
            console.log("无法创建缓冲区");
            return null;
        }

        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
        gl.enableVertexAttribArray(a_attribute);

        return buffer;
    }

    function readOBJFile(filename, gl, model, scale, reverse) {
        var request = new XMLHttpRequest();
        request.open("GET", filename, true);
        request.send();

        request.onreadystatechange = function () {
            if(request.readyState === 4 && request.status == 200){
                //获取到数据调用方法处理
                onReadOBJFile(request.responseText, filename, gl, model, scale, reverse);
            }
        }
    }

    var g_objDoc = null; //obj文件的信息数据
    var g_drawingInfo = null; //绘制3d模型的相关数据

    //obj文件读取成功后开始解析
    function onReadOBJFile(fileString, fileName, gl, obj, scale, reverse) {
        var objDoc = new OBJDoc(fileName); // 创建一个OBJDoc 对象
        var result = objDoc.parse(fileString, scale, reverse); //解析文件
        if(!result){
            g_objDoc = null;
            g_drawingInfo = null;
            console.log("obj文件解析错误");
            return;
        }else {
            //解析成功赋值给g_objDoc
            g_objDoc = objDoc;
        }
    }

    //obj文件已经呗成功读取解析后处理函数
    function onReadComplete(gl, model, objDoc) {
        //从OBJ文件获取顶点坐标和颜色
        var drawingInfo = objDoc.getDrawingInfo();

        //将数据写入缓冲区

        console.log("数据开始");
        console.log("顶点坐标",drawingInfo.vertices);
        console.log("法向量",drawingInfo.normals);
        console.log("颜色",drawingInfo.colors);
        console.log("索引值",drawingInfo.indices);
        //顶点
        gl.bindBuffer(gl.ARRAY_BUFFER, model.vertexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, drawingInfo.vertices, gl.STATIC_DRAW);

        //法向量
        gl.bindBuffer(gl.ARRAY_BUFFER, model.normalBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, drawingInfo.normals, gl.STATIC_DRAW);

        //颜色
        gl.bindBuffer(gl.ARRAY_BUFFER, model.colorBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, drawingInfo.colors, gl.STATIC_DRAW);

        //索引值
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, model.indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, drawingInfo.indices, gl.STATIC_DRAW);

        return drawingInfo;
    }

    //模型角度改变函数
    var angle_step = 30;
    var last = +new Date();
    function animate(angle) {
        var now = +new Date();
        var elapsed = now - last;
        last = now;
        var newAngle = angle + (angle_step*elapsed)/1000.0;
        return newAngle%360;
    }


    //------------------------------------------------------------------------------
    // OBJParser
    //------------------------------------------------------------------------------

    // OBJDoc object
    // Constructor
    var OBJDoc = function(fileName) {
        this.fileName = fileName;
        this.mtls = new Array(0);      // Initialize the property for MTL
        this.objects = new Array(0);   // Initialize the property for Object
        this.vertices = new Array(0);  // Initialize the property for Vertex
        this.normals = new Array(0);   // Initialize the property for Normal
    }

    // Parsing the OBJ file
    OBJDoc.prototype.parse = function(fileString, scale, reverse) {
        var lines = fileString.split('\n');  // Break up into lines and store them as array
        lines.push(null); // Append null
        var index = 0;    // Initialize index of line

        var currentObject = null;
        var currentMaterialName = "";

        // Parse line by line
        var line;         // A string in the line to be parsed
        var sp = new StringParser();  // Create StringParser
        while ((line = lines[index++]) != null) {
            sp.init(line);                  // init StringParser
            var command = sp.getWord();     // Get command
            if(command == null)	 continue;  // check null command

            switch(command){
                case '#':
                    continue;  // Skip comments
                case 'mtllib':     // Read Material chunk
                    var path = this.parseMtllib(sp, this.fileName);
                    var mtl = new MTLDoc();   // Create MTL instance
                    this.mtls.push(mtl);
                    var request = new XMLHttpRequest();
                    request.onreadystatechange = function() {
                        if (request.readyState == 4) {
                            if (request.status != 404) {
                                onReadMTLFile(request.responseText, mtl);
                            }else{
                                mtl.complete = true;
                            }
                        }
                    }
                    request.open('GET', path, true);  // Create a request to acquire the file
                    request.send();                   // Send the request
                    continue; // Go to the next line
                case 'o':
                case 'g':   // Read Object name
                    var object = this.parseObjectName(sp);
                    this.objects.push(object);
                    currentObject = object;
                    continue; // Go to the next line
                case 'v':   // Read vertex
                    var vertex = this.parseVertex(sp, scale);
                    this.vertices.push(vertex);
                    continue; // Go to the next line
                case 'vn':   // Read normal
                    var normal = this.parseNormal(sp);
                    this.normals.push(normal);
                    continue; // Go to the next line
                case 'usemtl': // Read Material name
                    currentMaterialName = this.parseUsemtl(sp);
                    continue; // Go to the next line
                case 'f': // Read face
                    var face = this.parseFace(sp, currentMaterialName, this.vertices, reverse);
                    currentObject.addFace(face);
                    continue; // Go to the next line
            }
        }

        return true;
    }

    OBJDoc.prototype.parseMtllib = function(sp, fileName) {
        // Get directory path
        var i = fileName.lastIndexOf("/");
        var dirPath = "";
        if(i > 0) dirPath = fileName.substr(0, i+1);

        return dirPath + sp.getWord();   // Get path
    }

    OBJDoc.prototype.parseObjectName = function(sp) {
        var name = sp.getWord();
        return (new OBJObject(name));
    }

    OBJDoc.prototype.parseVertex = function(sp, scale) {
        var x = sp.getFloat() * scale;
        var y = sp.getFloat() * scale;
        var z = sp.getFloat() * scale;
        return (new Vertex(x, y, z));
    }

    OBJDoc.prototype.parseNormal = function(sp) {
        var x = sp.getFloat();
        var y = sp.getFloat();
        var z = sp.getFloat();
        return (new Normal(x, y, z));
    }

    OBJDoc.prototype.parseUsemtl = function(sp) {
        return sp.getWord();
    }

    OBJDoc.prototype.parseFace = function(sp, materialName, vertices, reverse) {
        var face = new Face(materialName);
        // get indices
        for(;;){
            var word = sp.getWord();
            if(word == null) break;
            var subWords = word.split('/');
            if(subWords.length >= 1){
                var vi = parseInt(subWords[0]) - 1;
                face.vIndices.push(vi);
            }
            if(subWords.length >= 3){
                var ni = parseInt(subWords[2]) - 1;
                face.nIndices.push(ni);
            }else{
                face.nIndices.push(-1);
            }
        }

        // calc normal
        var v0 = [
            vertices[face.vIndices[0]].x,
            vertices[face.vIndices[0]].y,
            vertices[face.vIndices[0]].z];
        var v1 = [
            vertices[face.vIndices[1]].x,
            vertices[face.vIndices[1]].y,
            vertices[face.vIndices[1]].z];
        var v2 = [
            vertices[face.vIndices[2]].x,
            vertices[face.vIndices[2]].y,
            vertices[face.vIndices[2]].z];

        // 面の法線を計算してnormalに設定
        var normal = calcNormal(v0, v1, v2);
        // 法線が正しく求められたか調べる
        if (normal == null) {
            if (face.vIndices.length >= 4) { // 面が四角形なら別の3点の組み合わせで法線計算
                var v3 = [
                    vertices[face.vIndices[3]].x,
                    vertices[face.vIndices[3]].y,
                    vertices[face.vIndices[3]].z];
                normal = calcNormal(v1, v2, v3);
            }
            if(normal == null){         // 法線が求められなかったのでY軸方向の法線とする
                normal = [0.0, 1.0, 0.0];
            }
        }
        if(reverse){
            normal[0] = -normal[0];
            normal[1] = -normal[1];
            normal[2] = -normal[2];
        }
        face.normal = new Normal(normal[0], normal[1], normal[2]);

        // Devide to triangles if face contains over 3 points.
        if(face.vIndices.length > 3){
            var n = face.vIndices.length - 2;
            var newVIndices = new Array(n * 3);
            var newNIndices = new Array(n * 3);
            for(var i=0; i<n; i++){
                newVIndices[i * 3 + 0] = face.vIndices[0];
                newVIndices[i * 3 + 1] = face.vIndices[i + 1];
                newVIndices[i * 3 + 2] = face.vIndices[i + 2];
                newNIndices[i * 3 + 0] = face.nIndices[0];
                newNIndices[i * 3 + 1] = face.nIndices[i + 1];
                newNIndices[i * 3 + 2] = face.nIndices[i + 2];
            }
            face.vIndices = newVIndices;
            face.nIndices = newNIndices;
        }
        face.numIndices = face.vIndices.length;

        return face;
    }

    // Analyze the material file
    function onReadMTLFile(fileString, mtl) {
        var lines = fileString.split('\n');  // Break up into lines and store them as array
        lines.push(null);           // Append null
        var index = 0;              // Initialize index of line

        // Parse line by line
        var line;      // A string in the line to be parsed
        var name = ""; // Material name
        var sp = new StringParser();  // Create StringParser
        while ((line = lines[index++]) != null) {
            sp.init(line);                  // init StringParser
            var command = sp.getWord();     // Get command
            if(command == null)	 continue;  // check null command

            switch(command){
                case '#':
                    continue;    // Skip comments
                case 'newmtl': // Read Material chunk
                    name = mtl.parseNewmtl(sp);    // Get name
                    continue; // Go to the next line
                case 'Kd':   // Read normal
                    if(name == "") continue; // Go to the next line because of Error
                    var material = mtl.parseRGB(sp, name);
                    mtl.materials.push(material);
                    name = "";
                    continue; // Go to the next line
            }
        }
        mtl.complete = true;
    }

    // Check Materials
    OBJDoc.prototype.isMTLComplete = function() {
        if(this.mtls.length == 0) return true;
        for(var i = 0; i < this.mtls.length; i++){
            if(!this.mtls[i].complete) return false;
        }
        return true;
    }

    // Find color by material name
    OBJDoc.prototype.findColor = function(name){
        for(var i = 0; i < this.mtls.length; i++){
            for(var j = 0; j < this.mtls[i].materials.length; j++){
                if(this.mtls[i].materials[j].name == name){
                    return(this.mtls[i].materials[j].color)
                }
            }
        }
        return(new Color(0.8, 0.8, 0.8, 1));
    }

    //------------------------------------------------------------------------------
    // Retrieve the information for drawing 3D model
    OBJDoc.prototype.getDrawingInfo = function() {
        // Create an arrays for vertex coordinates, normals, colors, and indices
        var numIndices = 0;
        for(var i = 0; i < this.objects.length; i++){
            numIndices += this.objects[i].numIndices;
        }
        var numVertices = numIndices;
        var vertices = new Float32Array(numVertices * 3);
        var normals = new Float32Array(numVertices * 3);
        var colors = new Float32Array(numVertices * 4);
        var indices = new Uint16Array(numIndices);

        // Set vertex, normal and color
        var index_indices = 0;
        for(var i = 0; i < this.objects.length; i++){
            var object = this.objects[i];
            for(var j = 0; j < object.faces.length; j++){
                var face = object.faces[j];
                var color = this.findColor(face.materialName);
                var faceNormal = face.normal;
                for(var k = 0; k < face.vIndices.length; k++){
                    // Set index
                    indices[index_indices] = index_indices;
                    // Copy vertex
                    var vIdx = face.vIndices[k];
                    var vertex = this.vertices[vIdx];
                    vertices[index_indices * 3 + 0] = vertex.x;
                    vertices[index_indices * 3 + 1] = vertex.y;
                    vertices[index_indices * 3 + 2] = vertex.z;
                    // Copy color
                    colors[index_indices * 4 + 0] = color.r;
                    colors[index_indices * 4 + 1] = color.g;
                    colors[index_indices * 4 + 2] = color.b;
                    colors[index_indices * 4 + 3] = color.a;
                    // Copy normal
                    var nIdx = face.nIndices[k];
                    if(nIdx >= 0){
                        var normal = this.normals[nIdx];
                        normals[index_indices * 3 + 0] = normal.x;
                        normals[index_indices * 3 + 1] = normal.y;
                        normals[index_indices * 3 + 2] = normal.z;
                    }else{
                        normals[index_indices * 3 + 0] = faceNormal.x;
                        normals[index_indices * 3 + 1] = faceNormal.y;
                        normals[index_indices * 3 + 2] = faceNormal.z;
                    }
                    index_indices ++;
                }
            }
        }

        return new DrawingInfo(vertices, normals, colors, indices);
    }

    //------------------------------------------------------------------------------
    // MTLDoc Object
    //------------------------------------------------------------------------------
    var MTLDoc = function() {
        this.complete = false; // MTL is configured correctly
        this.materials = new Array(0);
    }

    MTLDoc.prototype.parseNewmtl = function(sp) {
        return sp.getWord();         // Get name
    }

    MTLDoc.prototype.parseRGB = function(sp, name) {
        var r = sp.getFloat();
        var g = sp.getFloat();
        var b = sp.getFloat();
        return (new Material(name, r, g, b, 1));
    }

    //------------------------------------------------------------------------------
    // Material Object
    //------------------------------------------------------------------------------
    var Material = function(name, r, g, b, a) {
        this.name = name;
        this.color = new Color(r, g, b, a);
    }

    //------------------------------------------------------------------------------
    // Vertex Object
    //------------------------------------------------------------------------------
    var Vertex = function(x, y, z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    //------------------------------------------------------------------------------
    // Normal Object
    //------------------------------------------------------------------------------
    var Normal = function(x, y, z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    //------------------------------------------------------------------------------
    // Color Object
    //------------------------------------------------------------------------------
    var Color = function(r, g, b, a) {
        this.r = r;
        this.g = g;
        this.b = b;
        this.a = a;
    }

    //------------------------------------------------------------------------------
    // OBJObject Object
    //------------------------------------------------------------------------------
    var OBJObject = function(name) {
        this.name = name;
        this.faces = new Array(0);
        this.numIndices = 0;
    }

    OBJObject.prototype.addFace = function(face) {
        this.faces.push(face);
        this.numIndices += face.numIndices;
    }

    //------------------------------------------------------------------------------
    // Face Object
    //------------------------------------------------------------------------------
    var Face = function(materialName) {
        this.materialName = materialName;
        if(materialName == null)  this.materialName = "";
        this.vIndices = new Array(0);
        this.nIndices = new Array(0);
    }

    //------------------------------------------------------------------------------
    // DrawInfo Object
    //------------------------------------------------------------------------------
    var DrawingInfo = function(vertices, normals, colors, indices) {
        this.vertices = vertices;
        this.normals = normals;
        this.colors = colors;
        this.indices = indices;
    }

    //------------------------------------------------------------------------------
    // Constructor
    var StringParser = function(str) {
        this.str;   // Store the string specified by the argument
        this.index; // Position in the string to be processed
        this.init(str);
    }
    // Initialize StringParser object
    StringParser.prototype.init = function(str){
        this.str = str;
        this.index = 0;
    }

    // Skip delimiters
    StringParser.prototype.skipDelimiters = function()  {
        for(var i = this.index, len = this.str.length; i < len; i++){
            var c = this.str.charAt(i);
            // Skip TAB, Space, '(', ')
            if (c == '\t'|| c == ' ' || c == '(' || c == ')' || c == '"') continue;
            break;
        }
        this.index = i;
    }

    // Skip to the next word
    StringParser.prototype.skipToNextWord = function() {
        this.skipDelimiters();
        var n = getWordLength(this.str, this.index);
        this.index += (n + 1);
    }

    // Get word
    StringParser.prototype.getWord = function() {
        this.skipDelimiters();
        var n = getWordLength(this.str, this.index);
        if (n == 0) return null;
        var word = this.str.substr(this.index, n);
        this.index += (n + 1);

        return word;
    }

    // Get integer
    StringParser.prototype.getInt = function() {
        return parseInt(this.getWord());
    }

    // Get floating number
    StringParser.prototype.getFloat = function() {
        return parseFloat(this.getWord());
    }

    // Get the length of word
    function getWordLength(str, start) {
        var n = 0;
        for(var i = start, len = str.length; i < len; i++){
            var c = str.charAt(i);
            if (c == '\t'|| c == ' ' || c == '(' || c == ')' || c == '"')
                break;
        }
        return i - start;
    }

    //------------------------------------------------------------------------------
    // Common function
    //------------------------------------------------------------------------------
    function calcNormal(p0, p1, p2) {
        // v0: a vector from p1 to p0, v1; a vector from p1 to p2
        var v0 = new Float32Array(3);
        var v1 = new Float32Array(3);
        for (var i = 0; i < 3; i++){
            v0[i] = p0[i] - p1[i];
            v1[i] = p2[i] - p1[i];
        }

        // The cross product of v0 and v1
        var c = new Float32Array(3);
        c[0] = v0[1] * v1[2] - v0[2] * v1[1];
        c[1] = v0[2] * v1[0] - v0[0] * v1[2];
        c[2] = v0[0] * v1[1] - v0[1] * v1[0];

        // Normalize the result
        var v = new Vector3(c);
        v.normalize();
        return v.elements;
    }

</script>
</html>
里面引入了一个文件读取组件,具体的不做介绍了。因为如果文件加载显示,还是three.js做的比较优秀,到学习three.js再进行解释。


  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值