object.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.gfx3d.object"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.gfx3d.object"] = true;
  8. dojo.provide("dojox.gfx3d.object");
  9. dojo.require("dojox.gfx");
  10. dojo.require("dojox.gfx3d.lighting");
  11. dojo.require("dojox.gfx3d.scheduler");
  12. dojo.require("dojox.gfx3d.vector");
  13. dojo.require("dojox.gfx3d.gradient");
  14. // FIXME: why the global "out" var here?
  15. var out = function(o, x){
  16. if(arguments.length > 1){
  17. // console.debug("debug:", o);
  18. o = x;
  19. }
  20. var e = {};
  21. for(var i in o){
  22. if(i in e){ continue; }
  23. // console.debug("debug:", i, typeof o[i], o[i]);
  24. }
  25. };
  26. dojo.declare("dojox.gfx3d.Object", null, {
  27. constructor: function(){
  28. // summary: a Object object, which knows how to map
  29. // 3D objects to 2D shapes.
  30. // object: Object: an abstract Object object
  31. // (see dojox.gfx3d.defaultEdges,
  32. // dojox.gfx3d.defaultTriangles,
  33. // dojox.gfx3d.defaultQuads
  34. // dojox.gfx3d.defaultOrbit
  35. // dojox.gfx3d.defaultCube
  36. // or dojox.gfx3d.defaultCylinder)
  37. this.object = null;
  38. // matrix: dojox.gfx3d.matrix: world transform
  39. this.matrix = null;
  40. // cache: buffer for intermediate result, used late for draw()
  41. this.cache = null;
  42. // renderer: a reference for the Viewport
  43. this.renderer = null;
  44. // parent: a reference for parent, Scene or Viewport object
  45. this.parent = null;
  46. // strokeStyle: Object: a stroke object
  47. this.strokeStyle = null;
  48. // fillStyle: Object: a fill object or texture object
  49. this.fillStyle = null;
  50. // shape: dojox.gfx.Shape: an underlying 2D shape
  51. this.shape = null;
  52. },
  53. setObject: function(newObject){
  54. // summary: sets a Object object
  55. // object: Object: an abstract Object object
  56. // (see dojox.gfx3d.defaultEdges,
  57. // dojox.gfx3d.defaultTriangles,
  58. // dojox.gfx3d.defaultQuads
  59. // dojox.gfx3d.defaultOrbit
  60. // dojox.gfx3d.defaultCube
  61. // or dojox.gfx3d.defaultCylinder)
  62. this.object = dojox.gfx.makeParameters(this.object, newObject);
  63. return this;
  64. },
  65. setTransform: function(matrix){
  66. // summary: sets a transformation matrix
  67. // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
  68. // (see an argument of dojox.gfx3d.matrix.Matrix
  69. // constructor for a list of acceptable arguments)
  70. this.matrix = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
  71. return this; // self
  72. },
  73. // apply left & right transformation
  74. applyRightTransform: function(matrix){
  75. // summary: multiplies the existing matrix with an argument on right side
  76. // (this.matrix * matrix)
  77. // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
  78. // (see an argument of dojox.gfx.matrix.Matrix
  79. // constructor for a list of acceptable arguments)
  80. return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
  81. },
  82. applyLeftTransform: function(matrix){
  83. // summary: multiplies the existing matrix with an argument on left side
  84. // (matrix * this.matrix)
  85. // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
  86. // (see an argument of dojox.gfx.matrix.Matrix
  87. // constructor for a list of acceptable arguments)
  88. return matrix ? this.setTransform([matrix, this.matrix]) : this; // self
  89. },
  90. applyTransform: function(matrix){
  91. // summary: a shortcut for dojox.gfx.Shape.applyRightTransform
  92. // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
  93. // (see an argument of dojox.gfx.matrix.Matrix
  94. // constructor for a list of acceptable arguments)
  95. return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
  96. },
  97. setFill: function(fill){
  98. // summary: sets a fill object
  99. // (the default implementation is to delegate to
  100. // the underlying 2D shape).
  101. // fill: Object: a fill object
  102. // (see dojox.gfx.defaultLinearGradient,
  103. // dojox.gfx.defaultRadialGradient,
  104. // dojox.gfx.defaultPattern,
  105. // dojo.Color
  106. // or dojox.gfx.MODEL)
  107. this.fillStyle = fill;
  108. return this;
  109. },
  110. setStroke: function(stroke){
  111. // summary: sets a stroke object
  112. // (the default implementation simply ignores it)
  113. // stroke: Object: a stroke object
  114. // (see dojox.gfx.defaultStroke)
  115. this.strokeStyle = stroke;
  116. return this;
  117. },
  118. toStdFill: function(lighting, normal){
  119. return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle;
  120. },
  121. invalidate: function(){
  122. this.renderer.addTodo(this);
  123. },
  124. destroy: function(){
  125. if(this.shape){
  126. var p = this.shape.getParent();
  127. if(p){
  128. p.remove(this.shape);
  129. }
  130. this.shape = null;
  131. }
  132. },
  133. // All the 3D objects need to override the following virtual functions:
  134. // render, getZOrder, getOutline, draw, redraw if necessary.
  135. render: function(camera){
  136. throw "Pure virtual function, not implemented";
  137. },
  138. draw: function(lighting){
  139. throw "Pure virtual function, not implemented";
  140. },
  141. getZOrder: function(){
  142. return 0;
  143. },
  144. getOutline: function(){
  145. return null;
  146. }
  147. });
  148. dojo.declare("dojox.gfx3d.Scene", dojox.gfx3d.Object, {
  149. // summary: the Scene is just a containter.
  150. // note: we have the following assumption:
  151. // all objects in the Scene are not overlapped with other objects
  152. // outside of the scene.
  153. constructor: function(){
  154. // summary: a containter of other 3D objects
  155. this.objects= [];
  156. this.todos = [];
  157. this.schedule = dojox.gfx3d.scheduler.zOrder;
  158. this._draw = dojox.gfx3d.drawer.conservative;
  159. },
  160. setFill: function(fill){
  161. this.fillStyle = fill;
  162. dojo.forEach(this.objects, function(item){
  163. item.setFill(fill);
  164. });
  165. return this;
  166. },
  167. setStroke: function(stroke){
  168. this.strokeStyle = stroke;
  169. dojo.forEach(this.objects, function(item){
  170. item.setStroke(stroke);
  171. });
  172. return this;
  173. },
  174. render: function(camera, deep){
  175. var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
  176. if(deep){
  177. this.todos = this.objects;
  178. }
  179. dojo.forEach(this.todos, function(item){ item.render(m, deep); });
  180. },
  181. draw: function(lighting){
  182. this.objects = this.schedule(this.objects);
  183. this._draw(this.todos, this.objects, this.renderer);
  184. },
  185. addTodo: function(newObject){
  186. // FIXME: use indexOf?
  187. if(dojo.every(this.todos, function(item){ return item != newObject; })){
  188. this.todos.push(newObject);
  189. this.invalidate();
  190. }
  191. },
  192. invalidate: function(){
  193. this.parent.addTodo(this);
  194. },
  195. getZOrder: function(){
  196. var zOrder = 0;
  197. dojo.forEach(this.objects, function(item){ zOrder += item.getZOrder(); });
  198. return (this.objects.length > 1) ? zOrder / this.objects.length : 0;
  199. }
  200. });
  201. dojo.declare("dojox.gfx3d.Edges", dojox.gfx3d.Object, {
  202. constructor: function(){
  203. // summary: a generic edge in 3D viewport
  204. this.object = dojo.clone(dojox.gfx3d.defaultEdges);
  205. },
  206. setObject: function(newObject, /* String, optional */ style){
  207. // summary: setup the object
  208. // newObject: Array of points || Object
  209. // style: String, optional
  210. this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject);
  211. return this;
  212. },
  213. getZOrder: function(){
  214. var zOrder = 0;
  215. dojo.forEach(this.cache, function(item){ zOrder += item.z;} );
  216. return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
  217. },
  218. render: function(camera){
  219. var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
  220. this.cache = dojo.map(this.object.points, function(item){
  221. return dojox.gfx3d.matrix.multiplyPoint(m, item);
  222. });
  223. },
  224. draw: function(){
  225. var c = this.cache;
  226. if(this.shape){
  227. this.shape.setShape("")
  228. }else{
  229. this.shape = this.renderer.createPath();
  230. }
  231. var p = this.shape.setAbsoluteMode("absolute");
  232. if(this.object.style == "strip" || this.object.style == "loop"){
  233. p.moveTo(c[0].x, c[0].y);
  234. dojo.forEach(c.slice(1), function(item){
  235. p.lineTo(item.x, item.y);
  236. });
  237. if(this.object.style == "loop"){
  238. p.closePath();
  239. }
  240. }else{
  241. for(var i = 0; i < this.cache.length; ){
  242. p.moveTo(c[i].x, c[i].y);
  243. i ++;
  244. p.lineTo(c[i].x, c[i].y);
  245. i ++;
  246. }
  247. }
  248. // FIXME: doe setFill make sense here?
  249. p.setStroke(this.strokeStyle);
  250. }
  251. });
  252. dojo.declare("dojox.gfx3d.Orbit", dojox.gfx3d.Object, {
  253. constructor: function(){
  254. // summary: a generic edge in 3D viewport
  255. this.object = dojo.clone(dojox.gfx3d.defaultOrbit);
  256. },
  257. render: function(camera){
  258. var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
  259. var angles = [0, Math.PI/4, Math.PI/3];
  260. var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
  261. var marks = dojo.map(angles, function(item){
  262. return {x: this.center.x + this.radius * Math.cos(item),
  263. y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
  264. }, this.object);
  265. marks = dojo.map(marks, function(item){
  266. return dojox.gfx3d.matrix.multiplyPoint(m, item);
  267. });
  268. var normal = dojox.gfx3d.vector.normalize(marks);
  269. marks = dojo.map(marks, function(item){
  270. return dojox.gfx3d.vector.substract(item, center);
  271. });
  272. // Use the algorithm here:
  273. // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
  274. // After we normalize the marks, the equation is:
  275. // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
  276. // so the final equation is:
  277. // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'
  278. var A = {
  279. xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
  280. yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
  281. zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
  282. dx: 0, dy: 0, dz: 0
  283. };
  284. var B = dojo.map(marks, function(item){
  285. return -Math.pow(item.x, 2);
  286. });
  287. // X is 2b, c, f
  288. var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A),B[0], B[1], B[2]);
  289. var theta = Math.atan2(X.x, 1 - X.y) / 2;
  290. // rotate the marks back to the canonical form
  291. var probes = dojo.map(marks, function(item){
  292. return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
  293. });
  294. // we are solving the equation: Ax = b
  295. // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
  296. // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
  297. // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );
  298. var a = Math.pow(probes[0].x, 2);
  299. var b = Math.pow(probes[0].y, 2);
  300. var c = Math.pow(probes[1].x, 2);
  301. var d = Math.pow(probes[1].y, 2);
  302. // the invert matrix is
  303. // 1/(ad -bc) [ d, -b; -c, a];
  304. var rx = Math.sqrt( (a*d - b*c)/ (d-b) );
  305. var ry = Math.sqrt( (a*d - b*c)/ (a-c) );
  306. this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal};
  307. },
  308. draw: function(lighting){
  309. if(this.shape){
  310. this.shape.setShape(this.cache);
  311. } else {
  312. this.shape = this.renderer.createEllipse(this.cache);
  313. }
  314. this.shape.applyTransform(dojox.gfx.matrix.rotateAt(this.cache.theta, this.cache.cx, this.cache.cy))
  315. .setStroke(this.strokeStyle)
  316. .setFill(this.toStdFill(lighting, this.cache.normal));
  317. }
  318. });
  319. dojo.declare("dojox.gfx3d.Path3d", dojox.gfx3d.Object, {
  320. // This object is still very immature !
  321. constructor: function(){
  322. // summary: a generic line
  323. // (this is a helper object, which is defined for convenience)
  324. this.object = dojo.clone(dojox.gfx3d.defaultPath3d);
  325. this.segments = [];
  326. this.absolute = true;
  327. this.last = {};
  328. this.path = "";
  329. },
  330. _collectArgs: function(array, args){
  331. // summary: converts an array of arguments to plain numeric values
  332. // array: Array: an output argument (array of numbers)
  333. // args: Array: an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them)
  334. for(var i = 0; i < args.length; ++i){
  335. var t = args[i];
  336. if(typeof(t) == "boolean"){
  337. array.push(t ? 1 : 0);
  338. }else if(typeof(t) == "number"){
  339. array.push(t);
  340. }else if(t instanceof Array){
  341. this._collectArgs(array, t);
  342. }else if("x" in t && "y" in t){
  343. array.push(t.x);
  344. array.push(t.y);
  345. }
  346. }
  347. },
  348. // a dictionary, which maps segment type codes to a number of their argemnts
  349. _validSegments: {m: 3, l: 3, z: 0},
  350. _pushSegment: function(action, args){
  351. // summary: adds a segment
  352. // action: String: valid SVG code for a segment's type
  353. // args: Array: a list of parameters for this segment
  354. var group = this._validSegments[action.toLowerCase()], segment;
  355. if(typeof(group) == "number"){
  356. if(group){
  357. if(args.length >= group){
  358. segment = {action: action, args: args.slice(0, args.length - args.length % group)};
  359. this.segments.push(segment);
  360. }
  361. }else{
  362. segment = {action: action, args: []};
  363. this.segments.push(segment);
  364. }
  365. }
  366. },
  367. moveTo: function(){
  368. // summary: formes a move segment
  369. var args = [];
  370. this._collectArgs(args, arguments);
  371. this._pushSegment(this.absolute ? "M" : "m", args);
  372. return this; // self
  373. },
  374. lineTo: function(){
  375. // summary: formes a line segment
  376. var args = [];
  377. this._collectArgs(args, arguments);
  378. this._pushSegment(this.absolute ? "L" : "l", args);
  379. return this; // self
  380. },
  381. closePath: function(){
  382. // summary: closes a path
  383. this._pushSegment("Z", []);
  384. return this; // self
  385. },
  386. render: function(camera){
  387. // TODO: we need to get the ancestors' matrix
  388. var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
  389. // iterate all the segments and convert them to 2D canvas
  390. // TODO consider the relative mode
  391. var path = ""
  392. var _validSegments = this._validSegments;
  393. dojo.forEach(this.segments, function(item){
  394. path += item.action;
  395. for(var i = 0; i < item.args.length; i+= _validSegments[item.action.toLowerCase()] ){
  396. var pt = dojox.gfx3d.matrix.multiplyPoint(m, item.args[i], item.args[i+1], item.args[i+2])
  397. path += " " + pt.x + " " + pt.y;
  398. }
  399. });
  400. this.cache = path;
  401. },
  402. _draw: function(){
  403. return this.parent.createPath(this.cache);
  404. }
  405. });
  406. dojo.declare("dojox.gfx3d.Triangles", dojox.gfx3d.Object, {
  407. constructor: function(){
  408. // summary: a generic triangle
  409. // (this is a helper object, which is defined for convenience)
  410. this.object = dojo.clone(dojox.gfx3d.defaultTriangles);
  411. },
  412. setObject: function(newObject, /* String, optional */ style){
  413. // summary: setup the object
  414. // newObject: Array of points || Object
  415. // style: String, optional
  416. if(newObject instanceof Array){
  417. this.object = dojox.gfx.makeParameters(this.object, { points: newObject, style: style } );
  418. } else {
  419. this.object = dojox.gfx.makeParameters(this.object, newObject);
  420. }
  421. return this;
  422. },
  423. render: function(camera){
  424. var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
  425. var c = dojo.map(this.object.points, function(item){
  426. return dojox.gfx3d.matrix.multiplyPoint(m, item);
  427. });
  428. this.cache = [];
  429. var pool = c.slice(0, 2);
  430. var center = c[0];
  431. if(this.object.style == "strip"){
  432. dojo.forEach(c.slice(2), function(item){
  433. pool.push(item);
  434. pool.push(pool[0]);
  435. this.cache.push(pool);
  436. pool = pool.slice(1, 3);
  437. }, this);
  438. } else if(this.object.style == "fan"){
  439. dojo.forEach(c.slice(2), function(item){
  440. pool.push(item);
  441. pool.push(center);
  442. this.cache.push(pool);
  443. pool = [center, item];
  444. }, this);
  445. } else {
  446. for(var i = 0; i < c.length; ){
  447. this.cache.push( [ c[i], c[i+1], c[i+2], c[i] ]);
  448. i += 3;
  449. }
  450. }
  451. },
  452. draw: function(lighting){
  453. // use the BSP to schedule
  454. this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
  455. if(this.shape){
  456. this.shape.clear();
  457. } else {
  458. this.shape = this.renderer.createGroup();
  459. }
  460. dojo.forEach(this.cache, function(item){
  461. this.shape.createPolyline(item)
  462. .setStroke(this.strokeStyle)
  463. .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
  464. }, this);
  465. },
  466. getZOrder: function(){
  467. var zOrder = 0;
  468. dojo.forEach(this.cache, function(item){
  469. zOrder += (item[0].z + item[1].z + item[2].z) / 3; });
  470. return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
  471. }
  472. });
  473. dojo.declare("dojox.gfx3d.Quads", dojox.gfx3d.Object, {
  474. constructor: function(){
  475. // summary: a generic triangle
  476. // (this is a helper object, which is defined for convenience)
  477. this.object = dojo.clone(dojox.gfx3d.defaultQuads);
  478. },
  479. setObject: function(newObject, /* String, optional */ style){
  480. // summary: setup the object
  481. // newObject: Array of points || Object
  482. // style: String, optional
  483. this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject );
  484. return this;
  485. },
  486. render: function(camera){
  487. var m = dojox.gfx3d.matrix.multiply(camera, this.matrix), i;
  488. var c = dojo.map(this.object.points, function(item){
  489. return dojox.gfx3d.matrix.multiplyPoint(m, item);
  490. });
  491. this.cache = [];
  492. if(this.object.style == "strip"){
  493. var pool = c.slice(0, 2);
  494. for(i = 2; i < c.length; ){
  495. pool = pool.concat( [ c[i], c[i+1], pool[0] ] );
  496. this.cache.push(pool);
  497. pool = pool.slice(2,4);
  498. i += 2;
  499. }
  500. }else{
  501. for(i = 0; i < c.length; ){
  502. this.cache.push( [c[i], c[i+1], c[i+2], c[i+3], c[i] ] );
  503. i += 4;
  504. }
  505. }
  506. },
  507. draw: function(lighting){
  508. // use the BSP to schedule
  509. this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
  510. if(this.shape){
  511. this.shape.clear();
  512. }else{
  513. this.shape = this.renderer.createGroup();
  514. }
  515. // using naive iteration to speed things up a bit by avoiding function call overhead
  516. for(var x=0; x<this.cache.length; x++){
  517. this.shape.createPolyline(this.cache[x])
  518. .setStroke(this.strokeStyle)
  519. .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(this.cache[x])));
  520. }
  521. /*
  522. dojo.forEach(this.cache, function(item){
  523. this.shape.createPolyline(item)
  524. .setStroke(this.strokeStyle)
  525. .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
  526. }, this);
  527. */
  528. },
  529. getZOrder: function(){
  530. var zOrder = 0;
  531. // using naive iteration to speed things up a bit by avoiding function call overhead
  532. for(var x=0; x<this.cache.length; x++){
  533. var i = this.cache[x];
  534. zOrder += (i[0].z + i[1].z + i[2].z + i[3].z) / 4;
  535. }
  536. /*
  537. dojo.forEach(this.cache, function(item){
  538. zOrder += (item[0].z + item[1].z + item[2].z + item[3].z) / 4; });
  539. */
  540. return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
  541. }
  542. });
  543. dojo.declare("dojox.gfx3d.Polygon", dojox.gfx3d.Object, {
  544. constructor: function(){
  545. // summary: a generic triangle
  546. // (this is a helper object, which is defined for convenience)
  547. this.object = dojo.clone(dojox.gfx3d.defaultPolygon);
  548. },
  549. setObject: function(newObject){
  550. // summary: setup the object
  551. // newObject: Array of points || Object
  552. this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? {path: newObject} : newObject)
  553. return this;
  554. },
  555. render: function(camera){
  556. var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
  557. this.cache = dojo.map(this.object.path, function(item){
  558. return dojox.gfx3d.matrix.multiplyPoint(m, item);
  559. });
  560. // add the first point to close the polyline
  561. this.cache.push(this.cache[0]);
  562. },
  563. draw: function(lighting){
  564. if(this.shape){
  565. this.shape.setShape({points: this.cache});
  566. }else{
  567. this.shape = this.renderer.createPolyline({points: this.cache});
  568. }
  569. this.shape.setStroke(this.strokeStyle)
  570. .setFill(this.toStdFill(lighting, dojox.gfx3d.matrix.normalize(this.cache)));
  571. },
  572. getZOrder: function(){
  573. var zOrder = 0;
  574. // using naive iteration to speed things up a bit by avoiding function call overhead
  575. for(var x=0; x<this.cache.length; x++){
  576. zOrder += this.cache[x].z;
  577. }
  578. return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
  579. },
  580. getOutline: function(){
  581. return this.cache.slice(0, 3);
  582. }
  583. });
  584. dojo.declare("dojox.gfx3d.Cube", dojox.gfx3d.Object, {
  585. constructor: function(){
  586. // summary: a generic triangle
  587. // (this is a helper object, which is defined for convenience)
  588. this.object = dojo.clone(dojox.gfx3d.defaultCube);
  589. this.polygons = [];
  590. },
  591. setObject: function(newObject){
  592. // summary: setup the object
  593. // newObject: Array of points || Object
  594. this.object = dojox.gfx.makeParameters(this.object, newObject);
  595. },
  596. render: function(camera){
  597. // parse the top, bottom to get 6 polygons:
  598. var a = this.object.top;
  599. var g = this.object.bottom;
  600. var b = {x: g.x, y: a.y, z: a.z};
  601. var c = {x: g.x, y: g.y, z: a.z};
  602. var d = {x: a.x, y: g.y, z: a.z};
  603. var e = {x: a.x, y: a.y, z: g.z};
  604. var f = {x: g.x, y: a.y, z: g.z};
  605. var h = {x: a.x, y: g.y, z: g.z};
  606. var polygons = [a, b, c, d, e, f, g, h];
  607. var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
  608. var p = dojo.map(polygons, function(item){
  609. return dojox.gfx3d.matrix.multiplyPoint(m, item);
  610. });
  611. a = p[0]; b = p[1]; c = p[2]; d = p[3]; e = p[4]; f = p[5]; g = p[6]; h = p[7];
  612. 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]];
  613. },
  614. draw: function(lighting){
  615. // use bsp to sort.
  616. this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
  617. // only the last 3 polys are visible.
  618. var cache = this.cache.slice(3);
  619. if(this.shape){
  620. this.shape.clear();
  621. }else{
  622. this.shape = this.renderer.createGroup();
  623. }
  624. for(var x=0; x<cache.length; x++){
  625. this.shape.createPolyline(cache[x])
  626. .setStroke(this.strokeStyle)
  627. .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(cache[x])));
  628. }
  629. /*
  630. dojo.forEach(cache, function(item){
  631. this.shape.createPolyline(item)
  632. .setStroke(this.strokeStyle)
  633. .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
  634. }, this);
  635. */
  636. },
  637. getZOrder: function(){
  638. var top = this.cache[0][0];
  639. var bottom = this.cache[1][2];
  640. return (top.z + bottom.z) / 2;
  641. }
  642. });
  643. dojo.declare("dojox.gfx3d.Cylinder", dojox.gfx3d.Object, {
  644. constructor: function(){
  645. this.object = dojo.clone(dojox.gfx3d.defaultCylinder);
  646. },
  647. render: function(camera){
  648. // get the bottom surface first
  649. var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
  650. var angles = [0, Math.PI/4, Math.PI/3];
  651. var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
  652. var marks = dojo.map(angles, function(item){
  653. return {x: this.center.x + this.radius * Math.cos(item),
  654. y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
  655. }, this.object);
  656. marks = dojo.map(marks, function(item){
  657. return dojox.gfx3d.vector.substract(dojox.gfx3d.matrix.multiplyPoint(m, item), center);
  658. });
  659. // Use the algorithm here:
  660. // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
  661. // After we normalize the marks, the equation is:
  662. // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
  663. // so the final equation is:
  664. // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'
  665. var A = {
  666. xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
  667. yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
  668. zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
  669. dx: 0, dy: 0, dz: 0
  670. };
  671. var B = dojo.map(marks, function(item){
  672. return -Math.pow(item.x, 2);
  673. });
  674. // X is 2b, c, f
  675. var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A), B[0], B[1], B[2]);
  676. var theta = Math.atan2(X.x, 1 - X.y) / 2;
  677. // rotate the marks back to the canonical form
  678. var probes = dojo.map(marks, function(item){
  679. return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
  680. });
  681. // we are solving the equation: Ax = b
  682. // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
  683. // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
  684. // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );
  685. var a = Math.pow(probes[0].x, 2);
  686. var b = Math.pow(probes[0].y, 2);
  687. var c = Math.pow(probes[1].x, 2);
  688. var d = Math.pow(probes[1].y, 2);
  689. // the invert matrix is
  690. // 1/(ad - bc) [ d, -b; -c, a];
  691. var rx = Math.sqrt((a * d - b * c) / (d - b));
  692. var ry = Math.sqrt((a * d - b * c) / (a - c));
  693. if(rx < ry){
  694. var t = rx;
  695. rx = ry;
  696. ry = t;
  697. theta -= Math.PI/2;
  698. }
  699. var top = dojox.gfx3d.matrix.multiplyPoint(m,
  700. dojox.gfx3d.vector.sum(this.object.center, {x: 0, y:0, z: this.object.height}));
  701. var gradient = this.fillStyle.type == "constant" ? this.fillStyle.color
  702. : dojox.gfx3d.gradient(this.renderer.lighting, this.fillStyle, this.object.center, this.object.radius, Math.PI, 2 * Math.PI, m);
  703. if(isNaN(rx) || isNaN(ry) || isNaN(theta)){
  704. // in case the cap is invisible (parallel to the incident vector)
  705. rx = this.object.radius, ry = 0, theta = 0;
  706. }
  707. this.cache = {center: center, top: top, rx: rx, ry: ry, theta: theta, gradient: gradient};
  708. },
  709. draw: function(){
  710. var c = this.cache, v = dojox.gfx3d.vector, m = dojox.gfx.matrix,
  711. centers = [c.center, c.top], normal = v.substract(c.top, c.center);
  712. if(v.dotProduct(normal, this.renderer.lighting.incident) > 0){
  713. centers = [c.top, c.center];
  714. normal = v.substract(c.center, c.top);
  715. }
  716. var color = this.renderer.lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color),
  717. d = Math.sqrt( Math.pow(c.center.x - c.top.x, 2) + Math.pow(c.center.y - c.top.y, 2) );
  718. if(this.shape){
  719. this.shape.clear();
  720. }else{
  721. this.shape = this.renderer.createGroup();
  722. }
  723. this.shape.createPath("")
  724. .moveTo(0, -c.rx)
  725. .lineTo(d, -c.rx)
  726. .lineTo(d, c.rx)
  727. .lineTo(0, c.rx)
  728. .arcTo(c.ry, c.rx, 0, true, true, 0, -c.rx)
  729. .setFill(c.gradient).setStroke(this.strokeStyle)
  730. .setTransform([m.translate(centers[0]),
  731. m.rotate(Math.atan2(centers[1].y - centers[0].y, centers[1].x - centers[0].x))]);
  732. if(c.rx > 0 && c.ry > 0){
  733. this.shape.createEllipse({cx: centers[1].x, cy: centers[1].y, rx: c.rx, ry: c.ry})
  734. .setFill(color).setStroke(this.strokeStyle)
  735. .applyTransform(m.rotateAt(c.theta, centers[1]));
  736. }
  737. }
  738. });
  739. // the ultimate container of 3D world
  740. dojo.declare("dojox.gfx3d.Viewport", dojox.gfx.Group, {
  741. constructor: function(){
  742. // summary: a viewport/container for 3D objects, which knows
  743. // the camera and lightings
  744. // matrix: dojox.gfx3d.matrix: world transform
  745. // dimension: Object: the dimension of the canvas
  746. this.dimension = null;
  747. // objects: Array: all 3d Objects
  748. this.objects = [];
  749. // todos: Array: all 3d Objects that needs to redraw
  750. this.todos = [];
  751. // FIXME: memory leak?
  752. this.renderer = this;
  753. // Using zOrder as the default scheduler
  754. this.schedule = dojox.gfx3d.scheduler.zOrder;
  755. this.draw = dojox.gfx3d.drawer.conservative;
  756. // deep: boolean, true means the whole viewport needs to re-render, redraw
  757. this.deep = false;
  758. // lights: Array: an array of light objects
  759. this.lights = [];
  760. this.lighting = null;
  761. },
  762. setCameraTransform: function(matrix){
  763. // summary: sets a transformation matrix
  764. // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
  765. // (see an argument of dojox.gfx.matrix.Matrix
  766. // constructor for a list of acceptable arguments)
  767. this.camera = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
  768. this.invalidate();
  769. return this; // self
  770. },
  771. applyCameraRightTransform: function(matrix){
  772. // summary: multiplies the existing matrix with an argument on right side
  773. // (this.matrix * matrix)
  774. // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
  775. // (see an argument of dojox.gfx3d.matrix.Matrix
  776. // constructor for a list of acceptable arguments)
  777. return matrix ? this.setCameraTransform([this.camera, matrix]) : this; // self
  778. },
  779. applyCameraLeftTransform: function(matrix){
  780. // summary: multiplies the existing matrix with an argument on left side
  781. // (matrix * this.matrix)
  782. // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
  783. // (see an argument of dojox.gfx3d.matrix.Matrix
  784. // constructor for a list of acceptable arguments)
  785. return matrix ? this.setCameraTransform([matrix, this.camera]) : this; // self
  786. },
  787. applyCameraTransform: function(matrix){
  788. // summary: a shortcut for dojox.gfx3d.Object.applyRightTransform
  789. // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
  790. // (see an argument of dojox.gfx3d.matrix.Matrix
  791. // constructor for a list of acceptable arguments)
  792. return this.applyCameraRightTransform(matrix); // self
  793. },
  794. setLights: function(/* Array || Object */lights, /* Color, optional */ ambient,
  795. /* Color, optional */ specular){
  796. // summary: set the lights
  797. // lights: Array: an array of light object
  798. // or lights object
  799. // ambient: Color: an ambient object
  800. // specular: Color: an specular object
  801. this.lights = (lights instanceof Array) ? {sources: lights, ambient: ambient, specular: specular} : lights;
  802. var view = {x: 0, y: 0, z: 1};
  803. this.lighting = new dojox.gfx3d.lighting.Model(view, this.lights.sources,
  804. this.lights.ambient, this.lights.specular);
  805. this.invalidate();
  806. return this;
  807. },
  808. addLights: function(lights){
  809. // summary: add new light/lights to the viewport.
  810. // lights: Array || light object: light object(s)
  811. return this.setLights(this.lights.sources.concat(lights));
  812. },
  813. addTodo: function(newObject){
  814. // NOTE: Viewport implements almost the same addTodo,
  815. // except calling invalidate, since invalidate is used as
  816. // any modification needs to redraw the object itself, call invalidate.
  817. // then call render.
  818. if(dojo.every(this.todos,
  819. function(item){
  820. return item != newObject;
  821. }
  822. )){
  823. this.todos.push(newObject);
  824. }
  825. },
  826. invalidate: function(){
  827. this.deep = true;
  828. this.todos = this.objects;
  829. },
  830. setDimensions: function(dim){
  831. if(dim){
  832. var w = dojo.isString(dim.width) ? parseInt(dim.width) : dim.width;
  833. var h = dojo.isString(dim.height) ? parseInt(dim.height) : dim.height;
  834. // there is no rawNode in canvas GFX implementation
  835. if(this.rawNode){
  836. var trs = this.rawNode.style;
  837. trs.height = h;
  838. trs.width = w;
  839. }
  840. this.dimension = {
  841. width: w,
  842. height: h
  843. };
  844. }else{
  845. this.dimension = null;
  846. }
  847. },
  848. render: function(){
  849. // summary: iterate all children and call their render callback function.
  850. if(!this.todos.length){ return; }
  851. // console.debug("Viewport::render");
  852. var m = dojox.gfx3d.matrix;
  853. // Iterate the todos and call render to prepare the rendering:
  854. for(var x=0; x<this.todos.length; x++){
  855. this.todos[x].render(dojox.gfx3d.matrix.normalize([
  856. m.cameraRotateXg(180),
  857. m.cameraTranslate(0, this.dimension.height, 0),
  858. this.camera
  859. ]), this.deep);
  860. }
  861. this.objects = this.schedule(this.objects);
  862. this.draw(this.todos, this.objects, this);
  863. this.todos = [];
  864. this.deep = false;
  865. }
  866. });
  867. //FIXME: Viewport cannot masquerade as a Group
  868. dojox.gfx3d.Viewport.nodeType = dojox.gfx.Group.nodeType;
  869. dojox.gfx3d._creators = {
  870. // summary: object creators
  871. createEdges: function(edges, style){
  872. // summary: creates an edge object
  873. // line: Object: a edge object (see dojox.gfx3d.defaultPath)
  874. return this.create3DObject(dojox.gfx3d.Edges, edges, style); // dojox.gfx3d.Edge
  875. },
  876. createTriangles: function(tris, style){
  877. // summary: creates an edge object
  878. // line: Object: a edge object (see dojox.gfx3d.defaultPath)
  879. return this.create3DObject(dojox.gfx3d.Triangles, tris, style); // dojox.gfx3d.Edge
  880. },
  881. createQuads: function(quads, style){
  882. // summary: creates an edge object
  883. // line: Object: a edge object (see dojox.gfx3d.defaultPath)
  884. return this.create3DObject(dojox.gfx3d.Quads, quads, style); // dojox.gfx3d.Edge
  885. },
  886. createPolygon: function(points){
  887. // summary: creates an triangle object
  888. // points: Array of points || Object
  889. return this.create3DObject(dojox.gfx3d.Polygon, points); // dojox.gfx3d.Polygon
  890. },
  891. createOrbit: function(orbit){
  892. // summary: creates an triangle object
  893. // points: Array of points || Object
  894. return this.create3DObject(dojox.gfx3d.Orbit, orbit); // dojox.gfx3d.Cube
  895. },
  896. createCube: function(cube){
  897. // summary: creates an triangle object
  898. // points: Array of points || Object
  899. return this.create3DObject(dojox.gfx3d.Cube, cube); // dojox.gfx3d.Cube
  900. },
  901. createCylinder: function(cylinder){
  902. // summary: creates an triangle object
  903. // points: Array of points || Object
  904. return this.create3DObject(dojox.gfx3d.Cylinder, cylinder); // dojox.gfx3d.Cube
  905. },
  906. createPath3d: function(path){
  907. // summary: creates an edge object
  908. // line: Object: a edge object (see dojox.gfx3d.defaultPath)
  909. return this.create3DObject(dojox.gfx3d.Path3d, path); // dojox.gfx3d.Edge
  910. },
  911. createScene: function(){
  912. // summary: creates an triangle object
  913. // line: Object: a triangle object (see dojox.gfx3d.defaultPath)
  914. return this.create3DObject(dojox.gfx3d.Scene); // dojox.gfx3d.Scene
  915. },
  916. create3DObject: function(objectType, rawObject, style){
  917. // summary: creates an instance of the passed shapeType class
  918. // shapeType: Function: a class constructor to create an instance of
  919. // rawShape: Object: properties to be passed in to the classes "setShape" method
  920. var obj = new objectType();
  921. this.adopt(obj);
  922. if(rawObject){ obj.setObject(rawObject, style); }
  923. return obj; // dojox.gfx3d.Object
  924. },
  925. // todo : override the add/remove if necessary
  926. adopt: function(obj){
  927. // summary: adds a shape to the list
  928. // shape: dojox.gfx.Shape: a shape
  929. obj.renderer = this.renderer; // obj._setParent(this, null); more TODOs HERER?
  930. obj.parent = this;
  931. this.objects.push(obj);
  932. this.addTodo(obj);
  933. return this;
  934. },
  935. abandon: function(obj, silently){
  936. // summary: removes a shape from the list
  937. // silently: Boolean?: if true, do not redraw a picture yet
  938. for(var i = 0; i < this.objects.length; ++i){
  939. if(this.objects[i] == obj){
  940. this.objects.splice(i, 1);
  941. }
  942. }
  943. // if(this.rawNode == shape.rawNode.parentNode){
  944. // this.rawNode.removeChild(shape.rawNode);
  945. // }
  946. // obj._setParent(null, null);
  947. obj.parent = null;
  948. return this; // self
  949. },
  950. setScheduler: function(scheduler){
  951. this.schedule = scheduler;
  952. },
  953. setDrawer: function(drawer){
  954. this.draw = drawer;
  955. }
  956. };
  957. dojo.extend(dojox.gfx3d.Viewport, dojox.gfx3d._creators);
  958. dojo.extend(dojox.gfx3d.Scene, dojox.gfx3d._creators);
  959. delete dojox.gfx3d._creators;
  960. //FIXME: extending dojox.gfx.Surface and masquerading Viewport as Group is hacky!
  961. // Add createViewport to dojox.gfx.Surface
  962. dojo.extend(dojox.gfx.Surface, {
  963. createViewport: function(){
  964. //FIXME: createObject is non-public method!
  965. var viewport = this.createObject(dojox.gfx3d.Viewport, null, true);
  966. //FIXME: this may not work with dojox.gfx.Group !!
  967. viewport.setDimensions(this.getDimensions());
  968. return viewport;
  969. }
  970. });
  971. }