xrviewer.js 7.81 KB
       
import ARCS from '../engine/arcs.js';
import * as THREE from '../deps/three.js/index.js';
import {ARButton} from '../deps/three.js/ARButton.js';
        
var XRViewer;

/** 
    * @class XRViewer 
    * @classdesc Simple compositing viewer for augmented reality using WebXR
    * @param [config] {object} configuration object for the viewer
    * @param [config.rootElement="document.body"] {string} element on which to attach renderer
    * @param [config.sessionConfig] {object} an object to configure an immersive 
    * session as formulated in WebXR specification.
    */        
XRViewer = ARCS.Component.create(
    /** @lends XRViewer.prototype */
    function (config) {
        let renderer, root, scene, camera;
        let sceneId;
        let container;
        let self = this;
        let defaultStyle;
        let controller;
        let button;
        let binding;
        
        let _config = config || {};
        
        container = (_config.rootElement !== undefined)?
            document.querySelector(_config.rootElement):document.body;
        container = container || document.body;
        let rootOverlay = container;

        if (_config.sessionConfig && _config.sessionConfig.domOverlay 
            && _config.sessionConfig.domOverlay && _config.sessionConfig.domOverlay.root) {
            rootOverlay = document.querySelector(_config.sessionConfig.domOverlay.root) || rootOverlay;
            _config.sessionConfig.domOverlay.root = rootOverlay; // || document.body;            
        }
        
        let defaultDisplay = window.getComputedStyle(rootOverlay).getPropertyValue("display");
        let firstFrame = true;
        
        // scenegraph initializations
        root = new THREE.Scene();        
        scene = new THREE.Group();
        root.add(scene);

        renderer = new THREE.WebGLRenderer({ alpha: true});
        //renderer.setClearColor(0x000000, 1);
        renderer.setSize(container.width, container.height);
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.xr.enabled = true;
        //renderer.xr.cameraAutoUpdate = false;
        container.appendChild(renderer.domElement);
        button = ARButton.createButton(renderer,_config.sessionConfig);
        rootOverlay.appendChild(button);

        var theta = 45;
        camera = new THREE.PerspectiveCamera(theta, container.width / container.height, 0.01, 10);

        var _light = new THREE.DirectionalLight(0xffffff);
        _light.position.set(0,5,5);

        scene.add(_light);
        scene.add(camera);
        let origin = new THREE.Mesh(new THREE.SphereGeometry(0.02,32,32), new THREE.MeshBasicMaterial({color: 'red', wireframe: true, side: THREE.DoubleSide}));
        root.add(origin);
        let sOrigin = new THREE.Mesh(new THREE.SphereGeometry(0.02,32,32), new THREE.MeshBasicMaterial({color: 'green', wireframe: true, side: THREE.DoubleSide}));
        scene.add(sOrigin); 

        controller = renderer.xr.getController(0);
        controller.addEventListener('select', () => {
            console.log('xr select');
            this.emit('onSelect',controller);
        });
        
        /*renderer.domElement.addEventListener('webglcontextlost', (event) => {
            console.log(event);
        });*/
        
        
        /**
            * Adds new objects to the current 3D scene
            * @param scene {object} 3D object as defined by Three.js to add to the scene.
            * @slot
            * @function XRViewer#addScene
            */
        this.addXRScene = function (subScene) {
            scene.add(subScene);
        };
      
        this.addXRCameraScene = function(subScene) {
            camera.add(subScene);
        };
        this.addScene = function(subScene) {
            root.add(subScene);
        };
    
        /**
            * Removes an object from the current 3D scene
            * @param scene {object} 3D object as defined by Three.js to remove from the scene.
            * @slot
            * @function XRViewer#removeScene
            */
        this.removeXRScene = function (subScene) {
            scene.remove(subScene);
        };
        
        
        this.removeScene = function(subScene) {
            root.remove(subScene);
        };
        
        this.setMatrixRotation = function(mat) {
            let m = new THREE.Matrix4();
            m.copy(mat);
            m.invert();
            scene.position.setFromMatrixRotation(m);          
        };
        
        
        this.setMatrixPosition = function(mat) {
            let m = new THREE.Matrix4();
            m.copy(mat);
            m.invert();
            scene.position.setFromMatrixPosition(m);          
        };
   

        this.setMatrixPose = function(mat) {
            scene.rotation.setFromRotationMatrix(mat);
            scene.position.setFromMatrixPosition(mat);
        };
        
                
        /**
         * Starts the animation loop.
         * Each time an animation frame is obtained, preRender and postRender 
         * signals are triggered
         * @slot 
         * @function XRViewer#start
         * @emits onRender
         * @emits onStarted
         * @emits onEnded
         */        
        this.start = function() {
            renderer.setAnimationLoop(render);
        };
        
        /* not a good idea, webxr MUST BE manually activated ! */
        /*this.activate = function() {
            button.click();            
            this.start();
        };*/
        
        let render = function (time, frame) {
            if (frame) {
                renderer.xr.updateCamera(camera);
                if (firstFrame)  {
                    binding  = new XRWebGLBinding(frame.session,renderer.getContext());
                    renderer.xr.getSession().addEventListener('end',() => {
                        firstFrame = true;
                        rootOverlay.style.display = defaultDisplay;
                        self.setMatrixPose(new THREE.Matrix4());
                        self.emit('onEnded');
                    });                    
                    firstFrame = false;
                    self.emit('onStarted');
                }
                                            
                // if this works, then we simplify the thing
                frame.manager = renderer.xr;
                frame.renderer = renderer;
                frame.binding = binding;
                self.emit("onRender",time,renderer.xr.getCamera(),frame);

            }
            renderer.render(root, camera);
        }
        
    },
    /** @lends XRViewer.slots */
    [
      'addScene','removeScene','addXRScene', 'addXRCameraScene', 'removeXRScene', 
      'start','setPose', 'setMatrixPose', 'setMatrixPosition', 'setMatrixRotation'
      
    ],
    ['onRender','onEnded', 'onStarted','onSelect']
);

/** 
 * emitted when a webXR session is operational
 * @function XRViewer#onStarted
 * @signal
 */

/**
 * emitted when a frame is obtained after rendering of the scene.
 * @function XRViewer#onRender
 * @signal
 * @param time {number} time elapsed since the beginning of the loop in seconds
 * @param camera {THREE.Camera} the camera object
 * @param frame {XRFrame} a frame as setup by the WebXR loop
 */ 

export default {XRViewer: XRViewer}; 

/*
ARCS.__lib__`
{
    "components": {
        "XRViewer": {
            "description": "A component to display a scene in a WebXR session",
            "keywords": ["xr","webxr","ar","vr","viewer"]
        }
    },
    "dependencies": ["three"],
    "recipes": [
        {
            "from": "node_modules/three/build/three.module.js",
            "to": "deps/three.js/index.js"
        },
        {
            "from": "node_modules/three/examples/jsm/webxr/ARButton.js", 
            "to": "deps/three.js/ARButton.js",
            "replace": {
                "../../../build/three.module.js": "./index.js"
            }
        }
    ]
}
`;
*/