index.html 64 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>ObJS</title>
  5. <link rel='shortcut icon' href='favicon.ico'/>
  6. <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
  7. <meta charset='utf-8'/>
  8. <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
  9. <link rel='author' href='I&ntilde;igo Valentin'/>
  10. <link rel='publisher' href='I&ntilde;igo Valentin'/>
  11. <meta name='description' content='Demo of ObJS - 3D viewer in pure Javascript'/>
  12. <!-- Script file -->
  13. <script type="text/javascript">
  14. /********************************************************************
  15. * The ObJS object initializes the canvas, assigning it to the HTML *
  16. * element, and set the required mouse events. *
  17. * #parameters: none *
  18. * #return: the ObJS object. *
  19. ********************************************************************/
  20. function ObJS(){
  21. /********************************************************************
  22. * Parameters for costumization. *
  23. ********************************************************************/
  24. var backColor = "rgba(200, 200, 200, 1)";
  25. var vertColor = "rgba(10, 10, 10, 1)";
  26. var edgeColor = "rgba(100, 70, 70, 1)";
  27. var faceColor = "rgba(180, 180, 255, .6)";
  28. var faceAlpha = .6;
  29. var textColor = "rgba(0, 0, 0, 1)";
  30. var vertSize = 4; //Make it even: at small scales will look better
  31. var rotationSpeed = 90; //Smaller = faster. 100 is a good speed
  32. var zoomSpeed = 0.161; //0-1. 0.2 or 0.3 are good speeds
  33. var moveSpeed = .5;
  34. /********************************************************************
  35. * Variable containing this object, to be refered from nested *
  36. * functions. *
  37. ********************************************************************/
  38. var thisObJS;
  39. /********************************************************************
  40. * Parameter used to scale the measures in the .obj file. Can be *
  41. * integer or float, greater than 0. 50 is a good default. *
  42. ********************************************************************/
  43. var scale;
  44. /********************************************************************
  45. * Variable to determine if the canvas has been initialized. *
  46. ********************************************************************/
  47. var canvasInitialized = false;
  48. /********************************************************************
  49. * Variable to determine if the mouse button is being holded down. *
  50. ********************************************************************/
  51. var mDown = false;
  52. var pinch = false;
  53. var pinchScale = 0;
  54. /********************************************************************
  55. * Variables containing the canvas and its context. *
  56. ********************************************************************/
  57. var canvas;
  58. var topCanvas;
  59. var frontCanvas;
  60. var sideCanvas;
  61. var ctx;
  62. var topCtx;
  63. var frontCtx;
  64. var sideCtx;
  65. /********************************************************************
  66. * Variables containing the number of elements. *
  67. ********************************************************************/
  68. var totalVert;
  69. var totalMaterial = 0;
  70. var totalFace
  71. /********************************************************************
  72. * Arrays containing the vertices, faces and materials. *
  73. ********************************************************************/
  74. var material;
  75. var vert;
  76. var face;
  77. /********************************************************************
  78. * Booleans to determine wich elements are to draw. *
  79. ********************************************************************/
  80. var dVerts = true;
  81. var dEdges = true;
  82. var dFaces = true;
  83. var dBackg = true;
  84. /********************************************************************
  85. * Booleans to indicate if mtl file should be used. *
  86. ********************************************************************/
  87. var hasMtl = true;
  88. var useMtl = true;
  89. /********************************************************************
  90. * Global variables that will contain the position of the mouse in *
  91. * the previous frame to calculate rotation. *
  92. ********************************************************************/
  93. var pX = null;
  94. var pY = null;
  95. /********************************************************************
  96. * Function that initializes the canvas, assignin it to the HTML *
  97. * element, and set the required mouse events. *
  98. * #parameters: *
  99. * canv (canvas): canvas to use. If omited, the one named *
  100. * named "ObJSView" will be used. *
  101. * #return: nothing *
  102. * #scope: public *
  103. ********************************************************************/
  104. this.initCanvas = function(canv){
  105. if (!canvasInitialized){
  106. if (canv != null){
  107. canvas = canv;
  108. }
  109. else{
  110. canvas = document.getElementById("ObJSView");
  111. }
  112. topCanvas = document.getElementById("ObJSTop");
  113. frontCanvas = document.getElementById("ObJSFront");
  114. sideCanvas = document.getElementById("ObJSSide");
  115. ctx = canvas.getContext("2d");
  116. topCtx = topCanvas.getContext("2d");
  117. frontCtx = frontCanvas.getContext("2d");
  118. sideCtx = sideCanvas.getContext("2d");
  119. ctx.translate(canvas.width / 2, canvas.height / 2);
  120. canvas.onmousedown = function(e){
  121. pX = e.offsetX == undefined?e.layerX:e.offsetX;
  122. pY = e.offsetY == undefined?e.layerY:e.offsetY;
  123. mDown = true;
  124. };
  125. canvas.onmouseup = function(e){
  126. if(mDown){
  127. mouseClick(e);
  128. }
  129. pX = null;
  130. pY = null;
  131. mDown = false;
  132. };
  133. canvas.onmousemove = function(e){
  134. if(!mDown){
  135. return;
  136. }
  137. var x = e.offsetX == undefined?e.layerX:e.offsetX;
  138. var y = e.offsetY == undefined?e.layerY:e.offsetY;
  139. if (e.shiftKey){
  140. thisObJS.moveHorizontal(x - pX);
  141. thisObJS.moveVertical(y - pY);
  142. }
  143. else{
  144. thisObJS.rotateY(x - pX);
  145. thisObJS.rotateX(y - pY);
  146. }
  147. pX = x;
  148. pY = y;
  149. return false;
  150. };
  151. canvas.onmouseout = function(e){
  152. if(mDown){
  153. mDown = false;
  154. }
  155. }
  156. canvas.addEventListener('mousewheel',function(e){
  157. e.preventDefault();
  158. if (event.wheelDelta > 0){
  159. thisObJS.zoom(1);
  160. }
  161. else{
  162. thisObJS.zoom(-1);
  163. }
  164. return false;
  165. }, false);
  166. canvas.addEventListener('touchstart',function(e){
  167. if(e.touches.length == 2) {
  168. pinch = true;
  169. pinchScale = Math.sqrt((e.touches[0].clientX-e.touches[1].clientX) * (e.touches[0].clientX-e.touches[1].clientX) + (e.touches[0].clientY-e.touches[1].clientY) * (e.touches[0].clientY-e.touches[1].clientY));
  170. }
  171. return false;
  172. }, false);
  173. canvas.addEventListener('touchend',function(e){
  174. if(pinch){
  175. pinch = false;
  176. }
  177. return false;
  178. }, false);
  179. canvas.addEventListener('touchcancel',function(e){
  180. if(pinch){
  181. pinch = false;
  182. }
  183. return false;
  184. }, false);
  185. canvas.addEventListener('touchleave',function(e){
  186. if(pinch){
  187. pinch = false;
  188. }
  189. return false;
  190. }, false);
  191. canvas.addEventListener('touchmove', function(e){
  192. e.preventDefault();
  193. if(pinch){
  194. var dist = Math.sqrt((e.touches[0].clientX-e.touches[1].clientX) * (e.touches[0].clientX-e.touches[1].clientX) + (e.touches[0].clientY-e.touches[1].clientY) * (e.touches[0].clientY-e.touches[1].clientY));
  195. if (dist > pinchScale){
  196. thisObJS.zoom(1);
  197. }
  198. else if (dist < pinchScale){
  199. thisObJS.zoom(-1);
  200. }
  201. pinchScale = dist;
  202. }
  203. else{
  204. var x = e.touches[0].clientX
  205. var y = e.touches[0].clientY;
  206. thisObJS.rotateY(x - pX);
  207. thisObJS.rotateX(y - pY);
  208. pX = x;
  209. pY = y;
  210. }
  211. return false;
  212. }, false);
  213. canvasInitialized = true;
  214. }
  215. };
  216. /********************************************************************
  217. * Main function, called every time a file is to be loaded. *
  218. * #parameters: *
  219. * file (string): name of the file, without path or extension. *
  220. * #return: nothing *
  221. * #scope: public *
  222. ********************************************************************/
  223. this.load = function(file){
  224. if(!canvasInitialized){
  225. initCanvas();
  226. }
  227. //Get obj file
  228. var xmlhttp = new XMLHttpRequest();
  229. xmlhttp.open("GET", file, false);
  230. xmlhttp.send();
  231. var fileContent = xmlhttp.responseText;
  232. //Get mtl file
  233. xmlhttp = new XMLHttpRequest();
  234. xmlhttp.open("GET",file.substring(0, file.lastIndexOf(".")) + ".mtl", false);
  235. xmlhttp.send();
  236. var mtlContent = xmlhttp.responseText;
  237. initArrays();
  238. readFile(fileContent);
  239. readMtlFile(mtlContent);
  240. draw();
  241. drawSides();
  242. };
  243. /********************************************************************
  244. * Getters that determine if an element is being drawn. *
  245. ********************************************************************/
  246. this.isBackgroundDrawn = function(){
  247. return dBackg;
  248. };
  249. this.ivertexDrawn = function(){
  250. return dVerts;
  251. };
  252. this.isEdgeDrawn = function(){
  253. return dEdges;
  254. };
  255. this.ifaceDrawn = function(){
  256. return dFaces;
  257. };
  258. this.isMaterialUsed = function(){
  259. return useMtl;
  260. };
  261. /********************************************************************
  262. * Getters that determine if the model has associated materials. *
  263. ********************************************************************/
  264. this.hasMaterial = function(){
  265. return hasMtl;
  266. };
  267. /********************************************************************
  268. * Getters for the color of the elements. *
  269. ********************************************************************/
  270. this.getBackgroundColor = function (){
  271. var str = backColor;
  272. var r = str.substring(5, str.indexOf(', '));
  273. str = str.substring(str.indexOf(', ') + 2);
  274. var g = str.substring(0, str.indexOf(', '));
  275. str = str.substring(str.indexOf(', ') + 2);
  276. var b = str.substring(0, str.indexOf(', '));
  277. var a = 1;
  278. return {red: r, green: g, blue: b, alpha: a};
  279. };
  280. this.gevertexColor = function (){
  281. var str = vertColor;
  282. var r = str.substring(5, str.indexOf(', '));
  283. str = str.substring(str.indexOf(', ') + 2);
  284. var g = str.substring(0, str.indexOf(', '));
  285. str = str.substring(str.indexOf(', ') + 2);
  286. var b = str.substring(0, str.indexOf(', '));
  287. var a = 1;
  288. return {red: r, green: g, blue: b, alpha: a};
  289. };
  290. this.getEdgeColor = function (){
  291. var str = edgeColor;
  292. var r = str.substring(5, str.indexOf(', '));
  293. str = str.substring(str.indexOf(', ') + 2);
  294. var g = str.substring(0, str.indexOf(', '));
  295. str = str.substring(str.indexOf(', ') + 2);
  296. var b = str.substring(0, str.indexOf(', '));
  297. var a = 1;
  298. return {red: r, green: g, blue: b, alpha: a};
  299. };
  300. this.gefaceColor = function (){
  301. var str = faceColor;
  302. var r = str.substring(5, str.indexOf(', '));
  303. str = str.substring(str.indexOf(', ') + 2);
  304. var g = str.substring(0, str.indexOf(', '));
  305. str = str.substring(str.indexOf(', ') + 2);
  306. var b = str.substring(0, str.indexOf(', '));
  307. str = str.substring(str.indexOf(', ') + 2);
  308. var a = str.substring(0, str.indexOf(')'));
  309. return {red: r, green: g, blue: b, alpha: a};
  310. };
  311. /********************************************************************
  312. * Functions switching the elements drawn (dVerts, dEdges, dFaces). *
  313. * #parameters: *
  314. * on (boolean): indicating if the element is to be drawn. *
  315. * #return: nothing *
  316. * #scope: public *
  317. ********************************************************************/
  318. this.drawVertizes = function(on){
  319. if (on == true || on == false){
  320. dVerts = on;
  321. draw();
  322. }
  323. };
  324. this.drawEdges = function(on){
  325. if (on == true || on == false){
  326. dEdges = on;
  327. draw();
  328. }
  329. };
  330. this.drawFaces = function(on){
  331. if (on == true || on == false){
  332. dFaces = on;
  333. draw();
  334. }
  335. };
  336. this.drawBackground = function(on){
  337. if (on == true || on == false){
  338. dBackg = on;
  339. draw();
  340. }
  341. };
  342. /********************************************************************
  343. * Functions switching the color of the elements. *
  344. * #parameters: *
  345. * code (string): hex color value, in #fff or #ffffff form. If *
  346. * wrong, a message will be printed to console, *
  347. * and nothing will be changed. *
  348. * #return: nothing *
  349. * #scope: public *
  350. ********************************************************************/
  351. this.severtizesColor = function(code){
  352. if (isColor(code)){
  353. var color = hexToRgb(code);
  354. vertColor = "rgba(" + color.r + ", " + color.g + ", " + color.b + ", 1)";
  355. draw();
  356. }
  357. else{
  358. console.log("severtizesColor(" + code + ") ERROR: " + code + " not a valid hex color");
  359. }
  360. };
  361. this.setEdgesColor = function(code){
  362. if (isColor(code)){
  363. var color = hexToRgb(code);
  364. edgeColor = "rgba(" + color.r + ", " + color.g + ", " + color.b + ", 1)";
  365. draw();
  366. }
  367. else{
  368. console.log("setEdgesColor(" + code + ") ERROR: " + code + " not a valid hex color");
  369. }
  370. };
  371. this.sefacesColor = function(code){
  372. if (isColor(code)){
  373. var color = hexToRgb(code);
  374. faceColor = "rgba(" + color.r + ", " + color.g + ", " + color.b + ", 1)";
  375. draw();
  376. }
  377. else{
  378. console.log("sefacesColor(" + code + ") ERROR: " + code + " not a valid hex color");
  379. }
  380. };
  381. this.setBackgroundColor = function(code){
  382. if (isColor(code)){
  383. var color = hexToRgb(code);
  384. backColor = "rgba(" + color.r + ", " + color.g + ", " + color.b + ", 1)";
  385. draw();
  386. }
  387. else{
  388. console.log("setBackgroundColor(" + code + ") ERROR: " + code + " not a valid hex color");
  389. }
  390. };
  391. /********************************************************************
  392. * Function switching the use of mtl file for color. *
  393. * #parameters: *
  394. * value (boolean): indicating if the element is to be drawn. *
  395. * #return: nothing *
  396. * #scope: public *
  397. ********************************************************************/
  398. this.linkMaterial = function(value){
  399. useMtl = value;
  400. draw();
  401. }
  402. /********************************************************************
  403. * Function switching the alpha value of the faces. *
  404. * #parameters: *
  405. * percent (int): Value (0-100) of alpha. Must be processed (0-1) *
  406. * #return: nothing *
  407. * #scope: public *
  408. ********************************************************************/
  409. this.setAlpha = function(percent){
  410. if (percent >= 0 && percent <= 100){
  411. faceAlpha = percent / 100;
  412. faceColor = faceColor.substring(0, faceColor.lastIndexOf(",") + 2) + faceAlpha + ")";
  413. draw();
  414. }
  415. else{
  416. console.log("setAlpha(" + percent + "); ERROR: Only values between 0 and 100 are allowed");
  417. }
  418. };
  419. /********************************************************************
  420. * Function switching the rotation speed. *
  421. * #parameters: *
  422. * percent (int): Value (0-10) of speed. *
  423. * #return: nothing *
  424. * #scope: public *
  425. ********************************************************************/
  426. this.setRotationSpeed = function(percent){
  427. if (percent >= 0 && percent <= 10){
  428. rotationSpeed = Math.round((10 - percent) * 30);
  429. if (rotationSpeed == 0){
  430. rotationSpeed = 1;
  431. }
  432. }
  433. else{
  434. console.log("setRotationSpeed(" + percent + "); ERROR: Only values between 0 and 10 are allowed");
  435. }
  436. }
  437. /********************************************************************
  438. * Functions that rotate elements in the canvas along the *
  439. * correspondent axys, calculating the new position for each vertex *
  440. * in the vert array. *
  441. * #parameters: *
  442. * des (int): distance to rotate. *
  443. * #return: nothing *
  444. * #scope: public *
  445. ********************************************************************/
  446. this.rotateY = function(des){
  447. for (var i = 0; i < vert.length; i ++) {
  448. var x = vert[i][0];
  449. var z = vert[i][2];
  450. vert[i][0] = x * Math.cos(des / rotationSpeed) - z * Math.sin(des / rotationSpeed);
  451. vert[i][2] = z * Math.cos(des / rotationSpeed) + x * Math.sin(des / rotationSpeed);
  452. }
  453. draw();
  454. };
  455. this.rotateX = function(des){
  456. for (var i = 0; i < vert.length; i ++) {
  457. var y = vert[i][1];
  458. var z = vert[i][2];
  459. vert[i][1] = y * Math.cos(des / rotationSpeed) - z * Math.sin(des / rotationSpeed);
  460. vert[i][2] = z * Math.cos(des / rotationSpeed) + y * Math.sin(des / rotationSpeed);
  461. }
  462. draw();
  463. };
  464. /********************************************************************
  465. * Function switching the movement speed of the model. *
  466. * #parameters: *
  467. * percent (int): Value (0-10) of speed. *
  468. * #return: nothing *
  469. * #scope: public *
  470. ********************************************************************/
  471. this.setMovementSpeed = function(percent){
  472. if (percent >= 0 && percent <= 10){
  473. moveSpeed = ((percent * 0.8) / 10) + 0.01;
  474. }
  475. else{
  476. console.log("setMoveSpeed(" + percent + "); ERROR: Only values between 0 and 10 are allowed");
  477. }
  478. }
  479. /********************************************************************
  480. * Functions that move elements in the canvas along the selected *
  481. * direction, calculating the new position for each vertex in the *
  482. * vert array. *
  483. * #parameters: *
  484. * des (int): distance to move. *
  485. * #return: nothing *
  486. * #scope: public *
  487. ********************************************************************/
  488. this.moveVertical = function(des){
  489. for (var i = 0; i < vert.length; i ++){
  490. vert[i][1] = (vert[i][1] * 1) + des * (moveSpeed / 50);
  491. }
  492. draw();
  493. };
  494. this.moveHorizontal = function(des){
  495. for (var i = 0; i < vert.length; i ++){
  496. vert[i][0] = (vert[i][0] * 1) + des * (moveSpeed / 50);
  497. }
  498. draw();
  499. };
  500. /********************************************************************
  501. * Function switching the zoom speede of the faces. *
  502. * #parameters: *
  503. * percent (int): Value (0-100) of speed. *
  504. * #return: nothing *
  505. * #scope: public *
  506. ********************************************************************/
  507. this.setZoomSpeed = function(percent){
  508. if (percent >= 0 && percent <= 10){
  509. zoomSpeed = ((percent * 0.8) / 10) + 0.01;
  510. }
  511. else{
  512. console.log("setZoomSpeed(" + percent + "); ERROR: Only values between 0 and 10 are allowed");
  513. }
  514. }
  515. /********************************************************************
  516. * Function that changes the scale variable, witch results in a *
  517. * zoom in or zoom out effect. *
  518. * #parameters: none *
  519. * #return: nothing *
  520. * #scope: public *
  521. ********************************************************************/
  522. this.zoom = function(val){
  523. scale = scale + val * scale * zoomSpeed;
  524. draw();
  525. };
  526. /********************************************************************
  527. * Function that clears the arrays and the canvas. Use to unload a *
  528. * model. *
  529. * #parameters: none *
  530. * #return: nothing *
  531. * #scope: public *
  532. ********************************************************************/
  533. this.clear = function(){
  534. initArrays();
  535. clearCanvas();
  536. };
  537. /********************************************************************
  538. * Function that turns a hex color code in RGB. *
  539. * stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb *
  540. * #parameters: *
  541. * hex (string): color code, for example "#33ff99" *
  542. * #return: (Array) r, g, b, with their correspondent color. *
  543. * #scope: private *
  544. ********************************************************************/
  545. var hexToRgb = function(hex) {
  546. var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  547. return result ? {
  548. r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16)
  549. } : null;
  550. };
  551. /********************************************************************
  552. * Function that checks if a string is a valid hexadecimal color *
  553. * code, in the format '#ffffff' or '#fff' *
  554. * #parameters: *
  555. * code (string): Code to check. *
  556. * #return: (Boolean) True if valid color code, false otherwise. *
  557. * #scope: private *
  558. ********************************************************************/
  559. var isColor = function(code){
  560. if (code[0] != '#'){
  561. return false;
  562. }
  563. if (code.length != 4 && code.length != 7){
  564. return false
  565. }
  566. for (var i = 1; i < code.length; i ++){
  567. if (code[i] < '0' || (code[i] > 9 && code[i] < 'A') || (code[i] > 'Z' && code[i] < 'a') || code[i] > 'z'){
  568. return false;
  569. }
  570. }
  571. return true;
  572. };
  573. /********************************************************************
  574. * Function that clears the canvas and gets it ready to draw on it. *
  575. * #parameters: none *
  576. * #return: nothing *
  577. * #scope: private *
  578. ********************************************************************/
  579. var clearCanvas = function(){
  580. ctx.clearRect(0, 0, canvas.width, canvas.height);
  581. if (dBackg){
  582. ctx.fillStyle = backColor;
  583. }
  584. else{
  585. ctx.fillStyle = "#ffffff";
  586. }
  587. ctx.fillRect(-canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height);
  588. };
  589. /********************************************************************
  590. * Function initializing the arrays obj, vert and face as empty *
  591. * arrays. *
  592. * #parameters: none *
  593. * #return: nothing *
  594. * #scope: private *
  595. ********************************************************************/
  596. var initArrays = function(){
  597. material = null;
  598. vert = null;
  599. face = null;
  600. material = new Array();
  601. vert = new Array();
  602. face = new Array();
  603. };
  604. /********************************************************************
  605. * Function tat populates the vert[] array with and array of three *
  606. * elements, containig the coordinates, and the face[] array with *
  607. * the vertices forming the face, plus one element indicating the *
  608. * codename of the material. *
  609. * #parameters: *
  610. * text (string): the content of the obj file. *
  611. * #return: nothing *
  612. * #scope: private *
  613. ********************************************************************/
  614. var readFile = function(text){
  615. var line;
  616. var v = 0;
  617. var f = 0;
  618. var m = 0;
  619. var max = 0;
  620. var j;
  621. var dist;
  622. var fac;
  623. var mat = "null";
  624. text = text + "\n";
  625. while (text.indexOf("\n") != -1){
  626. line = text.substring(0, text.indexOf("\n"));
  627. if (line.substring(0, 2) == "v "){
  628. vert[v] = new Array(3);
  629. line = line.substring(2);
  630. vert[v][0] = line.substring(0, line.indexOf(" "));
  631. line = line.substring(line.indexOf(" ") + 1);
  632. vert[v][1] = line.substring(0, line.indexOf(" "));
  633. line = line.substring(line.indexOf(" ") + 1);
  634. vert[v][2] = line;
  635. dist = Math.sqrt(vert[v][0] * vert[v][0] + vert[v][1] * vert[v][1] + vert[v][2] * vert[v][2]);
  636. if (dist > max){
  637. max = dist;
  638. }
  639. v = v + 1;
  640. }
  641. else if (line.substring(0,2) == "f "){
  642. line = line.substring(2);
  643. face[f] = new Array();
  644. j = 0;
  645. while(line.indexOf(" ") != -1){
  646. face[f][j] = line.substring(0, line.indexOf(" "));
  647. if (face[f][j].indexOf('/') != -1){
  648. face[f][j] = face[f][j].substring(0, face[f][j].indexOf('/'));
  649. }
  650. face[f][j] = face[f][j] - 1;
  651. line = line.substring(line.indexOf(" ") + 1);
  652. j = j + 1;
  653. }
  654. face[f][j] = line;
  655. if (face[f][j].indexOf('/') != -1){
  656. face[f][j] = face[f][j].substring(0, face[f][j].indexOf('/'));
  657. }
  658. face[f][j] = face[f][j] - 1;
  659. face[f][j + 1] = mat;
  660. f = f + 1;
  661. }
  662. else if (line.substring(0, 6) == "usemtl"){
  663. mat = line.substring(7);
  664. }
  665. text = text.substring(text.indexOf("\n") + 1);
  666. }
  667. scale = calculateScale(max);
  668. totalVert = vert.length;
  669. totalFace = face.length;
  670. };
  671. /********************************************************************
  672. * Function tat populates the material[] array with an array *
  673. * containig, four values: the material name, and the three RGB *
  674. * values. *
  675. * #parameters: *
  676. * text (string): the content of the mtl file. *
  677. * #return: nothing *
  678. * #scope: private *
  679. ********************************************************************/
  680. var readMtlFile = function(text){
  681. text = text + "\n";
  682. i = -1;
  683. while (text.indexOf("\n") != -1){
  684. line = text.substring(0, text.indexOf("\n"));
  685. if (line.substring(0, 6) == "newmtl"){
  686. i = i + 1;
  687. material[i] = new Array(4);
  688. material[i][0] = line.substring(line.indexOf(" ") + 1);
  689. }
  690. //Kd, ddifuse color, usefull
  691. //Ka, ambient color, not implemented
  692. //Ks, specular color, not implemented
  693. //d or TR, transparency, not now
  694. else if (line.substring(0, 3) == "Kd "){
  695. line = line.substring(3);
  696. material[i][1] = line.substring(0, line.indexOf(" "));
  697. line = line.substring(line.indexOf(" ") + 1);
  698. material[i][2] = line.substring(0, line.indexOf(" "));
  699. line = line.substring(line.indexOf(" ") + 1);
  700. material[i][3] = line;
  701. }
  702. text = text.substring(text.indexOf("\n") + 1);
  703. }
  704. totalMaterial = material.length;
  705. if (totalMaterial == 0){
  706. hasMtl = false;
  707. }
  708. };
  709. /********************************************************************
  710. * Function that automatically calculates the best scale for the *
  711. * modelermined number of elements, containing the verices that *
  712. * form a face, so the bigest dimension from the object can fit in *
  713. * the smallest dimension of the canvas. *
  714. * #parameters: *
  715. * text (int): the farthest vertex, from the center. *
  716. * #return: (int) The scale to be aplied. *
  717. * #scope: private *
  718. ********************************************************************/
  719. var calculateScale = function(max){
  720. var dim;
  721. if (canvas.width < canvas.height){
  722. dim = canvas.width;
  723. }
  724. else{
  725. dim = canvas.height;
  726. }
  727. var sc = Math.round((0.95 * dim) / (2 * max));
  728. return sc;
  729. };
  730. /********************************************************************
  731. * Function that compares the average Z coordinate of two faces. *
  732. * Must be used when sorting the face array. *
  733. * #parameters: *
  734. * a (Array): One face. *
  735. * b (Array): The other face. *
  736. * #return: (int) -1, 1 or 0. *
  737. * #scope: private *
  738. ********************************************************************/
  739. var faceComparator = function(a,b){
  740. var aZ;
  741. var bZ;
  742. var i = 0;
  743. var sum = 0.0;
  744. while (i < a.length - 1){
  745. sum = sum + parseFloat(vert[a[i]][2]);
  746. i = i + 1;
  747. }
  748. aZ = sum / parseFloat(i);
  749. i = 0;
  750. sum = 0;
  751. while (i < b.length - 1){
  752. sum = sum + parseFloat(1 * vert[b[i]][2]);
  753. i = i + 1;
  754. }
  755. bZ = sum / parseFloat(i);
  756. if (aZ < bZ) return -1;
  757. if (aZ > bZ) return 1;
  758. return 0;
  759. };
  760. /********************************************************************
  761. * Function that writes text in the corner of the canvas. Must be *
  762. * called when everithing has been drawn. *
  763. * #parameters: none *
  764. * #return: nothing *
  765. * #scope: private *
  766. ********************************************************************/
  767. var writeCredits = function(){
  768. /*ctx.fillStyle = textColor;
  769. ctx.font = "12px Arial";
  770. var h = canvas.height / 2 - 14;
  771. var w = 10 - (canvas.width / 2);
  772. ctx.fillText("ObJS: " + totalVert + " vertices " + totalFace + " faces " + totalMaterial + " materials", w, h);*/
  773. document.getElementById('details_verts').innerHTML = totalVert + ' vertices';
  774. document.getElementById('details_faces').innerHTML = totalFace + ' faces';
  775. document.getElementById('details_materials').innerHTML = totalMaterial + ' materials';
  776. };
  777. var drawSides = function(){
  778. var sScale = scale * 0.2;
  779. // TOP
  780. topCtx.clearRect(0, 0, canvas.width, canvas.height);
  781. topCtx.fillStyle = "#ffffff";
  782. topCtx.fillRect(-topCanvas.width / 2, -topCanvas.height / 2, topCanvas.width, canvas.height);
  783. var des = Math.PI / 2;
  784. for (var i = 0; i < vert.length; i ++) {
  785. var y = vert[i][1];
  786. var z = vert[i][2];
  787. vert[i][1] = y * Math.cos(des) - z * Math.sin(des);
  788. vert[i][2] = z * Math.cos(des) + y * Math.sin(des);
  789. }
  790. for (var i = 0; i < face.length; i++){
  791. topCtx.strokeStyle = '#00000099';
  792. topCtx.beginPath();
  793. topCtx.moveTo(50 + (sScale * vert[face[i][0]][0]), 50 + (sScale * vert[face[i][0]][1]));
  794. for (var j = 1; j < face[i].length - 1; j++) {
  795. topCtx.lineTo(50 + (sScale * vert[face[i][j]][0]), 50 + (sScale * vert[face[i][j]][1]));
  796. //topCtx.stroke();
  797. }
  798. topCtx.closePath();
  799. if (useMtl){
  800. //Get material with the same name as the one in the last element of the face
  801. j = 0;
  802. while (material[j][0] != face[i][face[i].length - 1] && j < material.length){
  803. j = j + 1;
  804. }
  805. if (material[j][0] == face[i][face[i].length - 1]){
  806. topCtx.fillStyle = "rgba(" + Math.round(255 * material[j][1]) + ", " + Math.round(255 * material[j][2]) + ", " + Math.round(255 * material[j][3]) + ", " + 1 + ")";
  807. }
  808. else{
  809. topCtx.fillStyle = faceColor;
  810. }
  811. }
  812. else{
  813. topCtx.fillStyle = faceColor;
  814. }
  815. topCtx.stroke();
  816. if(dFaces){
  817. topCtx.fill();
  818. }
  819. }
  820. //restore
  821. des = Math.PI / -2;
  822. for (var i = 0; i < vert.length; i ++) {
  823. var y = vert[i][1];
  824. var z = vert[i][2];
  825. vert[i][1] = y * Math.cos(des) - z * Math.sin(des);
  826. vert[i][2] = z * Math.cos(des) + y * Math.sin(des);
  827. }
  828. // FRONT
  829. frontCtx.clearRect(0, 0, canvas.width, canvas.height);
  830. frontCtx.fillStyle = "#ffffff";
  831. frontCtx.fillRect(-frontCanvas.width / 2, -frontCanvas.height / 2, frontCanvas.width, canvas.height);
  832. des = Math.PI / 2;
  833. for (var i = 0; i < face.length; i++){
  834. frontCtx.strokeStyle = '#00000099';
  835. frontCtx.beginPath();
  836. frontCtx.moveTo(50 + (sScale * vert[face[i][0]][0]), 50 + (sScale * vert[face[i][0]][1]));
  837. for (var j = 1; j < face[i].length - 1; j++) {
  838. frontCtx.lineTo(50 + (sScale * vert[face[i][j]][0]), 50 + (sScale * vert[face[i][j]][1]));
  839. //frontCtx.stroke();
  840. }
  841. frontCtx.closePath();
  842. if (useMtl){
  843. //Get material with the same name as the one in the last element of the face
  844. j = 0;
  845. while (material[j][0] != face[i][face[i].length - 1] && j < material.length){
  846. j = j + 1;
  847. }
  848. if (material[j][0] == face[i][face[i].length - 1]){
  849. frontCtx.fillStyle = "rgba(" + Math.round(255 * material[j][1]) + ", " + Math.round(255 * material[j][2]) + ", " + Math.round(255 * material[j][3]) + ", " + 1 + ")";
  850. }
  851. else{
  852. frontCtx.fillStyle = faceColor;
  853. }
  854. }
  855. else{
  856. frontCtx.fillStyle = faceColor;
  857. }
  858. frontCtx.stroke();
  859. if(dFaces){
  860. frontCtx.fill();
  861. }
  862. }
  863. // SIDE
  864. sideCtx.clearRect(0, 0, canvas.width, canvas.height);
  865. sideCtx.fillStyle = "#ffffff";
  866. sideCtx.fillRect(-sideCanvas.width / 2, -sideCanvas.height / 2, sideCanvas.width, canvas.height);
  867. des = Math.PI / 2;
  868. for (var i = 0; i < vert.length; i ++) {
  869. var x = vert[i][0];
  870. var z = vert[i][2];
  871. vert[i][0] = x * Math.cos(des) - z * Math.sin(des);
  872. vert[i][2] = z * Math.cos(des) + x * Math.sin(des);
  873. }
  874. for (var i = 0; i < face.length; i++){
  875. sideCtx.strokeStyle = '#00000099';
  876. sideCtx.beginPath();
  877. sideCtx.moveTo(50 + (sScale * vert[face[i][0]][0]), 50 + (sScale * vert[face[i][0]][1]));
  878. for (var j = 1; j < face[i].length - 1; j++) {
  879. sideCtx.lineTo(50 + (sScale * vert[face[i][j]][0]), 50 + (sScale * vert[face[i][j]][1]));
  880. //sideCtx.stroke();
  881. }
  882. sideCtx.closePath();
  883. if (useMtl){
  884. //Get material with the same name as the one in the last element of the face
  885. j = 0;
  886. while (material[j][0] != face[i][face[i].length - 1] && j < material.length){
  887. j = j + 1;
  888. }
  889. if (material[j][0] == face[i][face[i].length - 1]){
  890. sideCtx.fillStyle = "rgba(" + Math.round(255 * material[j][1]) + ", " + Math.round(255 * material[j][2]) + ", " + Math.round(255 * material[j][3]) + ", " + 1 + ")";
  891. }
  892. else{
  893. sideCtx.fillStyle = faceColor;
  894. }
  895. }
  896. else{
  897. sideCtx.fillStyle = faceColor;
  898. }
  899. sideCtx.stroke();
  900. if(dFaces){
  901. sideCtx.fill();
  902. }
  903. }
  904. //Restore
  905. des = Math.PI / -2;
  906. for (var i = 0; i < vert.length; i ++) {
  907. var x = vert[i][0];
  908. var z = vert[i][2];
  909. vert[i][0] = x * Math.cos(des) - z * Math.sin(des);
  910. vert[i][2] = z * Math.cos(des) + x * Math.sin(des);
  911. }
  912. };
  913. /********************************************************************
  914. * Function that draws th vertices, faces and edges in the canvas. *
  915. * #parameters: none *
  916. * #return: nothing *
  917. * #scope: private *
  918. ********************************************************************/
  919. var draw = function(){
  920. face = face.sort(faceComparator).reverse();
  921. clearCanvas();
  922. for (var i = 0; i < face.length; i++){
  923. ctx.strokeStyle = edgeColor;
  924. ctx.fillStyle = vertColor;
  925. ctx.beginPath();
  926. ctx.moveTo(scale * vert[face[i][0]][0], scale * vert[face[i][0]][1]);
  927. var j = 0;
  928. for (j = 0; j < face[i].length - 1; j++) {
  929. ctx.lineTo(scale * vert[face[i][j]][0], scale * vert[face[i][j]][1]);
  930. if(dEdges){
  931. ctx.stroke();
  932. }
  933. if(dVerts){
  934. var x = scale * vert[face[i][j]][0] - (vertSize / 2);
  935. var w = vertSize;
  936. var y = scale * vert[face[i][j]][1] - (vertSize / 2);
  937. var h = vertSize;
  938. ctx.fillRect(x, y, w, h);
  939. }
  940. }
  941. ctx.closePath();
  942. if (useMtl){
  943. //Get material with the same name as the one in the last element of the face
  944. j = 0;
  945. while (material[j][0] != face[i][face[i].length - 1] && j < material.length){
  946. j = j + 1;
  947. }
  948. if (material[j][0] == face[i][face[i].length - 1]){
  949. ctx.fillStyle = "rgba(" + Math.round(255 * material[j][1]) + ", " + Math.round(255 * material[j][2]) + ", " + Math.round(255 * material[j][3]) + ", " + faceAlpha + ")";
  950. }
  951. else{
  952. ctx.fillStyle = faceColor;
  953. }
  954. }
  955. else{
  956. ctx.fillStyle = faceColor;
  957. }
  958. if(dFaces){
  959. ctx.fill();
  960. }
  961. }
  962. writeCredits();
  963. };
  964. /********************************************************************
  965. * TODO: Function used when something in the canvas is clicked. *
  966. * #parameters: none *
  967. * #return: nothing *
  968. * #scope: private *
  969. ********************************************************************/
  970. var mouseClick = function(){
  971. };
  972. thisObJS = this;
  973. return this;
  974. }
  975. </script>
  976. <style>
  977. body{
  978. text-align: center;
  979. background-color: #002200;
  980. }
  981. h1{
  982. margin-bottom: 0;
  983. color: #ffffff;
  984. }
  985. h2{
  986. font-size: 110%;
  987. font-style: italic;
  988. margin-top: 0;
  989. color: #ffffff;
  990. }
  991. section{
  992. display: inline-block;
  993. vertical-align: top;
  994. height: 526px;
  995. margin: 0;
  996. border: 2px solid #000000;
  997. }
  998. section h3{
  999. height: 24px;
  1000. margin: 0;
  1001. border-bottom: 2px solid #000000;
  1002. background-color: #cceeff;
  1003. }
  1004. section#sideViews{
  1005. width: 120px;
  1006. background-color: #e0f5ff;
  1007. border-top-left-radius: 20px;
  1008. border-bottom-left-radius: 20px;
  1009. }
  1010. section#sideViews h3{
  1011. border-top-left-radius: 20px;
  1012. }
  1013. section#sideViews h4{
  1014. margin: 10px auto 0 auto;
  1015. }
  1016. section#sideViews canvas{
  1017. border: 0.1em solid #000000;
  1018. margin: 0 10px 10px 10px;
  1019. background-color: #ffffff;
  1020. }
  1021. section#sideViews div#details{
  1022. margin-top: 20px;
  1023. }
  1024. section#sideViews div#details span{
  1025. font-size: 70%;
  1026. display: block;
  1027. color: #444444;
  1028. font-weight: bold;
  1029. }
  1030. section#controls{
  1031. text-align: center;
  1032. width: 260px;
  1033. background-color: #e0f5ff;
  1034. border-top-right-radius: 20px;
  1035. border-bottom-right-radius: 20px;
  1036. }
  1037. section#controls h3{
  1038. border-top-right-radius: 20px;
  1039. margin-bottom: 20px;
  1040. }
  1041. section#controls table#colors{
  1042. margin: 20px auto;
  1043. border-collapse: collapse;
  1044. }
  1045. section#controls table#colors td{
  1046. border: 1px solid #000000;
  1047. }
  1048. section#controls table#colors td.action{
  1049. background-color: #aaaaaa;
  1050. }
  1051. section#controls table#colors td.item{
  1052. background-color: #bbbbbb;
  1053. }
  1054. section#controls table#colors td.action.item{
  1055. border: 0px;
  1056. background-color: initial;
  1057. }
  1058. section#controls table#colors td img{
  1059. width: 30px;
  1060. height: 30px;
  1061. }
  1062. section#controls table#colors td input[type="color"]{
  1063. width: 30px;
  1064. height: 30px;
  1065. padding: 1px;
  1066. }
  1067. section#controls input[type="range"]{
  1068. display: block;
  1069. width: 80%;
  1070. margin: auto auto 15px auto;
  1071. height: 20px;
  1072. }
  1073. canvas#ObJSView{
  1074. width: 500px;
  1075. height: 500px;
  1076. }
  1077. td#ObJSControl input[type="color"]{
  1078. width: 1.3em;
  1079. height: 1.3em;
  1080. padding: 0;
  1081. vertical-align: middle;
  1082. }
  1083. td#ObJSControl input[type="range"]{
  1084. vertical-align: middle;
  1085. }
  1086. </style>
  1087. </head>
  1088. <body onload="objs = new ObJS(); objs.initCanvas();objs.load(document.getElementById('objSelector').value);">
  1089. <h1>
  1090. ObJS
  1091. </h1>
  1092. <h2>
  1093. by I&ntilde;igo Valentin
  1094. </h2>
  1095. <section id='sideViews'>
  1096. <h3>
  1097. Details
  1098. </h3>
  1099. <h4>
  1100. Top
  1101. </h4>
  1102. <canvas id="ObJSTop" width="100" height="100">
  1103. Your browser does not support the HTML5 canvas tag.
  1104. </canvas>
  1105. <h4>
  1106. Front
  1107. </h4>
  1108. <canvas id="ObJSFront" width="100" height="100">
  1109. Your browser does not support the HTML5 canvas tag.
  1110. </canvas>
  1111. <h4>
  1112. Side
  1113. </h4>
  1114. <canvas id="ObJSSide" width="100" height="100">
  1115. Your browser does not support the HTML5 canvas tag.
  1116. </canvas>
  1117. <div id='details'>
  1118. <span id='details_verts'></span>
  1119. <span id='details_faces'></span>
  1120. <span id='details_materials'></span>
  1121. </div>
  1122. </section>
  1123. <section id='view'>
  1124. <h3>
  1125. View
  1126. </h3>
  1127. <canvas id="ObJSView" width="500" height="500">
  1128. Your browser does not support the HTML5 canvas tag.
  1129. </canvas>
  1130. </section>
  1131. <section id='controls'>
  1132. <h3>
  1133. Controls
  1134. </h3>
  1135. Choose a model:
  1136. <select id="objSelector" onChange="objs.load(this.value)">
  1137. <option value="obj/rainbow.obj" selected="selected">Rainbow</option>
  1138. <option value="obj/cube.obj">Cube</option>
  1139. <option value="obj/sphere.obj">Sphere</option>
  1140. <option value="obj/star.obj">Star</option>
  1141. <option value="obj/monkey.obj">Monkey</option>
  1142. <option value="obj/bolt.obj">Bolt</option>
  1143. <option value="obj/multiple.obj">Multiple Objects</option>
  1144. <option value="obj/seaplane.obj">Seaplane (Can freeze browser)</option>
  1145. </select>
  1146. <table id='colors'>
  1147. <tr>
  1148. <td class='action item'>
  1149. </td>
  1150. <td class='action'>
  1151. <img alt='Visible' title='Visible' src='img/eye.png'/>
  1152. </td>
  1153. <td class='action'>
  1154. <img alt='color' title='color' src='img/color.png'/>
  1155. </td>
  1156. </tr>
  1157. <tr>
  1158. <td class='item'>
  1159. <img alt='Vertices' title='Vertices' src='img/vert.png'/>
  1160. </td>
  1161. <td>
  1162. <input type="checkbox" value="verts" checked onChange="objs.drawVertizes(this.checked);"/>
  1163. </td>
  1164. <td>
  1165. <input type="color" value="#0A0A0A" onChange="objs.severtizesColor(this.value);"/>
  1166. </td>
  1167. </tr>
  1168. <tr>
  1169. <td class='item'>
  1170. <img alt='Edges' title='Edges' src='img/line.png'/>
  1171. </td>
  1172. <td>
  1173. <input type="checkbox" value="edges" checked onChange="objs.drawEdges(this.checked);"/>
  1174. </td>
  1175. <td>
  1176. <input type="color" value="#644646" onChange="objs.setEdgesColor(this.value);"/>
  1177. </td>
  1178. </tr>
  1179. <tr>
  1180. <td class='item'>
  1181. <img alt='Faces' title='Faces' src='img/face.png'/>
  1182. </td>
  1183. <td>
  1184. <input type="checkbox" value="faces" checked onChange="objs.drawFaces(this.checked);"/>
  1185. </td>
  1186. <td>
  1187. <input type="color" value="#B4B4FF" onChange="objs.sefacesColor(this.value);"/>
  1188. <!-- <input type="checkbox" checked onChange="objs.linkMaterial(this.checked);"/>Use materials -->
  1189. </td>
  1190. </tr>
  1191. <tr>
  1192. <td class='item'>
  1193. <img alt='Background' title='Background' src='img/bg.png'/>
  1194. </td>
  1195. <td>
  1196. <input type="checkbox" value="backg" checked onChange="objs.drawBackground(this.checked);"/>
  1197. </td>
  1198. <td>
  1199. <input type="color" value="#C8C8C8" onChange="objs.setBackgroundColor(this.value)"/>
  1200. </td>
  1201. </tr>
  1202. </table>
  1203. Alpha:
  1204. <input type="range" min="0" max="100" step="1" value="60" onInput="objs.setAlpha(this.value);" onChange="objs.setAlpha(this.value);"/>
  1205. Rotation speed:
  1206. <input type="range" min="0" max="10" step="1" value="3" onInput="objs.setRotationSpeed(this.value);" onChange="objs.setRotationSpeed(this.value);"/>
  1207. Zoom speed:
  1208. <input type="range" min="0" max="10" step="1" value="2" onInput="objs.setZoomSpeed(this.value);" onChange="objs.setZoomSpeed(this.value);"/>
  1209. Movement speed:
  1210. <input type="range" min="0" max="10" step="1" value="5" onInput="objs.setMovementSpeed(this.value);" onChange="objs.setMovementSpeed(this.value);"/>
  1211. </section>
  1212. </body>
  1213. </html>