define("dojox/gfx3d/object", [ "dojo/_base/array", "dojo/_base/declare", "dojo/_base/lang", "dojox/gfx", "dojox/gfx/matrix", "./_base", "./scheduler", "./gradient", "./vector", "./matrix", "./lighting" /*===== , "dojox/gfx/shape" =====*/ // gfx.Surface ], function(arrayUtil,declare,lang,gfx,matrixUtil2d,gfx3d,schedulerExtensions,Gradient,VectorUtil,matrixUtil,lightUtil){ var scheduler = schedulerExtensions.scheduler; // FIXME: why the "out" var here? var out = function(o, x){ if(arguments.length > 1){ // console.debug("debug:", o); o = x; } var e = {}; for(var i in o){ if(i in e){ continue; } // console.debug("debug:", i, typeof o[i], o[i]); } }; declare("dojox.gfx3d.Object", null, { constructor: function(){ // summary: // a Object object, which knows how to map // 3D objects to 2D shapes. // object: Object // an abstract Object object // (see dojox.gfx3d.defaultEdges, // dojox.gfx3d.defaultTriangles, // dojox.gfx3d.defaultQuads // dojox.gfx3d.defaultOrbit // dojox.gfx3d.defaultCube // or dojox.gfx3d.defaultCylinder) this.object = null; // matrix: dojox.gfx3d.matrix // world transform this.matrix = null; // cache: // buffer for intermediate result, used late for draw() this.cache = null; // renderer: // a reference for the Viewport this.renderer = null; // parent: // a reference for parent, Scene or Viewport object this.parent = null; // strokeStyle: Object // a stroke object this.strokeStyle = null; // fillStyle: Object // a fill object or texture object this.fillStyle = null; // shape: dojox.gfx.Shape // an underlying 2D shape this.shape = null; }, setObject: function(newObject){ // summary: // sets a Object object // object: Object // an abstract Object object // (see dojox.gfx3d.defaultEdges, // dojox.gfx3d.defaultTriangles, // dojox.gfx3d.defaultQuads // dojox.gfx3d.defaultOrbit // dojox.gfx3d.defaultCube // or dojox.gfx3d.defaultCylinder) this.object = gfx.makeParameters(this.object, newObject); return this; }, setTransform: function(matrix){ // summary: // sets a transformation matrix // matrix: dojox.gfx3d.matrix.Matrix // a matrix or a matrix-like object // (see an argument of dojox.gfx3d.matrix.Matrix // constructor for a list of acceptable arguments) this.matrix = matrixUtil.clone(matrix ? matrixUtil.normalize(matrix) : gfx3d.identity, true); return this; // self }, // apply left & right transformation applyRightTransform: function(matrix){ // summary: // multiplies the existing matrix with an argument on right side // (this.matrix * matrix) // matrix: dojox.gfx3d.matrix.Matrix // a matrix or a matrix-like object // (see an argument of dojox.gfx.matrix.Matrix // constructor for a list of acceptable arguments) return matrix ? this.setTransform([this.matrix, matrix]) : this; // self }, applyLeftTransform: function(matrix){ // summary: // multiplies the existing matrix with an argument on left side // (matrix * this.matrix) // matrix: dojox.gfx3d.matrix.Matrix // a matrix or a matrix-like object // (see an argument of dojox.gfx.matrix.Matrix // constructor for a list of acceptable arguments) return matrix ? this.setTransform([matrix, this.matrix]) : this; // self }, applyTransform: function(matrix){ // summary: // a shortcut for dojox.gfx.Shape.applyRightTransform // matrix: dojox.gfx3d.matrix.Matrix // a matrix or a matrix-like object // (see an argument of dojox.gfx.matrix.Matrix // constructor for a list of acceptable arguments) return matrix ? this.setTransform([this.matrix, matrix]) : this; // self }, setFill: function(fill){ // summary: // sets a fill object // (the default implementation is to delegate to // the underlying 2D shape). // fill: Object // a fill object // (see dojox.gfx.defaultLinearGradient, // dojox.gfx.defaultRadialGradient, // dojox.gfx.defaultPattern, // dojo.Color // or dojox.gfx.MODEL) this.fillStyle = fill; return this; }, setStroke: function(stroke){ // summary: // sets a stroke object // (the default implementation simply ignores it) // stroke: Object // a stroke object // (see dojox.gfx.defaultStroke) this.strokeStyle = stroke; return this; }, toStdFill: function(lighting, normal){ return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle; }, invalidate: function(){ this.renderer.addTodo(this); }, destroy: function(){ if(this.shape){ var p = this.shape.getParent(); if(p){ p.remove(this.shape); } this.shape = null; } }, // All the 3D objects need to override the following virtual functions: // render, getZOrder, getOutline, draw, redraw if necessary. render: function(camera){ throw "Pure virtual function, not implemented"; }, draw: function(lighting){ throw "Pure virtual function, not implemented"; }, getZOrder: function(){ return 0; }, getOutline: function(){ return null; } }); declare("dojox.gfx3d.Scene", gfx3d.Object, { // summary: // the Scene is just a container. // note: we have the following assumption: // all objects in the Scene are not overlapped with other objects // outside of the scene. constructor: function(){ // summary: // a container of other 3D objects this.objects= []; this.todos = []; this.schedule = scheduler.zOrder; this._draw = gfx3d.drawer.conservative; }, setFill: function(fill){ this.fillStyle = fill; arrayUtil.forEach(this.objects, function(item){ item.setFill(fill); }); return this; }, setStroke: function(stroke){ this.strokeStyle = stroke; arrayUtil.forEach(this.objects, function(item){ item.setStroke(stroke); }); return this; }, render: function(camera, deep){ var m = matrixUtil.multiply(camera, this.matrix); if(deep){ this.todos = this.objects; } arrayUtil.forEach(this.todos, function(item){ item.render(m, deep); }); }, draw: function(lighting){ this.objects = this.schedule(this.objects); this._draw(this.todos, this.objects, this.renderer); }, addTodo: function(newObject){ // FIXME: use indexOf? if(arrayUtil.every(this.todos, function(item){ return item != newObject; })){ this.todos.push(newObject); this.invalidate(); } }, invalidate: function(){ this.parent.addTodo(this); }, getZOrder: function(){ var zOrder = 0; arrayUtil.forEach(this.objects, function(item){ zOrder += item.getZOrder(); }); return (this.objects.length > 1) ? zOrder / this.objects.length : 0; } }); declare("dojox.gfx3d.Edges", gfx3d.Object, { constructor: function(){ // summary: // a generic edge in 3D viewport this.object = lang.clone(gfx3d.defaultEdges); }, setObject: function(/*Points[]|Object*/ newObject, /*String?*/ style){ // summary: // setup the object this.object = gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject); return this; }, getZOrder: function(){ var zOrder = 0; arrayUtil.forEach(this.cache, function(item){ zOrder += item.z;} ); return (this.cache.length > 1) ? zOrder / this.cache.length : 0; }, render: function(camera){ var m = matrixUtil.multiply(camera, this.matrix); this.cache = arrayUtil.map(this.object.points, function(item){ return matrixUtil.multiplyPoint(m, item); }); }, draw: function(){ var c = this.cache; if(this.shape){ this.shape.setShape("") }else{ this.shape = this.renderer.createPath(); } var p = this.shape.setAbsoluteMode("absolute"); if(this.object.style == "strip" || this.object.style == "loop"){ p.moveTo(c[0].x, c[0].y); arrayUtil.forEach(c.slice(1), function(item){ p.lineTo(item.x, item.y); }); if(this.object.style == "loop"){ p.closePath(); } }else{ for(var i = 0; i < this.cache.length; ){ p.moveTo(c[i].x, c[i].y); i ++; p.lineTo(c[i].x, c[i].y); i ++; } } // FIXME: doe setFill make sense here? p.setStroke(this.strokeStyle); } }); declare("dojox.gfx3d.Orbit", gfx3d.Object, { constructor: function(){ // summary: // a generic edge in 3D viewport this.object = lang.clone(gfx3d.defaultOrbit); }, render: function(camera){ var m = matrixUtil.multiply(camera, this.matrix); var angles = [0, Math.PI/4, Math.PI/3]; var center = matrixUtil.multiplyPoint(m, this.object.center); var marks = arrayUtil.map(angles, function(item){ return {x: this.center.x + this.radius * Math.cos(item), y: this.center.y + this.radius * Math.sin(item), z: this.center.z}; }, this.object); marks = arrayUtil.map(marks, function(item){ return matrixUtil.multiplyPoint(m, item); }); var normal = VectorUtil.normalize(marks); marks = arrayUtil.map(marks, function(item){ return VectorUtil.substract(item, center); }); // Use the algorithm here: // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/ // After we normalize the marks, the equation is: // a x^2 + 2b xy + cy^2 + f = 0: let a = 1 // so the final equation is: // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]' var A = { xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1, yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1, zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1, dx: 0, dy: 0, dz: 0 }; var B = arrayUtil.map(marks, function(item){ return -Math.pow(item.x, 2); }); // X is 2b, c, f var X = matrixUtil.multiplyPoint(matrixUtil.invert(A),B[0], B[1], B[2]); var theta = Math.atan2(X.x, 1 - X.y) / 2; // rotate the marks back to the canonical form var probes = arrayUtil.map(marks, function(item){ return matrixUtil2d.multiplyPoint(matrixUtil2d.rotate(-theta), item.x, item.y); }); // we are solving the equation: Ax = b // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]' // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) ); // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) ); var a = Math.pow(probes[0].x, 2); var b = Math.pow(probes[0].y, 2); var c = Math.pow(probes[1].x, 2); var d = Math.pow(probes[1].y, 2); // the invert matrix is // 1/(ad -bc) [ d, -b; -c, a]; var rx = Math.sqrt( (a*d - b*c)/ (d-b) ); var ry = Math.sqrt( (a*d - b*c)/ (a-c) ); this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal}; }, draw: function(lighting){ if(this.shape){ this.shape.setShape(this.cache); } else { this.shape = this.renderer.createEllipse(this.cache); } this.shape.applyTransform(matrixUtil2d.rotateAt(this.cache.theta, this.cache.cx, this.cache.cy)) .setStroke(this.strokeStyle) .setFill(this.toStdFill(lighting, this.cache.normal)); } }); declare("dojox.gfx3d.Path3d", gfx3d.Object, { // This object is still very immature ! constructor: function(){ // summary: // a generic line // (this is a helper object, which is defined for convenience) this.object = lang.clone(gfx3d.defaultPath3d); this.segments = []; this.absolute = true; this.last = {}; this.path = ""; }, _collectArgs: function(array, args){ // summary: // converts an array of arguments to plain numeric values // array: Array // an output argument (array of numbers) // args: Array // an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them) for(var i = 0; i < args.length; ++i){ var t = args[i]; if(typeof(t) == "boolean"){ array.push(t ? 1 : 0); }else if(typeof(t) == "number"){ array.push(t); }else if(t instanceof Array){ this._collectArgs(array, t); }else if("x" in t && "y" in t){ array.push(t.x); array.push(t.y); } } }, // a dictionary, which maps segment type codes to a number of their argemnts _validSegments: {m: 3, l: 3, z: 0}, _pushSegment: function(action, args){ // summary: // adds a segment // action: String // valid SVG code for a segment's type // args: Array // a list of parameters for this segment var group = this._validSegments[action.toLowerCase()], segment; if(typeof(group) == "number"){ if(group){ if(args.length >= group){ segment = {action: action, args: args.slice(0, args.length - args.length % group)}; this.segments.push(segment); } }else{ segment = {action: action, args: []}; this.segments.push(segment); } } }, moveTo: function(){ // summary: // forms a move segment var args = []; this._collectArgs(args, arguments); this._pushSegment(this.absolute ? "M" : "m", args); return this; // self }, lineTo: function(){ // summary: // forms a line segment var args = []; this._collectArgs(args, arguments); this._pushSegment(this.absolute ? "L" : "l", args); return this; // self }, closePath: function(){ // summary: // closes a path this._pushSegment("Z", []); return this; // self }, render: function(camera){ // TODO: we need to get the ancestors' matrix var m = matrixUtil.multiply(camera, this.matrix); // iterate all the segments and convert them to 2D canvas // TODO consider the relative mode var path = ""; var _validSegments = this._validSegments; arrayUtil.forEach(this.segments, function(item){ path += item.action; for(var i = 0; i < item.args.length; i+= _validSegments[item.action.toLowerCase()] ){ var pt = matrixUtil.multiplyPoint(m, item.args[i], item.args[i+1], item.args[i+2]) path += " " + pt.x + " " + pt.y; } }); this.cache = path; }, _draw: function(){ return this.parent.createPath(this.cache); } }); declare("dojox.gfx3d.Triangles", gfx3d.Object, { constructor: function(){ // summary: // a generic triangle // (this is a helper object, which is defined for convenience) this.object = lang.clone(gfx3d.defaultTriangles); }, setObject: function(/*Points[]|Object*/ newObject, /*String?*/ style){ // summary: // setup the object if(newObject instanceof Array){ this.object = gfx.makeParameters(this.object, { points: newObject, style: style } ); } else { this.object = gfx.makeParameters(this.object, newObject); } return this; }, render: function(camera){ var m = matrixUtil.multiply(camera, this.matrix); var c = arrayUtil.map(this.object.points, function(item){ return matrixUtil.multiplyPoint(m, item); }); this.cache = []; var pool = c.slice(0, 2); var center = c[0]; if(this.object.style == "strip"){ arrayUtil.forEach(c.slice(2), function(item){ pool.push(item); pool.push(pool[0]); this.cache.push(pool); pool = pool.slice(1, 3); }, this); } else if(this.object.style == "fan"){ arrayUtil.forEach(c.slice(2), function(item){ pool.push(item); pool.push(center); this.cache.push(pool); pool = [center, item]; }, this); } else { for(var i = 0; i < c.length; ){ this.cache.push( [ c[i], c[i+1], c[i+2], c[i] ]); i += 3; } } }, draw: function(lighting){ // use the BSP to schedule this.cache = scheduler.bsp(this.cache, function(it){ return it; }); if(this.shape){ this.shape.clear(); } else { this.shape = this.renderer.createGroup(); } arrayUtil.forEach(this.cache, function(item){ this.shape.createPolyline(item) .setStroke(this.strokeStyle) .setFill(this.toStdFill(lighting, VectorUtil.normalize(item))); }, this); }, getZOrder: function(){ var zOrder = 0; arrayUtil.forEach(this.cache, function(item){ zOrder += (item[0].z + item[1].z + item[2].z) / 3; }); return (this.cache.length > 1) ? zOrder / this.cache.length : 0; } }); declare("dojox.gfx3d.Quads", gfx3d.Object, { constructor: function(){ // summary: // a generic quad // (this is a helper object, which is defined for convenience) this.object = lang.clone(gfx3d.defaultQuads); }, setObject: function(/*Points[]|Object*/ newObject, /*String?*/ style){ // summary: // setup the object this.object = gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject ); return this; }, render: function(camera){ var m = matrixUtil.multiply(camera, this.matrix), i; var c = arrayUtil.map(this.object.points, function(item){ return matrixUtil.multiplyPoint(m, item); }); this.cache = []; if(this.object.style == "strip"){ var pool = c.slice(0, 2); for(i = 2; i < c.length; ){ pool = pool.concat( [ c[i], c[i+1], pool[0] ] ); this.cache.push(pool); pool = pool.slice(2,4); i += 2; } }else{ for(i = 0; i < c.length; ){ this.cache.push( [c[i], c[i+1], c[i+2], c[i+3], c[i] ] ); i += 4; } } }, draw: function(lighting){ // use the BSP to schedule this.cache = gfx3d.scheduler.bsp(this.cache, function(it){ return it; }); if(this.shape){ this.shape.clear(); }else{ this.shape = this.renderer.createGroup(); } // using naive iteration to speed things up a bit by avoiding function call overhead for(var x=0; x 1) ? zOrder / this.cache.length : 0; } }); declare("dojox.gfx3d.Polygon", gfx3d.Object, { constructor: function(){ // summary: // a generic polygon // (this is a helper object, which is defined for convenience) this.object = lang.clone(gfx3d.defaultPolygon); }, setObject: function(/*Points[]|Object*/ newObject){ // summary: // setup the object this.object = gfx.makeParameters(this.object, (newObject instanceof Array) ? {path: newObject} : newObject) return this; }, render: function(camera){ var m = matrixUtil.multiply(camera, this.matrix); this.cache = arrayUtil.map(this.object.path, function(item){ return matrixUtil.multiplyPoint(m, item); }); // add the first point to close the polyline this.cache.push(this.cache[0]); }, draw: function(lighting){ if(this.shape){ this.shape.setShape({points: this.cache}); }else{ this.shape = this.renderer.createPolyline({points: this.cache}); } this.shape.setStroke(this.strokeStyle) .setFill(this.toStdFill(lighting, matrixUtil.normalize(this.cache))); }, getZOrder: function(){ var zOrder = 0; // using naive iteration to speed things up a bit by avoiding function call overhead for(var x=0; x 1) ? zOrder / this.cache.length : 0; }, getOutline: function(){ return this.cache.slice(0, 3); } }); declare("dojox.gfx3d.Cube", gfx3d.Object, { constructor: function(){ // summary: // a generic cube // (this is a helper object, which is defined for convenience) this.object = lang.clone(gfx3d.defaultCube); this.polygons = []; }, setObject: function(/*Points[]|Object*/ newObject){ // summary: // setup the object this.object = gfx.makeParameters(this.object, newObject); }, render: function(camera){ // parse the top, bottom to get 6 polygons: var a = this.object.top; var g = this.object.bottom; var b = {x: g.x, y: a.y, z: a.z}; var c = {x: g.x, y: g.y, z: a.z}; var d = {x: a.x, y: g.y, z: a.z}; var e = {x: a.x, y: a.y, z: g.z}; var f = {x: g.x, y: a.y, z: g.z}; var h = {x: a.x, y: g.y, z: g.z}; var polygons = [a, b, c, d, e, f, g, h]; var m = matrixUtil.multiply(camera, this.matrix); var p = arrayUtil.map(polygons, function(item){ return matrixUtil.multiplyPoint(m, item); }); a = p[0]; b = p[1]; c = p[2]; d = p[3]; e = p[4]; f = p[5]; g = p[6]; h = p[7]; this.cache = [[a, b, c, d, a], [e, f, g, h, e], [a, d, h, e, a], [d, c, g, h, d], [c, b, f, g, c], [b, a, e, f, b]]; }, draw: function(lighting){ // use bsp to sort. this.cache = gfx3d.scheduler.bsp(this.cache, function(it){ return it; }); // only the last 3 polys are visible. var cache = this.cache.slice(3); if(this.shape){ this.shape.clear(); }else{ this.shape = this.renderer.createGroup(); } for(var x=0; x 0){ centers = [c.top, c.center]; normal = v.substract(c.center, c.top); } var color = this.renderer.lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color), d = Math.sqrt( Math.pow(c.center.x - c.top.x, 2) + Math.pow(c.center.y - c.top.y, 2) ); if(this.shape){ this.shape.clear(); }else{ this.shape = this.renderer.createGroup(); } this.shape.createPath("") .moveTo(0, -c.rx) .lineTo(d, -c.rx) .lineTo(d, c.rx) .lineTo(0, c.rx) .arcTo(c.ry, c.rx, 0, true, true, 0, -c.rx) .setFill(c.gradient).setStroke(this.strokeStyle) .setTransform([m.translate(centers[0]), m.rotate(Math.atan2(centers[1].y - centers[0].y, centers[1].x - centers[0].x))]); if(c.rx > 0 && c.ry > 0){ this.shape.createEllipse({cx: centers[1].x, cy: centers[1].y, rx: c.rx, ry: c.ry}) .setFill(color).setStroke(this.strokeStyle) .applyTransform(m.rotateAt(c.theta, centers[1])); } } }); // the ultimate container of 3D world declare("dojox.gfx3d.Viewport", gfx.Group, { constructor: function(){ // summary: // a viewport/container for 3D objects, which knows // the camera and lightings // matrix: dojox.gfx3d.matrix // world transform // dimension: Object // the dimension of the canvas this.dimension = null; // objects: Array // all 3d Objects this.objects = []; // todos: Array // all 3d Objects that needs to redraw this.todos = []; // FIXME: memory leak? this.renderer = this; // Using zOrder as the default scheduler this.schedule = gfx3d.scheduler.zOrder; this.draw = gfx3d.drawer.conservative; // deep: boolean, true means the whole viewport needs to re-render, redraw this.deep = false; // lights: Array // an array of light objects this.lights = []; this.lighting = null; }, setCameraTransform: function(matrix){ // summary: // sets a transformation matrix // matrix: dojox.gfx3d.matrix.Matrix // a matrix or a matrix-like object // (see an argument of dojox.gfx.matrix.Matrix // constructor for a list of acceptable arguments) this.camera = matrixUtil.clone(matrix ? matrixUtil.normalize(matrix) : gfx3d.identity, true); this.invalidate(); return this; // self }, applyCameraRightTransform: function(matrix){ // summary: // multiplies the existing matrix with an argument on right side // (this.matrix * matrix) // matrix: dojox.gfx3d.matrix.Matrix // a matrix or a matrix-like object // (see an argument of dojox.gfx3d.matrix.Matrix // constructor for a list of acceptable arguments) return matrix ? this.setCameraTransform([this.camera, matrix]) : this; // self }, applyCameraLeftTransform: function(matrix){ // summary: // multiplies the existing matrix with an argument on left side // (matrix * this.matrix) // matrix: dojox.gfx3d.matrix.Matrix // a matrix or a matrix-like object // (see an argument of dojox.gfx3d.matrix.Matrix // constructor for a list of acceptable arguments) return matrix ? this.setCameraTransform([matrix, this.camera]) : this; // self }, applyCameraTransform: function(matrix){ // summary: // a shortcut for dojox.gfx3d.Object.applyRightTransform // matrix: dojox.gfx3d.matrix.Matrix // a matrix or a matrix-like object // (see an argument of dojox.gfx3d.matrix.Matrix // constructor for a list of acceptable arguments) return this.applyCameraRightTransform(matrix); // self }, setLights: function(/* Array|Object */lights, /* Color? */ ambient, /* Color? */ specular){ // summary: // set the lights // lights: Array // an array of light object // or lights object // ambient: Color // an ambient object // specular: Color // an specular object this.lights = (lights instanceof Array) ? {sources: lights, ambient: ambient, specular: specular} : lights; var view = {x: 0, y: 0, z: 1}; this.lighting = new lightUtil.Model(view, this.lights.sources, this.lights.ambient, this.lights.specular); this.invalidate(); return this; }, addLights: function(lights){ // summary: // add new light/lights to the viewport. // lights: Array|Object // light object(s) return this.setLights(this.lights.sources.concat(lights)); }, addTodo: function(newObject){ // NOTE: Viewport implements almost the same addTodo, // except calling invalidate, since invalidate is used as // any modification needs to redraw the object itself, call invalidate. // then call render. if(arrayUtil.every(this.todos, function(item){ return item != newObject; } )){ this.todos.push(newObject); } }, invalidate: function(){ this.deep = true; this.todos = this.objects; }, setDimensions: function(dim){ if(dim){ var w = lang.isString(dim.width) ? parseInt(dim.width) : dim.width; var h = lang.isString(dim.height) ? parseInt(dim.height) : dim.height; // there is no rawNode in canvas GFX implementation if(this.rawNode){ var trs = this.rawNode.style; if(trs){ trs.height = h; trs.width = w; }else{ // silverlight this.rawNode.width = w; this.rawNode.height = h; } } this.dimension = { width: w, height: h }; }else{ this.dimension = null; } }, render: function(){ // summary: // iterate all children and call their render callback function. if(!this.todos.length){ return; } // console.debug("Viewport::render"); var m = matrixUtil; // Iterate the todos and call render to prepare the rendering: for(var x=0; x