interaction.js Example File

interaction/qml/interaction/interaction.js
 /****************************************************************************
 **
 ** Copyright (C) 2016 The Qt Company Ltd.
 ** Contact: https://www.qt.io/licensing/
 **
 ** This file is part of the QtCanvas3D module of the Qt Toolkit.
 **
 ** $QT_BEGIN_LICENSE:BSD$
 ** Commercial License Usage
 ** Licensees holding valid commercial Qt licenses may use this file in
 ** accordance with the commercial license agreement provided with the
 ** Software or, alternatively, in accordance with the terms contained in
 ** a written agreement between you and The Qt Company. For licensing terms
 ** and conditions see https://www.qt.io/terms-conditions. For further
 ** information use the contact form at https://www.qt.io/contact-us.
 **
 ** BSD License Usage
 ** Alternatively, you may use this file under the terms of the BSD license
 ** as follows:
 **
 ** "Redistribution and use in source and binary forms, with or without
 ** modification, are permitted provided that the following conditions are
 ** met:
 **   * Redistributions of source code must retain the above copyright
 **     notice, this list of conditions and the following disclaimer.
 **   * Redistributions in binary form must reproduce the above copyright
 **     notice, this list of conditions and the following disclaimer in
 **     the documentation and/or other materials provided with the
 **     distribution.
 **   * Neither the name of The Qt Company Ltd nor the names of its
 **     contributors may be used to endorse or promote products derived
 **     from this software without specific prior written permission.
 **
 **
 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 **
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/

 Qt.include("gl-matrix.js")
 Qt.include("ThreeJSLoader.js")

 var gl;

 var texturedShaderProgram = 0;
 var vertexShader = 0;
 var fragmentShader = 0;

 var vertexPositionAttribute;
 var textureCoordAttribute;
 var vertexNormalAttribute;

 var pMatrixUniform;
 var mvMatrixUniform;
 var nMatrixUniform;
 var textureSamplerUniform;

 var barrelTexture = 0;

 var mvMatrix = mat4.create();
 var pMatrix  = mat4.create();
 var nMatrix  = mat4.create();
 var startTime;

 var canvas3d;
 var isLogEnabled = false;

 function log(message) {
     if (isLogEnabled)
         console.log(message)
 }

 function Model() {
     this.verticesVBO = 0;
     this.normalsVBO  = 0;
     this.texCoordVBO = 0;
     this.indexVBO    = 0;
     this.count       = 0;
 }

 var theModel = new Model();

 function initializeGL(canvas) {
     canvas3d = canvas
     log("*******************************************************************************************")
     log("initializeGL ENTER...")
     try {
         startTime = Date.now();

         // Get the OpenGL context jsonObj that represents the API we call
         log("Getting Context");
         gl = canvas.getContext("canvas3d", {depth:true, antialias:true, alpha:false});
         log("Context received "+gl);

         var contextConfig = gl.getContextAttributes();
         log("Depth: "+contextConfig.alpha);
         log("Stencil: "+contextConfig.stencil);
         log("Antialiasing: "+contextConfig.antialias);
         log("Premultiplied alpha: "+contextConfig.premultipliedAlpha);
         log("Preserve drawingbuffer: "+contextConfig.preserveDrawingBuffer);
         log("Prefer Low Power To High Performance: "+contextConfig.preferLowPowerToHighPerformance);
         log("Fail If Major Performance Caveat: "+contextConfig.failIfMajorPerformanceCaveat);

         // Setup the OpenGL state
         gl.enable(gl.DEPTH_TEST);
         gl.enable(gl.CULL_FACE);

         gl.frontFace(gl.CCW);
         gl.cullFace(gl.BACK);

         gl.disable(gl.BLEND);

         gl.clearColor(0.98, 0.98, 0.98, 1.0);
         gl.clearDepth(1.0);

         // Set viewport
         gl.viewport(0, 0,
                     canvas.width * canvas.devicePixelRatio,
                     canvas.height * canvas.devicePixelRatio);

         // Initialize the shader program
         initShaders();

         // Initialize buffers
         theModel.verticesVBO = gl.createBuffer();
         theModel.verticesVBO.name = "BarrelModel.verticesVBO";
         theModel.normalsVBO = gl.createBuffer();
         theModel.normalsVBO.name = "BarrelModel.normalsVBO";
         theModel.texCoordVBO = gl.createBuffer();
         theModel.texCoordVBO.name = "BarrelModel.texCoordVBO";
         theModel.indexVBO = gl.createBuffer();
         theModel.indexVBO.name = "BarrelModel.indexVBO";

         // Load the barrel texture
         gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
         // Load the Qt logo as texture
         var barrelImage = TextureImageFactory.newTexImage();
         barrelImage.imageLoaded.connect(function() {
             barrelTexture = gl.createTexture();
             barrelTexture.name = "barrelTexture"
             gl.bindTexture(gl.TEXTURE_2D, barrelTexture);
             gl.texImage2D(gl.TEXTURE_2D,    // target
                           0,                // level
                           gl.RGBA,          // internalformat
                           gl.RGBA,          // format
                           gl.UNSIGNED_BYTE, // type
                           barrelImage);     // pixels

             gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
             gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
             gl.generateMipmap(gl.TEXTURE_2D);
         });
         barrelImage.imageLoadingFailed.connect(function() {
             console.log("Texture load FAILED, "+barrelImage.errorString);
         });
         // Aliasing doesn't work correctly with QtQuick compiler for some reason,
         // so use full names for barrel.jpg and barrel.json
         barrelImage.src = "qrc:/qml/interaction/barrel.jpg";

         // Load the model
         log("    Create XMLHttpRequest")
         var request = new XMLHttpRequest();
         log("    XMLHttpRequest.open")
         request.open("GET", "qrc:/qml/interaction/barrel.json");
         log("    XMLHttpRequest.onreadystatechange")
         request.onreadystatechange = function () {
             if (request.readyState === XMLHttpRequest.DONE) {
                 handleLoadedModel(JSON.parse(request.responseText));
             }
         }
         log("    XMLHttpRequest.send")
         request.send();

         log("...initializeGL EXIT");
     } catch(e) {
         console.log("...initializeGL FAILURE!");
         console.log(""+e);
         console.log(""+e.message);
     }
     log("*******************************************************************************************");
 }

 function resizeGL(canvas)
 {
     var pixelRatio = canvas.devicePixelRatio;
     canvas.pixelSize = Qt.size(canvas.width * pixelRatio,
                                canvas.height * pixelRatio);
     if (gl)
         gl.viewport(0, 0,
                     canvas.width * canvas.devicePixelRatio,
                     canvas.height * canvas.devicePixelRatio);
 }

 function paintGL(canvas) {
     // draw
     gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

     // draw only when we have the mesh and texture
     if (theModel.count <= 0 || barrelTexture == 0) return;

     gl.useProgram(texturedShaderProgram);

     // Calculate the perspective projection
     mat4.perspective(pMatrix, degToRad(45), canvas.width / canvas.height, 0.1, 10000.0);
     gl.uniformMatrix4fv(pMatrixUniform, false, pMatrix);

     // Bind the correct buffers
     gl.bindBuffer(gl.ARRAY_BUFFER, theModel.verticesVBO);
     gl.enableVertexAttribArray(vertexPositionAttribute);
     gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);

     gl.bindBuffer(gl.ARRAY_BUFFER, theModel.normalsVBO);
     gl.enableVertexAttribArray(vertexNormalAttribute);
     gl.vertexAttribPointer(vertexNormalAttribute, 3, gl.FLOAT, false, 0, 0);

     gl.bindBuffer(gl.ARRAY_BUFFER, theModel.texCoordVBO);
     gl.enableVertexAttribArray(textureCoordAttribute);
     gl.vertexAttribPointer(textureCoordAttribute, 2, gl.FLOAT, false, 0, 0);

     gl.activeTexture(gl.TEXTURE0);
     gl.bindTexture(gl.TEXTURE_2D, barrelTexture);
     gl.uniform1i(textureSamplerUniform, 0);

     // Calculate and apply the modelview matrix
     mvMatrix = mat4.identity(mvMatrix);
     mvMatrix = mat4.translate(mvMatrix, mvMatrix, [0, -40, -700]);
     mvMatrix = mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.xRotSlider), [1, 0, 0]);
     mvMatrix = mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.yRotSlider), [0, 1, 0]);
     mvMatrix = mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.zRotSlider), [0, 0, 1]);
     gl.uniformMatrix4fv(mvMatrixUniform, false, mvMatrix);

     // Calculate normal matrix
     nMatrix = mat4.invert(nMatrix, mvMatrix);
     nMatrix = mat4.transpose(nMatrix, nMatrix);
     gl.uniformMatrix4fv(nMatrixUniform, false, nMatrix);

     // Draw the barrel
     gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, theModel.indexVBO);
     gl.drawElements(gl.TRIANGLES, theModel.count, gl.UNSIGNED_SHORT, 0);

     // Calculate and apply the modelview matrix
     mvMatrix = mat4.identity(mvMatrix);
     mvMatrix = mat4.translate(mvMatrix, mvMatrix, [-250, -50, -700]);
     mvMatrix = mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.xRotSlider), [0, 1, 0]);
     gl.uniformMatrix4fv(mvMatrixUniform, false, mvMatrix);

     // Calculate normal matrix
     nMatrix = mat4.invert(nMatrix, mvMatrix);
     nMatrix = mat4.transpose(nMatrix, nMatrix);
     gl.uniformMatrix4fv(nMatrixUniform, false, nMatrix);

     // Draw the barrel
     gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, theModel.indexVBO);
     gl.drawElements(gl.POINTS, theModel.count, gl.UNSIGNED_SHORT, 0);

     // Calculate and apply the modelview matrix
     mvMatrix = mat4.identity(mvMatrix);
     mvMatrix = mat4.translate(mvMatrix, mvMatrix, [250, -50, -700]);
     mvMatrix = mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.zRotSlider), [0, 1, 0]);
     gl.uniformMatrix4fv(mvMatrixUniform, false, mvMatrix);

     // Calculate normal matrix
     nMatrix = mat4.invert(nMatrix, mvMatrix);
     nMatrix = mat4.transpose(nMatrix, nMatrix);
     gl.uniformMatrix4fv(nMatrixUniform, false, nMatrix);

     // Draw the barrel
     gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, theModel.indexVBO);
     gl.drawElements(gl.LINES, theModel.count, gl.UNSIGNED_SHORT, 0);
 }

 function handleLoadedModel(jsonObj) {
     log("*******************************************************************************************");
     log("handleLoadedModel ENTER...")
     var modelData = parseJSON3DModel(jsonObj, "");

     log("    "+theModel.verticesVBO);
     gl.bindBuffer(gl.ARRAY_BUFFER, theModel.verticesVBO);
     gl.bufferData(gl.ARRAY_BUFFER,
                   new Float32Array(modelData.vertices),
                   gl.STATIC_DRAW);

     log("    "+theModel.normalsVBO);
     gl.bindBuffer(gl.ARRAY_BUFFER, theModel.normalsVBO);
     gl.bufferData(gl.ARRAY_BUFFER,
                   new Float32Array(modelData.normals),
                   gl.STATIC_DRAW);

     log("    "+theModel.texCoordVBO);
     gl.bindBuffer(gl.ARRAY_BUFFER, theModel.texCoordVBO);
     gl.bufferData(gl.ARRAY_BUFFER,
                   new Float32Array(modelData.texCoords[0]),
                   gl.STATIC_DRAW);

     log("    "+theModel.indexVBO);
     gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, theModel.indexVBO);
     gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,
                   new Uint16Array(modelData.indices),
                   gl.STATIC_DRAW);

     theModel.count = modelData.indices.length;
     log("...handleLoadedModel EXIT");
     log("*******************************************************************************************");
 }

 function degToRad(degrees) {
     return degrees * Math.PI / 180;
 }

 function initShaders()
 {
     log("    initShaders ENTER...")

     vertexShader = getShader(gl,
                              "attribute highp vec3 aVertexNormal;       \
                               attribute highp vec3 aVertexPosition;     \
                               attribute highp vec2 aTextureCoord;       \
                                                                         \
                               uniform highp mat4 uNormalMatrix;         \
                               uniform mat4 uMVMatrix;                   \
                               uniform mat4 uPMatrix;                    \
                                                                         \
                               varying highp vec2 vTextureCoord;         \
                               varying highp vec3 vLighting;             \
                                                                         \
                               void main(void) {                         \
                                  gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); \
                                  vTextureCoord = aTextureCoord;                                   \
                                  highp vec3 ambientLight = vec3(0.5, 0.5, 0.5);                   \
                                  highp vec3 directionalLightColor = vec3(0.75, 0.75, 0.75);       \
                                  highp vec3 directionalVector = vec3(0.85, 0.8, 0.75);            \
                                  highp vec4 transformedNormal = uNormalMatrix * vec4(aVertexNormal, 1.0); \
                                                                                                           \
                                  highp float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0); \
                                  vLighting = ambientLight + (directionalLightColor * directional);                  \
                                  gl_PointSize = 1.0; \
                              }", gl.VERTEX_SHADER);

     fragmentShader = getShader(gl,
                                "varying highp vec2 vTextureCoord;             \
                                 varying highp vec3 vLighting;                 \
                                                                               \
                                 uniform sampler2D uSampler;                   \
                                                                               \
                                 void main(void) {                             \
                                     mediump vec4 texelColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));  \
                                     gl_FragColor = vec4(texelColor.rgb * vLighting, 1.0);                                   \
                                 }", gl.FRAGMENT_SHADER);

     texturedShaderProgram = gl.createProgram();
     texturedShaderProgram.name = "TexturedShaderProgram";
     gl.attachShader(texturedShaderProgram, vertexShader);
     gl.attachShader(texturedShaderProgram, fragmentShader);
     gl.linkProgram(texturedShaderProgram);

     if (!gl.getProgramParameter(texturedShaderProgram, gl.LINK_STATUS)) {
         console.log("Could not initialize shaders");
         console.log(gl.getProgramInfoLog(texturedShaderProgram));
     }

     gl.useProgram(texturedShaderProgram);

     // look up where the vertex data needs to go.
     vertexPositionAttribute = gl.getAttribLocation(texturedShaderProgram, "aVertexPosition");
     vertexPositionAttribute.name = "aVertexPosition_AttribLocation";
     gl.enableVertexAttribArray(vertexPositionAttribute);
     vertexNormalAttribute = gl.getAttribLocation(texturedShaderProgram, "aVertexNormal");
     vertexPositionAttribute.name = "aVertexNormal_AttribLocation";
     gl.enableVertexAttribArray(vertexNormalAttribute);
     textureCoordAttribute = gl.getAttribLocation(texturedShaderProgram, "aTextureCoord");
     vertexPositionAttribute.name = "aTextureCoord_AttribLocation";
     gl.enableVertexAttribArray(textureCoordAttribute);

     pMatrixUniform  = gl.getUniformLocation(texturedShaderProgram, "uPMatrix");
     pMatrixUniform.name = "uPMatrix_UniformLocation";
     mvMatrixUniform = gl.getUniformLocation(texturedShaderProgram, "uMVMatrix");
     mvMatrixUniform.name = "uMVMatrix_UniformLocation";
     textureSamplerUniform = gl.getUniformLocation(texturedShaderProgram, "uSampler")
     textureSamplerUniform.name = "uSampler_UniformLocation";
     nMatrixUniform = gl.getUniformLocation(texturedShaderProgram, "uNormalMatrix");
     nMatrixUniform.name = "uNormalMatrix_UniformLocation";
     log("    ... initShaders EXIT");
 }

 function getShader(gl, str, type) {
     var shader = gl.createShader(type);
     gl.shaderSource(shader, str);
     gl.compileShader(shader);

     if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
         console.log("JS:Shader compile failed");
         console.log(gl.getShaderInfoLog(shader));
         return null;
     }

     return shader;
 }