Home Reference Source

src/BasicGLProvider.ts

import GLProvider from './GLProvider';
import checkGLError from 'parsegraph-checkglerror';
import Color from 'parsegraph-color';
import Rect from 'parsegraph-rect';

let providerCount = 0;
export default class BasicGLProvider implements GLProvider {
  _shaders:{ [shaderName:string]: WebGLProgram};
  _id:string;
  _gl:WebGLRenderingContext;
  _container:HTMLDivElement;
  _canvas:HTMLCanvasElement;
  _backgroundColor:Color;
  _explicitWidth:number;
  _explicitHeight:number;

  constructor(id:string, background:Color) {
    if (id) {
      this._id = id;
    } else {
      this._id = "" + (++providerCount);
    }
    this._shaders = {};
    this._gl = null;

    if (background) {
      this._backgroundColor = background;
    } else {
      this._backgroundColor = new Color(0, 0, 0, 1);
    }

    this._container = document.createElement('div');

    // The 3D canvas that will be drawn to.
    this._canvas = document.createElement('canvas');
    /* if(WebGLDebugUtils) {
      this._canvas = WebGLDebugUtils.makeLostContextSimulatingCanvas(
      this._canvas);
    }*/
    this._canvas.addEventListener(
        'webglcontextlost',
        (event)=>{
          // console.log('Context lost');
          event.preventDefault();
          this.onContextChanged(true);
        },
        false,
    );
    this._canvas.addEventListener(
        'webglcontextrestored',
        ()=>{
          console.log('Context restored');
          this.onContextChanged(false);
        },
        false,
    );
    this._canvas.addEventListener(
        'contextmenu',
        (e)=>{
          e.preventDefault();
        },
        false,
    );
    this._canvas.style.display = 'block';

    this._container.appendChild(this._canvas);
    this._explicitWidth = null;
    this._explicitHeight = null;
  }

  onContextChanged(isLost:boolean):void {
    if (isLost) {
      const keys = [];
      for (const k in this._shaders) {
        if (Object.prototype.hasOwnProperty.call(this._shaders, k)) {
          keys.push(k);
        }
      }
      for (const i in keys) {
        if (Object.prototype.hasOwnProperty.call(keys, i)) {
          delete this._shaders[keys[i]];
        }
      }
    }
  }

  shaders():{ [shaderName:string]:WebGLProgram } {
    return this._shaders;
  }

  id():string {
    return this._id;
  }

  gl():WebGLRenderingContext {
    if (this._gl) {
      return this._gl;
    }
    // this._gl = this._canvas.getContext("webgl2", {
    // antialias:true
    // });
    if (this._gl) {
      // this.render = this.renderWebgl2;
      checkGLError(this._gl, 'WebGL2 creation');
      return this._gl;
    }
    this._gl = this._canvas.getContext('webgl');
    if (this._gl) {
      checkGLError(this._gl, 'WebGL creation');
      return this._gl;
    }
    throw new Error('GL context is not supported');
  };

  canvas():HTMLCanvasElement {
    return this._canvas;
  }

  getSize(sizeOut?:Rect) {
    sizeOut.setX(0);
    sizeOut.setY(0);
    sizeOut.setWidth(this.width());
    sizeOut.setHeight(this.height());
  };

  resize(w:number, h:number):void {
    this.container().style.width = typeof w === 'number' ? w + 'px' : w;
    if (arguments.length === 1) {
      h = w;
    }
    this.container().style.height = typeof h === 'number' ? h + 'px' : h;
  };

  setExplicitSize(w:number, h:number):void {
    this._explicitWidth = w;
    this._explicitHeight = h;
    this.resize(w, h);
  };

  getWidth():number  {
    return this._explicitWidth || this.container().clientWidth;
  };

  width():number {
    return this.getWidth();
  }

  getHeight():number {
    return this._explicitHeight || this.container().clientHeight;
  };
  height() {
    return this.getHeight();
  }

  /**
   * @return {HTMLElement} Returns the container that holds the canvas for this graph.
   */
  container():HTMLElement {
    return this._container;
  };

  setBackground(...args:Array<number>):void {
    if (args.length > 1) {
      return this.setBackground(new Color(...args));
    }
    this._backgroundColor = args[0];
  };

  /**
   * @return {Color} Retrieves the current background color.
   */
  backgroundColor():Color {
    return this._backgroundColor;
  };

  /**
   * @return {boolean} Returns whether the window has a nonzero client width and height.
   */
  canProject():boolean {
    const displayWidth = this.getWidth();
    const displayHeight = this.getHeight();
    return displayWidth != 0 && displayHeight != 0;
  };

  /**
   * Renders this provider's content, resizing the canvas to its container.
   *
   * The default implementation only sets the WebGL's clear color to the
   * background but does not clear the canvas.
   *
   * @return {boolean} true if this provider needs an additional call to complete rendering its content.
   * @throws if the scene cannot be projected to, as determined by canProject
   */
  render():boolean {
    this._container.style.backgroundColor = this._backgroundColor.asRGB();

    const gl = this.gl();
    if (this.gl().isContextLost()) {
      return false;
    }
    // console.log("Rendering window");
    if (!this.canProject()) {
      throw new Error(
          'Refusing to render to an unprojectable window.' +
          ' Use canProject() to handle, and parent this' +
          ' window\'s container to fix.',
      );
    }

    // Lookup the size the browser is displaying the canvas.
    const displayWidth = this.width();
    const displayHeight = this.height();
    // Check if the canvas is not the same size.
    if (
      this.canvas().width != displayWidth ||
      this.canvas().height != displayHeight
    ) {
      // Make the canvas the same size
      this.canvas().width = displayWidth;
      this.canvas().height = displayHeight;
    }

    const bg = this.backgroundColor();
    gl.clearColor(bg.r(), bg.g(), bg.b(), bg.a());

    return false;
  }
}