Web Dev Jenga

Back in the distant past, some very smart people wrote a paper about the past, present and future of user interface software tools. In it they discuss the idea of a tool having a low threshold to learning and a high ceiling of capability. Inevitably, they say, we build tools that start with low threshold and slowly add capability until the high ceiling of capability is reached. Unfortunately, this (almost?) always means that the low threshold to learn is lost amid all the added complexity.

I have seen this happen with FORTRAN, C/C++, Java, JavaScript, and HTML. It’s a pain, but I think it’s inevitable. Interestingly, I think that if you keep things hard, they paradoxically stay simple. The difference between GL, OpenGL and WebGL is really not all that different. There was a big change with the introduction of shaders, but that’s one major shift in something like 20 years.

Now it’s happening to tools. It’s easy to write a quick tool that handles some aspect of development. If it has low threshold for learning and good utility, then it gets picked up and suddenly we have a new way of doing the same old thing. Maybe it’s better, but often it’s just different. The unfortunate result is now we have stacks of frameworks, languages and tools that we don’t understand well. The normal scenario is:

  • Have a confounding problem.
  • Ask Google/StackOverflow about it.
  • Try the responses that seem best until something works
  • Move on to the next confounding problem

As a professional developer, I only have so much time to drill down into things to obtain deep understanding. Many times, you have to trust. It’s faith-based coding, and it really reminds me of building a tower from Jenga blocks. We add and subtract things all the time. It’s a miracle that the thing stays up as often as they do.

My adventures with ‘thrangularJS’ has settled down to the point where I’m building reasonably complex pieces that need to be assembled in a particular order. The watcher in IntelliJ will compile TypeScript to JavaScript, but just in the context of that one file. If a change has been made in a TypeScript file, chances are that it will have to ramify through the project. This is one of those things that has to happen using compilers that doesn’t happen with interpreters.

So, I start to look at what the web development community is doing with dependency management these days. The answer seemed to be Grunt with a typescript task. This worked, but it compiled all the files even if only one needed to be changed. What I really wanted was a makefile. But the makefile needed to be triggered by a watcher. It turns out that there has been some thought on this, and it works in a clean way.

Since I’m on a windows box, I’m using GnuMake. It’s extremely stable, last updated in 2006 (when iPhones were introduced, I think). I’m also using Grunt, installed by npm. Not as stable as make, but it’s never done me wrong. Following the install of make, and Grunt the components have to be set up in the project directory:

npm init
npm install grunt --save-dev
npm install grunt-exec --save-dev
npm install grunt-contrib-watch --save-dev

Then we need a makefile. This is mine, and there are probably better ways to do it. But it’s clear (you can learn everything you ever wanted to know about make here):

CC = tsc 
CFLAGS=  --declaration --noImplicitAny --target ES5 --sourcemap

build: modules/AppMain.js

modules/AppMain.js : directives/WGLA2_directives.js controllers/WGLA1_controller.js \
   modules/AppMain.ts
   $(CC) $(CFLAGS) modules/AppMain.ts

classes/WebGlInterfaces.js : classes/WebGlInterfaces.ts
   $(CC) $(CFLAGS) classes/WebGlInterfaces.ts

classes/WebGlCanvasClasses.js : classes/WebGlInterfaces.js \
           classes/WebGlCanvasClasses.ts
   $(CC) $(CFLAGS) classes/WebGlCanvasClasses.ts

classes/WebGlComponentClasses.js : classes/WebGlInterfaces.js  \
   classes/WebGlComponentClasses.ts
   $(CC) $(CFLAGS) classes/WebGlComponentClasses.ts

controllers/WGLA1_controller.js : classes/WebGlInterfaces.js classes/WebGlComponentClasses.js classes/WebGlCanvasClasses.js \
   controllers/WGLA1_controller.ts
   $(CC) $(CFLAGS) controllers/WGLA1_controller.ts

directives/WGLA2_directives.js : classes/WebGlInterfaces.js classes/WebGlComponentClasses.js classes/WebGlCanvasClasses.js \
   directives/WGLA2_directives.ts
   $(CC) $(CFLAGS) directives/WGLA2_directives.ts

Last, we need a GruntFile.js to knit it all together:

module.exports = function (grunt) {
    //grunt.loadNpmTasks('grunt-ts');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-exec');

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        exec: {
            make: {
                command: 'make build'
            }
        },
        watch: {
            files: ['**/*.ts', '!**/*.d.ts'],
            tasks:['exec:make'] //tasks: ['ts']
        }

    });

    grunt.registerTask('default', ['watch']);

}

And that’s it. Make is a little tricky to learn (high threshold), but has tremendous power and flexibility (high ceiling). Grunt is kept simple and obvious and can be swapped out easily if something better comes along. Other tasks can be added to make such as uglify, test, deploy, pretty much whatever you want. And it’s guaranteed to go in the right order.

It’s a good block to keep in your Jenga tower.

Advertisements

Typescript Headers and Browser Quirks.

It’s been a pretty good week. The WebGl graphics in the directive are connected to the user functionality in the controller, I have tooltips running, and even have raycasting working, so the 2D items appear in the overlay plane above the 3D object:

AngularJSWebGl

 

The big problem that I needed to chase down was circular references in the typescript files. TypeScript uses reference path comments to tell the compiler where to look for type and structure information. Below is the information that I need for the angular module that creates the above application

/// <reference path="../../definitelytyped/angularjs/angular.d.ts" />
/// <reference path="../controllers/WGLA1_controller.d.ts" />
/// <reference path="../directives/WGLA2_directives.d.ts" />

In this case note that there is a path for controller and directive code. In this case, pointing directly to the code file is fine, but I have a case where my WebGLCanvas has to know about WebGLComponents and vice versa. The typescript compiler (tsc) doesn’t like that, and barfs a ‘duplicate definition’ error. At this point, I was wondering why TypeScript doesn’t have a #pragma once directive that would prevent this sort of thing, or even an #ifndef capability. It’s a preprocessor after all, and it should be able to do this. Easily.

But TypeScript does have interfaces. So in this case, I put interfaces for both modules in a single file, which I could then refer to in the downstream files and avoid the circular dependency issue.

The other issue was browsers not playing well together. I kind of thought that we had gotten beyond that, but no.

I develop with IntelliJ, and their debugger plays the best with Chrome, so that’s my default browser. At the end of the day, I’ll check to see that everything runs in IE and FF. And today FF was not playing well, and the tooltips I worked so hard on were not showing. WTF, I say.

If you look at the screenshot above, you’ll see the white text at the upper left. That’s my real-time logging (it’s pointless to write to the console at 30hz). And I could see that the unit mouse values were NaN. Again, WTF.

Now FF has my favorite debugger, and it even works (generally) with typescript, as long as you have all the .ts and .map files alongside your .js files. So I stepped into the code at the handleMouseEvents() method in WebGlCanvasClasses and started looking.

I’ve been getting the mouse coordinate from MouseEvent.offsetX. That turns out be used by IE and Chrome, but not FF. so I changed

var sx:number = ev.offsetX; to var sx:number = ev.offsetX | ev.layerX;

All fixed, I thought. But wait! There’s more! It turns out that IE has both of these values, and they don’t mean the same thing. so in the end I wind up with the following monkeypatch:

handleMouseEvents = (ev:MouseEvent):void => {
    var sx = ev.layerX;
    var sy = ev.layerY;

    if(ev.offsetX < sx){
        sx = ev.offsetX;
        sy = ev.offsetY;
    }
}

This works because the smaller value has to be the coordinate of the mouse on the div I’m interested, since all screen coordinates increase from 0. So it’s quick, but jeez.

Text Overlay for ThreeJS with Angular and TypeScript

This is not my first foray into WebGL. The last time I was working on a 3D charting API using the YUI framework, which could do things like this:

Personally, I can’t do any debugging at 30fps without having a live list of debugging text that I can watch. So almost immediately after the ‘hello world’ spinning cube, I set that up. And now I’m in the middle of moving my framework over to Angular and TypeScript. For the most part, I like how things are working out, but when it comes to lining up a transparent text plane over a threeJS element, YUI gives a lot more support than Angular. The following is so brute-force that I feel like I must be doing it wrong (And there may be a jquery-lite pattern, but after trying a few StackOverflow suggestions that didn’t work), I went with the following.

First, this all happens in the directive. I try to keep that pretty clean:

// The webGL directive. Instantiates a webGlBase-derived class for each scope
export class ngWebgl {
   private myDirective:ng.IDirective;

   constructor() {
      this.myDirective = null;
   }

   private linkFn = (scope:any, element:any, attrs:any) => {
      //var rb:WebGLBaseClasses.RootBase = new WebGLBaseClasses.RootBase(scope, element, attrs);
      var rb:WebGlRoot = new WebGlRoot(scope, element, attrs);
      scope.webGlBase = rb;
      var initObj:any = {
         showStage: true
      };
      rb.initializer(initObj);
      rb.animate();
   };

   public ctor = ():ng.IDirective => {
      if (!this.myDirective) {
         this.myDirective = {
            restrict: 'AE',
            scope: {
               'width': '=',
               'height': '=',
            },
            link: this.linkFn
         }
      }
      return this.myDirective;
   }
}

The interface with all the webGL code happens in the linkFn() method. Note that the WebGLRoot class gets assigned to the scope. This allows for multiple canvases.

WebGLRoot is a class that inherits from WebGLBaseClasses.CanvasBase, which is one of the two big classes I’m currently working on. It’s mostly there to make sure that everything inherits correctly and I don’t break that without noticing:-)

Within WebGLBaseClasses.CanvasBase is the initializer() method. That in turn calls the methods that set up the WebGL and the ‘stage’ that I want to interact with. The part we’re interested for our overlay plane is the overlay canvas’ context. You’ll needthat  to draw into later:

overlayContext:CanvasRenderingContext2D;

This is set up along with the renderer. Interesting bits are in bold:

this.renderer = new THREE.WebGLRenderer({antialias: true});
this.renderer.setClearColor(this.blackColor, 1);
this.renderer.setSize(this.contW, this.contH);

// element is provided by the angular directive
this.renderer.domElement.setAttribute("class", "glContainer");
this.myElements[0].appendChild(this.renderer.domElement);

var overlayElement:HTMLCanvasElement = document.createElement("canvas");
overlayElement.setAttribute("class", "overlayContainer");
this.myElements[0].appendChild(overlayElement);
this.overlayContext = this.overlayElement.getContext("2d");

The first thing to notice is that I have to add CSS classes to the elements. These are pretty simple, just setting absolute and Z-index:

.glContainer {
    position: absolute;
    z-index: 0;
}

.overlayContainer {
    position: absolute;
    z-index: 1;
}

That forces everything to have the same upper left corner. And once that problem was solved, drawing is pretty straightforward. The way I have things set up is with an animate method that uses requestAnimationFrame() wich then calls the render() method. That draws the 3D, and then hands the 2D context off to the draw2D() method:

draw2D = (ctx:CanvasRenderingContext2D):void =>{
   var canvas:HTMLCanvasElement = ctx.canvas;
   canvas.width = this.contW;
   canvas.height = this.contH;
   ctx.clearRect(0, 0, canvas.width, canvas.height);
   ctx.font = '12px "Times New Roman"';
   ctx.fillStyle = 'rgba( 255, 255, 255, 1)'; // Set the letter color
   ctx.fillText("Hello, framecount: "+this.frameCount, 10, 20);
};

render = ():void => {
   // do the 3D rendering
   this.camera.lookAt(this.scene.position);
   this.renderer.render(this.scene, this.camera);
   this.frameCount++;

   this.draw2D(this.overlayContext);
};

I’m supplying links to to the running code and directives, but please bear in mind that this is in-process development and not an minimal application for clarity.