C3DL Canvas2DPresenter Proposal
giberson | 16 September, 2008 | 20:36
This proposal addresses some of the issues brought up in Andrew’s post about providing a text API for the C3DL framework with out over complicating parametrization or usage. The nature of how text is accomplished is inherently related to a 2D drawing API, so the proposal tackles both issues. The suggested method for providing an easy to use, fast interface for 2D drawing and text is to provide an extended canvas element with a built in “to GL Texture” method. A method that which when called, converts the content currently on the 2D context into a GL Texture that can be applied to any model or primitive. From there, we can add any additional helpers to speed up and improve efficiency of drawing or writing text–as demonstrated by the TextHelper function which provides a wrapper to the above stated canvas2DPresenter designed to simply drawing text. Use cases for the proposed methods are show below the code for the function. I apologize for the inconsistency in thoroughness, as some parts of the code have been left as ambiguous pseudo code — ie present() which actually creates the GL Texture. This was left in its condition as the code is available in other areas of the source code.
Changes to the Scene object:
/**
* Helper function that takes a any texture (but especially a texture from canvas2DPresenter.present()) and applies it to a 3D plane.
*
* @return PlanePrimitive
*/
this.addTexturePlane(glTexture) {
// create a 3d plane
// .. var plane = new PlanePrimitive();
// apply glTexture to plane
// .. plane.addTexture(glTexture);
// add plane to scene
this.addObjectToScene(plane);
// return reference to plane so user can adjust position/orientation if desired
return plane;
}
/**
* Helper Function that takes any texture (but especially a texture from canvas2dPresneter.present()) and applies it to a given model.
* However, ideally, this method should be obsolete as any model should incorporate a method that applies a given texture to itself.
*/
this.addTextureToModel = function(glTexture, model)
{
model.applyTexture(glTexture);
}
/**
Create a 2D canvas for drawing text and other stuff. Keep a reference to it.
*/
this.create2Dcanvas = function(width, height)
{
var newCanvas = document.createElement('canvas');
newCanvas.id = 'changemetorandomstring';
newCanvas.width = width;
newCanvas.height = height;
cvs.appendChild(newCanvas);
canvas2Dlist.push(newCanvas);
return newCanvas;
}
/**
* returns a new canvas element with an additional function "present" which generates a glTexture to be applied to models/primitives.
*
* @return CanvasElement
*/
this.create2DPresenter = function () {
var newcanvas = document.createElement('canvas');
/**
* Takes current 2d canvas context and turns it into a glTexture
*
* @return glTexture
*/
newcanvas.present = function () {
// psuedo code
var texture = createGlTexture(this);
return texture;
}
// return extended canvas element
return newcanvas;
}
/**
* TextHelper is a wrapper to the extended canvas we get from create2DPresenter that has been specialized for text writing.
*
* @return TextHelper
*/
this.getTextHelper = function () {
// return an object TextHelper
return function () {
// set some defaults so it works with out any setup calls
this.fontStyle = "12pt Sans-Serif";
this.width = 0; // default to auto
this.height = 0; // default to auto
this.fillStyle = '#000000'; // supports any valid canvas fillStyle (grad, rgb, rgba, #hex)
this.globalAlpha = 1.0; // float: 1 = opaque, 0 = transparent
this.backgroundFillStyle = null; // default to no background
this.drawtext = function (text) {
/**
* Get ourselves a cavas2dPresenter that lets us have access to canvas's built in 2d api (including text) with a built in toGlCanvas method.
*/
this.canvas = Scene.create2DPresenter();
this.context = this.canvas.getContext("2d");
var w, h, lw, lh;
var fsize;
lw = this.width;
lh = this.height;
// if width or height was set manually, we dont want to reset
// to autosize after drawing text, so we save current values and
// refer to them to check if we reset to 0 (autosize) or leave it as is.
// calculate width/height
var tempSpan = document.createElement('span');
var tempText = document.createTextNode(text);
tempSpan.appendChild(tempText);
tempSpan.style.font = this.fontStyle;
// get the font-size used
fsize = parseInt(tempSpan.style.fontSize);
tempSpan.style.visibility = 'hidden'; // dont show element on page
tempSpan.style.padding = '0';
document.body.appendChild(tempSpan);
// chose set size, or html size, or 1
w = this.width || tempSpan.offsetWidth || 1; // no zeros allowed!
h = this.height || tempSpan.offsetHeight || 1; // no zeros allowed!
// remove junk html tag
document.body.removeChild(tempSpan);
if(!lw) this.width = 0; // reset for autosize
if(!lh) this.height = 0; // reset for autosize
// round w/h to power of 2
// ..
// if background color was specified
this.canvas.width = w;
this.canvas.height = h;
if(this.backgroundFillStyle) {
this.context.fillStyle = this.backgroundFillStyle;
this.context.fillRect(0, 0, w, h);
}
// apply helper settings to context
this.context.fillStyle = this.fillStyle;
this.context.globalAlpha = this.globalAlpha;
this.context.width = w;
this.context.height = h;
// move to draw origin
// height hack to center verticaly & account for span padding
// works best on pt values for font size
this.context.translate(0, h - ((h-fsize)/2));
// render text using font style
this.context.mozTextStyle = this.fontStyle;
this.context.mozDrawText(text);
this.canvas.focus();
}
/**
* Accessor to our canvas2DPresenter present method
* @return
*/
this.present = function () {
return this.canvas.present();
}
}();
}
The suggested use then, to either draw in 2D or draw text, is to ask the API for a canvas2DPresenter, work with it as you would any canvas element for graphic drawing (see Mozilla Developer Center on Drawing with the Canvas) and then call the extended present() to generate a GL Texture for you. Once you have the texture, you can do anything you want with it, from applying it to a model, or a primitive shape.
Example usage:
var c2dp = Scene.create2DPresenter();
var ctx = c2dp.getContext('2D');
c2dp.width = 150;
c2dp.height = 150;
ctx.beginPath();
ctx.arc(75,75,50,0,Math.PI*2,true); // Outer circle
ctx.moveTo(110,75);
ctx.arc(75,75,35,0,Math.PI,false); // Mouth (clockwise)
ctx.moveTo(65,65);
ctx.arc(60,65,5,0,Math.PI*2,true); // Left eye
ctx.moveTo(95,65);
ctx.arc(90,65,5,0,Math.PI*2,true); // Right eye
ctx.stroke();
var texture = c2dp.present();
var model = new PrimitivePlane();
model.applyTexture(texture);
Scene.addModel(model);
Example using a specialized helper
var th = Scene.textHelper();
th.drawText("Hello World");
var texture = th.present();
var model = new PrimitivePlane();
model.applyTexture(texture);
Scene.addModel(model);
// more advanced usage of helper
var th3 = new textHelper();
var lingrad = th3.context.createLinearGradient(0,0,0,150);
lingrad.addColorStop(0, '#00ABEB');
lingrad.addColorStop(0.5, '#fff');
var img = document.getElementById('pattern');
var ptrn = th3.context.createPattern(img, 'repeat');
th3.fillStyle = ptrn;
th3.fontStyle = "40pt Arial Black";
th3.drawtext("Hello World!");
var texture = th3.present();
var model = new PrimitivePlane();
model.applyTexture(texture);
Scene.addModel(model);
My use cases employ a fictional PrimitivePlane class that ideally creates a 3D plane facing the viewer and applies its texture stretched onto the plane.
In any case I hope this post clarified my ideas for the canvas2D drawing and text support implementation. 
Vlad was in town and he demo'd some of the
Cathy Leung | 18 September, 2008 | 14:27Vlad was in town and he demo’d some of the other stuff he was doing with 2D canvas in combination with other technology. I think this could have even more potential than we had initially planned.
Thanks for posting this Jeremy
Andrew Smith | 18 September, 2008 | 18:19Thanks for posting this Jeremy