2
0

FbxLibrary.hx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. // Adapted fbx parser originally developed by Nicolas Cannasse
  2. // https://github.com/HeapsIO/heaps/tree/master/hxd/fmt/fbx
  3. // The MIT License (MIT)
  4. // Copyright (c) 2013 Nicolas Cannasse
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy of
  6. // this software and associated documentation files (the "Software"), to deal in
  7. // the Software without restriction, including without limitation the rights to
  8. // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  9. // the Software, and to permit persons to whom the Software is furnished to do so,
  10. // subject to the following conditions:
  11. // The above copyright notice and this permission notice shall be included in all
  12. // copies or substantial portions of the Software.
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  15. // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  16. // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  17. // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  18. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  19. package arm.format;
  20. enum FbxProp {
  21. PInt( v : Int );
  22. PFloat( v : Float );
  23. PString( v : String );
  24. PIdent( i : String );
  25. PInts( v : Array<Int> );
  26. PFloats( v : Array<Float> );
  27. }
  28. typedef FbxNode = {
  29. var name : String;
  30. var props : Array<FbxProp>;
  31. var childs : Array<FbxNode>;
  32. }
  33. class FbxTools {
  34. public static function get( n : FbxNode, path : String, opt = false ) {
  35. var parts = path.split(".");
  36. var cur = n;
  37. for( p in parts ) {
  38. var found = false;
  39. for( c in cur.childs )
  40. if( c.name == p ) {
  41. cur = c;
  42. found = true;
  43. break;
  44. }
  45. if( !found ) {
  46. if( opt )
  47. return null;
  48. throw n.name + " does not have " + path+" ("+p+" not found)";
  49. }
  50. }
  51. return cur;
  52. }
  53. public static function getAll( n : FbxNode, path : String ) {
  54. var parts = path.split(".");
  55. var cur = [n];
  56. for( p in parts ) {
  57. var out = [];
  58. for( n in cur )
  59. for( c in n.childs )
  60. if( c.name == p )
  61. out.push(c);
  62. cur = out;
  63. if( cur.length == 0 )
  64. return cur;
  65. }
  66. return cur;
  67. }
  68. public static function getInts( n : FbxNode ) {
  69. if( n.props.length != 1 )
  70. throw n.name + " has " + n.props + " props";
  71. switch( n.props[0] ) {
  72. case PInts(v):
  73. return v;
  74. default:
  75. throw n.name + " has " + n.props + " props";
  76. }
  77. }
  78. public static function getFloats( n : FbxNode ) {
  79. if( n.props.length != 1 )
  80. throw n.name + " has " + n.props + " props";
  81. switch( n.props[0] ) {
  82. case PFloats(v):
  83. return v;
  84. case PInts(i):
  85. var fl = new Array<Float>();
  86. for( x in i )
  87. fl.push(x);
  88. n.props[0] = PFloats(fl); // keep data synchronized
  89. // this is necessary for merging geometries since we are pushing directly into the
  90. // float buffer
  91. return fl;
  92. default:
  93. throw n.name + " has " + n.props + " props";
  94. }
  95. }
  96. public static function hasProp( n : FbxNode, p : FbxProp ) {
  97. for( p2 in n.props )
  98. if( Type.enumEq(p, p2) )
  99. return true;
  100. return false;
  101. }
  102. static inline function idToInt( f : Float ) {
  103. #if (neko || hl)
  104. // ids are unsigned
  105. f %= 4294967296.;
  106. if( f >= 2147483648. )
  107. f -= 4294967296.;
  108. else if( f < -2147483648. )
  109. f += 4294967296.;
  110. #end
  111. return Std.int(f);
  112. }
  113. public static function toInt( n : FbxProp ) {
  114. if( n == null ) throw "null prop";
  115. return switch( n ) {
  116. case PInt(v): v;
  117. case PFloat(f): idToInt(f);
  118. default: throw "Invalid prop " + n;
  119. }
  120. }
  121. public static function toFloat( n : FbxProp ) {
  122. if( n == null ) throw "null prop";
  123. return switch( n ) {
  124. case PInt(v): v * 1.0;
  125. case PFloat(v): v;
  126. default: throw "Invalid prop " + n;
  127. }
  128. }
  129. public static function toString( n : FbxProp ) {
  130. if( n == null ) throw "null prop";
  131. return switch( n ) {
  132. case PString(v): v;
  133. default: throw "Invalid prop " + n;
  134. }
  135. }
  136. public static function getId( n : FbxNode ) {
  137. if( n.props.length != 3 )
  138. throw n.name + " is not an object";
  139. return switch( n.props[0] ) {
  140. case PInt(id): id;
  141. case PFloat(id) : idToInt(id);
  142. default: throw n.name + " is not an object " + n.props;
  143. }
  144. }
  145. public static function getName( n : FbxNode ) {
  146. if( n.props.length != 3 )
  147. throw n.name + " is not an object";
  148. return switch( n.props[1] ) {
  149. case PString(n): n.split("::").pop();
  150. default: throw n.name + " is not an object";
  151. }
  152. }
  153. public static function getType( n : FbxNode ) {
  154. if( n.props.length != 3 )
  155. throw n.name + " is not an object";
  156. return switch( n.props[2] ) {
  157. case PString(n): n;
  158. default: throw n.name + " is not an object";
  159. }
  160. }
  161. }
  162. private enum Token {
  163. TIdent( s : String );
  164. TNode( s : String );
  165. TInt( s : String );
  166. TFloat( s : String );
  167. TString( s : String );
  168. TLength( v : Int );
  169. TBraceOpen;
  170. TBraceClose;
  171. TColon;
  172. TEof;
  173. }
  174. class Parser {
  175. var line : Int;
  176. var buf : String;
  177. var pos : Int;
  178. var token : Null<Token>;
  179. function new() {
  180. }
  181. function parseText( str ) : FbxNode {
  182. this.buf = str;
  183. this.pos = 0;
  184. this.line = 1;
  185. token = null;
  186. return {
  187. name : "Root",
  188. props : [PInt(0),PString("Root"),PString("Root")],
  189. childs : parseNodes(),
  190. };
  191. }
  192. function parseNodes() {
  193. var nodes = [];
  194. while( true ) {
  195. switch( peek() ) {
  196. case TEof, TBraceClose:
  197. return nodes;
  198. default:
  199. }
  200. nodes.push(parseNode());
  201. }
  202. return nodes;
  203. }
  204. function parseNode() : FbxNode {
  205. var t = next();
  206. var name = switch( t ) {
  207. case TNode(n): n;
  208. default: unexpected(t);
  209. };
  210. var props = [], childs = null;
  211. while( true ) {
  212. t = next();
  213. switch( t ) {
  214. case TFloat(s):
  215. props.push(PFloat(Std.parseFloat(s)));
  216. case TInt(s):
  217. props.push(PInt(Std.parseInt(s)));
  218. case TString(s):
  219. props.push(PString(s));
  220. case TIdent(s):
  221. props.push(PIdent(s));
  222. case TBraceOpen, TBraceClose, TNode(_):
  223. token = t;
  224. case TLength(v):
  225. except(TBraceOpen);
  226. except(TNode("a"));
  227. var ints : Array<Int> = [];
  228. var floats : Array<Float> = null;
  229. var i = 0;
  230. while( i < v ) {
  231. t = next();
  232. switch( t ) {
  233. case TColon:
  234. continue;
  235. case TInt(s):
  236. i++;
  237. if( floats == null )
  238. ints.push(Std.parseInt(s));
  239. else
  240. floats.push(Std.parseInt(s));
  241. case TFloat(s):
  242. i++;
  243. if( floats == null ) {
  244. floats = [];
  245. for( i in ints )
  246. floats.push(i);
  247. ints = null;
  248. }
  249. floats.push(Std.parseFloat(s));
  250. default:
  251. unexpected(t);
  252. }
  253. }
  254. props.push(floats == null ? PInts(ints) : PFloats(floats));
  255. except(TBraceClose);
  256. break;
  257. default:
  258. unexpected(t);
  259. }
  260. t = next();
  261. switch( t ) {
  262. case TNode(_), TBraceClose:
  263. token = t; // next
  264. break;
  265. case TColon:
  266. // next prop
  267. case TBraceOpen:
  268. childs = parseNodes();
  269. except(TBraceClose);
  270. break;
  271. default:
  272. unexpected(t);
  273. }
  274. }
  275. if( childs == null ) childs = [];
  276. return { name : name, props : props, childs : childs };
  277. }
  278. function except( except : Token ) {
  279. var t = next();
  280. if( !Type.enumEq(t, except) )
  281. error("Unexpected '" + tokenStr(t) + "' (" + tokenStr(except) + " expected)");
  282. }
  283. function peek() {
  284. if( token == null )
  285. token = nextToken();
  286. return token;
  287. }
  288. function next() {
  289. if( token == null )
  290. return nextToken();
  291. var tmp = token;
  292. token = null;
  293. return tmp;
  294. }
  295. function error( msg : String ) : Dynamic {
  296. throw msg + " (line " + line + ")";
  297. return null;
  298. }
  299. function unexpected( t : Token ) : Dynamic {
  300. return error("Unexpected "+tokenStr(t));
  301. }
  302. function tokenStr( t : Token ) {
  303. return switch( t ) {
  304. case TEof: "<eof>";
  305. case TBraceOpen: '{';
  306. case TBraceClose: '}';
  307. case TIdent(i): i;
  308. case TNode(i): i+":";
  309. case TFloat(f): f;
  310. case TInt(i): i;
  311. case TString(s): '"' + s + '"';
  312. case TColon: ',';
  313. case TLength(l): '*' + l;
  314. };
  315. }
  316. inline function nextChar() {
  317. return StringTools.fastCodeAt(buf, pos++);
  318. }
  319. inline function getBuf( pos : Int, len : Int ) {
  320. return buf.substr(pos, len);
  321. }
  322. inline function isIdentChar(c) {
  323. return (c >= 'a'.code && c <= 'z'.code) || (c >= 'A'.code && c <= 'Z'.code) || (c >= '0'.code && c <= '9'.code) || c == '_'.code || c == '-'.code;
  324. }
  325. @:noDebug
  326. function nextToken() {
  327. var start = pos;
  328. while( true ) {
  329. var c = nextChar();
  330. switch( c ) {
  331. case ' '.code, '\r'.code, '\t'.code:
  332. start++;
  333. case '\n'.code:
  334. line++;
  335. start++;
  336. case ';'.code:
  337. while( true ) {
  338. var c = nextChar();
  339. if( StringTools.isEof(c) || c == '\n'.code ) {
  340. pos--;
  341. break;
  342. }
  343. }
  344. start = pos;
  345. case ','.code:
  346. return TColon;
  347. case '{'.code:
  348. return TBraceOpen;
  349. case '}'.code:
  350. return TBraceClose;
  351. case '"'.code:
  352. start = pos;
  353. while( true ) {
  354. c = nextChar();
  355. if( c == '"'.code )
  356. break;
  357. if( StringTools.isEof(c) || c == '\n'.code )
  358. error("Unclosed string");
  359. }
  360. return TString(getBuf(start, pos - start - 1));
  361. case '*'.code:
  362. start = pos;
  363. do {
  364. c = nextChar();
  365. } while( c >= '0'.code && c <= '9'.code );
  366. pos--;
  367. return TLength(Std.parseInt(getBuf(start, pos - start)));
  368. default:
  369. if( (c >= 'a'.code && c <= 'z'.code) || (c >= 'A'.code && c <= 'Z'.code) || c == '_'.code ) {
  370. do {
  371. c = nextChar();
  372. } while( isIdentChar(c) );
  373. if( c == ':'.code )
  374. return TNode(getBuf(start, pos - start - 1));
  375. pos--;
  376. return TIdent(getBuf(start, pos - start));
  377. }
  378. if( (c >= '0'.code && c <= '9'.code) || c == '-'.code ) {
  379. do {
  380. c = nextChar();
  381. } while( c >= '0'.code && c <= '9'.code );
  382. if( c != '.'.code && c != 'E'.code && c != 'e'.code && pos - start < 10 ) {
  383. pos--;
  384. return TInt(getBuf(start, pos - start));
  385. }
  386. if( c == '.'.code ) {
  387. do {
  388. c = nextChar();
  389. } while( c >= '0'.code && c <= '9'.code );
  390. }
  391. if( c == 'e'.code || c == 'E'.code ) {
  392. c = nextChar();
  393. if( c != '-'.code && c != '+'.code )
  394. pos--;
  395. do {
  396. c = nextChar();
  397. } while( c >= '0'.code && c <= '9'.code );
  398. }
  399. pos--;
  400. return TFloat(getBuf(start, pos - start));
  401. }
  402. if( StringTools.isEof(c) ) {
  403. pos--;
  404. return TEof;
  405. }
  406. error("Unexpected char '" + String.fromCharCode(c) + "'");
  407. }
  408. }
  409. }
  410. public static function parse( text : String ) {
  411. return new Parser().parseText(text);
  412. }
  413. }
  414. class FbxLibrary {
  415. var root : FbxNode;
  416. var ids : Map<Int,FbxNode>;
  417. var connect : Map<Int,Array<Int>>;
  418. var namedConnect : Map<Int,Map<String,Int>>;
  419. var invConnect : Map<Int,Array<Int>>;
  420. // var uvAnims : Map<String, Array<{ t : Float, u : Float, v : Float }>>;
  421. // var animationEvents : Array<{ frame : Int, data : String }>;
  422. /**
  423. The FBX version that was decoded
  424. **/
  425. public var version : Float = 0.;
  426. public function new() {
  427. root = { name : "Root", props : [], childs : [] };
  428. reset();
  429. }
  430. function reset() {
  431. ids = new Map();
  432. connect = new Map();
  433. namedConnect = new Map();
  434. invConnect = new Map();
  435. }
  436. public function load( root : FbxNode ) {
  437. reset();
  438. this.root = root;
  439. version = FbxTools.toInt(FbxTools.get(root, "FBXHeaderExtension.FBXVersion").props[0]) / 1000;
  440. if( Std.int(version) != 7 )
  441. throw "FBX Version 7.x required : use FBX 2010 export";
  442. for( c in root.childs )
  443. init(c);
  444. // init properties
  445. // for( m in FbxTools.getAll(this.root, "Objects.Model") ) {
  446. // for( p in FbxTools.getAll(m, "Properties70.P") )
  447. // switch( FbxTools.toString(p.props[0]) ) {
  448. // case "UDP3DSMAX":
  449. // var userProps = FbxTools.toString(p.props[4]).split("&cr;&lf;");
  450. // for( p in userProps ) {
  451. // var pl = p.split("=");
  452. // var pname = StringTools.trim(pl.shift());
  453. // var pval = StringTools.trim(pl.join("="));
  454. // switch( pname ) {
  455. // case "UV" if( pval != "" ):
  456. // var xml = try Xml.parse(pval) catch( e : Dynamic ) throw "Invalid UV data in " + FbxTools.getName(m);
  457. // var frames = [for( f in new haxe.xml.Access(xml.firstElement()).elements ) { var f = f.innerData.split(" "); { t : Std.parseFloat(f[0]) * 9622116.25, u : Std.parseFloat(f[1]), v : Std.parseFloat(f[2]) }} ];
  458. // if( uvAnims == null ) uvAnims = new Map();
  459. // uvAnims.set(FbxTools.getName(m), frames);
  460. // case "Events":
  461. // var xml = try Xml.parse(pval) catch( e : Dynamic ) throw "Invalid Events data in " + FbxTools.getName(m);
  462. // animationEvents = [for( f in new haxe.xml.Access(xml.firstElement()).elements ) { var f = f.innerData.split(" "); { frame : Std.parseInt(f.shift()), data : StringTools.trim(f.join(" ")) }} ];
  463. // default:
  464. // }
  465. // }
  466. // default:
  467. // }
  468. // }
  469. }
  470. function init( n : FbxNode ) {
  471. switch( n.name ) {
  472. case "Connections":
  473. for( c in n.childs ) {
  474. if( c.name != "C" )
  475. continue;
  476. var child = FbxTools.toInt(c.props[1]);
  477. var parent = FbxTools.toInt(c.props[2]);
  478. // Maya exports invalid references
  479. if( ids.get(child) == null || ids.get(parent) == null ) continue;
  480. var name = c.props[3];
  481. if( name != null ) {
  482. var name = FbxTools.toString(name);
  483. var nc = namedConnect.get(parent);
  484. if( nc == null ) {
  485. nc = new Map();
  486. namedConnect.set(parent, nc);
  487. }
  488. nc.set(name, child);
  489. // don't register as a parent, since the target can also be the child of something else
  490. if( name == "LookAtProperty" ) continue;
  491. }
  492. var c = connect.get(parent);
  493. if( c == null ) {
  494. c = [];
  495. connect.set(parent, c);
  496. }
  497. c.push(child);
  498. if( parent == 0 )
  499. continue;
  500. var c = invConnect.get(child);
  501. if( c == null ) {
  502. c = [];
  503. invConnect.set(child, c);
  504. }
  505. c.push(parent);
  506. }
  507. case "Objects":
  508. for( c in n.childs )
  509. ids.set(FbxTools.getId(c), c);
  510. default:
  511. }
  512. }
  513. public function getGeometry( name : String = "" ) {
  514. var geom = null;
  515. for( g in FbxTools.getAll(root, "Objects.Geometry") )
  516. if( FbxTools.hasProp(g, PString("Geometry::" + name)) ) {
  517. geom = g;
  518. break;
  519. }
  520. if( geom == null )
  521. throw "Geometry " + name + " not found";
  522. return new Geometry(this, geom);
  523. }
  524. public function getFirstGeometry() {
  525. var geom = FbxTools.getAll(root, "Objects.Geometry")[0];
  526. return new Geometry(this, geom);
  527. }
  528. public function getAllGeometries() {
  529. var geoms = FbxTools.getAll(root, "Objects.Geometry");
  530. var res:Array<Geometry> = [];
  531. for (g in geoms) res.push(new Geometry(this, g));
  532. return res;
  533. }
  534. }
  535. class Geometry {
  536. var lib : FbxLibrary;
  537. var root : FbxNode;
  538. public function new(l, root) {
  539. this.lib = l;
  540. this.root = root;
  541. }
  542. public function getRoot() {
  543. return root;
  544. }
  545. public function getVertices() {
  546. return FbxTools.getFloats(FbxTools.get(root, "Vertices"));
  547. }
  548. public function getPolygons() {
  549. return FbxTools.getInts(FbxTools.get(root, "PolygonVertexIndex"));
  550. }
  551. /**
  552. Decode polygon informations into triangle indexes and vertices indexes.
  553. Returns vidx, which is the list of vertices indexes and iout which is the index buffer for the full vertex model
  554. **/
  555. public function getIndexes() {
  556. var count = 0, pos = 0;
  557. var index = getPolygons();
  558. var vout = [], iout = [];
  559. for( i in index ) {
  560. count++;
  561. if( i < 0 ) {
  562. index[pos] = -i - 1;
  563. var start = pos - count + 1;
  564. for( n in 0...count )
  565. vout.push(index[n + start]);
  566. for( n in 0...count - 2 ) {
  567. iout.push(start + n);
  568. iout.push(start + count - 1);
  569. iout.push(start + n + 1);
  570. }
  571. index[pos] = i; // restore
  572. count = 0;
  573. }
  574. pos++;
  575. }
  576. return { vidx : vout, idx : iout };
  577. }
  578. public function getNormals() {
  579. return processVectors("LayerElementNormal", "Normals");
  580. }
  581. public function getTangents( opt = false ) {
  582. return processVectors("LayerElementTangent", "Tangents", opt);
  583. }
  584. function processVectors( layer, name, opt = false ) {
  585. var vect = FbxTools.get(root, layer + "." + name, opt);
  586. if( vect == null ) return null;
  587. var nrm = FbxTools.getFloats(vect);
  588. // if by-vertice (Maya in some cases, unless maybe "Split per-Vertex Normals" is checked)
  589. // let's reindex based on polygon indexes
  590. if( FbxTools.toString(FbxTools.get(root, layer+".MappingInformationType").props[0]) == "ByVertice" ) {
  591. var nout = [];
  592. for( i in getPolygons() ) {
  593. var vid = i;
  594. if( vid < 0 ) vid = -vid - 1;
  595. nout.push(nrm[vid * 3]);
  596. nout.push(nrm[vid * 3 + 1]);
  597. nout.push(nrm[vid * 3 + 2]);
  598. }
  599. nrm = nout;
  600. }
  601. return nrm;
  602. }
  603. public function getColors() {
  604. var color = FbxTools.get(root, "LayerElementColor",true);
  605. return color == null ? null : { values : FbxTools.getFloats(FbxTools.get(color, "Colors")), index : FbxTools.getInts(FbxTools.get(color, "ColorIndex")) };
  606. }
  607. public function getUVs() {
  608. var uvs = [];
  609. for( v in FbxTools.getAll(root, "LayerElementUV") ) {
  610. var index = FbxTools.get(v, "UVIndex", true);
  611. var values = FbxTools.getFloats(FbxTools.get(v, "UV"));
  612. var index = if( index == null ) {
  613. // ByVertice/Direct (Maya sometimes...)
  614. [for( i in getPolygons() ) if( i < 0 ) -i - 1 else i];
  615. } else FbxTools.getInts(index);
  616. uvs.push({ values : values, index : index });
  617. }
  618. return uvs;
  619. }
  620. public function getBuffers(binary:Bool, p:FbxParser) {
  621. // triangulize indexes :
  622. // format is A,B,...,-X : negative values mark the end of the polygon
  623. var pbuf = getVertices();
  624. var nbuf = getNormals();
  625. var tbuf = getUVs()[0];
  626. var polys = getPolygons();
  627. if (FbxParser.parseTransform) {
  628. for (i in 0...Std.int(pbuf.length / 3)) {
  629. pbuf[i * 3 ] *= p.sx;
  630. // q.fromEuler(p.rx, p.ry, p.rz);
  631. // v.applyQuat(q);
  632. pbuf[i * 3 ] += p.tx;
  633. pbuf[i * 3 + 1] *= p.sy;
  634. pbuf[i * 3 + 1] += p.ty;
  635. pbuf[i * 3 + 2] *= p.sz;
  636. pbuf[i * 3 + 2] += p.tz;
  637. }
  638. }
  639. // Pack positions to (-1, 1) range
  640. var scalePos = 0.0;
  641. for (p in pbuf) {
  642. var f = Math.abs(p);
  643. if (scalePos < f) scalePos = f;
  644. }
  645. var inv = 32767 * (1 / scalePos);
  646. var pos = 0;
  647. var count = 0;
  648. var vlen = 0;
  649. var ilen = 0;
  650. for (i in polys) {
  651. count++;
  652. if (i < 0) {
  653. for (n in 0...count) vlen++;
  654. for (n in 0...count - 2) ilen += 3;
  655. count = 0;
  656. }
  657. pos++;
  658. }
  659. // Pack into 16bit
  660. var posa = new kha.arrays.Int16Array(vlen * 4);
  661. var nora = new kha.arrays.Int16Array(vlen * 2);
  662. var texa = tbuf != null ? new kha.arrays.Int16Array(vlen * 2) : null;
  663. var inda = new kha.arrays.Uint32Array(ilen);
  664. pos = 0;
  665. count = 0;
  666. vlen = 0;
  667. ilen = 0;
  668. for (i in polys) {
  669. count++;
  670. if (i < 0) {
  671. polys[pos] = -i - 1;
  672. var start = pos - count + 1;
  673. for (n in 0...count) {
  674. var k = n + start;
  675. var vidx = polys[k];
  676. posa[vlen * 4 ] = Std.int(pbuf[vidx * 3 ] * inv);
  677. posa[vlen * 4 + 1] = Std.int(pbuf[vidx * 3 + 1] * inv);
  678. posa[vlen * 4 + 2] = Std.int(pbuf[vidx * 3 + 2] * inv);
  679. posa[vlen * 4 + 3] = Std.int(nbuf[k * 3 + 2] * 32767);
  680. nora[vlen * 2 ] = Std.int(nbuf[k * 3 ] * 32767);
  681. nora[vlen * 2 + 1] = Std.int(nbuf[k * 3 + 1] * 32767);
  682. if (tbuf != null) {
  683. var iuv = tbuf.index[k];
  684. texa[vlen * 2 ] = Std.int( tbuf.values[iuv * 2 ] * 32767);
  685. texa[vlen * 2 + 1] = Std.int((1.0 - tbuf.values[iuv * 2 + 1]) * 32767);
  686. }
  687. vlen++;
  688. }
  689. // polygons are actually triangle fans
  690. for (n in 0...count - 2) {
  691. inda[ilen + 2] = start + n;
  692. inda[ilen + 1] = start + count - 1;
  693. inda[ilen ] = start + n + 1;
  694. ilen += 3;
  695. }
  696. polys[pos] = i; // restore
  697. count = 0;
  698. }
  699. pos++;
  700. }
  701. return { posa: posa, nora: nora, texa: texa, inda: inda, scalePos: scalePos };
  702. }
  703. }