Skip to content
Snippets Groups Projects
gzmultiview.js 9.81 KiB
/**
 * Created by Sandro Weber (webers@in.tum.de).
 */

GZ3D.MULTIVIEW_MAX_VIEW_COUNT = 10;
GZ3D.MULTIVIEW_MAINVIEW_NAME = 'main_view';

/**
 * GZ3D.MULTIVIEW_RENDER_VIEWPORTS uses view containers as transparent references to determine viewports for rendering (one single canvas).
 * This is broken in combination with shadowmaps at the moment. See renderToViewport() method.
 *
 * @type {number}
 */
GZ3D.MULTIVIEW_RENDER_VIEWPORTS = 1;
/**
 * GZ3D.MULTIVIEW_RENDER_COPY2CANVAS renders views offscreen then copies into the view containers (multiple separate canvases).
 * @type {number}
 */
GZ3D.MULTIVIEW_RENDER_COPY2CANVAS = 2;

GZ3D.MultiView = function(gz3dScene, mainContainer)
{
    this.gz3dScene = gz3dScene;
    this.mainContainer = mainContainer;

    this.init();
};

GZ3D.MultiView.prototype.init = function()
{
    this.views = [];

    this.mainContainer.style.zIndex = 0;

    this.renderMethod = GZ3D.MULTIVIEW_RENDER_COPY2CANVAS;
    if (this.renderMethod === GZ3D.MULTIVIEW_RENDER_VIEWPORTS) {
        this.mainContainer.appendChild(this.gz3dScene.getDomElement());
    }

    this.createMainUserView();
};

GZ3D.MultiView.prototype.setCallbackCreateRenderContainer = function(callback)
{
    this.createRenderContainerCallback = callback;
};

/**
 * This creates the main view the user will interact with and should always be there.
 */
GZ3D.MultiView.prototype.createMainUserView = function()
{
    var displayParams = {
        left: '0px',
        top: '0px',
        width: '100%',
        height: '100%',
        adjustable: false
    };
    var cameraParams = {
        width: 960,
        height: 600,
        fov: 60,
        near: 0.1,
        far: 100
    };

    this.mainUserView = this.createView(GZ3D.MULTIVIEW_MAINVIEW_NAME, displayParams, cameraParams);

    return this.mainUserView;
};

/**
 *
 * @param name
 * @param displayParams {left, top, width, height, zIndex, adjustable}
 * @param cameraParams {width, height, fov, near, far}
 */
GZ3D.MultiView.prototype.createView = function(name, displayParams, cameraParams)
{
    if (angular.isDefined(this.getViewByName(name))) {
        console.error('GZ3D.MultiView.createView() - a view of that name already exists (' + name + ')');
        return undefined;
    }

    if (this.views.length >= this.MULTIVIEW_MAX_VIEW_COUNT) {
        console.warn('GZ3D.MultiView.createView() - creating new view will exceed MULTIVIEW_MAX_VIEW_COUNT(' + this.MULTIVIEW_MAX_VIEW_COUNT + '). This may cause z-ordering issues.');
    }

    var container = this.createViewContainer(displayParams, name);
    if (!angular.isDefined(container)) {
        return undefined;
    }

    // camera
    var camera = new THREE.PerspectiveCamera(
      cameraParams.fov,
      cameraParams.aspectRatio,
      cameraParams.near,
      cameraParams.far
    );
    camera.name = name;

    // assemble view
    var view = {
        name: name,
        active: true,
        container: container,
        camera: camera
    };

    this.views.push(view);
    this.mainContainer.appendChild(view.container);

    return view;
};

GZ3D.MultiView.prototype.createViewContainer = function(displayParams, name)
{
    if (!angular.isDefined(this.createRenderContainerCallback)) {
        console.error('GZ3D.MultiView.createViewContainer() - no callback for creating view reference container defined');
        return undefined;
    }

    // container div
    var viewContainer;
    if (name === GZ3D.MULTIVIEW_MAINVIEW_NAME) {
        viewContainer = document.createElement('div');
    } else {
        viewContainer = this.createRenderContainerCallback(displayParams.adjustable, name);
    }
    if (!angular.isDefined(viewContainer)) {
        console.error('GZ3D.MultiView.createViewContainer() - could not create view container via callback');
        return undefined;
    }


    // z-index
    var zIndexTop = parseInt(this.mainContainer.style.zIndex, 10) + this.views.length + 1;
    viewContainer.style.zIndex = angular.isDefined(displayParams.zIndex) ? displayParams.zIndex : zIndexTop;

    // positioning
    viewContainer.style.position = 'absolute';
    viewContainer.style.left = displayParams.left;
    viewContainer.style.top = displayParams.top;
    viewContainer.style.width = displayParams.width;
    viewContainer.style.height = displayParams.height;

    // We set 50px as a min-width for now and set the min height accordingly
    viewContainer.style.minWidth = '50px';
    var aspectRatio = parseInt(displayParams.height, 10) / parseInt(displayParams.width, 10);
    viewContainer.style.minHeight = Math.floor(50 * aspectRatio) + 'px';
    viewContainer.style.maxWidth = '100%';
    viewContainer.style.maxHeight = '100%';

    // Transparent view-container so that we can render viewport with one renderer in the same context
    // view-container is only taken as reference for viewport
    viewContainer.style.boxShadow = '0px 0px 0px 3px rgba(0,0,0,0.3)';
    viewContainer.style.borderRadius = '2px';
    viewContainer.style.background = 'rgba(0,0,0,0)';

    if (this.renderMethod === GZ3D.MULTIVIEW_RENDER_COPY2CANVAS) {
        // canvas
        viewContainer.canvas = document.createElement('canvas');
        viewContainer.appendChild( viewContainer.canvas );
        viewContainer.canvas.style.width = '100%';
        viewContainer.canvas.style.height = '100%';
    }

    return viewContainer;
};

GZ3D.MultiView.prototype.getViewByName = function(name)
{
    for (var i = 0; i < this.views.length; i = i+1) {
        if (this.views[i].name === name) {
            return this.views[i];
        }
    }

    return undefined;
};

GZ3D.MultiView.prototype.setViewVisibility = function(view, visible)
{
    view.active = visible;
    if (view.active) {
        view.container.style.visibility = 'visible';
    } else {
        view.container.style.visibility = 'hidden';
    }
};

GZ3D.MultiView.prototype.updateCamera = function(view)
{
    view.camera.aspect = view.container.clientWidth / view.container.clientHeight;
    view.camera.updateProjectionMatrix();
};

GZ3D.MultiView.prototype.getViewport = function(view)
{
    var viewport = {
        x: view.container.offsetLeft,
        y: this.mainContainer.clientHeight - view.container.clientHeight - view.container.offsetTop,
        w: view.container.clientWidth,
        h: view.container.clientHeight
    };

    return viewport;
};

GZ3D.MultiView.prototype.setWindowSize = function(width, height)
{

};

GZ3D.MultiView.prototype.renderViews = function()
{
    // sort views into rendering order
    this.views.sort(function(a, b) {
       return a.container.style.zIndex - b.container.style.zIndex;
    });

    for (var i = 0, l = this.views.length; i < l; i = i+1) {
        var view = this.views[i];

        if (view.active) {
            switch(this.renderMethod) {
                case GZ3D.MULTIVIEW_RENDER_VIEWPORTS:
                    this.renderToViewport(view);
                    break;
                case GZ3D.MULTIVIEW_RENDER_COPY2CANVAS:
                    this.renderAndCopyToCanvas(view);
                    break;
            }
        }
    }
};

/**
 * !IMPORTANT!
 * https://github.com/mrdoob/three.js/issues/3532
 * This is at the moment not a good idea, see issue above. ScissorTest will scramble shadowmaps.
 *
 * @param view
 */
GZ3D.MultiView.prototype.renderToViewport = function(view)
{
    this.updateCamera(view);  //TODO: better solution with resize callback, also adjust camera helper

    var webglRenderer = this.gz3dScene.renderer;

    var viewport = this.getViewport(view);
    webglRenderer.setViewport( viewport.x, viewport.y, viewport.w, viewport.h );
    webglRenderer.setScissor( viewport.x, viewport.y, viewport.w, viewport.h );
    webglRenderer.enableScissorTest ( true );

    if (this.gz3dScene.effectsEnabled) {
        this.gz3dScene.scene.overrideMaterial = this.depthMaterial;
        this.gz3dScene.renderer.render(this.gz3dScene.scene, view.camera, this.depthTarget);
        this.gz3dScene.scene.overrideMaterial = null;
        this.gz3dScene.composer.render();
    } else {
        webglRenderer.render(this.gz3dScene.scene, view.camera);
    }
};

GZ3D.MultiView.prototype.renderAndCopyToCanvas = function(view)
{
    this.updateCamera(view);  //TODO: better solution with resize callback, also adjust camera helper

    var webglRenderer = this.gz3dScene.renderer;

    var width = view.container.canvas.clientWidth;
    var height = view.container.canvas.clientHeight;
    view.container.canvas.width = width;
    view.container.canvas.height = height;

    if (webglRenderer.context.canvas.width < width) {
        webglRenderer.context.canvas.width = width;
    }
    if (webglRenderer.context.canvas.height < height) {
        webglRenderer.context.canvas.height = height;
    }
    webglRenderer.setViewport( 0, 0, width, height );

    if (this.gz3dScene.effectsEnabled) {
        this.gz3dScene.scene.overrideMaterial = this.depthMaterial;
        this.gz3dScene.renderer.render(this.gz3dScene.scene, view.camera, this.depthTarget);
        this.gz3dScene.scene.overrideMaterial = null;
        this.gz3dScene.composer.render();
    } else {
        webglRenderer.render(this.gz3dScene.scene, view.camera);
    }

    // copy rendered image over to view canvas
    var srcX = 0;
    var srcY = webglRenderer.context.canvas.height - height + 1;
    var dstX = 0;
    var dstY = 0;
    // no cleaner solution right now, should be coming though: https://github.com/mrdoob/three.js/pull/6723#issuecomment-134129027
    // this kills performance a little ...
    if ( srcX >= 0 && srcY >= 0 && width >= 1 && height >= 1 ) {
        view.container.canvas.getContext('2d').drawImage( webglRenderer.context.canvas,
            srcX, srcY, width, height,
            dstX, dstY, width, height );
    }
};

GZ3D.MultiView.prototype.showCameras = function(show)
{
    for (var i = 0; i < this.views.length; i = i+1) {
        if (this.views[i].type === 'camera') {
            this.setViewVisibility(this.views[i], show);
        }
    }
};