/lesson_132_animation_retargeting/fungi.misc/GLTF.js

https://github.com/sketchpunk/FunWithWebGL2 · JavaScript · 639 lines · 350 code · 106 blank · 183 comment · 118 complexity · b07cfc5e4cea4e7ff572921a22721659 MD5 · raw file

  1. import Downloader, { HandlerTypes } from "../fungi/engine/lib/Downloader.js";
  2. import App, { Vao } from "../fungi/engine/App.js";
  3. import Armature from "../fungi.armature/Armature.js";
  4. import ArmaturePreview from "../fungi.armature/ArmaturePreview.js";
  5. /**
  6. * Handle parsing data from GLTF Json and Bin Files
  7. * @example <caption></caption>
  8. * let aryPrim = Gltf.getMesh( names[0], json, bin );
  9. * let p = aryPrim[ 0 ];
  10. *
  11. * let vao = Vao.buildStandard( "Drill", p.vertices.compLen, p.vertices.data,
  12. * (p.normal)? p.normal.data : null,
  13. * (p.uv)? p.uv.data : null,
  14. * (p.indices)? p.indices.data : null );
  15. *
  16. * let e = App.$Draw( "Drill", vao, "LowPolyPhong", p.mode );
  17. * if( p.rotation ) e.Node.setRot( p.rotation );
  18. * if( p.position ) e.Node.setPos( p.position );
  19. * if( p.scale ) e.Node.setScl( p.scale );
  20. *
  21. * ---- OR ----
  22. * let vao = Vao.buildFromBin( "ToBufferTest", p, bin );
  23. * let e = App.$Draw( "BinBufTestMesh", vao, "LowPolyPhong", gl.ctx.TRIANGLES );
  24. * if( p.rotation ) e.Node.setRot( p.rotation );
  25. * if( p.position ) e.Node.setPos( p.position );
  26. * if( p.scale ) e.Node.setScl( p.scale );
  27. */
  28. class Gltf{
  29. ////////////////////////////////////////////////////////
  30. // HELPERS
  31. ////////////////////////////////////////////////////////
  32. /**
  33. * Parse out a single buffer of data from the bin file based on an accessor index. (Vertices, Normal, etc)
  34. * @param {number} idx - Index of an Accessor
  35. * @param {object} json - GLTF Json Object
  36. * @param {ArrayBuffer} bin - Array buffer of a bin file
  37. * @param {bool} specOnly - Returns only Buffer Spec data related to the Bin File
  38. * @public @return {data:TypeArray, min, max, elmCount, compLen, byteStart, byteLen, arrayType }
  39. */
  40. //https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md
  41. static parseAccessor( idx, json, bin, specOnly = false ){
  42. let acc = json.accessors[ idx ], // Reference to Accessor JSON Element
  43. bView = json.bufferViews[ acc.bufferView ], // Buffer Information
  44. compLen = Gltf[ "COMP_" + acc.type ], // Component Length for Data Element
  45. ary = null, // Final Type array that will be filled with data
  46. byteStart = 0,
  47. byteLen = 0,
  48. TAry, // Reference to Type Array to create
  49. DFunc; // Reference to GET function in Type Array
  50. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  51. // Figure out which Type Array we need to save the data in
  52. switch( acc.componentType ){
  53. case Gltf.TYPE_FLOAT: TAry = Float32Array; DFunc = "getFloat32"; break;
  54. case Gltf.TYPE_SHORT: TAry = Int16Array; DFunc = "getInt16"; break;
  55. case Gltf.TYPE_UNSIGNED_SHORT: TAry = Uint16Array; DFunc = "getUint16"; break;
  56. case Gltf.TYPE_UNSIGNED_INT: TAry = Uint32Array; DFunc = "getUint32"; break;
  57. case Gltf.TYPE_UNSIGNED_BYTE: TAry = Uint8Array; DFunc = "getUint8"; break;
  58. default: console.log("ERROR processAccessor","componentType unknown",a.componentType); return null; break;
  59. }
  60. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  61. let out = {
  62. min : acc.min,
  63. max : acc.max,
  64. elmCount : acc.count,
  65. compLen : compLen
  66. };
  67. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  68. // Data is Interleaved
  69. if( bView.byteStride ){
  70. if( specOnly ) console.error( "GLTF STRIDE SPEC ONLY OPTION NEEDS TO BE IMPLEMENTED ");
  71. /*
  72. The RiggedSimple sample seems to be using stride the wrong way. The data all works out
  73. but Weight and Joint indicate stride length BUT the data is not Interleaved in the buffer.
  74. They both exists in their own individual block of data just like non-Interleaved data.
  75. In the sample, Vertices and Normals ARE actually Interleaved. This make it a bit
  76. difficult to parse when dealing with interlanced data with WebGL Buffers.
  77. TODO: Can prob check if not interlanced by seeing if the Stride Length equals the length
  78. of the data in question.
  79. For example related to the RiggedSimple sample.
  80. Stride Length == FloatByteLength(4) * Accessor.type's ComponentLength(Vec3||Vec4)
  81. -- So if Stride is 16 Bytes
  82. -- The data is grouped as Vec4 ( 4 Floats )
  83. -- And Each Float = 4 bytes.
  84. -- Then Stride 16 Bytes == Vec4 ( 4f loats * 4 Bytes )
  85. -- So the stride length equals the data we're looking for, So the BufferView in question
  86. IS NOT Interleaved.
  87. By the looks of things. If the Accessor.bufferView number is shared between BufferViews
  88. then there is a good chance its really Interleaved. Its ashame that things can be designed
  89. to be more straight forward when it comes to Interleaved and Non-Interleaved data.
  90. */
  91. // console.log("BView", bView );
  92. // console.log("Accessor", acc );
  93. let stride = bView.byteStride, // Stride Length in bytes
  94. elmCnt = acc.count, // How many stride elements exist.
  95. bOffset = (bView.byteOffset || 0), // Buffer Offset
  96. sOffset = (acc.byteOffset || 0), // Stride Offset
  97. bPer = TAry.BYTES_PER_ELEMENT, // How many bytes to make one value of the data type
  98. aryLen = elmCnt * compLen, // How many "floats/ints" need for this array
  99. dView = new DataView( bin ), // Access to Binary Array Buffer
  100. p = 0, // Position Index of Byte Array
  101. j = 0, // Loop Component Length ( Like a Vec3 at a time )
  102. k = 0; // Position Index of new Type Array
  103. ary = new TAry( aryLen ); //Final Array
  104. //Loop for each element of by stride
  105. for(var i=0; i < elmCnt; i++){
  106. // Buffer Offset + (Total Stride * Element Index) + Sub Offset within Stride Component
  107. p = bOffset + ( stride * i ) + sOffset; //Calc Starting position for the stride of data
  108. //Then loop by compLen to grab stuff out of the DataView and into the Typed Array
  109. for(j=0; j < compLen; j++) ary[ k++ ] = dView[ DFunc ]( p + (j * bPer) , true );
  110. }
  111. out.data = ary;
  112. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  113. // Data is NOT Interleaved
  114. // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
  115. // TArray example from documentation works pretty well for data that is not interleaved.
  116. }else{
  117. if( specOnly ){
  118. out.arrayType = TAry.name.substring( 0, TAry.name.length - 5 );
  119. out.byteStart = ( acc.byteOffset || 0 ) + ( bView.byteOffset || 0 );
  120. out.byteLen = acc.count * compLen * TAry.BYTES_PER_ELEMENT;
  121. //console.log( bin );
  122. }else{
  123. let bOffset = ( acc.byteOffset || 0 ) + ( bView.byteOffset || 0 )
  124. out.data = new TAry( bin, bOffset, acc.count * compLen ); // ElementCount * ComponentLength
  125. }
  126. }
  127. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  128. return out;
  129. }
  130. ////////////////////////////////////////////////////////
  131. // MESH
  132. ////////////////////////////////////////////////////////
  133. /**
  134. * Returns the geometry data for all the primatives that make up a Mesh based on the name
  135. * that the mesh appears in the nodes list.
  136. * @param {string} name - Name of a node in the GLTF Json file
  137. * @param {object} json - GLTF Json Object
  138. * @param {ArrayBuffer} bin - Array buffer of a bin file
  139. * @param {bool} specOnly - Returns only Buffer Spec data related to the Bin File
  140. * @public @return {Array.{name,mode,position,vertices,normal,uv,weights,joints}}
  141. */
  142. static getMesh( name, json, bin, specOnly = false ){
  143. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  144. // Find Mesh to parse out.
  145. let i, n = null, mesh_idx = null;
  146. for( i of json.nodes ) if( i.name === name && i.mesh != undefined ){ n = i; mesh_idx = n.mesh; break; }
  147. //No node Found, Try looking in mesh array for the name.
  148. if( !n ){
  149. for(i=0; i < json.meshes.length; i++ ) if( json.meshes[i].name == name ){ mesh_idx = i; break; }
  150. }
  151. if( mesh_idx == null ){
  152. console.error("Node or Mesh by the name", name, "not found in GLTF");
  153. return null;
  154. }
  155. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  156. // Loop through all the primatives that make up a single mesh
  157. let m = json.meshes[ mesh_idx ],
  158. pLen = m.primitives.length,
  159. ary = new Array( pLen ),
  160. itm,
  161. prim,
  162. attr;
  163. for( let i=0; i < pLen; i++ ){
  164. //.......................................
  165. // Setup some vars
  166. prim = m.primitives[ i ];
  167. attr = prim.attributes;
  168. itm = {
  169. name : name + (( pLen != 1 )? "_p" + i : ""),
  170. mode : ( prim.mode != undefined )? prim.mode : Gltf.MODE_TRIANGLES
  171. };
  172. //.......................................
  173. // Save Position, Rotation and Scale if Available.
  174. if( n ){
  175. if( n.translation ) itm.position = n.translation.slice( 0 );
  176. if( n.rotation ) itm.rotation = n.rotation.slice( 0 );
  177. if( n.scale ) itm.scale = n.scale.slice( 0 );
  178. }
  179. //.......................................
  180. // Parse out all the raw Geometry Data from the Bin file
  181. itm.vertices = Gltf.parseAccessor( attr.POSITION, json, bin, specOnly );
  182. if( prim.indices != undefined ) itm.indices = Gltf.parseAccessor( prim.indices, json, bin, specOnly );
  183. if( attr.NORMAL != undefined ) itm.normal = Gltf.parseAccessor( attr.NORMAL, json, bin, specOnly );
  184. if( attr.TEXCOORD_0 != undefined ) itm.uv = Gltf.parseAccessor( attr.TEXCOORD_0, json, bin, specOnly );
  185. if( attr.WEIGHTS_0 != undefined ) itm.weights = Gltf.parseAccessor( attr.WEIGHTS_0, json, bin, specOnly );
  186. if( attr.JOINTS_0 != undefined ) itm.joints = Gltf.parseAccessor( attr.JOINTS_0, json, bin, specOnly );
  187. //.......................................
  188. // Save to return array
  189. ary[ i ] = itm;
  190. }
  191. return ary;
  192. }
  193. ////////////////////////////////////////////////////////
  194. // SKIN
  195. ////////////////////////////////////////////////////////
  196. //INFO : https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_020_Skins.md
  197. static getSkin( name, json, node_info=null ){
  198. if( !json.skins ){
  199. console.error( "There is no skin in the GLTF file." );
  200. return null;
  201. }
  202. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  203. let ji, skin = null;
  204. for( ji of json.skins ) if( ji.name == name ){ skin = ji; break; }
  205. if( !skin ){ console.error( "skin not found", name ); return null; }
  206. //skin.inverseBindMatrices = Accessor Idx for the BindPose for each joint.
  207. //skin.skeleton - Node Index of Root Bone (Optional, may need to find it yourself)
  208. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  209. // Create Bone Items
  210. let boneCnt = skin.joints.length,
  211. bones = new Array(boneCnt),
  212. n2j = {}, // Lookup table to link Parent-Child (Node to Joint Indexes)
  213. n, // Node
  214. ni, // Node Index
  215. itm; // Bone Item
  216. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  217. // Create Bone Array and Loopup Table.
  218. for(ji=0; ji < boneCnt; ji++ ){
  219. ni = skin.joints[ ji ];
  220. n2j[ "n"+ni ] = ji;
  221. bones[ ji ] = {
  222. idx : ji, p_idx : null, lvl : 0, name : null,
  223. pos : null, rot : null, scl : null };
  224. }
  225. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  226. // Collect bone information, inc
  227. for( ji=0; ji < boneCnt; ji++){
  228. n = json.nodes[ skin.joints[ ji ] ];
  229. itm = bones[ ji ];
  230. // Get Transform Data if Available
  231. if( n.translation ) itm.pos = n.translation.slice(0);
  232. if( n.rotation ) itm.rot = n.rotation.slice(0);
  233. // Each Bone Needs a Name, create one if one does not exist.
  234. if( n.name === undefined || n.name == "" ) itm.name = "bone_" + ji;
  235. else{
  236. itm.name = n.name.replace("mixamorig:", "");
  237. }
  238. // Scale isn't always available
  239. if( n.scale ){
  240. // Near Zero Testing, Clean up the data because of Floating point issues.
  241. itm.scl = n.scale.slice(0);
  242. if( Math.abs( 1 - itm.scl[0] ) <= 0.000001 ) itm.scl[0] = 1;
  243. if( Math.abs( 1 - itm.scl[1] ) <= 0.000001 ) itm.scl[1] = 1;
  244. if( Math.abs( 1 - itm.scl[2] ) <= 0.000001 ) itm.scl[2] = 1;
  245. }
  246. // Set Children who the parent is.
  247. if( n.children && n.children.length > 0 ){
  248. for( ni of n.children ) bones[ n2j["n"+ni] ].p_idx = ji;
  249. }
  250. }
  251. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  252. // Set the Hierarchy Level for each bone
  253. let lvl;
  254. for( ji=0; ji < boneCnt; ji++){
  255. // Check for Root Bones
  256. itm = bones[ ji ];
  257. if( itm.p_idx == null ){ itm.lvl = 0; continue; }
  258. // Traverse up the tree to count how far down the bone is
  259. lvl = 0;
  260. while( itm.p_idx != null ){ lvl++; itm = bones[ itm.p_idx ]; }
  261. bones[ ji ].lvl = lvl;
  262. }
  263. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  264. // Set the Hierarchy Level for each bone
  265. if( node_info ){
  266. for( ji of json.nodes ){
  267. if( ji.name == name ){
  268. if( ji.rotation ) node_info["rot"] = ji.rotation;
  269. if( ji.scale ) node_info["scl"] = ji.scale;
  270. if( ji.position ) node_info["pos"] = ji.position;
  271. break;
  272. }
  273. }
  274. }
  275. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  276. return bones;
  277. }
  278. ////////////////////////////////////////////////////////
  279. // Animation
  280. ////////////////////////////////////////////////////////
  281. //TODO, DELETE
  282. static getSkinAnimation( name, json, bin ){
  283. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  284. // Validate there is animations and an anaimtion by a name exists in the json file.
  285. if( json.animations === undefined || json.animations.length == 0 ){ console.error("There is no animations in gltf"); return null; }
  286. let i, a = null;
  287. for( i of json.animations ) if( i.name === name ){ a = i; break; }
  288. if( !a ){ console.error("Animation by the name", name, "not found in GLTF"); return null; }
  289. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  290. // Create Lookup table for Node to Joint. Each Joint will be updated with pos, rot or scl data.
  291. let tbl = {},
  292. itms = json.skins[0].joints;
  293. for( i=0; i < itms.length; i++ ) tbl[ itms[i] ] = { bone_idx: i };
  294. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  295. //Process Channels and pull out the sampling data ( Rot, Pos, Scl & Time Stamp for Each );
  296. let chi = 0, ch, s, n, prop, tData, dData ;
  297. for( chi=0; chi < a.channels.length; chi++ ){
  298. //.......................
  299. // Must check that the channel points to a node that the animation is attached to.
  300. ch = a.channels[ chi ];
  301. if( ch.target.node == undefined ) continue;
  302. n = ch.target.node;
  303. if( !tbl[ n ] ){
  304. console.log("Channel's target node is not joint of the first skin.");
  305. continue;
  306. }
  307. //.......................
  308. // User a smaller pproperty name then what GLTF uses.
  309. switch( ch.target.path ){
  310. case "translation" : prop = "pos"; break;
  311. case "rotation" : prop = "rot"; break;
  312. case "scale" : prop = "scl"; break;
  313. default: console.log( "unknown channel path", ch.path ); continue;
  314. }
  315. //.......................
  316. // Parse out the Sampler Data from the Bin file.
  317. s = a.samplers[ ch.sampler ];
  318. tData = this.parseAccessor( s.input, json, bin ); // Get Time for all keyframes
  319. dData = this.parseAccessor( s.output, json, bin ); // Get Value that changes per keyframe
  320. console.log( prop, s.input, s.output );
  321. // Using the Node Index, Save it to the lookup table with the prop name.
  322. tbl[ n ][ prop ] = { time: tData.data, data: dData.data, lerp:s.interpolation };
  323. }
  324. return tbl;
  325. }
  326. static get_animation( name, json, bin, limit=true, frame_inv=true ){
  327. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  328. // Validate there is animations and an anaimtion by a name exists in the json file.
  329. if( json.animations === undefined || json.animations.length == 0 ){ console.error("There is no animations in gltf"); return null; }
  330. let i, a = null;
  331. for( i of json.animations ) if( i.name === name ){ a = i; break; }
  332. if( !a ){ console.error("Animation by the name", name, "not found in GLTF"); return null; }
  333. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  334. // Create Lookup Table For Node Index to Bone Index.
  335. let joints = {},
  336. j_ary = json.skins[0].joints;
  337. for( i=0; i < j_ary.length; i++ ){
  338. joints[ j_ary[i] ] = i; // Node Index to Joint Index
  339. //console.log( "Idx :", i, " Name: ", json.nodes[ j_ary[i] ].name );
  340. }
  341. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  342. let chi = 0, ch, s, n, prop,
  343. t_idx, n_name, s_time,
  344. ch_ary = [], // Store all the channel information structs
  345. time_ary = [], // Shared Time Information between Tracks
  346. time_tbl = {}, // Keep Track of Time Accessor ID thats been extracted
  347. time_max = 0; // Max Time of the Track Animations
  348. for( chi=0; chi < a.channels.length; chi++ ){
  349. //.......................
  350. // Must check that the channel points to a node that the animation is attached to.
  351. ch = a.channels[ chi ];
  352. if( ch.target.node == undefined ) continue;
  353. n = ch.target.node;
  354. if( joints[ n ] == undefined ){
  355. console.log("Channel's target node is not a joint of the first skin.");
  356. continue;
  357. }
  358. //.......................
  359. // User a smaller property name then what GLTF uses.
  360. switch( ch.target.path ){
  361. case "rotation" : prop = "rot"; break;
  362. case "translation" : prop = "pos"; break;
  363. case "scale" : prop = "scl"; break;
  364. default: console.log( "unknown channel path", ch.path ); continue;
  365. }
  366. //.......................
  367. // When limit it set, only want rotation tracks and if its the hip, position to.
  368. n_name = json.nodes[ n ].name.toLowerCase();
  369. if( limit &&
  370. !( prop == "rot" ||
  371. ( n_name.indexOf("hip") != -1 && prop == "pos" )
  372. )
  373. ) continue;
  374. //.......................
  375. // Parse out the Sampler Data from the Bin file.
  376. s = a.samplers[ ch.sampler ];
  377. // Get Time for all keyframes, cache it since its shared.
  378. t_idx = time_tbl[ s.input ];
  379. if( t_idx == undefined ){
  380. time_tbl[ s.input ] = t_idx = time_ary.length;
  381. s_time = this.parseAccessor( s.input, json, bin );
  382. time_ary.push( s_time.data );
  383. time_max = Math.max( time_max, s_time.max[0] );
  384. }
  385. //.......................
  386. // Get the changing value per frame for the property
  387. ch_ary.push({
  388. type : prop,
  389. time_idx : t_idx,
  390. joint_idx : joints[ n ],
  391. lerp : s.interpolation,
  392. data : this.parseAccessor( s.output, json, bin ).data,
  393. });
  394. }
  395. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  396. let rtn = { time_max, times: time_ary, tracks:ch_ary };
  397. // if Requested, create an array of values to help normalize time between frames.
  398. // Normal time equation would be: (x - a) / (b - a) with A and B being time of two frames
  399. // Can cache b - a, then invert it with 1 / (b-a), which allows us have a new equation of
  400. // ( x - a ) * frame_inv
  401. if( frame_inv ){
  402. let j, t, tt;
  403. rtn.frame_inv = new Array();
  404. for( i=0; i < rtn.times.length; i++ ){
  405. t = rtn.times[i];
  406. tt = new Float32Array( t.length-1 );
  407. for( j=0; j < t.length-1; j++ ) tt[ j ] = 1 / ( t[j+1] - t[j] ); // 1 / (b - a)
  408. rtn.frame_inv.push( tt );
  409. }
  410. }
  411. return rtn;
  412. }
  413. }
  414. ////////////////////////////////////////////////////////
  415. // CONSTANTS
  416. ////////////////////////////////////////////////////////
  417. Gltf.MODE_POINTS = 0; // Mode Constants for GLTF and WebGL are identical
  418. Gltf.MODE_LINES = 1; // https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Constants
  419. Gltf.MODE_LINE_LOOP = 2;
  420. Gltf.MODE_LINE_STRIP = 3;
  421. Gltf.MODE_TRIANGLES = 4;
  422. Gltf.MODE_TRIANGLE_STRIP = 5;
  423. Gltf.MODE_TRIANGLE_FAN = 6;
  424. Gltf.TYPE_BYTE = 5120;
  425. Gltf.TYPE_UNSIGNED_BYTE = 5121;
  426. Gltf.TYPE_SHORT = 5122;
  427. Gltf.TYPE_UNSIGNED_SHORT = 5123;
  428. Gltf.TYPE_UNSIGNED_INT = 5125;
  429. Gltf.TYPE_FLOAT = 5126;
  430. Gltf.COMP_SCALAR = 1; // Component Length based on Type
  431. Gltf.COMP_VEC2 = 2;
  432. Gltf.COMP_VEC3 = 3;
  433. Gltf.COMP_VEC4 = 4;
  434. Gltf.COMP_MAT2 = 4;
  435. Gltf.COMP_MAT3 = 9;
  436. Gltf.COMP_MAT4 = 16;
  437. Gltf.TARGET_ARY_BUF = 34962; // bufferview.target
  438. Gltf.TARGET_ELM_ARY_BUF = 34963;
  439. //###############################################################################
  440. // Hacking the prototype is to be frawned apo, but it makes a better API usage.
  441. Downloader.prototype.addGLTF = function( name, file, matName, meshNames, skinName=null, loadSkin=true, loadPrev=true ){
  442. this._queue.push( { name, matName,
  443. handler : "gltf",
  444. meshNames,
  445. skinName,
  446. loadSkin,
  447. loadPrev,
  448. files : [
  449. { url: file + ".gltf", type:"json" },
  450. { url: file + ".bin", type:"arraybuffer" }
  451. ]
  452. });
  453. return this;
  454. }
  455. HandlerTypes.gltf = class{
  456. static downloaded( dl, xhr ){
  457. switch( xhr.activeItem.files.length ){
  458. case 1: xhr.activeItem.json = xhr.response; break;
  459. case 0: xhr.activeItem.bin = xhr.response; break;
  460. }
  461. }
  462. static load( dl ){
  463. let e, bones;
  464. for( let i of dl.gltf ){
  465. e = this.meshEntity( i.name, i.matName, i.meshNames, i.json, i.bin );
  466. if( i.skinName ){
  467. Armature.$( e );
  468. let bones = Gltf.getSkin( i.skinName, i.json );
  469. this.loadBones( e, bones );
  470. //console.log( JSON.stringify( Armature.serialize( e.Armature, false ) ) );
  471. if( i.loadPrev ) ArmaturePreview.$( e, "ArmaturePreview", 2 );
  472. }
  473. }
  474. return true;
  475. }
  476. static meshEntity( name, matName, meshNames, json, bin ){
  477. let e = App.$Draw( name );
  478. let prims, p, vao, mName;
  479. for( mName of meshNames ){
  480. prims = Gltf.getMesh( mName, json, bin, true ); // Spec Only
  481. for( p of prims ){
  482. vao = Vao.buildFromBin( name + "_" + p.name, p, bin );
  483. e.Draw.add( vao, matName, p.mode );
  484. }
  485. //This only Works if each Primitive is its own Entity, but not for meshes broken out as pieces
  486. //and each piece shares the same main mesh local space
  487. //if( p.rotation ) e.Node.setRot( p.rotation );
  488. //if( p.position ) e.Node.setPos( p.position );
  489. //if( p.scale ) e.Node.setScl( p.scale );
  490. }
  491. return e;
  492. }
  493. static loadBones( e, bones ){
  494. let arm = e.Armature;
  495. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  496. // Create Bones
  497. let i, b, ab, bLen = bones.length;
  498. for( i=0; i < bLen; i++ ){
  499. b = bones[i];
  500. ab = Armature.addBone( arm, b.name, 1, null, b.idx );
  501. if( b.rot ) ab.Node.setRot( b.rot );
  502. if( b.pos ) ab.Node.setPos( b.pos );
  503. if( b.scl ) ab.Node.setScl( b.scl );
  504. }
  505. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  506. // Setting up Parent-Child
  507. for( i=0; i < bLen; i++ ){
  508. b = bones[ i ];
  509. ab = arm.bones[ b.idx ];
  510. // Can not have levels updated automaticly, Callstack limits get hit
  511. // Instead, using the Level from bones to manually set it.
  512. if( b.p_idx != null ) App.node.addChild( arm.bones[ b.p_idx ], ab, false );
  513. // Manual set node level, Must do it after addChild, else it will get overwritten.
  514. ab.Node.level = b.lvl;
  515. }
  516. Armature.finalize( e ); //This updates World Transforms
  517. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  518. // With the WorldTransforms update, Calculate the Length of each bone.
  519. for( i=0; i < bLen; i++ ){
  520. ab = arm.bones[ i ];
  521. if( ab.Node.children.length == 0 ){
  522. ab.Bone.length = 0.03;
  523. continue;
  524. }
  525. b = ab.Node.children[0].Node.world; // First Child's World Space Transform
  526. ab.Bone.length = ab.Node.world.pos.length( b.pos ); // Distance from Parent to Child
  527. }
  528. }
  529. }; HandlerTypes.gltf.priority = 100;
  530. //###############################################################################
  531. export default Gltf;