From 45e17276143e1dfdb28805a29ac6f22632a172ec Mon Sep 17 00:00:00 2001 From: luboslenco Date: Thu, 2 Jan 2020 11:53:10 +0100 Subject: [PATCH] Include ObjParser --- mesh_import/Sources/arm/ImportMesh.hx | 11 +- mesh_import/Sources/arm/ObjParser.hx | 395 +++++++++++++++++++++++++ server_stream/Sources/arm/LoadTrait.hx | 1 - server_stream/Sources/arm/ObjParser.hx | 395 +++++++++++++++++++++++++ 4 files changed, 794 insertions(+), 8 deletions(-) create mode 100644 mesh_import/Sources/arm/ObjParser.hx create mode 100644 server_stream/Sources/arm/ObjParser.hx diff --git a/mesh_import/Sources/arm/ImportMesh.hx b/mesh_import/Sources/arm/ImportMesh.hx index 774863a..2a02c05 100644 --- a/mesh_import/Sources/arm/ImportMesh.hx +++ b/mesh_import/Sources/arm/ImportMesh.hx @@ -7,9 +7,6 @@ import iron.data.MeshData; import iron.data.MaterialData; import iron.system.Input; -// Format library should be enabled in `Armory Project - Modules - Format` -import iron.format.obj.ObjParser; - class ImportMesh extends iron.Trait { var meshData:MeshData; @@ -23,13 +20,13 @@ class ImportMesh extends iron.Trait { // Parse obj file var mesh = new ObjParser(blob); - + // Positions, normals and indices var pos:TVertexArray = { attrib: "pos", values: mesh.posa }; var nor:TVertexArray = { attrib: "nor", values: mesh.nora }; var ind:TIndexArray = { material: 0, values: mesh.inda }; - var rawmeshData:TMeshData = { + var rawmeshData:TMeshData = { name: "BoxMesh", vertex_arrays: [pos, nor], index_arrays: [ind], @@ -41,7 +38,7 @@ class ImportMesh extends iron.Trait { new MeshData(rawmeshData, function(data:MeshData) { meshData = data; meshData.geom.calculateAABB(); - + // Fetch material from scene data Data.getMaterial("Scene", "Material", function(data:MaterialData) { // Material loaded @@ -58,7 +55,7 @@ class ImportMesh extends iron.Trait { if (mouse.started()) { // Create new object in active scene var object = Scene.active.addMeshObject(meshData, materials); - + // Just for testing, add rigid body trait var aabb = meshData.geom.aabb; object.transform.loc.set(Math.random() * 8 - 4, Math.random() * 8 - 4, 5); diff --git a/mesh_import/Sources/arm/ObjParser.hx b/mesh_import/Sources/arm/ObjParser.hx new file mode 100644 index 0000000..012cfe3 --- /dev/null +++ b/mesh_import/Sources/arm/ObjParser.hx @@ -0,0 +1,395 @@ +package arm; + +class ObjParser { + + public static var splitCode = "o".code; // Object split, "g" for groups, "u"semtl for materials + public var posa: kha.arrays.Int16Array = null; + public var nora: kha.arrays.Int16Array = null; + public var texa: kha.arrays.Int16Array = null; + public var inda: kha.arrays.Uint32Array = null; + public var udims: Array = null; // Indices split per udim tile + public var udimsU = 1; // Number of horizontal udim tiles + public var scalePos = 1.0; + public var scaleTex = 1.0; + public var name = ""; + public var hasNext = false; // File contains multiple objects + public var pos = 0; + var posTemp: Array; + var uvTemp: Array; + var norTemp: Array; + var va: kha.arrays.Uint32Array; + var ua: kha.arrays.Uint32Array; + var na: kha.arrays.Uint32Array; + var vi = 0; + var ui = 0; + var ni = 0; + var buf: haxe.io.UInt8Array = null; + + static var vindOff = 0; + static var tindOff = 0; + static var nindOff = 0; + static var bytes: haxe.io.Bytes = null; + static var posFirst: Array; + static var uvFirst: Array; + static var norFirst: Array; + + public function new(blob: kha.Blob, startPos = 0, udim = false) { + pos = startPos; + var posIndices: Array = []; + var uvIndices: Array = []; + var norIndices: Array = []; + var readingFaces = false; + var readingObject = false; + var fullAttrib = false; + bytes = blob.bytes; + + posTemp = []; + uvTemp = []; + norTemp = []; + va = new kha.arrays.Uint32Array(60); + ua = new kha.arrays.Uint32Array(60); + na = new kha.arrays.Uint32Array(60); + buf = new haxe.io.UInt8Array(64); + + if (splitCode == "u".code && startPos > 0) { + posTemp = posFirst; + norTemp = norFirst; + uvTemp = uvFirst; + } + + while (true) { + if (pos >= bytes.length) break; + + var c0 = bytes.get(pos++); + if (readingObject && readingFaces && (c0 == "v".code || c0 == splitCode)) { + pos--; + hasNext = true; + break; + } + + if (c0 == "v".code) { + var c1 = bytes.get(pos++); + if (c1 == " ".code) { + // Some exporters put additional space directly after "v" + if (bytes.get(pos) == " ".code) pos++; + posTemp.push(readFloat()); + pos++; // Space + posTemp.push(readFloat()); + pos++; // Space + posTemp.push(readFloat()); + } + else if (c1 == "t".code) { + pos++; // Space + uvTemp.push(readFloat()); + pos++; // Space + uvTemp.push(readFloat()); + if (norTemp.length > 0) fullAttrib = true; + } + else if (c1 == "n".code) { + pos++; // Space + norTemp.push(readFloat()); + pos++; // Space + norTemp.push(readFloat()); + pos++; // Space + norTemp.push(readFloat()); + if (uvTemp.length > 0) fullAttrib = true; + } + } + else if (c0 == "f".code) { + pos++; // Space + readingFaces = true; + vi = 0; + ui = 0; + ni = 0; + fullAttrib ? readFaceFast() : readFace(); + + posIndices.push(va[0]); + posIndices.push(va[1]); + posIndices.push(va[2]); + for (i in 3...vi) { + posIndices.push(va[0]); + posIndices.push(va[i - 1]); + posIndices.push(va[i]); + } + if (uvTemp.length > 0) { + uvIndices.push(ua[0]); + uvIndices.push(ua[1]); + uvIndices.push(ua[2]); + for (i in 3...ui) { + uvIndices.push(ua[0]); + uvIndices.push(ua[i - 1]); + uvIndices.push(ua[i]); + } + } + if (norTemp.length > 0) { + norIndices.push(na[0]); + norIndices.push(na[1]); + norIndices.push(na[2]); + for (i in 3...ni) { + norIndices.push(na[0]); + norIndices.push(na[i - 1]); + norIndices.push(na[i]); + } + } + } + else if (c0 == splitCode) { + if (splitCode == "u".code) pos += 5; // "u"semtl + pos++; // Space + if (!udim) readingObject = true; + name = readString(); + } + nextLine(); + } + + if (startPos > 0) { + if (splitCode != "u".code) { + for (i in 0...posIndices.length) posIndices[i] -= vindOff; + for (i in 0...uvIndices.length) uvIndices[i] -= tindOff; + for (i in 0...norIndices.length) norIndices[i] -= nindOff; + } + } + else { + vindOff = tindOff = nindOff = 0; + + if (splitCode == "u".code) { + posFirst = posTemp; + norFirst = norTemp; + uvFirst = uvTemp; + } + } + vindOff += Std.int(posTemp.length / 3); // Assumes separate vertex data per object + tindOff += Std.int(uvTemp.length / 2); + nindOff += Std.int(norTemp.length / 3); + + // Pack positions to (-1, 1) range + scalePos = 0.0; + for (i in 0...posTemp.length) { + var f = Math.abs(posTemp[i]); + if (scalePos < f) scalePos = f; + } + var inv = 32767 * (1 / scalePos); + + posa = new kha.arrays.Int16Array(posIndices.length * 4); + inda = new kha.arrays.Uint32Array(posIndices.length); + for (i in 0...posIndices.length) { + posa[i * 4 ] = Std.int( posTemp[posIndices[i] * 3 ] * inv); + posa[i * 4 + 1] = Std.int(-posTemp[posIndices[i] * 3 + 2] * inv); + posa[i * 4 + 2] = Std.int( posTemp[posIndices[i] * 3 + 1] * inv); + inda[i] = i; + } + + if (norIndices.length > 0) { + nora = new kha.arrays.Int16Array(norIndices.length * 2); + for (i in 0...posIndices.length) { + nora[i * 2 ] = Std.int( norTemp[norIndices[i] * 3 ] * 32767); + nora[i * 2 + 1] = Std.int(-norTemp[norIndices[i] * 3 + 2] * 32767); + posa[i * 4 + 3] = Std.int( norTemp[norIndices[i] * 3 + 1] * 32767); + } + } + else { + // Calc normals + nora = new kha.arrays.Int16Array(inda.length * 2); + var va = new iron.math.Vec4(); + var vb = new iron.math.Vec4(); + var vc = new iron.math.Vec4(); + var cb = new iron.math.Vec4(); + var ab = new iron.math.Vec4(); + for (i in 0...Std.int(inda.length / 3)) { + var i1 = inda[i * 3 ]; + var i2 = inda[i * 3 + 1]; + var i3 = inda[i * 3 + 2]; + va.set(posa[i1 * 4], posa[i1 * 4 + 1], posa[i1 * 4 + 2]); + vb.set(posa[i2 * 4], posa[i2 * 4 + 1], posa[i2 * 4 + 2]); + vc.set(posa[i3 * 4], posa[i3 * 4 + 1], posa[i3 * 4 + 2]); + cb.subvecs(vc, vb); + ab.subvecs(va, vb); + cb.cross(ab); + cb.normalize(); + nora[i1 * 2 ] = Std.int(cb.x * 32767); + nora[i1 * 2 + 1] = Std.int(cb.y * 32767); + posa[i1 * 4 + 3] = Std.int(cb.z * 32767); + nora[i2 * 2 ] = Std.int(cb.x * 32767); + nora[i2 * 2 + 1] = Std.int(cb.y * 32767); + posa[i2 * 4 + 3] = Std.int(cb.z * 32767); + nora[i3 * 2 ] = Std.int(cb.x * 32767); + nora[i3 * 2 + 1] = Std.int(cb.y * 32767); + posa[i3 * 4 + 3] = Std.int(cb.z * 32767); + } + } + + if (uvIndices.length > 0) { + if (udim) { + // Find number of tiles + var tilesU = 1; + var tilesV = 1; + for (i in 0...Std.int(uvTemp.length / 2)) { + while (uvTemp[i * 2 ] > tilesU) tilesU++; + while (uvTemp[i * 2 + 1] > tilesV) tilesV++; + } + + function getTile(i1: Int, i2: Int, i3: Int): Int { + var u1 = uvTemp[uvIndices[i1] * 2 ]; + var v1 = uvTemp[uvIndices[i1] * 2 + 1]; + var u2 = uvTemp[uvIndices[i2] * 2 ]; + var v2 = uvTemp[uvIndices[i2] * 2 + 1]; + var u3 = uvTemp[uvIndices[i3] * 2 ]; + var v3 = uvTemp[uvIndices[i3] * 2 + 1]; + var tileU = Std.int((u1 + u2 + u3) / 3); + var tileV = Std.int((v1 + v2 + v3) / 3); + return tileU + tileV * tilesU; + } + + // Amount of indices pre tile + var num = new kha.arrays.Uint32Array(tilesU * tilesV); + for (i in 0...Std.int(inda.length / 3)) { + var tile = getTile(inda[i * 3], inda[i * 3 + 1], inda[i * 3 + 2]); + num[tile] += 3; + } + + // Split indices per tile + udims = []; + udimsU = tilesU; + for (i in 0...tilesU * tilesV) { udims.push(new kha.arrays.Uint32Array(num[i])); num[i] = 0; } + + for (i in 0...Std.int(inda.length / 3)) { + var i1 = inda[i * 3 ]; + var i2 = inda[i * 3 + 1]; + var i3 = inda[i * 3 + 2]; + var tile = getTile(i1, i2, i3); + udims[tile][num[tile]++] = i1; + udims[tile][num[tile]++] = i2; + udims[tile][num[tile]++] = i3; + } + + // Normalize uvs to 0-1 range + var uvtiles = new kha.arrays.Int16Array(uvTemp.length); + for (i in 0...Std.int(inda.length / 3)) { // TODO: merge loops + var i1 = inda[i * 3 ]; + var i2 = inda[i * 3 + 1]; + var i3 = inda[i * 3 + 2]; + var tile = getTile(i1, i2, i3); + var tileU = tile % tilesU; + var tileV = Std.int(tile / tilesU); + uvtiles[uvIndices[i1] * 2 ] = tileU; + uvtiles[uvIndices[i1] * 2 + 1] = tileV; + uvtiles[uvIndices[i2] * 2 ] = tileU; + uvtiles[uvIndices[i2] * 2 + 1] = tileV; + uvtiles[uvIndices[i3] * 2 ] = tileU; + uvtiles[uvIndices[i3] * 2 + 1] = tileV; + } + for (i in 0...uvtiles.length) uvTemp[i] -= uvtiles[i]; + } + + texa = new kha.arrays.Int16Array(uvIndices.length * 2); + for (i in 0...posIndices.length) { + texa[i * 2 ] = Std.int( uvTemp[uvIndices[i] * 2 ] * 32767); + texa[i * 2 + 1] = Std.int((1.0 - uvTemp[uvIndices[i] * 2 + 1]) * 32767); + } + } + bytes = null; + if (!hasNext) { posFirst = norFirst = uvFirst = null; } + } + + function readFaceFast() { + while (true) { + va[vi++] = readInt() - 1; + pos++; // "/" + ua[ui++] = readInt() - 1; + pos++; // "/" + na[ni++] = readInt() - 1; + if (bytes.get(pos) == "\n".code || bytes.get(pos) == "\r".code) break; + pos++; // " " + // Some exporters put space at the end of "f" line + if (vi >= 3 && (bytes.get(pos) == "\n".code || bytes.get(pos) == "\r".code)) break; + } + } + + function readFace() { + while (true) { + va[vi++] = readInt() - 1; + if (uvTemp.length > 0 || norTemp.length > 0) { + pos++; // "/" + } + if (uvTemp.length > 0) { + ua[ui++] = readInt() - 1; + } + if (norTemp.length > 0) { + pos++; // "/" + na[ni++] = readInt() - 1; + } + if (bytes.get(pos) == "\n".code || bytes.get(pos) == "\r".code) break; + pos++; // " " + // Some exporters put space at the end of "f" line + if (vi >= 3 && (bytes.get(pos) == "\n".code || bytes.get(pos) == "\r".code)) break; + } + } + + function readFloat(): Float { + var bi = 0; + while (true) { // Read into buffer + var c = bytes.get(pos); + if (c == " ".code || c == "\n".code || c == "\r".code) break; + if (c == "E".code || c == "e".code) { + while (true) { + pos++; + c = bytes.get(pos); + if (c == " ".code || c == "\n".code || c == "\r".code) break; + } + return 0.0; // Assume number close to zero for now + } + pos++; + buf[bi++] = c; + } + var res = 0.0; // Parse buffer into float + var dot = 1; + var dec = 1; + var off = buf[0] == "-".code ? 1 : 0; + var len = bi - 1; + for (i in 0...bi - off) { + var c = buf[len - i]; + if (c == ".".code) { dot = dec; continue; } + res += (c - 48) * dec; + dec *= 10; + } + off > 0 ? res /= -dot : res /= dot; + return res; + } + + function readInt(): Int { + var bi = 0; + while (true) { // Read into buffer + var c = bytes.get(pos); + if (c == "/".code || c == "\n".code || c == "\r".code || c == " ".code) break; + pos++; + buf[bi++] = c; + } + var res = 0; // Parse buffer into int + var dec = 1; + var off = buf[0] == "-".code ? 1 : 0; + var len = bi - 1; + for (i in 0...bi - off) { + res += (buf[len - i] - 48) * dec; + dec *= 10; + } + if (off > 0) res *= -1; + return res; + } + + function readString(): String { + var s = ""; + while (true) { + var c = bytes.get(pos); + if (c == "\n".code || c == "\r".code || c == " ".code) break; + pos++; + s += String.fromCharCode(c); + } + return s; + } + + function nextLine() { + while (true) { + var c = bytes.get(pos++); + if (c == "\n".code || pos >= bytes.length) break; // \n, \r\n + } + } +} diff --git a/server_stream/Sources/arm/LoadTrait.hx b/server_stream/Sources/arm/LoadTrait.hx index a4750ef..e6afe1d 100644 --- a/server_stream/Sources/arm/LoadTrait.hx +++ b/server_stream/Sources/arm/LoadTrait.hx @@ -5,7 +5,6 @@ import iron.object.MeshObject; import iron.data.MeshData; import iron.data.SceneFormat; import iron.Scene; -import iron.format.obj.ObjParser; class LoadTrait extends iron.Trait { diff --git a/server_stream/Sources/arm/ObjParser.hx b/server_stream/Sources/arm/ObjParser.hx new file mode 100644 index 0000000..012cfe3 --- /dev/null +++ b/server_stream/Sources/arm/ObjParser.hx @@ -0,0 +1,395 @@ +package arm; + +class ObjParser { + + public static var splitCode = "o".code; // Object split, "g" for groups, "u"semtl for materials + public var posa: kha.arrays.Int16Array = null; + public var nora: kha.arrays.Int16Array = null; + public var texa: kha.arrays.Int16Array = null; + public var inda: kha.arrays.Uint32Array = null; + public var udims: Array = null; // Indices split per udim tile + public var udimsU = 1; // Number of horizontal udim tiles + public var scalePos = 1.0; + public var scaleTex = 1.0; + public var name = ""; + public var hasNext = false; // File contains multiple objects + public var pos = 0; + var posTemp: Array; + var uvTemp: Array; + var norTemp: Array; + var va: kha.arrays.Uint32Array; + var ua: kha.arrays.Uint32Array; + var na: kha.arrays.Uint32Array; + var vi = 0; + var ui = 0; + var ni = 0; + var buf: haxe.io.UInt8Array = null; + + static var vindOff = 0; + static var tindOff = 0; + static var nindOff = 0; + static var bytes: haxe.io.Bytes = null; + static var posFirst: Array; + static var uvFirst: Array; + static var norFirst: Array; + + public function new(blob: kha.Blob, startPos = 0, udim = false) { + pos = startPos; + var posIndices: Array = []; + var uvIndices: Array = []; + var norIndices: Array = []; + var readingFaces = false; + var readingObject = false; + var fullAttrib = false; + bytes = blob.bytes; + + posTemp = []; + uvTemp = []; + norTemp = []; + va = new kha.arrays.Uint32Array(60); + ua = new kha.arrays.Uint32Array(60); + na = new kha.arrays.Uint32Array(60); + buf = new haxe.io.UInt8Array(64); + + if (splitCode == "u".code && startPos > 0) { + posTemp = posFirst; + norTemp = norFirst; + uvTemp = uvFirst; + } + + while (true) { + if (pos >= bytes.length) break; + + var c0 = bytes.get(pos++); + if (readingObject && readingFaces && (c0 == "v".code || c0 == splitCode)) { + pos--; + hasNext = true; + break; + } + + if (c0 == "v".code) { + var c1 = bytes.get(pos++); + if (c1 == " ".code) { + // Some exporters put additional space directly after "v" + if (bytes.get(pos) == " ".code) pos++; + posTemp.push(readFloat()); + pos++; // Space + posTemp.push(readFloat()); + pos++; // Space + posTemp.push(readFloat()); + } + else if (c1 == "t".code) { + pos++; // Space + uvTemp.push(readFloat()); + pos++; // Space + uvTemp.push(readFloat()); + if (norTemp.length > 0) fullAttrib = true; + } + else if (c1 == "n".code) { + pos++; // Space + norTemp.push(readFloat()); + pos++; // Space + norTemp.push(readFloat()); + pos++; // Space + norTemp.push(readFloat()); + if (uvTemp.length > 0) fullAttrib = true; + } + } + else if (c0 == "f".code) { + pos++; // Space + readingFaces = true; + vi = 0; + ui = 0; + ni = 0; + fullAttrib ? readFaceFast() : readFace(); + + posIndices.push(va[0]); + posIndices.push(va[1]); + posIndices.push(va[2]); + for (i in 3...vi) { + posIndices.push(va[0]); + posIndices.push(va[i - 1]); + posIndices.push(va[i]); + } + if (uvTemp.length > 0) { + uvIndices.push(ua[0]); + uvIndices.push(ua[1]); + uvIndices.push(ua[2]); + for (i in 3...ui) { + uvIndices.push(ua[0]); + uvIndices.push(ua[i - 1]); + uvIndices.push(ua[i]); + } + } + if (norTemp.length > 0) { + norIndices.push(na[0]); + norIndices.push(na[1]); + norIndices.push(na[2]); + for (i in 3...ni) { + norIndices.push(na[0]); + norIndices.push(na[i - 1]); + norIndices.push(na[i]); + } + } + } + else if (c0 == splitCode) { + if (splitCode == "u".code) pos += 5; // "u"semtl + pos++; // Space + if (!udim) readingObject = true; + name = readString(); + } + nextLine(); + } + + if (startPos > 0) { + if (splitCode != "u".code) { + for (i in 0...posIndices.length) posIndices[i] -= vindOff; + for (i in 0...uvIndices.length) uvIndices[i] -= tindOff; + for (i in 0...norIndices.length) norIndices[i] -= nindOff; + } + } + else { + vindOff = tindOff = nindOff = 0; + + if (splitCode == "u".code) { + posFirst = posTemp; + norFirst = norTemp; + uvFirst = uvTemp; + } + } + vindOff += Std.int(posTemp.length / 3); // Assumes separate vertex data per object + tindOff += Std.int(uvTemp.length / 2); + nindOff += Std.int(norTemp.length / 3); + + // Pack positions to (-1, 1) range + scalePos = 0.0; + for (i in 0...posTemp.length) { + var f = Math.abs(posTemp[i]); + if (scalePos < f) scalePos = f; + } + var inv = 32767 * (1 / scalePos); + + posa = new kha.arrays.Int16Array(posIndices.length * 4); + inda = new kha.arrays.Uint32Array(posIndices.length); + for (i in 0...posIndices.length) { + posa[i * 4 ] = Std.int( posTemp[posIndices[i] * 3 ] * inv); + posa[i * 4 + 1] = Std.int(-posTemp[posIndices[i] * 3 + 2] * inv); + posa[i * 4 + 2] = Std.int( posTemp[posIndices[i] * 3 + 1] * inv); + inda[i] = i; + } + + if (norIndices.length > 0) { + nora = new kha.arrays.Int16Array(norIndices.length * 2); + for (i in 0...posIndices.length) { + nora[i * 2 ] = Std.int( norTemp[norIndices[i] * 3 ] * 32767); + nora[i * 2 + 1] = Std.int(-norTemp[norIndices[i] * 3 + 2] * 32767); + posa[i * 4 + 3] = Std.int( norTemp[norIndices[i] * 3 + 1] * 32767); + } + } + else { + // Calc normals + nora = new kha.arrays.Int16Array(inda.length * 2); + var va = new iron.math.Vec4(); + var vb = new iron.math.Vec4(); + var vc = new iron.math.Vec4(); + var cb = new iron.math.Vec4(); + var ab = new iron.math.Vec4(); + for (i in 0...Std.int(inda.length / 3)) { + var i1 = inda[i * 3 ]; + var i2 = inda[i * 3 + 1]; + var i3 = inda[i * 3 + 2]; + va.set(posa[i1 * 4], posa[i1 * 4 + 1], posa[i1 * 4 + 2]); + vb.set(posa[i2 * 4], posa[i2 * 4 + 1], posa[i2 * 4 + 2]); + vc.set(posa[i3 * 4], posa[i3 * 4 + 1], posa[i3 * 4 + 2]); + cb.subvecs(vc, vb); + ab.subvecs(va, vb); + cb.cross(ab); + cb.normalize(); + nora[i1 * 2 ] = Std.int(cb.x * 32767); + nora[i1 * 2 + 1] = Std.int(cb.y * 32767); + posa[i1 * 4 + 3] = Std.int(cb.z * 32767); + nora[i2 * 2 ] = Std.int(cb.x * 32767); + nora[i2 * 2 + 1] = Std.int(cb.y * 32767); + posa[i2 * 4 + 3] = Std.int(cb.z * 32767); + nora[i3 * 2 ] = Std.int(cb.x * 32767); + nora[i3 * 2 + 1] = Std.int(cb.y * 32767); + posa[i3 * 4 + 3] = Std.int(cb.z * 32767); + } + } + + if (uvIndices.length > 0) { + if (udim) { + // Find number of tiles + var tilesU = 1; + var tilesV = 1; + for (i in 0...Std.int(uvTemp.length / 2)) { + while (uvTemp[i * 2 ] > tilesU) tilesU++; + while (uvTemp[i * 2 + 1] > tilesV) tilesV++; + } + + function getTile(i1: Int, i2: Int, i3: Int): Int { + var u1 = uvTemp[uvIndices[i1] * 2 ]; + var v1 = uvTemp[uvIndices[i1] * 2 + 1]; + var u2 = uvTemp[uvIndices[i2] * 2 ]; + var v2 = uvTemp[uvIndices[i2] * 2 + 1]; + var u3 = uvTemp[uvIndices[i3] * 2 ]; + var v3 = uvTemp[uvIndices[i3] * 2 + 1]; + var tileU = Std.int((u1 + u2 + u3) / 3); + var tileV = Std.int((v1 + v2 + v3) / 3); + return tileU + tileV * tilesU; + } + + // Amount of indices pre tile + var num = new kha.arrays.Uint32Array(tilesU * tilesV); + for (i in 0...Std.int(inda.length / 3)) { + var tile = getTile(inda[i * 3], inda[i * 3 + 1], inda[i * 3 + 2]); + num[tile] += 3; + } + + // Split indices per tile + udims = []; + udimsU = tilesU; + for (i in 0...tilesU * tilesV) { udims.push(new kha.arrays.Uint32Array(num[i])); num[i] = 0; } + + for (i in 0...Std.int(inda.length / 3)) { + var i1 = inda[i * 3 ]; + var i2 = inda[i * 3 + 1]; + var i3 = inda[i * 3 + 2]; + var tile = getTile(i1, i2, i3); + udims[tile][num[tile]++] = i1; + udims[tile][num[tile]++] = i2; + udims[tile][num[tile]++] = i3; + } + + // Normalize uvs to 0-1 range + var uvtiles = new kha.arrays.Int16Array(uvTemp.length); + for (i in 0...Std.int(inda.length / 3)) { // TODO: merge loops + var i1 = inda[i * 3 ]; + var i2 = inda[i * 3 + 1]; + var i3 = inda[i * 3 + 2]; + var tile = getTile(i1, i2, i3); + var tileU = tile % tilesU; + var tileV = Std.int(tile / tilesU); + uvtiles[uvIndices[i1] * 2 ] = tileU; + uvtiles[uvIndices[i1] * 2 + 1] = tileV; + uvtiles[uvIndices[i2] * 2 ] = tileU; + uvtiles[uvIndices[i2] * 2 + 1] = tileV; + uvtiles[uvIndices[i3] * 2 ] = tileU; + uvtiles[uvIndices[i3] * 2 + 1] = tileV; + } + for (i in 0...uvtiles.length) uvTemp[i] -= uvtiles[i]; + } + + texa = new kha.arrays.Int16Array(uvIndices.length * 2); + for (i in 0...posIndices.length) { + texa[i * 2 ] = Std.int( uvTemp[uvIndices[i] * 2 ] * 32767); + texa[i * 2 + 1] = Std.int((1.0 - uvTemp[uvIndices[i] * 2 + 1]) * 32767); + } + } + bytes = null; + if (!hasNext) { posFirst = norFirst = uvFirst = null; } + } + + function readFaceFast() { + while (true) { + va[vi++] = readInt() - 1; + pos++; // "/" + ua[ui++] = readInt() - 1; + pos++; // "/" + na[ni++] = readInt() - 1; + if (bytes.get(pos) == "\n".code || bytes.get(pos) == "\r".code) break; + pos++; // " " + // Some exporters put space at the end of "f" line + if (vi >= 3 && (bytes.get(pos) == "\n".code || bytes.get(pos) == "\r".code)) break; + } + } + + function readFace() { + while (true) { + va[vi++] = readInt() - 1; + if (uvTemp.length > 0 || norTemp.length > 0) { + pos++; // "/" + } + if (uvTemp.length > 0) { + ua[ui++] = readInt() - 1; + } + if (norTemp.length > 0) { + pos++; // "/" + na[ni++] = readInt() - 1; + } + if (bytes.get(pos) == "\n".code || bytes.get(pos) == "\r".code) break; + pos++; // " " + // Some exporters put space at the end of "f" line + if (vi >= 3 && (bytes.get(pos) == "\n".code || bytes.get(pos) == "\r".code)) break; + } + } + + function readFloat(): Float { + var bi = 0; + while (true) { // Read into buffer + var c = bytes.get(pos); + if (c == " ".code || c == "\n".code || c == "\r".code) break; + if (c == "E".code || c == "e".code) { + while (true) { + pos++; + c = bytes.get(pos); + if (c == " ".code || c == "\n".code || c == "\r".code) break; + } + return 0.0; // Assume number close to zero for now + } + pos++; + buf[bi++] = c; + } + var res = 0.0; // Parse buffer into float + var dot = 1; + var dec = 1; + var off = buf[0] == "-".code ? 1 : 0; + var len = bi - 1; + for (i in 0...bi - off) { + var c = buf[len - i]; + if (c == ".".code) { dot = dec; continue; } + res += (c - 48) * dec; + dec *= 10; + } + off > 0 ? res /= -dot : res /= dot; + return res; + } + + function readInt(): Int { + var bi = 0; + while (true) { // Read into buffer + var c = bytes.get(pos); + if (c == "/".code || c == "\n".code || c == "\r".code || c == " ".code) break; + pos++; + buf[bi++] = c; + } + var res = 0; // Parse buffer into int + var dec = 1; + var off = buf[0] == "-".code ? 1 : 0; + var len = bi - 1; + for (i in 0...bi - off) { + res += (buf[len - i] - 48) * dec; + dec *= 10; + } + if (off > 0) res *= -1; + return res; + } + + function readString(): String { + var s = ""; + while (true) { + var c = bytes.get(pos); + if (c == "\n".code || c == "\r".code || c == " ".code) break; + pos++; + s += String.fromCharCode(c); + } + return s; + } + + function nextLine() { + while (true) { + var c = bytes.get(pos++); + if (c == "\n".code || pos >= bytes.length) break; // \n, \r\n + } + } +}