venerdì 21 marzo 2014

Realta' aumentata con Aruco JS e WebGL

Una piccola introduzione: le librerie di realta' aumentata non gestiscono la fase di creazione degli oggetti 3D per cui si deve avere come integrazione una libreria grafica 3D

Detto cio' viene mostrato un breve esempio di come gestire un marker per la realta' aumentata catturato da una WebCam e creare un oggetto 3D che interagisca con il marker, il tutto gestito all'interno di un browser (in questo caso Firefox...Chrome non ha funzionato ma credo che sia dovuto ai permessi della webcam)

La libreria di AR utilizzata e' stata ArucoJS, la versione javascript di una libreria disponibile anche in C

Per prima cosa si deve stampare il marker (attenzione, una volta stampato il marker non deve essere tagliato a filo con il bordo nero ma deve essere lasciata una striscia bianca all'esterno del quadrato nero, altrimenti il marker non viene riconosciuto)


successivamente ho leggermente modificato l'esempio debug-posit presente nella cartella samples per generare un cubo al di sopra del marker con i comandi WebGL. Le modifiche, veramente minime, sono indicate dal segno giallo nel sorgente

Da notare che quando e' in esecuzione la pagina su Firefox, il processore entra in funzione in modo pesante con accensione delle ventole (su MacBook) e la pagina diventa un po' scattosa

il progetto puo' essere scaricato da questo link
---------------------------------------------------------------------------
<html>

<head>
  <title>Augmented Reality</title>

  <script type="text/javascript" src="libs/Three.js"></script> 

  <script type="text/javascript" src="svd.js"></script> 
  <script type="text/javascript" src="posit1.js"></script> 
  <script type="text/javascript" src="cv.js"></script> 
  <script type="text/javascript" src="aruco.js"></script> 

  <script>
    var video, canvas, context, imageData, detector, posit;
    var renderer1, renderer2, renderer3;
    var scene1, scene2, scene3, scene4;
    var camera1, camera2, camera3, camera4;
    var plane1, plane2, model, texture;
    var step = 0.0;

    var modelSize = 35.0; //millimeters

    function onLoad(){
      video = document.getElementById("video");
      canvas = document.getElementById("canvas");
      context = canvas.getContext("2d");
    
      canvas.width = parseInt(canvas.style.width);
      canvas.height = parseInt(canvas.style.height);
      
      navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
      if (navigator.getUserMedia){
        init();
      }
    };
    
    function init(){
      navigator.getUserMedia({video:true}, 
        function (stream){
          if (window.webkitURL) {
            video.src = window.webkitURL.createObjectURL(stream);
          } else if (video.mozSrcObject !== undefined) {
            video.mozSrcObject = stream;
          } else {
            video.src = stream;
          }
        },
        function(error){
        }
      );
      
      detector = new AR.Detector();
      posit = new POS.Posit(modelSize, canvas.width);

      createRenderers();
      createScenes();

      requestAnimationFrame(tick);
    };

    function tick(){
      requestAnimationFrame(tick);
      
      if (video.readyState === video.HAVE_ENOUGH_DATA){
        snapshot();

        var markers = detector.detect(imageData);
        drawCorners(markers);
        updateScenes(markers);
        
        render();
      }
    };

    function snapshot(){
      context.drawImage(video, 0, 0, canvas.width, canvas.height);
      imageData = context.getImageData(0, 0, canvas.width, canvas.height);
    };
    
    function drawCorners(markers){
      var corners, corner, i, j;
    
      context.lineWidth = 3;

      for (i = 0; i < markers.length; ++ i){
        corners = markers[i].corners;
        
        context.strokeStyle = "red";
        context.beginPath();
        
        for (j = 0; j < corners.length; ++ j){
          corner = corners[j];
          context.moveTo(corner.x, corner.y);
          corner = corners[(j + 1) % corners.length];
          context.lineTo(corner.x, corner.y);
        }

        context.stroke();
        context.closePath();
        
        context.strokeStyle = "green";
        context.strokeRect(corners[0].x - 2, corners[0].y - 2, 4, 4);
      }
    };

    function createRenderers(){
      renderer1 = new THREE.WebGLRenderer();
      renderer1.setClearColorHex(0xffff00, 1);
      renderer1.setSize(canvas.width, canvas.height);
      document.getElementById("container1").appendChild(renderer1.domElement);
      scene1 = new THREE.Scene();
      camera1 = new THREE.PerspectiveCamera(40, canvas.width / canvas.height, 1, 1000);
      scene1.add(camera1);

      renderer2 = new THREE.WebGLRenderer();
      renderer2.setClearColorHex(0xffff00, 1);
      renderer2.setSize(canvas.width, canvas.height);
      document.getElementById("container2").appendChild(renderer2.domElement);
      scene2 = new THREE.Scene();
      camera2 = new THREE.PerspectiveCamera(40, canvas.width / canvas.height, 1, 1000);
      scene2.add(camera2);

      renderer3 = new THREE.WebGLRenderer();
      renderer3.setClearColorHex(0xffffff, 1);
      renderer3.setSize(canvas.width, canvas.height);
      document.getElementById("container").appendChild(renderer3.domElement);
      
      scene3 = new THREE.Scene();
      camera3 = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5);
      scene3.add(camera3);
      
      scene4 = new THREE.Scene();
      camera4 = new THREE.PerspectiveCamera(40, canvas.width / canvas.height, 1, 1000);
      scene4.add(camera4);
    };

    function render(){
      renderer1.clear();
      renderer1.render(scene1, camera1);
      
      renderer2.clear();
      renderer2.render(scene2, camera2);

      renderer3.autoClear = false;
      renderer3.clear();
      renderer3.render(scene3, camera3);
      renderer3.render(scene4, camera4);
    };

    function createScenes(){
      plane1 = createPlane();
      scene1.add(plane1);

      plane2 = createPlane();
      scene2.add(plane2);
      
      texture = createTexture();
      scene3.add(texture);
    
      model = createModel();
      scene4.add(model);
    };
    
    function createPlane(){
      var object = new THREE.Object3D(),
          geometry = new THREE.PlaneGeometry(1.0, 1.0, 0.0),
          material = new THREE.MeshNormalMaterial(),
          mesh = new THREE.Mesh(geometry, material);
      
      object.add(mesh);
      
      return object;
    };
    
    function createTexture(){
      var texture = new THREE.Texture(video),
          object = new THREE.Object3D(),
          geometry = new THREE.PlaneGeometry(1.0, 1.0, 0.0),
          material = new THREE.MeshBasicMaterial( {map: texture, depthTest: false, depthWrite: false} ),
          mesh = new THREE.Mesh(geometry, material);
      
      object.position.z = -1;
      
      object.add(mesh);
      
      return object;
    };
    
    function createModel(){
      var object = new THREE.Object3D(),
        
          cubo_geometry = new THREE.CubeGeometry(1,1,1),
          cubo_material = new THREE.MeshNormalMaterial(),
          mesh = new THREE.Mesh(cubo_geometry, cubo_material);
      
      object.add(mesh);
      
      return object;
    };

    function updateScenes(markers){
      var corners, corner, pose, i;
      
      if (markers.length > 0){
        corners = markers[0].corners;
        
        for (i = 0; i < corners.length; ++ i){
          corner = corners[i];
          
          corner.x = corner.x - (canvas.width / 2);
          corner.y = (canvas.height / 2) - corner.y;
        }
        
        pose = posit.pose(corners);
        
        updateObject(plane1, pose.bestRotation, pose.bestTranslation);
        updateObject(plane2, pose.alternativeRotation, pose.alternativeTranslation);
        updateObject(model, pose.bestRotation, pose.bestTranslation);

        updatePose("pose1", pose.bestError, pose.bestRotation, pose.bestTranslation);
        updatePose("pose2", pose.alternativeError, pose.alternativeRotation, pose.alternativeTranslation);
        
        step += 0.025;
        
        model.rotation.z -= step;
      }
      
      texture.children[0].material.map.needsUpdate = true;
    };
    
    function updateObject(object, rotation, translation){
      object.scale.x = modelSize;
      object.scale.y = modelSize;
      object.scale.z = modelSize;
      
      object.rotation.x = -Math.asin(-rotation[1][2]);
      object.rotation.y = -Math.atan2(rotation[0][2], rotation[2][2]);
      object.rotation.z = Math.atan2(rotation[1][0], rotation[1][1]);

      object.position.x = translation[0];
      object.position.y = translation[1];
      object.position.z = -translation[2];
    };
    
    function updatePose(id, error, rotation, translation){
      var yaw = -Math.atan2(rotation[0][2], rotation[2][2]);
      var pitch = -Math.asin(-rotation[1][2]);
      var roll = Math.atan2(rotation[1][0], rotation[1][1]);
      
      var d = document.getElementById(id);
      d.innerHTML = " error: " + error
                  + "<br/>"
                  + " x: " + (translation[0] | 0)
                  + " y: " + (translation[1] | 0)
                  + " z: " + (translation[2] | 0)
                  + "<br/>"
                  + " yaw: " + Math.round(-yaw * 180.0/Math.PI)
                  + " pitch: " + Math.round(-pitch * 180.0/Math.PI)
                  + " roll: " + Math.round(roll * 180.0/Math.PI);
    };

    window.onload = onLoad;
  </script>

</head>

<body style="text-align: center; font-family: monospace;">

  <video id="video" width=320 height=240 autoplay="true" style="display:none;"></video>
  
  <div style="margin: 10px;"><strong>-= Augmented Reality =-</strong></div>
  <div style="width: 100%;">
    <div style="width: 650px; margin-left:auto; margin-right:auto;">
      <canvas id="canvas" style="width: 320px; height: 240px; float: left; border: solid 1px black;"></canvas>
      <div id="container" style="width: 320px; height: 240px; float: left; border: solid 1px black; background: green;"></div>
      <div style="clear: both;"></div>
      <div style="float: left; border: solid 1px black;">
        <div id="container1" style="width: 320px; height: 240px; background: red;"></div>
        <div id="pose1"></div>
      </div>
      <div style="float: left; border: solid 1px black;">
        <div id="container2" style="width: 320px; height: 240px; background: blue;"></div>
        <div id="pose2"></div>
      </div>
    </div>
  </div>
  <div style="clear: both;"></div>
  <div style="margin: 15px;"><strong>Powered by <a href="http://code.google.com/p/js-aruco/">js-aruco</a> and <a href="https://github.com/mrdoob/three.js">Three.js</a></strong></div>

</body>
  
</html>