ObJS.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. /********************************************************************
  2. * The ObJS objectnitializes the canvas, assignin it to the HTML *
  3. * element, and set the required mouse events. *
  4. * #parameters: none *
  5. * #return: nothing *
  6. ********************************************************************/
  7. function ObJS(file, canv){
  8. /********************************************************************
  9. * Parameters for costumization. *
  10. ********************************************************************/
  11. var backColor = "rgba(200, 200, 200, 1)";
  12. var vertColor = "rgba(10, 10, 10, 1)";
  13. var edgeColor = "rgba(100, 70, 70, 1)";
  14. var faceColor = "rgba(180, 180, 255, .6)";
  15. var faceAlpha = .6;
  16. var textColor = "rgba(0, 0, 0, 1)";
  17. var vertSize = 4; //Make it even: at small scales will look better
  18. var rotationSpeed = 90; //Smaller = faster. 100 is a good speed
  19. var zoomSpeed = 0.161; //0-1. 0.2 or 0.3 are good speeds
  20. /********************************************************************
  21. * Variable containing this object, to be refered from nested *
  22. * functions. *
  23. ********************************************************************/
  24. var thisObJS;
  25. /********************************************************************
  26. * Parameter used to scale the measures in the .obj file. Can be *
  27. * integer or float, greater than 0. 50 is a good default. *
  28. ********************************************************************/
  29. var scale;
  30. /********************************************************************
  31. * Variable to determine if the canvas has been initialized. *
  32. ********************************************************************/
  33. var canvasInitialized = false;
  34. /********************************************************************
  35. * Variable to determine if the mouse button is being holded down. *
  36. ********************************************************************/
  37. var mDown = false;
  38. /********************************************************************
  39. * Variables containing the canvas and its context. *
  40. ********************************************************************/
  41. var canvas;
  42. var ctx;
  43. /********************************************************************
  44. * Variables containing the number of elements. *
  45. ********************************************************************/
  46. var totalVert;
  47. var totalMaterial = 0;
  48. var totalFace
  49. /********************************************************************
  50. * Arrays containing the vertices, faces and materials. *
  51. ********************************************************************/
  52. var material;
  53. var vert;
  54. var face;
  55. /********************************************************************
  56. * Booleans to determine wich elements are to draw. *
  57. ********************************************************************/
  58. var dVerts = true;
  59. var dEdges = true;
  60. var dFaces = true;
  61. var dBackg = true;
  62. /********************************************************************
  63. * Booleans to indicate if mtl file should be used. *
  64. ********************************************************************/
  65. var useMtl = true;
  66. /********************************************************************
  67. * Global variables that will contain the position of the mouse in *
  68. * the previous frame to calculate rotation. *
  69. ********************************************************************/
  70. var pX = null;
  71. var pY = null;
  72. /********************************************************************
  73. * Function that initializes the canvas, assignin it to the HTML *
  74. * element, and set the required mouse events. *
  75. * #parameters: *
  76. * canv (canvas): canvas to use. If omited, the one named *
  77. * named "ObJSCanvas" will be used. *
  78. * #return: nothing *
  79. * #scope: public *
  80. ********************************************************************/
  81. this.initCanvas = function(canv){
  82. if (!canvasInitialized){
  83. if (canv!= null)
  84. canvas = canv;
  85. else
  86. canvas = document.getElementById("ObJSCanvas");
  87. ctx = canvas.getContext("2d");
  88. ctx.translate(canvas.width / 2, canvas.height / 2);
  89. canvas.onmousedown = function(e){
  90. pX = e.offsetX==undefined?e.layerX:e.offsetX;
  91. pY = e.offsetY==undefined?e.layerY:e.offsetY;
  92. mDown = true;
  93. };
  94. canvas.onmouseup = function(e){
  95. if(mDown)
  96. mouseClick(e);
  97. pX = null;
  98. pY = null;
  99. mDown = false;
  100. };
  101. canvas.onmousemove = function(e){
  102. if(!mDown)
  103. return;
  104. var x = e.offsetX==undefined?e.layerX:e.offsetX;
  105. var y = e.offsetY==undefined?e.layerY:e.offsetY;
  106. thisObJS.rotateY(x - pX);
  107. thisObJS.rotateX(y - pY);
  108. pX = x;
  109. pY = y;
  110. draw();
  111. return false;
  112. };
  113. canvas.addEventListener('mousewheel',function(event){
  114. if (event.wheelDelta > 0)
  115. thisObJS.zoom("-");
  116. else
  117. thisObJS.zoom("+");
  118. return false;
  119. }, false);
  120. canvasInitialized = true;
  121. }
  122. };
  123. /********************************************************************
  124. * Main function, called every time a file is to be loaded. *
  125. * #parameters: *
  126. * file (string): name of the file, without path or extension. *
  127. * #return: nothing *
  128. * #scope: public *
  129. ********************************************************************/
  130. this.load = function(file){
  131. //Get obj file
  132. var xmlhttp = new XMLHttpRequest();
  133. xmlhttp.open("GET", file, false);
  134. xmlhttp.send();
  135. var fileContent = xmlhttp.responseText;
  136. //Get mtl file
  137. xmlhttp = new XMLHttpRequest();
  138. xmlhttp.open("GET",file.substring(0, file.lastIndexOf(".")) + ".mtl", false);
  139. console.log("GET" + file.substring(0, file.lastIndexOf(".")) + ".mtl");
  140. xmlhttp.send();
  141. var mtlContent = xmlhttp.responseText;
  142. initArrays();
  143. readFile(fileContent);
  144. readMtlFile(mtlContent);
  145. draw();
  146. };
  147. /********************************************************************
  148. * Function switching the elements to draw (dVerts, dEdges, dFaces) *
  149. * #parameters: *
  150. * name (string): key to the element to draw. *
  151. * value (boolean): indicating if the element is to be drawn. *
  152. * #return: nothing *
  153. * #scope: public *
  154. ********************************************************************/
  155. this.drawElement = function(name, value){
  156. switch (name){
  157. case "verts":
  158. dVerts = value;
  159. break;
  160. case "edges":
  161. dEdges = value;
  162. break;
  163. case "faces":
  164. dFaces = value;
  165. break;
  166. case "backg":
  167. dBackg = value;
  168. break;
  169. }
  170. draw();
  171. };
  172. /********************************************************************
  173. * Function that turns a hex color code in RGB. *
  174. * stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb *
  175. * #parameters: *
  176. * hex (string): color code, for example "#33ff99" *
  177. * #return: (Array) r, g, b, with their correspondent color. *
  178. * #scope: private *
  179. ********************************************************************/
  180. var hexToRgb = function(hex) {
  181. var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  182. return result ? {
  183. r: parseInt(result[1], 16),
  184. g: parseInt(result[2], 16),
  185. b: parseInt(result[3], 16)
  186. } : null;
  187. };
  188. /********************************************************************
  189. * Function switching the color of the elements. *
  190. * #parameters: *
  191. * name (string): key to the element to colorize. *
  192. * value (boolean): indicating if the element is to be drawn. *
  193. * #return: nothing *
  194. * #scope: public *
  195. ********************************************************************/
  196. this.colorElement = function(name, value){
  197. var color = hexToRgb(value)
  198. var str = "rgba(" + color.r + ", " + color.g + ", " + color.b + ", 1)"
  199. switch (name){
  200. case "backg":
  201. backColor = "rgba(" + color.r + ", " + color.g + ", " + color.b + ", 1)";
  202. break;
  203. case "verts":
  204. vertColor = "rgba(" + color.r + ", " + color.g + ", " + color.b + ", 1)";
  205. break;
  206. case "edges":
  207. edgeColor = "rgba(" + color.r + ", " + color.g + ", " + color.b + ", 1)";
  208. break;
  209. case "faces":
  210. faceColor = "rgba(" + color.r + ", " + color.g + ", " + color.b + ", " + faceAlpha + ")";
  211. break;
  212. }
  213. draw();
  214. };
  215. /********************************************************************
  216. * Function switching the use of mtl file for color. *
  217. * #parameters: *
  218. * value (boolean): indicating if the element is to be drawn. *
  219. * #return: nothing *
  220. * #scope: public *
  221. ********************************************************************/
  222. this.linkMaterial = function(value){
  223. useMtl = value;
  224. draw();
  225. }
  226. /********************************************************************
  227. * Function switching the alpha value of the faces. *
  228. * #parameters: *
  229. * percent (int): Value (0-100) of alpha. Must be processed (0-1) *
  230. * #return: nothing *
  231. * #scope: public *
  232. ********************************************************************/
  233. this.setAlpha = function(percent){
  234. if (percent >= 0 && percent <= 100){
  235. faceAlpha = percent / 100;
  236. faceColor = faceColor.substring(0, faceColor.lastIndexOf(",") + 2) + faceAlpha + ")";
  237. draw();
  238. }
  239. else
  240. console.log("setAlpha(" + percent + "); ERROR: Only values between 0 and 100 are allowed");
  241. };
  242. /********************************************************************
  243. * Function switching the rotation speed. *
  244. * #parameters: *
  245. * percent (int): Value (0-10) of speed. *
  246. * #return: nothing *
  247. * #scope: public *
  248. ********************************************************************/
  249. this.setRotationSpeed = function(percent){
  250. if (percent >= 0 && percent <= 10){
  251. rotationSpeed = Math.round((10 - percent) * 30);
  252. if (rotationSpeed == 0)
  253. rotationSpeed = 1;
  254. }
  255. else
  256. console.log("setRotationSpeed(" + percent + "); ERROR: Only values between 0 and 10 are allowed");
  257. }
  258. /********************************************************************
  259. * Function that rotates elements in the canvas along the Y axis, *
  260. * calculating the new position for each vertex in the vert array. *
  261. * #parameters: *
  262. * des (int): mouse displacement from the last frame. *
  263. * #return: nothing *
  264. * #scope: public *
  265. ********************************************************************/
  266. this.rotateY = function(des){
  267. for (var i = 0; i < vert.length; i ++) {
  268. var x = vert[i][0];
  269. var z = vert[i][2];
  270. vert[i][0] = x * Math.cos(des / rotationSpeed) - z * Math.sin(des / rotationSpeed);
  271. vert[i][2] = z * Math.cos(des / rotationSpeed) + x * Math.sin(des / rotationSpeed);
  272. }
  273. };
  274. /********************************************************************
  275. * Function that rotates elements in the canvas along the X axis, *
  276. * calculating the new position for each vertex in the vert array. *
  277. * #parameters: *
  278. * des (int): mouse displacement from the last frame. *
  279. * #return: nothing *
  280. * #scope: public *
  281. ********************************************************************/
  282. this.rotateX = function(des){
  283. for (var i = 0; i < vert.length; i ++) {
  284. var y = vert[i][1];
  285. var z = vert[i][2];
  286. vert[i][1] = y * Math.cos(des / rotationSpeed) - z * Math.sin(des / rotationSpeed);
  287. vert[i][2] = z * Math.cos(des / rotationSpeed) + y * Math.sin(des / rotationSpeed);
  288. }
  289. };
  290. /********************************************************************
  291. * Function switching the zoom speede of the faces. *
  292. * #parameters: *
  293. * percent (int): Value (0-100) of speed. *
  294. * #return: nothing *
  295. * #scope: public *
  296. ********************************************************************/
  297. this.setZoomSpeed = function(percent){
  298. if (percent >= 0 && percent <= 10)
  299. zoomSpeed = ((percent * 0.8) / 10) + 0.01;
  300. else
  301. console.log("setZoomSpeed(" + percent + "); ERROR: Only values between 0 and 10 are allowed");
  302. }
  303. /********************************************************************
  304. * Function that changes the scale variable, witch results in a *
  305. * zoom in or zoom out effect. *
  306. * #parameters: none *
  307. * #return: nothing *
  308. * #scope: public *
  309. ********************************************************************/
  310. this.zoom = function(dir){
  311. if (dir == "-")
  312. scale = Math.round(scale * (1 + zoomSpeed));
  313. if (dir == "+")
  314. scale = Math.round(scale * (1 - zoomSpeed));
  315. draw();
  316. };
  317. this.clear = function(){
  318. initArrays();
  319. clearCanvas();
  320. }
  321. /********************************************************************
  322. * Function that clears the canvas and gets it ready to draw on it. *
  323. * #parameters: none *
  324. * #return: nothing *
  325. * #scope: public *
  326. ********************************************************************/
  327. var clearCanvas = function(){
  328. ctx.clearRect(0, 0, canvas.width, canvas.height);
  329. if (dBackg)
  330. ctx.fillStyle = backColor;
  331. else
  332. ctx.fillStyle = "#ffffff";
  333. ctx.fillRect(-canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height);
  334. };
  335. /********************************************************************
  336. * Function initializing the arrays obj, vert and face as empty *
  337. * arrays. *
  338. * #parameters: none *
  339. * #return: nothing *
  340. * #scope: private *
  341. ********************************************************************/
  342. var initArrays = function(){
  343. material = null;
  344. vert = null;
  345. face = null;
  346. material = new Array();
  347. vert = new Array();
  348. face = new Array();
  349. };
  350. /********************************************************************
  351. * Function tat populates the vert[] array with and array of three *
  352. * elements, containig the coordinates, and the face[] array with *
  353. * the vertizes forming the face, plus one element indicating the *
  354. * codename of the material. *
  355. * #parameters: *
  356. * text (string): the content of the obj file. *
  357. * #return: nothing *
  358. * #scope: private *
  359. ********************************************************************/
  360. var readFile = function(text){
  361. var line;
  362. var v = 0;
  363. var f = 0;
  364. var m = 0;
  365. var max = 0;
  366. var j;
  367. var dist;
  368. var mat = "null";
  369. text = text + "\n";
  370. while (text.indexOf("\n") != -1){
  371. line = text.substring(0, text.indexOf("\n"));
  372. if (line.substring(0, 2) == "v "){
  373. vert[v] = new Array(3);
  374. line = line.substring(2);
  375. vert[v][0] = line.substring(0, line.indexOf(" "));
  376. line = line.substring(line.indexOf(" ") + 1);
  377. vert[v][1] = line.substring(0, line.indexOf(" "));
  378. line = line.substring(line.indexOf(" ") + 1);
  379. vert[v][2] = line;
  380. dist = Math.sqrt(vert[v][0] * vert[v][0] + vert[v][1] * vert[v][1] + vert[v][2] * vert[v][2]);
  381. if (dist > max)
  382. max = dist;
  383. v = v + 1;
  384. }
  385. else if (line.substring(0,2) == "f "){
  386. line = line.substring(2);
  387. face[f] = new Array();
  388. j = 0;
  389. while(line.indexOf(" ") != -1){
  390. face[f][j] = line.substring(0, line.indexOf(" ")) - 1;
  391. line = line.substring(line.indexOf(" ") + 1);
  392. j = j + 1;
  393. }
  394. face[f][j] = line - 1;
  395. face[f][j + 1] = mat;
  396. f = f + 1;
  397. }
  398. else if (line.substring(0, 6) == "usemtl"){
  399. mat = line.substring(7);
  400. }
  401. text = text.substring(text.indexOf("\n") + 1);
  402. }
  403. scale = calculateScale(max);
  404. totalVert = vert.length;
  405. totalFace = face.length;
  406. };
  407. /********************************************************************
  408. * Function tat populates the material[] array with an array *
  409. * containig, four values: the material name, and the three RGB *
  410. * values. *
  411. * #parameters: *
  412. * text (string): the content of the mtl file. *
  413. * #return: nothing *
  414. * #scope: private *
  415. ********************************************************************/
  416. var readMtlFile = function(text){
  417. text = text + "\n";
  418. i = -1;
  419. while (text.indexOf("\n") != -1){
  420. line = text.substring(0, text.indexOf("\n"));
  421. if (line.substring(0, 6) == "newmtl"){
  422. i = i + 1;
  423. material[i] = new Array(4);
  424. material[i][0] = line.substring(line.indexOf(" ") + 1);
  425. }
  426. //Kd, ddifuse color, usefull
  427. //Ka, ambient color, not now
  428. //Ks, specular color, not now
  429. //d or TR, transparency, not now
  430. else if (line.substring(0, 3) == "Kd "){
  431. line = line.substring(3);
  432. material[i][1] = line.substring(0, line.indexOf(" "));
  433. line = line.substring(line.indexOf(" ") + 1);
  434. material[i][2] = line.substring(0, line.indexOf(" "));
  435. line = line.substring(line.indexOf(" ") + 1);
  436. material[i][3] = line;
  437. }
  438. text = text.substring(text.indexOf("\n") + 1);
  439. }
  440. totalMaterial = material.length;
  441. };
  442. /********************************************************************
  443. * Function that automatically calculates the best scale for the *
  444. * modelermined number of elements, containing the verices that *
  445. * form a face, so the bigest dimension from the object can fit in *
  446. * the smallest dimension of the canvas. *
  447. * #parameters: *
  448. * text (int): the farthest vertex, from the center. *
  449. * #return: (int) The scale to be aplied. *
  450. * #scope: private *
  451. ********************************************************************/
  452. var calculateScale = function(max){
  453. var dim;
  454. if (canvas.width < canvas.height)
  455. dim = canvas.width;
  456. else
  457. dim = canvas.height;
  458. var sc = Math.round((0.95 * dim) / (2 * max));
  459. return sc;
  460. };
  461. /********************************************************************
  462. * Function that compares the average Z coordinate of two faces. *
  463. * Must be used when sorting the face array. *
  464. * #parameters: *
  465. * a (Array): One face. *
  466. * b (Array): The other face. *
  467. * #return: (int) -1, 1 or 0. *
  468. * #scope: private *
  469. ********************************************************************/
  470. var faceComparator = function(a,b){
  471. var aZ;
  472. var bZ;
  473. var i = 0;
  474. var sum = 0.0;
  475. while (i < a.length - 1){
  476. sum = sum + parseFloat(vert[a[i]][2]);
  477. i = i + 1;
  478. }
  479. aZ = sum / parseFloat(i);
  480. i = 0;
  481. sum = 0;
  482. while (i < b.length - 1){
  483. sum = sum + parseFloat(1 * vert[b[i]][2]);
  484. i = i + 1;
  485. }
  486. bZ = sum / parseFloat(i);
  487. if (aZ < bZ) return -1;
  488. if (aZ > bZ) return 1;
  489. return 0;
  490. };
  491. /********************************************************************
  492. * Function that writes text in the corner of the canvas. Must be *
  493. * called when everithing has been drawn. *
  494. * #parameters: none *
  495. * #return: nothing *
  496. * #scope: private *
  497. ********************************************************************/
  498. var writeCredits = function(){
  499. ctx.fillStyle = textColor;
  500. ctx.font = "12px Arial";
  501. var h = canvas.height / 2 - 14;
  502. var w = 10 - (canvas.height / 2);
  503. ctx.fillText("ObJS: " + totalVert + " vertizes " + totalFace + " faces " + totalMaterial + " materials", w, h);
  504. };
  505. /********************************************************************
  506. * Function that draws th vertizes, faces and edges in the canvas. *
  507. * #parameters: none *
  508. * #return: nothing *
  509. * #scope: private *
  510. ********************************************************************/
  511. var draw = function(){
  512. face = face.sort(faceComparator).reverse();
  513. clearCanvas();
  514. for (var i = 0; i < face.length; i++){
  515. ctx.strokeStyle = edgeColor;
  516. ctx.fillStyle = vertColor;
  517. ctx.beginPath();
  518. ctx.moveTo(scale * vert[face[i][0]][0], scale * vert[face[i][0]][1]);
  519. for (var j = 1; j < face[i].length - 1; j++) {
  520. ctx.lineTo(scale * vert[face[i][j]][0], scale * vert[face[i][j]][1]);
  521. if(dEdges)
  522. ctx.stroke();
  523. if(dVerts){
  524. var x = scale * vert[face[i][j]][0] - (vertSize / 2);
  525. var w = vertSize;
  526. var y = scale * vert[face[i][j]][1] - (vertSize / 2);
  527. var h = vertSize;
  528. ctx.fillRect(x, y, w, h);
  529. }
  530. }
  531. ctx.closePath();
  532. if (useMtl){
  533. //Get material with the same name as the one in the last element of the face
  534. j = 0;
  535. while (material[j][0] != face[i][face[i].length - 1] && j < material.length)
  536. j = j + 1;
  537. if (material[j][0] == face[i][face[i].length - 1])
  538. ctx.fillStyle = "rgba(" + Math.round(255 * material[j][1]) + ", " + Math.round(255 * material[j][2]) + ", " + Math.round(255 * material[j][3]) + ", " + faceAlpha + ")";
  539. else
  540. ctx.fillStyle = faceColor;
  541. }
  542. else
  543. ctx.fillStyle = faceColor;
  544. if(dFaces)
  545. ctx.fill();
  546. }
  547. writeCredits();
  548. };
  549. /********************************************************************
  550. * TODO: Function used when something in the canvas is clicked. *
  551. * #parameters: none *
  552. * #return: nothing *
  553. * #scope: private *
  554. ********************************************************************/
  555. var mouseClick = function(){
  556. };
  557. thisObJS = this;
  558. return this;
  559. }