object.js 33 KB

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