FbxLibrary.hx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881
  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. }
  46. if (!found) {
  47. if (opt) 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. }
  63. }
  64. }
  65. cur = out;
  66. if (cur.length == 0) return cur;
  67. }
  68. return cur;
  69. }
  70. public static function getInts(n: FbxNode) {
  71. if (n.props.length != 1) {
  72. throw n.name + " has " + n.props + " props";
  73. }
  74. switch (n.props[0]) {
  75. case PInts(v):
  76. return v;
  77. default:
  78. throw n.name + " has " + n.props + " props";
  79. }
  80. }
  81. public static function getFloats(n: FbxNode) {
  82. if (n.props.length != 1) {
  83. throw n.name + " has " + n.props + " props";
  84. }
  85. switch(n.props[0]) {
  86. case PFloats(v):
  87. return v;
  88. case PInts(i):
  89. var fl = new Array<Float>();
  90. for(x in i) fl.push(x);
  91. n.props[0] = PFloats(fl); // keep data synchronized
  92. // this is necessary for merging geometries since we are pushing directly into the
  93. // float buffer
  94. return fl;
  95. default:
  96. throw n.name + " has " + n.props + " props";
  97. }
  98. }
  99. public static function hasProp(n: FbxNode, p: FbxProp) {
  100. for (p2 in n.props) {
  101. if (Type.enumEq(p, p2)) {
  102. return true;
  103. }
  104. }
  105. return false;
  106. }
  107. static inline function idToInt(f: Float) {
  108. return Std.int(f);
  109. }
  110. public static function toInt(n: FbxProp) {
  111. if (n == null) throw "null prop";
  112. return switch(n) {
  113. case PInt(v): v;
  114. case PFloat(f): idToInt(f);
  115. default: throw "Invalid prop " + n;
  116. }
  117. }
  118. public static function toFloat(n: FbxProp) {
  119. if (n == null) throw "null prop";
  120. return switch(n) {
  121. case PInt(v): v * 1.0;
  122. case PFloat(v): v;
  123. default: throw "Invalid prop " + n;
  124. }
  125. }
  126. public static function toString(n: FbxProp) {
  127. if (n == null) throw "null prop";
  128. return switch(n) {
  129. case PString(v): v;
  130. default: throw "Invalid prop " + n;
  131. }
  132. }
  133. public static function getId(n: FbxNode) {
  134. if (n.props.length != 3) throw n.name + " is not an object";
  135. return switch(n.props[0]) {
  136. case PInt(id): id;
  137. case PFloat(id): idToInt(id);
  138. default: throw n.name + " is not an object " + n.props;
  139. }
  140. }
  141. public static function getName(n: FbxNode) {
  142. if (n.props.length != 3) throw n.name + " is not an object";
  143. return switch(n.props[1]) {
  144. case PString(n): n.split("::").pop();
  145. default: throw n.name + " is not an object";
  146. }
  147. }
  148. public static function getType(n: FbxNode) {
  149. if (n.props.length != 3) throw n.name + " is not an object";
  150. return switch(n.props[2]) {
  151. case PString(n): n;
  152. default: throw n.name + " is not an object";
  153. }
  154. }
  155. }
  156. private enum Token {
  157. TIdent(s: String);
  158. TNode(s: String);
  159. TInt(s: String);
  160. TFloat(s: String);
  161. TString(s: String);
  162. TLength(v: Int);
  163. TBraceOpen;
  164. TBraceClose;
  165. TColon;
  166. TEof;
  167. }
  168. class Parser {
  169. var line: Int;
  170. var buf: String;
  171. var pos: Int;
  172. var token: Null<Token>;
  173. function new() {}
  174. function parseText(str): FbxNode {
  175. this.buf = str;
  176. this.pos = 0;
  177. this.line = 1;
  178. token = null;
  179. return {
  180. name: "Root",
  181. props: [PInt(0),PString("Root"),PString("Root")],
  182. childs: parseNodes(),
  183. };
  184. }
  185. function parseNodes() {
  186. var nodes = [];
  187. while (true) {
  188. switch(peek()) {
  189. case TEof, TBraceClose:
  190. return nodes;
  191. default:
  192. }
  193. nodes.push(parseNode());
  194. }
  195. return nodes;
  196. }
  197. function parseNode(): FbxNode {
  198. var t = next();
  199. var name = switch(t) {
  200. case TNode(n): n;
  201. default: unexpected(t);
  202. };
  203. var props = [], childs = null;
  204. while (true) {
  205. t = next();
  206. switch(t) {
  207. case TFloat(s):
  208. props.push(PFloat(Std.parseFloat(s)));
  209. case TInt(s):
  210. props.push(PInt(Std.parseInt(s)));
  211. case TString(s):
  212. props.push(PString(s));
  213. case TIdent(s):
  214. props.push(PIdent(s));
  215. case TBraceOpen, TBraceClose, TNode(_):
  216. token = t;
  217. case TLength(v):
  218. except(TBraceOpen);
  219. except(TNode("a"));
  220. var ints: Array<Int> = [];
  221. var floats: Array<Float> = null;
  222. var i = 0;
  223. while (i < v) {
  224. t = next();
  225. switch(t) {
  226. case TColon:
  227. continue;
  228. case TInt(s):
  229. i++;
  230. if (floats == null) {
  231. ints.push(Std.parseInt(s));
  232. }
  233. else {
  234. floats.push(Std.parseInt(s));
  235. }
  236. case TFloat(s):
  237. i++;
  238. if (floats == null) {
  239. floats = [];
  240. for(i in ints) {
  241. floats.push(i);
  242. }
  243. ints = null;
  244. }
  245. floats.push(Std.parseFloat(s));
  246. default:
  247. unexpected(t);
  248. }
  249. }
  250. props.push(floats == null ? PInts(ints) : PFloats(floats));
  251. except(TBraceClose);
  252. break;
  253. default:
  254. unexpected(t);
  255. }
  256. t = next();
  257. switch(t) {
  258. case TNode(_), TBraceClose:
  259. token = t; // next
  260. break;
  261. case TColon:
  262. // next prop
  263. case TBraceOpen:
  264. childs = parseNodes();
  265. except(TBraceClose);
  266. break;
  267. default:
  268. unexpected(t);
  269. }
  270. }
  271. if (childs == null) childs = [];
  272. return { name: name, props: props, childs: childs };
  273. }
  274. function except(except: Token) {
  275. var t = next();
  276. if (!Type.enumEq(t, except)) {
  277. error("Unexpected '" + tokenStr(t) + "' (" + tokenStr(except) + " expected)");
  278. }
  279. }
  280. function peek() {
  281. if (token == null) {
  282. token = nextToken();
  283. }
  284. return token;
  285. }
  286. function next() {
  287. if (token == null) {
  288. return nextToken();
  289. }
  290. var tmp = token;
  291. token = null;
  292. return tmp;
  293. }
  294. function error(msg: String): Dynamic {
  295. throw msg + " (line " + line + ")";
  296. return null;
  297. }
  298. function unexpected(t: Token): Dynamic {
  299. return error("Unexpected " + tokenStr(t));
  300. }
  301. function tokenStr(t: Token) {
  302. return switch(t) {
  303. case TEof: "<eof>";
  304. case TBraceOpen: '{';
  305. case TBraceClose: '}';
  306. case TIdent(i): i;
  307. case TNode(i): i+":";
  308. case TFloat(f): f;
  309. case TInt(i): i;
  310. case TString(s): '"' + s + '"';
  311. case TColon: ',';
  312. case TLength(l): '*' + l;
  313. };
  314. }
  315. inline function nextChar() {
  316. return StringTools.fastCodeAt(buf, pos++);
  317. }
  318. inline function getBuf(pos: Int, len: Int) {
  319. return buf.substr(pos, len);
  320. }
  321. inline function isIdentChar(c) {
  322. return (c >= 'a'.code && c <= 'z'.code) || (c >= 'A'.code && c <= 'Z'.code) || (c >= '0'.code && c <= '9'.code) || c == '_'.code || c == '-'.code;
  323. }
  324. @:noDebug
  325. function nextToken() {
  326. var start = pos;
  327. while (true) {
  328. var c = nextChar();
  329. switch(c) {
  330. case ' '.code, '\r'.code, '\t'.code:
  331. start++;
  332. case '\n'.code:
  333. line++;
  334. start++;
  335. case ';'.code:
  336. while (true) {
  337. var c = nextChar();
  338. if (StringTools.isEof(c) || c == '\n'.code) {
  339. pos--;
  340. break;
  341. }
  342. }
  343. start = pos;
  344. case ','.code:
  345. return TColon;
  346. case '{'.code:
  347. return TBraceOpen;
  348. case '}'.code:
  349. return TBraceClose;
  350. case '"'.code:
  351. start = pos;
  352. while (true) {
  353. c = nextChar();
  354. if (c == '"'.code) {
  355. break;
  356. }
  357. if (StringTools.isEof(c) || c == '\n'.code) {
  358. error("Unclosed string");
  359. }
  360. }
  361. return TString(getBuf(start, pos - start - 1));
  362. case '*'.code:
  363. start = pos;
  364. do {
  365. c = nextChar();
  366. } while (c >= '0'.code && c <= '9'.code);
  367. pos--;
  368. return TLength(Std.parseInt(getBuf(start, pos - start)));
  369. default:
  370. if ((c >= 'a'.code && c <= 'z'.code) || (c >= 'A'.code && c <= 'Z'.code) || c == '_'.code) {
  371. do {
  372. c = nextChar();
  373. } while (isIdentChar(c));
  374. if (c == ':'.code) {
  375. return TNode(getBuf(start, pos - start - 1));
  376. }
  377. pos--;
  378. return TIdent(getBuf(start, pos - start));
  379. }
  380. if ((c >= '0'.code && c <= '9'.code) || c == '-'.code) {
  381. do {
  382. c = nextChar();
  383. } while (c >= '0'.code && c <= '9'.code);
  384. if (c != '.'.code && c != 'E'.code && c != 'e'.code && pos - start < 10) {
  385. pos--;
  386. return TInt(getBuf(start, pos - start));
  387. }
  388. if (c == '.'.code) {
  389. do {
  390. c = nextChar();
  391. } while (c >= '0'.code && c <= '9'.code);
  392. }
  393. if (c == 'e'.code || c == 'E'.code) {
  394. c = nextChar();
  395. if (c != '-'.code && c != '+'.code)
  396. pos--;
  397. do {
  398. c = nextChar();
  399. } while (c >= '0'.code && c <= '9'.code);
  400. }
  401. pos--;
  402. return TFloat(getBuf(start, pos - start));
  403. }
  404. if (StringTools.isEof(c)) {
  405. pos--;
  406. return TEof;
  407. }
  408. error("Unexpected char '" + String.fromCharCode(c) + "'");
  409. }
  410. }
  411. }
  412. public static function parse(text: String) {
  413. return new Parser().parseText(text);
  414. }
  415. }
  416. class FbxLibrary {
  417. var root: FbxNode;
  418. var ids: Map<Int,FbxNode>;
  419. var connect: Map<Int,Array<Int>>;
  420. var namedConnect: Map<Int,Map<String,Int>>;
  421. var invConnect: Map<Int,Array<Int>>;
  422. // var uvAnims: Map<String, Array<{ t : Float, u : Float, v : Float }>>;
  423. // var animationEvents: Array<{ frame : Int, data : String }>;
  424. /**
  425. The FBX version that was decoded
  426. **/
  427. public var version: Float = 0.;
  428. public function new() {
  429. root = { name: "Root", props: [], childs: [] };
  430. reset();
  431. }
  432. function reset() {
  433. ids = new Map();
  434. connect = new Map();
  435. namedConnect = new Map();
  436. invConnect = new Map();
  437. }
  438. public function load(root: FbxNode) {
  439. reset();
  440. this.root = root;
  441. version = FbxTools.toInt(FbxTools.get(root, "FBXHeaderExtension.FBXVersion").props[0]) / 1000;
  442. if (Std.int(version) != 7) {
  443. throw "FBX Version 7.x required : use FBX 2010 export";
  444. }
  445. for (c in root.childs) {
  446. init(c);
  447. }
  448. // init properties
  449. // for( m in FbxTools.getAll(this.root, "Objects.Model") ) {
  450. // for( p in FbxTools.getAll(m, "Properties70.P") )
  451. // switch( FbxTools.toString(p.props[0]) ) {
  452. // case "UDP3DSMAX":
  453. // var userProps = FbxTools.toString(p.props[4]).split("&cr;&lf;");
  454. // for( p in userProps ) {
  455. // var pl = p.split("=");
  456. // var pname = StringTools.trim(pl.shift());
  457. // var pval = StringTools.trim(pl.join("="));
  458. // switch( pname ) {
  459. // case "UV" if( pval != "" ):
  460. // var xml = try Xml.parse(pval) catch( e : Dynamic ) throw "Invalid UV data in " + FbxTools.getName(m);
  461. // 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]) }} ];
  462. // if( uvAnims == null ) uvAnims = new Map();
  463. // uvAnims.set(FbxTools.getName(m), frames);
  464. // case "Events":
  465. // var xml = try Xml.parse(pval) catch( e : Dynamic ) throw "Invalid Events data in " + FbxTools.getName(m);
  466. // 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(" ")) }} ];
  467. // default:
  468. // }
  469. // }
  470. // default:
  471. // }
  472. // }
  473. }
  474. function init(n: FbxNode) {
  475. switch (n.name) {
  476. case "Connections":
  477. for (c in n.childs) {
  478. if (c.name != "C") {
  479. continue;
  480. }
  481. var child = FbxTools.toInt(c.props[1]);
  482. var parent = FbxTools.toInt(c.props[2]);
  483. // Maya exports invalid references
  484. if (ids.get(child) == null || ids.get(parent) == null) continue;
  485. var name = c.props[3];
  486. if (name != null) {
  487. var name = FbxTools.toString(name);
  488. var nc = namedConnect.get(parent);
  489. if (nc == null) {
  490. nc = new Map();
  491. namedConnect.set(parent, nc);
  492. }
  493. nc.set(name, child);
  494. // don't register as a parent, since the target can also be the child of something else
  495. if (name == "LookAtProperty") continue;
  496. }
  497. var c = connect.get(parent);
  498. if (c == null) {
  499. c = [];
  500. connect.set(parent, c);
  501. }
  502. c.push(child);
  503. if (parent == 0) {
  504. continue;
  505. }
  506. var c = invConnect.get(child);
  507. if (c == null) {
  508. c = [];
  509. invConnect.set(child, c);
  510. }
  511. c.push(parent);
  512. }
  513. case "Objects":
  514. for (c in n.childs) {
  515. ids.set(FbxTools.getId(c), c);
  516. }
  517. default:
  518. }
  519. }
  520. public function getGeometry(name: String = "") {
  521. var geom = null;
  522. for (g in FbxTools.getAll(root, "Objects.Geometry")) {
  523. if (FbxTools.hasProp(g, PString("Geometry::" + name))) {
  524. geom = g;
  525. break;
  526. }
  527. }
  528. if (geom == null) {
  529. throw "Geometry " + name + " not found";
  530. }
  531. return new Geometry(this, geom);
  532. }
  533. public function getFirstGeometry() {
  534. var geom = FbxTools.getAll(root, "Objects.Geometry")[0];
  535. return new Geometry(this, geom);
  536. }
  537. public function getAllGeometries() {
  538. var geoms = FbxTools.getAll(root, "Objects.Geometry");
  539. var res: Array<Geometry> = [];
  540. for (g in geoms) res.push(new Geometry(this, g));
  541. return res;
  542. }
  543. }
  544. class Geometry {
  545. var lib: FbxLibrary;
  546. var root: FbxNode;
  547. static inline var eps = 1.0 / 32767;
  548. public function new(l, root) {
  549. this.lib = l;
  550. this.root = root;
  551. }
  552. public function getRoot() {
  553. return root;
  554. }
  555. public function getVertices() {
  556. return FbxTools.getFloats(FbxTools.get(root, "Vertices"));
  557. }
  558. public function getPolygons() {
  559. return FbxTools.getInts(FbxTools.get(root, "PolygonVertexIndex"));
  560. }
  561. /**
  562. Decode polygon informations into triangle indexes and vertices indexes.
  563. Returns vidx, which is the list of vertices indexes and iout which is the index buffer for the full vertex model
  564. **/
  565. public function getIndexes() {
  566. var count = 0, pos = 0;
  567. var index = getPolygons();
  568. var vout = [], iout = [];
  569. for (i in index) {
  570. count++;
  571. if (i < 0) {
  572. index[pos] = -i - 1;
  573. var start = pos - count + 1;
  574. for (n in 0...count) {
  575. vout.push(index[n + start]);
  576. }
  577. for (n in 0...count - 2) {
  578. iout.push(start + n);
  579. iout.push(start + count - 1);
  580. iout.push(start + n + 1);
  581. }
  582. index[pos] = i; // restore
  583. count = 0;
  584. }
  585. pos++;
  586. }
  587. return { vidx: vout, idx: iout };
  588. }
  589. public function getNormals() {
  590. return processVectors("LayerElementNormal", "Normals");
  591. }
  592. public function getTangents(opt = false) {
  593. return processVectors("LayerElementTangent", "Tangents", opt);
  594. }
  595. function processVectors(layer, name, opt = false) {
  596. var vect = FbxTools.get(root, layer + "." + name, opt);
  597. if (vect == null) return null;
  598. var nrm = FbxTools.getFloats(vect);
  599. // if by-vertice (Maya in some cases, unless maybe "Split per-Vertex Normals" is checked)
  600. // let's reindex based on polygon indexes
  601. if (FbxTools.toString(FbxTools.get(root, layer+".MappingInformationType").props[0]) == "ByVertice") {
  602. var nout = [];
  603. for (i in getPolygons()) {
  604. var vid = i;
  605. if (vid < 0) vid = -vid - 1;
  606. nout.push(nrm[vid * 3]);
  607. nout.push(nrm[vid * 3 + 1]);
  608. nout.push(nrm[vid * 3 + 2]);
  609. }
  610. nrm = nout;
  611. }
  612. return nrm;
  613. }
  614. public function getColors() {
  615. var color = FbxTools.get(root, "LayerElementColor",true);
  616. return color == null ? null : { values: FbxTools.getFloats(FbxTools.get(color, "Colors")), index: FbxTools.getInts(FbxTools.get(color, "ColorIndex")) };
  617. }
  618. public function getUVs() {
  619. var uvs = [];
  620. for (v in FbxTools.getAll(root, "LayerElementUV")) {
  621. var index = FbxTools.get(v, "UVIndex", true);
  622. var values = FbxTools.getFloats(FbxTools.get(v, "UV"));
  623. var index = if (index == null) {
  624. // ByVertice/Direct (Maya sometimes...)
  625. [for (i in getPolygons()) if (i < 0) -i - 1 else i];
  626. }
  627. else FbxTools.getInts(index);
  628. uvs.push({ values: values, index: index });
  629. }
  630. return uvs;
  631. }
  632. public function getBuffers(binary: Bool, p: FbxParser) {
  633. // triangulize indexes :
  634. // format is A,B,...,-X : negative values mark the end of the polygon
  635. var pbuf = getVertices();
  636. var nbuf = getNormals();
  637. var tbuf = getUVs()[0];
  638. var cbuf = FbxParser.parseVCols ? getColors() : null;
  639. var polys = getPolygons();
  640. if (FbxParser.parseTransform) {
  641. var m = iron.math.Mat4.identity();
  642. var v = new iron.math.Vec4(p.tx, p.ty, p.tz);
  643. var q = new iron.math.Quat();
  644. q.fromEuler(p.rx, p.ry, p.rz);
  645. var sc = new iron.math.Vec4(p.sx, p.sy, p.sz);
  646. m.compose(v, q, sc);
  647. for (i in 0...Std.int(pbuf.length / 3)) {
  648. v.set(pbuf[i * 3], pbuf[i * 3 + 1], pbuf[i * 3 + 2]);
  649. v.applymat(m);
  650. pbuf[i * 3 ] = v.x;
  651. pbuf[i * 3 + 1] = v.y;
  652. pbuf[i * 3 + 2] = v.z;
  653. }
  654. for (i in 0...Std.int(nbuf.length / 3)) {
  655. v.set(nbuf[i * 3], nbuf[i * 3 + 1], nbuf[i * 3 + 2]);
  656. v.applyQuat(q);
  657. nbuf[i * 3 ] = v.x;
  658. nbuf[i * 3 + 1] = v.y;
  659. nbuf[i * 3 + 2] = v.z;
  660. }
  661. }
  662. // Pack positions to (-1, 1) range
  663. var scalePos = 0.0;
  664. for (p in pbuf) {
  665. var f = Math.abs(p);
  666. if (scalePos < f) scalePos = f;
  667. }
  668. var inv = 32767 * (1 / scalePos);
  669. var pos = 0;
  670. var count = 0;
  671. var vlen = 0;
  672. var ilen = 0;
  673. for (i in polys) {
  674. count++;
  675. if (i < 0) {
  676. for (n in 0...count) vlen++;
  677. for (n in 0...count - 2) ilen += 3;
  678. count = 0;
  679. }
  680. pos++;
  681. }
  682. // Pack into 16bit
  683. var posa = new kha.arrays.Int16Array(vlen * 4);
  684. var nora = new kha.arrays.Int16Array(vlen * 2);
  685. var texa = tbuf != null ? new kha.arrays.Int16Array(vlen * 2) : null;
  686. var cola = cbuf != null ? new kha.arrays.Int16Array(vlen * 3) : null;
  687. var inda = new kha.arrays.Uint32Array(ilen);
  688. pos = 0;
  689. count = 0;
  690. vlen = 0;
  691. ilen = 0;
  692. for (i in polys) {
  693. count++;
  694. if (i < 0) {
  695. polys[pos] = -i - 1;
  696. var start = pos - count + 1;
  697. for (n in 0...count) {
  698. var k = n + start;
  699. var vidx = polys[k];
  700. posa[vlen * 4 ] = Std.int(pbuf[vidx * 3 ] * inv);
  701. posa[vlen * 4 + 1] = Std.int(pbuf[vidx * 3 + 1] * inv);
  702. posa[vlen * 4 + 2] = Std.int(pbuf[vidx * 3 + 2] * inv);
  703. posa[vlen * 4 + 3] = Std.int(nbuf[k * 3 + 2] * 32767);
  704. nora[vlen * 2 ] = Std.int(nbuf[k * 3 ] * 32767);
  705. nora[vlen * 2 + 1] = Std.int(nbuf[k * 3 + 1] * 32767);
  706. if (tbuf != null) {
  707. var iuv = tbuf.index[k];
  708. var uvx = tbuf.values[iuv * 2];
  709. if (uvx > 1.0 + eps) uvx = uvx - Std.int(uvx);
  710. var uvy = tbuf.values[iuv * 2 + 1];
  711. if (uvy > 1.0 + eps) uvy = uvy - Std.int(uvy);
  712. texa[vlen * 2 ] = Std.int( uvx * 32767);
  713. texa[vlen * 2 + 1] = Std.int((1.0 - uvy) * 32767);
  714. }
  715. if (cbuf != null) {
  716. var icol = cbuf.index[k];
  717. cola[vlen * 3 ] = Std.int(cbuf.values[icol * 4 ] * 32767);
  718. cola[vlen * 3 + 1] = Std.int(cbuf.values[icol * 4 + 1] * 32767);
  719. cola[vlen * 3 + 2] = Std.int(cbuf.values[icol * 4 + 2] * 32767);
  720. // cola[vlen * 4 + 3] = Std.int(cbuf.values[icol * 4 + 3] * 32767);
  721. }
  722. vlen++;
  723. }
  724. if (count <= 4) { // Convex, fan triangulation
  725. for (n in 0...count - 2) {
  726. inda[ilen + 2] = start + n;
  727. inda[ilen + 1] = start + count - 1;
  728. inda[ilen ] = start + n + 1;
  729. ilen += 3;
  730. }
  731. }
  732. else { // Convex or concave, ear clipping
  733. var va: Array<Int> = [];
  734. for (i in 0...count) va.push(start + i);
  735. var nx = nbuf[start * 3 ];
  736. var ny = nbuf[start * 3 + 1];
  737. var nz = nbuf[start * 3 + 2];
  738. var nxabs = Math.abs(nx);
  739. var nyabs = Math.abs(ny);
  740. var nzabs = Math.abs(nz);
  741. var flip = nx + ny + nz > 0;
  742. var axis = nxabs > nyabs && nxabs > nzabs ? 0 : nyabs > nxabs && nyabs > nzabs ? 1 : 2;
  743. var axis0 = axis == 0 ? (flip ? 2 : 1) : axis == 1 ? (flip ? 0 : 2) : (flip ? 1 : 0);
  744. var axis1 = axis == 0 ? (flip ? 1 : 2) : axis == 1 ? (flip ? 2 : 0) : (flip ? 0 : 1);
  745. var vi = count;
  746. var loops = 0;
  747. var i = -1;
  748. while (vi > 3 && loops++ < vi) {
  749. i = (i + 1) % vi;
  750. var i1 = (i + 1) % vi;
  751. var i2 = (i + 2) % vi;
  752. var vi0 = polys[va[i ]] * 3;
  753. var vi1 = polys[va[i1]] * 3;
  754. var vi2 = polys[va[i2]] * 3;
  755. var v0x = pbuf[vi0 + axis0];
  756. var v0y = pbuf[vi0 + axis1];
  757. var v1x = pbuf[vi1 + axis0];
  758. var v1y = pbuf[vi1 + axis1];
  759. var v2x = pbuf[vi2 + axis0];
  760. var v2y = pbuf[vi2 + axis1];
  761. var e0x = v0x - v1x; // Not an interior vertex
  762. var e0y = v0y - v1y;
  763. var e1x = v2x - v1x;
  764. var e1y = v2y - v1y;
  765. var cross = e0x * e1y - e0y * e1x;
  766. if (cross <= 0) continue;
  767. var overlap = false; // Other vertex found inside this triangle
  768. for (j in 0...vi - 3) {
  769. var j0 = polys[va[(i + 3 + j) % vi]] * 3;
  770. var px = pbuf[j0 + axis0];
  771. var py = pbuf[j0 + axis1];
  772. if (MeshParser.pnpoly(v0x, v0y, v1x, v1y, v2x, v2y, px, py)) {
  773. overlap = true;
  774. break;
  775. }
  776. }
  777. if (overlap) continue;
  778. inda[ilen++] = va[i ]; // Found ear
  779. inda[ilen++] = va[i1];
  780. inda[ilen++] = va[i2];
  781. for (j in ((i + 1) % vi)...vi - 1) { // Consume vertex
  782. va[j] = va[j + 1];
  783. }
  784. vi--;
  785. i--;
  786. loops = 0;
  787. }
  788. inda[ilen++] = va[0]; // Last one
  789. inda[ilen++] = va[1];
  790. inda[ilen++] = va[2];
  791. }
  792. polys[pos] = i; // restore
  793. count = 0;
  794. }
  795. pos++;
  796. }
  797. return { posa: posa, nora: nora, texa: texa, cola: cola, inda: inda, scalePos: scalePos };
  798. }
  799. }