blob: 9b49e3cdaca373a888b4a8a8820d138a074294f4 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
var fs = require('fs');
var metadata = {
vertexCount: 0,
aabb: [[null, null], [null, null], [null, null]],
emitVertex: function(v) {
++metadata.vertexCount;
var aabb = metadata.aabb;
if (aabb[0][0] === null || v[0] < aabb[0][0]) // min x
aabb[0][0] = v[0];
if (aabb[0][1] === null || v[0] > aabb[0][1]) // max x
aabb[0][1] = v[0];
if (aabb[1][0] === null || v[1] < aabb[1][0]) // min y
aabb[1][0] = v[1];
if (aabb[1][1] === null || v[1] > aabb[1][1]) // max y
aabb[1][1] = v[1];
if (aabb[2][0] === null || v[2] < aabb[2][0]) // min z
aabb[2][0] = v[2];
if (aabb[2][1] === null || v[2] > aabb[2][1]) // max z
aabb[2][1] = v[2];
},
getBuffer: function() {
var aabb = metadata.aabb;
console.log(metadata.vertexCount + " vertices");
console.log("AABB: " + aabb[0][0] + ".." + aabb[0][1]
+ ", " + aabb[1][0] + ".." + aabb[1][1]
+ ", " + aabb[2][0] + ".." + aabb[2][1]);
var buf = new Buffer((2 + 6) * 4);
var format = 1, p = 0;
buf.writeUInt32LE(format, p++);
buf.writeUInt32LE(metadata.vertexCount, p++ * 4);
for (var i = 0; i < 3; ++i) {
buf.writeFloatLE(aabb[i][0], p++ * 4);
buf.writeFloatLE(aabb[i][1], p++ * 4);
}
return buf;
}
};
function makeVec(s, n) {
var v = [];
s.split(' ').forEach(function (coordStr) {
var coord = parseFloat(coordStr);
if (!isNaN(coord))
v.push(coord);
});
if (v.length != n) {
console.error("Wrong vector size, expected " + n + ", got " + v.length);
process.exit();
}
return v;
}
function parseObj(filename, callback) {
fs.readFile(filename, "ascii", function (err, data) {
if (err)
throw err;
var groupCount = 0;
var parsed = { 'vertices': [], 'normals': [], 'texcoords': [], 'links': [] };
var missingTexCount = 0, missingNormCount = 0;
data.split('\n').forEach(function (line) {
var s = line.trim();
if (!s.length || groupCount > 1)
return;
if (s[0] === '#')
return;
if (s[0] === 'g') {
++groupCount;
} else if (s.substr(0, 2) === "v ") {
parsed.vertices.push(makeVec(s, 3));
} else if (s.substr(0, 3) === "vn ") {
parsed.normals.push(makeVec(s, 3));
} else if (s.substr(0, 3) === "vt ") {
parsed.texcoords.push(makeVec(s, 2));
} else if (s.substr(0, 2) === "f ") {
var refs = s.split(' ');
var vertCount = refs.length - 1;
if (vertCount != 3)
console.warn("Face " + parsed.links.length / 3 + " has " + vertCount + " vertices! (not triangulated?)");
for (var i = 1, ie = Math.min(4, refs.length); i < ie; ++i) {
var refComps = refs[i].split('/');
var vertIndex = parseInt(refComps[0]) - 1;
var texIndex = -1;
if (refComps.length >= 2 && refComps[1].length)
texIndex = parseInt(refComps[1]) - 1;
var normIndex = -1;
if (refComps.length >= 3 && refComps[2].length)
normIndex = parseInt(refComps[2]) - 1;
parsed.links.push([vertIndex, texIndex, normIndex]);
if (texIndex == -1)
++missingTexCount;
if (normIndex == -1)
++missingNormCount;
}
}
});
console.log(missingTexCount + " missing texture coordinates, " + missingNormCount + " missing normals");
callback(parsed);
});
}
function fillVert(src, index, dst, elemCount, isVertexCoord) {
var vertex = [];
if (index >= 0) {
for (var i = 0; i < elemCount; ++i) {
var elem = src[index][i];
if (isVertexCoord)
vertex.push(elem);
dst.buf.writeFloatLE(elem, dst.bufptr++ * 4);
}
if (vertex.length == 3)
metadata.emitVertex(vertex);
} else {
if (isVertexCoord) {
console.error("Missing vertex");
process.exit();
}
for (var i = 0; i < elemCount; ++i)
dst.buf.writeFloatLE(0, dst.bufptr++ * 4);
}
return vertex;
}
function normalize(v) {
var len = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
if (len == 0.0 || len == 1.0)
return;
len = Math.sqrt(len);
return [ v[0] / len, v[1] / len, v[2] / len ];
}
function surfaceNormal(a, b, c) {
var u = [ b[0] - a[0], b[1] - a[1], b[2] - a[2] ];
var v = [ c[0] - a[0], c[1] - a[1], c[2] - a[2] ];
var result = [ u[1] * v[2] - u[2] * v[1],
u[2] * v[0] - u[0] * v[2],
u[0] * v[1] - u[1] * v[0] ];
return normalize(result);
}
function objDataToBuf(parsed) {
var floatCount = parsed.links.length * (3 + 2 + 3);
var buf = new Buffer(floatCount * 4);
var dst = { 'buf': buf, 'bufptr': 0 };
var tri = [];
var genNormals = false;
var genNormCount = 0;
for (var i = 0; i < parsed.links.length; ++i) {
var link = parsed.links[i];
var vertIndex = link[0], texIndex = link[1], normIndex = link[2];
tri.push(fillVert(parsed.vertices, vertIndex, dst, 3, true));
fillVert(parsed.texcoords, texIndex, dst, 2);
fillVert(parsed.normals, normIndex, dst, 3);
if (normIndex == -1)
genNormals = true;
if (tri.length == 3) {
if (genNormals) {
var norm = surfaceNormal(tri[0], tri[1], tri[2]);
for (var nvIdx = 0; nvIdx < 3; ++nvIdx) {
dst.buf.writeFloatLE(norm[0], (dst.bufptr - 3 - nvIdx * 8) * 4);
dst.buf.writeFloatLE(norm[1], (dst.bufptr - 2 - nvIdx * 8) * 4);
dst.buf.writeFloatLE(norm[2], (dst.bufptr - 1 - nvIdx * 8) * 4);
}
genNormCount += 3;
}
tri = [];
}
}
if (genNormCount)
console.log("Generated " + genNormCount + " normals");
return buf;
}
var inFilename = process.argv[2];
var outFilename = process.argv[3];
if (process.argv.length < 4) {
console.log("Usage: objconvert file.obj file.buf");
process.exit();
}
parseObj(inFilename, function (parsed) {
var buf = objDataToBuf(parsed);
var f = fs.createWriteStream(outFilename);
f.on("error", function (e) { console.error(e); });
f.write(metadata.getBuffer());
f.write(buf);
f.end();
console.log("Written to " + outFilename + ", format is:");
console.log(" uint32 version, uint32 vertex_count, float32 aabb[6], vertex_count * (float32 vertex[3], float32 texcoord[2], float32 normal[3])");
});