From c8c4d5674480c0fa577333e20e21b2e2b66c9113 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 16 Oct 2015 17:32:23 -0400 Subject: [PATCH 1/2] Convert obj to gltf --- .gitignore | 12 + bin/obj2gltf.js | 61 +++ lib/WebGLConstants.js | 300 +++++++++++++++ lib/gltf.js | 435 +++++++++++++++++++++ lib/image.js | 44 +++ lib/modelMaterialsCommon.js | 750 ++++++++++++++++++++++++++++++++++++ lib/mtl.js | 118 ++++++ lib/obj.js | 251 ++++++++++++ lib/util.js | 16 + package.json | 10 + 10 files changed, 1997 insertions(+) create mode 100644 .gitignore create mode 100644 bin/obj2gltf.js create mode 100644 lib/WebGLConstants.js create mode 100644 lib/gltf.js create mode 100644 lib/image.js create mode 100644 lib/modelMaterialsCommon.js create mode 100644 lib/mtl.js create mode 100644 lib/obj.js create mode 100644 lib/util.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f431c7b --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.sdf +*.opensdf +*.suo +*.csproj.user +*.vcxproj.user +*.log +x64 +**/node_modules + +# WebStorm user-specific +.idea/ +data/ \ No newline at end of file diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js new file mode 100644 index 0000000..27347e7 --- /dev/null +++ b/bin/obj2gltf.js @@ -0,0 +1,61 @@ +#!/usr/bin/env node +"use strict"; +var fs = require('fs'); +var path = require('path'); +var argv = require('minimist')(process.argv.slice(2)); +var parseObj = require('../lib/obj'); +var createGltf = require('../lib/gltf'); +var util = require('../lib/util'); +var defined = util.defined; +var defaultValue = util.defaultValue; + +// TODO : assuming all paths in obj are relative, but should consider absolute paths +// TODO : add command line flag for y-up to z-up +// TODO : support zlib +// TODO : support binary export +// TODO : generate normals if they don't exist +if (process.argv.length < 3 || defined(argv.h) || defined(argv.help)) { + console.log('Usage: ./bin/obj2gltf.js [INPUT] [OPTIONS]\n'); + console.log(' -i, --input Path to obj file'); + console.log(' -o, --output Directory or filename for the exported glTF file'); + console.log(' -b, --binary Output binary glTF'); + console.log(' -c --combine Combine glTF resources into a single file'); + console.log(' -h, --help Display this help'); + process.exit(0); +} + +var objFile = defaultValue(argv._[0], defaultValue(argv.i, argv.input)); +var outputPath = defaultValue(defaultValue(argv.o, argv.output)); +var binary = defaultValue(defaultValue(argv.b, argv.binary), false); +var combine = defaultValue(defaultValue(argv.c, argv.combine), false); + +if (!defined(objFile)) { + console.error('-i or --input argument is required. See --help for details.'); + process.exit(1); +} + +if (!defined(outputPath)) { + outputPath = path.dirname(objFile); +} + +var inputPath = path.dirname(objFile); +var modelName = path.basename(objFile, '.obj'); + +var outputIsGltf = /.gltf$/.test(outputPath); +if (outputIsGltf) { + modelName = path.basename(outputPath, '.gltf'); + outputPath = path.dirname(outputPath); +} + +fs.mkdir(outputPath, function(){ + console.time('Total'); + console.time('Parse Obj'); + parseObj(objFile, inputPath, function(data) { + console.timeEnd('Parse Obj'); + console.time('Create glTF'); + createGltf(data, modelName, inputPath, outputPath, binary, combine, function() { + console.timeEnd('Create glTF'); + console.timeEnd('Total'); + }); + }); +}); diff --git a/lib/WebGLConstants.js b/lib/WebGLConstants.js new file mode 100644 index 0000000..80098a2 --- /dev/null +++ b/lib/WebGLConstants.js @@ -0,0 +1,300 @@ +"use strict"; +module.exports = { + DEPTH_BUFFER_BIT : 0x00000100, + STENCIL_BUFFER_BIT : 0x00000400, + COLOR_BUFFER_BIT : 0x00004000, + POINTS : 0x0000, + LINES : 0x0001, + LINE_LOOP : 0x0002, + LINE_STRIP : 0x0003, + TRIANGLES : 0x0004, + TRIANGLE_STRIP : 0x0005, + TRIANGLE_FAN : 0x0006, + ZERO : 0, + ONE : 1, + SRC_COLOR : 0x0300, + ONE_MINUS_SRC_COLOR : 0x0301, + SRC_ALPHA : 0x0302, + ONE_MINUS_SRC_ALPHA : 0x0303, + DST_ALPHA : 0x0304, + ONE_MINUS_DST_ALPHA : 0x0305, + DST_COLOR : 0x0306, + ONE_MINUS_DST_COLOR : 0x0307, + SRC_ALPHA_SATURATE : 0x0308, + FUNC_ADD : 0x8006, + BLEND_EQUATION : 0x8009, + BLEND_EQUATION_RGB : 0x8009, /* same as BLEND_EQUATION */ + BLEND_EQUATION_ALPHA : 0x883D, + FUNC_SUBTRACT : 0x800A, + FUNC_REVERSE_SUBTRACT : 0x800B, + BLEND_DST_RGB : 0x80C8, + BLEND_SRC_RGB : 0x80C9, + BLEND_DST_ALPHA : 0x80CA, + BLEND_SRC_ALPHA : 0x80CB, + CONSTANT_COLOR : 0x8001, + ONE_MINUS_CONSTANT_COLOR : 0x8002, + CONSTANT_ALPHA : 0x8003, + ONE_MINUS_CONSTANT_ALPHA : 0x8004, + BLEND_COLOR : 0x8005, + ARRAY_BUFFER : 0x8892, + ELEMENT_ARRAY_BUFFER : 0x8893, + ARRAY_BUFFER_BINDING : 0x8894, + ELEMENT_ARRAY_BUFFER_BINDING : 0x8895, + STREAM_DRAW : 0x88E0, + STATIC_DRAW : 0x88E4, + DYNAMIC_DRAW : 0x88E8, + BUFFER_SIZE : 0x8764, + BUFFER_USAGE : 0x8765, + CURRENT_VERTEX_ATTRIB : 0x8626, + FRONT : 0x0404, + BACK : 0x0405, + FRONT_AND_BACK : 0x0408, + CULL_FACE : 0x0B44, + BLEND : 0x0BE2, + DITHER : 0x0BD0, + STENCIL_TEST : 0x0B90, + DEPTH_TEST : 0x0B71, + SCISSOR_TEST : 0x0C11, + POLYGON_OFFSET_FILL : 0x8037, + SAMPLE_ALPHA_TO_COVERAGE : 0x809E, + SAMPLE_COVERAGE : 0x80A0, + NO_ERROR : 0, + INVALID_ENUM : 0x0500, + INVALID_VALUE : 0x0501, + INVALID_OPERATION : 0x0502, + OUT_OF_MEMORY : 0x0505, + CW : 0x0900, + CCW : 0x0901, + LINE_WIDTH : 0x0B21, + ALIASED_POINT_SIZE_RANGE : 0x846D, + ALIASED_LINE_WIDTH_RANGE : 0x846E, + CULL_FACE_MODE : 0x0B45, + FRONT_FACE : 0x0B46, + DEPTH_RANGE : 0x0B70, + DEPTH_WRITEMASK : 0x0B72, + DEPTH_CLEAR_VALUE : 0x0B73, + DEPTH_FUNC : 0x0B74, + STENCIL_CLEAR_VALUE : 0x0B91, + STENCIL_FUNC : 0x0B92, + STENCIL_FAIL : 0x0B94, + STENCIL_PASS_DEPTH_FAIL : 0x0B95, + STENCIL_PASS_DEPTH_PASS : 0x0B96, + STENCIL_REF : 0x0B97, + STENCIL_VALUE_MASK : 0x0B93, + STENCIL_WRITEMASK : 0x0B98, + STENCIL_BACK_FUNC : 0x8800, + STENCIL_BACK_FAIL : 0x8801, + STENCIL_BACK_PASS_DEPTH_FAIL : 0x8802, + STENCIL_BACK_PASS_DEPTH_PASS : 0x8803, + STENCIL_BACK_REF : 0x8CA3, + STENCIL_BACK_VALUE_MASK : 0x8CA4, + STENCIL_BACK_WRITEMASK : 0x8CA5, + VIEWPORT : 0x0BA2, + SCISSOR_BOX : 0x0C10, + COLOR_CLEAR_VALUE : 0x0C22, + COLOR_WRITEMASK : 0x0C23, + UNPACK_ALIGNMENT : 0x0CF5, + PACK_ALIGNMENT : 0x0D05, + MAX_TEXTURE_SIZE : 0x0D33, + MAX_VIEWPORT_DIMS : 0x0D3A, + SUBPIXEL_BITS : 0x0D50, + RED_BITS : 0x0D52, + GREEN_BITS : 0x0D53, + BLUE_BITS : 0x0D54, + ALPHA_BITS : 0x0D55, + DEPTH_BITS : 0x0D56, + STENCIL_BITS : 0x0D57, + POLYGON_OFFSET_UNITS : 0x2A00, + POLYGON_OFFSET_FACTOR : 0x8038, + TEXTURE_BINDING_2D : 0x8069, + SAMPLE_BUFFERS : 0x80A8, + SAMPLES : 0x80A9, + SAMPLE_COVERAGE_VALUE : 0x80AA, + SAMPLE_COVERAGE_INVERT : 0x80AB, + COMPRESSED_TEXTURE_FORMATS : 0x86A3, + DONT_CARE : 0x1100, + FASTEST : 0x1101, + NICEST : 0x1102, + GENERATE_MIPMAP_HINT : 0x8192, + BYTE : 0x1400, + UNSIGNED_BYTE : 0x1401, + SHORT : 0x1402, + UNSIGNED_SHORT : 0x1403, + INT : 0x1404, + UNSIGNED_INT : 0x1405, + FLOAT : 0x1406, + DEPTH_COMPONENT : 0x1902, + ALPHA : 0x1906, + RGB : 0x1907, + RGBA : 0x1908, + LUMINANCE : 0x1909, + LUMINANCE_ALPHA : 0x190A, + UNSIGNED_SHORT_4_4_4_4 : 0x8033, + UNSIGNED_SHORT_5_5_5_1 : 0x8034, + UNSIGNED_SHORT_5_6_5 : 0x8363, + FRAGMENT_SHADER : 0x8B30, + VERTEX_SHADER : 0x8B31, + MAX_VERTEX_ATTRIBS : 0x8869, + MAX_VERTEX_UNIFORM_VECTORS : 0x8DFB, + MAX_VARYING_VECTORS : 0x8DFC, + MAX_COMBINED_TEXTURE_IMAGE_UNITS : 0x8B4D, + MAX_VERTEX_TEXTURE_IMAGE_UNITS : 0x8B4C, + MAX_TEXTURE_IMAGE_UNITS : 0x8872, + MAX_FRAGMENT_UNIFORM_VECTORS : 0x8DFD, + SHADER_TYPE : 0x8B4F, + DELETE_STATUS : 0x8B80, + LINK_STATUS : 0x8B82, + VALIDATE_STATUS : 0x8B83, + ATTACHED_SHADERS : 0x8B85, + ACTIVE_UNIFORMS : 0x8B86, + ACTIVE_ATTRIBUTES : 0x8B89, + SHADING_LANGUAGE_VERSION : 0x8B8C, + CURRENT_PROGRAM : 0x8B8D, + NEVER : 0x0200, + LESS : 0x0201, + EQUAL : 0x0202, + LEQUAL : 0x0203, + GREATER : 0x0204, + NOTEQUAL : 0x0205, + GEQUAL : 0x0206, + ALWAYS : 0x0207, + KEEP : 0x1E00, + REPLACE : 0x1E01, + INCR : 0x1E02, + DECR : 0x1E03, + INVERT : 0x150A, + INCR_WRAP : 0x8507, + DECR_WRAP : 0x8508, + VENDOR : 0x1F00, + RENDERER : 0x1F01, + VERSION : 0x1F02, + NEAREST : 0x2600, + LINEAR : 0x2601, + NEAREST_MIPMAP_NEAREST : 0x2700, + LINEAR_MIPMAP_NEAREST : 0x2701, + NEAREST_MIPMAP_LINEAR : 0x2702, + LINEAR_MIPMAP_LINEAR : 0x2703, + TEXTURE_MAG_FILTER : 0x2800, + TEXTURE_MIN_FILTER : 0x2801, + TEXTURE_WRAP_S : 0x2802, + TEXTURE_WRAP_T : 0x2803, + TEXTURE_2D : 0x0DE1, + TEXTURE : 0x1702, + TEXTURE_CUBE_MAP : 0x8513, + TEXTURE_BINDING_CUBE_MAP : 0x8514, + TEXTURE_CUBE_MAP_POSITIVE_X : 0x8515, + TEXTURE_CUBE_MAP_NEGATIVE_X : 0x8516, + TEXTURE_CUBE_MAP_POSITIVE_Y : 0x8517, + TEXTURE_CUBE_MAP_NEGATIVE_Y : 0x8518, + TEXTURE_CUBE_MAP_POSITIVE_Z : 0x8519, + TEXTURE_CUBE_MAP_NEGATIVE_Z : 0x851A, + MAX_CUBE_MAP_TEXTURE_SIZE : 0x851C, + TEXTURE0 : 0x84C0, + TEXTURE1 : 0x84C1, + TEXTURE2 : 0x84C2, + TEXTURE3 : 0x84C3, + TEXTURE4 : 0x84C4, + TEXTURE5 : 0x84C5, + TEXTURE6 : 0x84C6, + TEXTURE7 : 0x84C7, + TEXTURE8 : 0x84C8, + TEXTURE9 : 0x84C9, + TEXTURE10 : 0x84CA, + TEXTURE11 : 0x84CB, + TEXTURE12 : 0x84CC, + TEXTURE13 : 0x84CD, + TEXTURE14 : 0x84CE, + TEXTURE15 : 0x84CF, + TEXTURE16 : 0x84D0, + TEXTURE17 : 0x84D1, + TEXTURE18 : 0x84D2, + TEXTURE19 : 0x84D3, + TEXTURE20 : 0x84D4, + TEXTURE21 : 0x84D5, + TEXTURE22 : 0x84D6, + TEXTURE23 : 0x84D7, + TEXTURE24 : 0x84D8, + TEXTURE25 : 0x84D9, + TEXTURE26 : 0x84DA, + TEXTURE27 : 0x84DB, + TEXTURE28 : 0x84DC, + TEXTURE29 : 0x84DD, + TEXTURE30 : 0x84DE, + TEXTURE31 : 0x84DF, + ACTIVE_TEXTURE : 0x84E0, + REPEAT : 0x2901, + CLAMP_TO_EDGE : 0x812F, + MIRRORED_REPEAT : 0x8370, + FLOAT_VEC2 : 0x8B50, + FLOAT_VEC3 : 0x8B51, + FLOAT_VEC4 : 0x8B52, + INT_VEC2 : 0x8B53, + INT_VEC3 : 0x8B54, + INT_VEC4 : 0x8B55, + BOOL : 0x8B56, + BOOL_VEC2 : 0x8B57, + BOOL_VEC3 : 0x8B58, + BOOL_VEC4 : 0x8B59, + FLOAT_MAT2 : 0x8B5A, + FLOAT_MAT3 : 0x8B5B, + FLOAT_MAT4 : 0x8B5C, + SAMPLER_2D : 0x8B5E, + SAMPLER_CUBE : 0x8B60, + VERTEX_ATTRIB_ARRAY_ENABLED : 0x8622, + VERTEX_ATTRIB_ARRAY_SIZE : 0x8623, + VERTEX_ATTRIB_ARRAY_STRIDE : 0x8624, + VERTEX_ATTRIB_ARRAY_TYPE : 0x8625, + VERTEX_ATTRIB_ARRAY_NORMALIZED : 0x886A, + VERTEX_ATTRIB_ARRAY_POINTER : 0x8645, + VERTEX_ATTRIB_ARRAY_BUFFER_BINDING : 0x889F, + IMPLEMENTATION_COLOR_READ_TYPE : 0x8B9A, + IMPLEMENTATION_COLOR_READ_FORMAT : 0x8B9B, + COMPILE_STATUS : 0x8B81, + LOW_FLOAT : 0x8DF0, + MEDIUM_FLOAT : 0x8DF1, + HIGH_FLOAT : 0x8DF2, + LOW_INT : 0x8DF3, + MEDIUM_INT : 0x8DF4, + HIGH_INT : 0x8DF5, + FRAMEBUFFER : 0x8D40, + RENDERBUFFER : 0x8D41, + RGBA4 : 0x8056, + RGB5_A1 : 0x8057, + RGB565 : 0x8D62, + DEPTH_COMPONENT16 : 0x81A5, + STENCIL_INDEX : 0x1901, + STENCIL_INDEX8 : 0x8D48, + DEPTH_STENCIL : 0x84F9, + RENDERBUFFER_WIDTH : 0x8D42, + RENDERBUFFER_HEIGHT : 0x8D43, + RENDERBUFFER_INTERNAL_FORMAT : 0x8D44, + RENDERBUFFER_RED_SIZE : 0x8D50, + RENDERBUFFER_GREEN_SIZE : 0x8D51, + RENDERBUFFER_BLUE_SIZE : 0x8D52, + RENDERBUFFER_ALPHA_SIZE : 0x8D53, + RENDERBUFFER_DEPTH_SIZE : 0x8D54, + RENDERBUFFER_STENCIL_SIZE : 0x8D55, + FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE : 0x8CD0, + FRAMEBUFFER_ATTACHMENT_OBJECT_NAME : 0x8CD1, + FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL : 0x8CD2, + FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE : 0x8CD3, + COLOR_ATTACHMENT0 : 0x8CE0, + DEPTH_ATTACHMENT : 0x8D00, + STENCIL_ATTACHMENT : 0x8D20, + DEPTH_STENCIL_ATTACHMENT : 0x821A, + NONE : 0, + FRAMEBUFFER_COMPLETE : 0x8CD5, + FRAMEBUFFER_INCOMPLETE_ATTACHMENT : 0x8CD6, + FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT : 0x8CD7, + FRAMEBUFFER_INCOMPLETE_DIMENSIONS : 0x8CD9, + FRAMEBUFFER_UNSUPPORTED : 0x8CDD, + FRAMEBUFFER_BINDING : 0x8CA6, + RENDERBUFFER_BINDING : 0x8CA7, + MAX_RENDERBUFFER_SIZE : 0x84E8, + INVALID_FRAMEBUFFER_OPERATION : 0x0506, + UNPACK_FLIP_Y_WEBGL : 0x9240, + UNPACK_PREMULTIPLY_ALPHA_WEBGL : 0x9241, + CONTEXT_LOST_WEBGL : 0x9242, + UNPACK_COLORSPACE_CONVERSION_WEBGL : 0x9243, + BROWSER_DEFAULT_WEBGL : 0x9244 +}; diff --git a/lib/gltf.js b/lib/gltf.js new file mode 100644 index 0000000..aeae108 --- /dev/null +++ b/lib/gltf.js @@ -0,0 +1,435 @@ +"use strict"; +var fs = require('fs'); +var fsExtra = require('fs-extra'); +var path = require('path'); +var async = require('async'); +var zlib = require('zlib'); +var util = require('./util'); +var defined = util.defined; +var defaultValue = util.defaultValue; +var imageInfo = require('./image'); +var WebGLConstants = require('./WebGLConstants'); +var modelMaterialsCommon = require('./modelMaterialsCommon'); + +module.exports = createGltf; + +function getImages(inputPath, outputPath, combine, materials, done) { + var images = []; + for (var name in materials) { + if (materials.hasOwnProperty(name)) { + var material = materials[name]; + for (var property in material) { + if (material.hasOwnProperty(property)) { + var image = material[property]; + if (typeof image === 'string') { + images.push(image); + } + } + } + } + } + + var imagesInfo = {}; + async.each(images, function (image, callback) { + var imagePath = path.join(inputPath, image); + var copyPath = path.join(outputPath, path.basename(image)); + imageInfo(imagePath, function(info) { + var uri; + if (combine) { + uri = 'data:application/octet-stream;base64,' + info.data.toString('base64'); + } else { + uri = image; + } + + imagesInfo[image] = { + transparent : info.transparent, + channels : info.channels, + uri : uri + }; + + fsExtra.copy(imagePath, copyPath, function (err) { + if (err) { + throw err; + } + callback(); + }); + }); + }, function (err) { + if (err) { + throw err; + } + done(imagesInfo); + }); +} + +function createGltf(data, modelName, inputPath, outputPath, binary, combine, done) { + var vertexCount = data.vertexCount; + var vertexArray = data.vertexArray; + var positionMin = data.positionMin; + var positionMax = data.positionMax; + var hasUVs = data.hasUVs; + var materialGroups = data.materialGroups; + var materials = data.materials; + + getImages(inputPath, outputPath, combine, materials, function(images) { + var i, j, name; + + var sizeOfFloat32 = 4; + var sizeOfUint32 = 4; + var sizeOfUint16 = 2; + + var indexComponentType; + var indexComponentSize; + + // Reserve the 65535 index for primitive restart + if (vertexCount < 65535) { + indexComponentType = WebGLConstants.UNSIGNED_SHORT; + indexComponentSize = sizeOfUint16; + } else { + indexComponentType = WebGLConstants.UNSIGNED_INT; + indexComponentSize = sizeOfUint32; + } + + // Create primitives + var primitives = []; + var indexArrayLength = 0; + var indexArray; + var indexCount; + for (name in materialGroups) { + if (materialGroups.hasOwnProperty(name)) { + indexArray = materialGroups[name]; + indexCount = indexArray.length; + primitives.push({ + indexArray : indexArray, + indexOffset : indexArrayLength, + indexCount : indexCount, + material : name + }); + indexArrayLength += indexCount; + } + } + + // Create buffer to store vertex and index data + var indexArrayByteLength = indexArrayLength * indexComponentSize; + var vertexArrayLength = vertexArray.length; // In floats + var vertexArrayByteLength = vertexArrayLength * sizeOfFloat32; + var bufferByteLength = vertexArrayByteLength + indexArrayByteLength; + var buffer = new Buffer(bufferByteLength); + + // Write vertex data + var byteOffset = 0; + for (i = 0; i < vertexArrayLength; ++i) { + buffer.writeFloatLE(vertexArray[i], byteOffset); + byteOffset += sizeOfFloat32; + } + + // Write index data + var primitivesLength = primitives.length; + for (i = 0; i < primitivesLength; ++i) { + indexArray = primitives[i].indexArray; + indexCount = indexArray.length; + for (j = 0; j < indexCount; ++j) { + if (indexComponentSize === sizeOfUint16) { + buffer.writeUInt16LE(indexArray[j], byteOffset); + } else { + buffer.writeUInt32LE(indexArray[j], byteOffset); + } + byteOffset += indexComponentSize; + } + } + + var positionByteOffset = 0; + var normalByteOffset = sizeOfFloat32 * 3; + var uvByteOffset = sizeOfFloat32 * 6; + var vertexByteStride = hasUVs ? sizeOfFloat32 * 8 : sizeOfFloat32 * 6; + + var binaryRelPath = modelName + '.bin'; + var binaryPath = path.join(outputPath, binaryRelPath); + var bufferId = 'buffer_' + modelName; + var bufferViewVertexId = 'bufferView_vertex'; + var bufferViewIndexId = 'bufferView_index'; + var accessorPositionId = 'accessor_position'; + var accessorUVId = 'accessor_uv'; + var accessorNormalId = 'accessor_normal'; + var meshId = 'mesh_' + modelName; + var sceneId = 'scene_' + modelName; + var nodeId = 'node_' + modelName; + var samplerId = 'sampler_0'; + + function getAccessorIndexId(i) { + return 'accessor_index_' + i; + } + + function getMaterialId(material) { + return 'material_' + material; + } + + function getTextureId(image) { + if (!defined(image)) { + return undefined; + } + return 'texture_' + image.substr(0, image.lastIndexOf('.')); + } + + function getImageId(image) { + return 'image_' + image.substr(0, image.lastIndexOf('.')); + } + + var gltf = { + accessors : {}, + asset : {}, + buffers : {}, + bufferViews : {}, + extensionsUsed : ['KHR_materials_common'], + images : {}, + materials : {}, + meshes : {}, + nodes : {}, + samplers : {}, + scene : sceneId, + scenes : {}, + textures : {} + }; + + gltf.asset = { + "generator": "OBJ2GLTF", + "premultipliedAlpha": true, + "profile": { + "api": "WebGL", + "version": "1.0" + }, + "version": 1 + }; + + gltf.scenes[sceneId] = { + nodes : [nodeId] + }; + + gltf.nodes[nodeId] = { + children : [], + matrix : [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], + meshes : [meshId], + name : modelName + }; + + gltf.samplers[samplerId] = {}; // Use default values + + var bufferUri; + if (combine) { + bufferUri = 'data:application/octet-stream;base64,' + buffer.toString('base64'); + } else { + bufferUri = binaryRelPath; + } + + gltf.buffers[bufferId] = { + byteLength : bufferByteLength, + type : 'arraybuffer', + uri : bufferUri + }; + + gltf.bufferViews[bufferViewVertexId] = { + buffer : bufferId, + byteLength : vertexArrayByteLength, + byteOffset : 0, + target : WebGLConstants.ARRAY_BUFFER + }; + gltf.bufferViews[bufferViewIndexId] = { + buffer : bufferId, + byteLength : indexArrayByteLength, + byteOffset : vertexArrayByteLength, + target : WebGLConstants.ELEMENT_ARRAY_BUFFER + }; + + for (i = 0; i < primitivesLength; ++i) { + var primitive = primitives[i]; + gltf.accessors[getAccessorIndexId(i)] = { + bufferView : bufferViewIndexId, + byteOffset : primitive.indexOffset * indexComponentSize, + byteStride : 0, + componentType : indexComponentType, + count : primitive.indexCount, + type : 'SCALAR' + }; + } + + gltf.accessors[accessorPositionId] = { + bufferView : bufferViewVertexId, + byteOffset : positionByteOffset, + byteStride : vertexByteStride, + componentType : WebGLConstants.FLOAT, + count : vertexCount, + min : positionMin, + max : positionMax, + type : 'VEC3' + }; + + gltf.accessors[accessorNormalId] = { + bufferView : bufferViewVertexId, + byteOffset : normalByteOffset, + byteStride : vertexByteStride, + componentType : WebGLConstants.FLOAT, + count : vertexCount, + type : 'VEC3' + }; + + if (hasUVs) { + gltf.accessors[accessorUVId] = { + bufferView : bufferViewVertexId, + byteOffset : uvByteOffset, + byteStride : vertexByteStride, + componentType : WebGLConstants.FLOAT, + count : vertexCount, + type : 'VEC2' + }; + } + + var gltfPrimitives = []; + gltf.meshes[meshId] = { + name : modelName, + primitives : gltfPrimitives + }; + + var gltfAttributes = {}; + gltfAttributes.POSITION = accessorPositionId; + gltfAttributes.NORMAL = accessorNormalId; + if (hasUVs) { + gltfAttributes.TEXCOORD_0 = accessorUVId; + } + + for (i = 0; i < primitivesLength; ++i) { + gltfPrimitives.push({ + attributes : gltfAttributes, + indices : getAccessorIndexId(i), + material : getMaterialId(primitives[i].material), + primitive : WebGLConstants.TRIANGLES + }); + } + + function addTexture(name) { + var imageId = getImageId(name); + var textureId = getTextureId(name); + + // Don't add the texture twice + if (defined(gltf.images[imageId])) { + return; + } + + var image = images[name]; + var format; + var channels = image.channels; + switch (channels) { + case 1: + format = WebGLConstants.ALPHA; + break; + case 2: + format = WebGLConstants.LUMINANCE_ALPHA; + break; + case 3: + format = WebGLConstants.RGB; + break; + case 4: + format = WebGLConstants.RGBA; + break; + } + + gltf.images[imageId] = { + uri : image.uri + }; + gltf.textures[textureId] = { + format : format, + internalFormat : format, + sampler : samplerId, + source : imageId, + target : WebGLConstants.TEXTURE_2D, + type : WebGLConstants.UNSIGNED_BYTE + }; + } + + for (i = 0; i < primitivesLength; ++i) { + var materialName = primitives[i].material; + var material = materials[materialName]; + var materialId = getMaterialId(materialName); + + // Get specular color as combination of specular color and intensity + var specularColor = material.specularColor; + var specularIntensity = material.specularIntensity; + if (defined(specularColor)) { + specularIntensity = defaultValue(specularIntensity, 1.0); + specularColor[0] *= specularIntensity; + specularColor[1] *= specularIntensity; + specularColor[2] *= specularIntensity; + } else if(defined(specularIntensity)){ + specularColor = [specularIntensity, specularIntensity, specularIntensity, 1]; + } else { + specularColor = [0, 0, 0, 1]; + } + + var specularMap = defaultValue(material.specularColorMap, material.specularIntensityMap); + var shadingTechnique = 'PHONG'; // or 'BLINN' + if (!defined(specularMap) && (specularColor[0] === 0) && (specularColor[1] === 0) && (specularColor[2] === 0)) { + shadingTechnique = 'LAMBERT'; + } + + if (defined(material.ambientColorMap)) { + addTexture(material.ambientColorMap); + } + if (defined(material.diffuseColorMap)) { + addTexture(material.diffuseColorMap); + } + if (defined(material.emissionColorMap)) { + addTexture(material.emissionColorMap); + } + if (defined(specularMap)) { + addTexture(specularMap); + } + + var values = { + ambient : defaultValue(defaultValue(getTextureId(material.ambientColorMap), material.ambientColor), [0, 0, 0, 1]), + diffuse : defaultValue(defaultValue(getTextureId(material.diffuseColorMap), material.diffuseColor), [0, 0, 0, 1]), + emission : defaultValue(defaultValue(getTextureId(material.emissionColorMap), material.emissionColor), [0, 0, 0, 1]), + specular : defaultValue(getTextureId(specularMap), specularColor), + shininess : defaultValue(material.specularShininess, 0.0), + transparency : defaultValue(material.alpha, 1.0) + }; + + // If an image is transparent, set transparency to 0.99 to force alpha path + var diffuseColorMap = material.diffuseColorMap; + if (defined(diffuseColorMap) && images[diffuseColorMap].transparent) { + values.transparency = 0.99 * values.transparency; + } + + gltf.materials[materialId] = { + name : materialName, + extensions : { + KHR_materials_common: { + technique: shadingTechnique, + values: values + } + } + }; + } + + // Generate techniques, shaders, and programs + gltf = modelMaterialsCommon(gltf); + + // Save .gltf file + var gltfPath = path.join(outputPath, modelName) + '.gltf'; + var gltfString = JSON.stringify(gltf, null, 4); + fs.writeFile(gltfPath, gltfString, function(error) { + if (error) { + throw error; + } + if (combine) { + done(); + } else { + // Save .bin file + fs.writeFile(binaryPath, buffer, function(error) { + if (error) { + throw error; + } + done(); + }); + } + }); + }); +} diff --git a/lib/image.js b/lib/image.js new file mode 100644 index 0000000..d435b83 --- /dev/null +++ b/lib/image.js @@ -0,0 +1,44 @@ +"use strict"; +var fs = require('fs'); +var path = require('path'); + +module.exports = imageInfo; + +function getChannels(colorType) { + switch (colorType) { + case 0: // greyscale + return 1; + case 2: // RGB + return 3; + case 4: // greyscale + alpha + return 2; + case 6: // RGB + alpha + return 4; + default: + return 3; + } +} + +function imageInfo(imagePath, done) { + fs.readFile(imagePath, function(err, data) { + if (err) { + throw(err); + } + + var info = { + transparent : false, + channels : 3, + data : data + }; + + if (path.extname(imagePath) === '.png') { + // Color type is encoded in the 25th bit of the png + var colorType = data[25]; + var channels = getChannels(colorType); + info.channels = channels; + info.transparent = (channels === 4); + } + + done(info); + }); +} diff --git a/lib/modelMaterialsCommon.js b/lib/modelMaterialsCommon.js new file mode 100644 index 0000000..d1b78bd --- /dev/null +++ b/lib/modelMaterialsCommon.js @@ -0,0 +1,750 @@ +"use strict"; +var WebGLConstants = require('./WebGLConstants'); +var util = require('./util'); +var defined = util.defined; +var defaultValue = util.defaultValue; + +module.exports = modelMaterialsCommon; + +function webGLConstantToGlslType(webGLValue) { + switch(webGLValue) { + case WebGLConstants.FLOAT: + return 'float'; + case WebGLConstants.FLOAT_VEC2: + return 'vec2'; + case WebGLConstants.FLOAT_VEC3: + return 'vec3'; + case WebGLConstants.FLOAT_VEC4: + return 'vec4'; + case WebGLConstants.FLOAT_MAT2: + return 'mat2'; + case WebGLConstants.FLOAT_MAT3: + return 'mat3'; + case WebGLConstants.FLOAT_MAT4: + return 'mat4'; + case WebGLConstants.SAMPLER_2D: + return 'sampler2D'; + } +} + +function generateLightParameters(gltf) { + var result = {}; + + var lights; + if (defined(gltf.extensions) && defined(gltf.extensions.KHR_materials_common)) { + lights = gltf.extensions.KHR_materials_common.lights; + } + + if (defined(lights)) { + // Figure out which node references the light + var nodes = gltf.nodes; + for (var nodeName in nodes) { + if (nodes.hasOwnProperty(nodeName)) { + var node = nodes[nodeName]; + if (defined(node.extensions) && defined(node.extensions.KHR_materials_common)) { + var nodeLightId = node.extensions.KHR_materials_common.light; + if (defined(nodeLightId) && defined(lights[nodeLightId])) { + lights[nodeLightId].node = nodeName; + } + delete node.extensions.KHR_materials_common; + } + } + } + + // Add light parameters to result + var lightCount = 0; + for(var lightName in lights) { + if (lights.hasOwnProperty(lightName)) { + var light = lights[lightName]; + var lightType = light.type; + if ((lightType !== 'ambient') && !defined(light.node)) { + delete lights[lightName]; + continue; + } + var lightBaseName = 'light' + lightCount.toString(); + light.baseName = lightBaseName; + switch(lightType) { + case 'ambient': + var ambient = light.ambient; + result[lightBaseName + 'Color'] = { + type: WebGLConstants.FLOAT_VEC3, + value: ambient.color + }; + break; + case 'directional': + var directional = light.directional; + result[lightBaseName + 'Color'] = + { + type: WebGLConstants.FLOAT_VEC3, + value: directional.color + }; + if (defined(light.node)) { + result[lightBaseName + 'Transform'] = + { + node: light.node, + semantic: 'MODELVIEW', + type: WebGLConstants.FLOAT_MAT4 + }; + } + break; + case 'point': + var point = light.point; + result[lightBaseName + 'Color'] = + { + type: WebGLConstants.FLOAT_VEC3, + value: point.color + }; + if (defined(light.node)) { + result[lightBaseName + 'Transform'] = + { + node: light.node, + semantic: 'MODELVIEW', + type: WebGLConstants.FLOAT_MAT4 + }; + } + result[lightBaseName + 'Attenuation'] = + { + type: WebGLConstants.FLOAT_VEC3, + value: [point.constantAttenuation, point.linearAttenuation, point.quadraticAttenuation] + }; + break; + case 'spot': + var spot = light.spot; + result[lightBaseName + 'Color'] = + { + type: WebGLConstants.FLOAT_VEC3, + value: spot.color + }; + if (defined(light.node)) { + result[lightBaseName + 'Transform'] = + { + node: light.node, + semantic: 'MODELVIEW', + type: WebGLConstants.FLOAT_MAT4 + }; + result[lightBaseName + 'InverseTransform'] = { + node: light.node, + semantic: 'MODELVIEWINVERSE', + type: WebGLConstants.FLOAT_MAT4, + useInFragment: true + }; + } + result[lightBaseName + 'Attenuation'] = + { + type: WebGLConstants.FLOAT_VEC3, + value: [spot.constantAttenuation, spot.linearAttenuation, spot.quadraticAttenuation] + }; + + result[lightBaseName + 'FallOff'] = + { + type: WebGLConstants.FLOAT_VEC2, + value: [spot.fallOffAngle, spot.fallOffExponent] + }; + break; + } + ++lightCount; + } + } + } + + return result; +} + +function getNextId(dictionary, baseName, startingCount) { + var count = defaultValue(startingCount, 0); + var nextId; + do { + nextId = baseName + (count++).toString(); + } while(defined(dictionary[nextId])); + + return nextId; +} + +var techniqueCount = 0; +var vertexShaderCount = 0; +var fragmentShaderCount = 0; +var programCount = 0; +function generateTechnique(gltf, khrMaterialsCommon, attributes, lightParameters, jointCount) { + var techniques = gltf.techniques; + var shaders = gltf.shaders; + var programs = gltf.programs; + attributes = defaultValue(attributes, []); + var attributesCount = attributes.length; + var lightingModel = khrMaterialsCommon.technique.toUpperCase(); + var lights; + if (defined(gltf.extensions) && defined(gltf.extensions.KHR_materials_common)) { + lights = gltf.extensions.KHR_materials_common.lights; + } + var hasSkinning = (jointCount > 0); + var values = khrMaterialsCommon.values; + var isDoubleSided = values.doubleSided; + delete values.doubleSided; + + var vertexShader = 'precision highp float;\n'; + var fragmentShader = 'precision highp float;\n'; + + // Generate IDs for our new objects + var techniqueId = getNextId(techniques, 'technique', techniqueCount); + var vertexShaderId = getNextId(shaders, 'vertexShader', vertexShaderCount); + var fragmentShaderId = getNextId(shaders, 'fragmentShader', fragmentShaderCount); + var programId = getNextId(programs, 'program', programCount); + + // Add techniques + var lowerCase; + var techniqueAttributes = {}; + for (var i=0;i 0) { + lowerCase = name.toLowerCase(); + techniqueParameters[lowerCase] = { + type: typeValue + }; + } + } + } + + // Copy light parameters into technique parameters + if (defined(lightParameters)) { + for (var lightParamName in lightParameters) { + if (lightParameters.hasOwnProperty(lightParamName)) { + techniqueParameters[lightParamName] = lightParameters[lightParamName]; + } + } + } + + // Generate uniforms object before attributes are added + var techniqueUniforms = {}; + for (var paramName in techniqueParameters) { + if (techniqueParameters.hasOwnProperty(paramName)) { + var param = techniqueParameters[paramName]; + techniqueUniforms['u_' + paramName] = paramName; + var arraySize = defined(param.count) ? '['+param.count+']' : ''; + if (((param.type !== WebGLConstants.FLOAT_MAT3) && (param.type !== WebGLConstants.FLOAT_MAT4)) || + param.useInFragment) { + fragmentShader += 'uniform ' + webGLConstantToGlslType(param.type) + ' u_' + paramName + arraySize + ';\n'; + delete param.useInFragment; + } + else { + vertexShader += 'uniform ' + webGLConstantToGlslType(param.type) + ' u_' + paramName + arraySize + ';\n'; + } + } + } + + // Add attributes with semantics + var vertexShaderMain = ''; + if (hasSkinning) { + vertexShaderMain += ' mat4 skinMat = a_weight.x * u_jointMatrix[int(a_joint.x)];\n'; + vertexShaderMain += ' skinMat += a_weight.y * u_jointMatrix[int(a_joint.y)];\n'; + vertexShaderMain += ' skinMat += a_weight.z * u_jointMatrix[int(a_joint.z)];\n'; + vertexShaderMain += ' skinMat += a_weight.w * u_jointMatrix[int(a_joint.w)];\n'; + } + + // TODO: Handle multiple texture coordinates for now we just use 1 + var v_texcoord; + for (i=0;i 0) { + lowerCase = attribute.toLowerCase(); + techniqueParameters[lowerCase] = { + semantic: attribute, + type: typeValue + }; + } + } + + var hasNormals = defined(techniqueParameters.normal); + var hasSpecular = hasNormals && ((lightingModel === 'BLINN') || (lightingModel === 'PHONG')) && + defined(techniqueParameters.specular) && defined(techniqueParameters.shininess); + + // Generate lighting code blocks + var hasNonAmbientLights = false; + var hasAmbientLights = false; + var fragmentLightingBlock = ''; + for (var lightName in lights) { + if (lights.hasOwnProperty(lightName)) { + var light = lights[lightName]; + var lightType = light.type.toLowerCase(); + var lightBaseName = light.baseName; + fragmentLightingBlock += ' {\n'; + var lightColorName = 'u_' + lightBaseName + 'Color'; + var varyingName; + if(lightType === 'ambient') { + hasAmbientLights = true; + fragmentLightingBlock += ' ambientLight += ' + lightColorName + ';\n'; + } + else if (hasNormals && (lightingModel !== 'CONSTANT')) { + hasNonAmbientLights = true; + varyingName = 'v_' + lightBaseName + 'Direction'; + vertexShader += 'varying vec3 ' + varyingName + ';\n'; + fragmentShader += 'varying vec3 ' + varyingName + ';\n'; + + if (lightType === 'directional') { + vertexShaderMain += ' ' + varyingName + ' = mat3(u_' + lightBaseName + 'Transform) * vec3(0.,0.,1.);\n'; + fragmentLightingBlock += ' float attenuation = 1.0;\n'; + } + else { + vertexShaderMain += ' ' + varyingName + ' = u_' + lightBaseName + 'Transform[3].xyz - pos.xyz;\n'; + fragmentLightingBlock += ' float range = length(' + varyingName + ');\n'; + fragmentLightingBlock += ' float attenuation = 1.0 / (u_' + lightBaseName + 'Attenuation.x + '; + fragmentLightingBlock += '(u_' + lightBaseName + 'Attenuation.y * range) + '; + fragmentLightingBlock += '(u_' + lightBaseName + 'Attenuation.z * range * range));\n'; + } + + fragmentLightingBlock += ' vec3 l = normalize(' + varyingName + ');\n'; + + if (lightType === 'spot') { + fragmentLightingBlock += ' vec4 spotPosition = u_' + lightBaseName + 'InverseTransform * vec4(v_positionEC, 1.0);\n'; + fragmentLightingBlock += ' float cosAngle = dot(vec3(0.0,0.0,-1.0), normalize(spotPosition.xyz));\n'; + fragmentLightingBlock += ' if (cosAngle < cos(u_' + lightBaseName + 'FallOff.x * 0.5))\n'; + fragmentLightingBlock += ' {\n'; + fragmentLightingBlock += ' attenuation *= max(0.0, pow(cosAngle, u_' + lightBaseName + 'FallOff.y));\n'; + } + + fragmentLightingBlock += ' diffuseLight += ' + lightColorName + '* max(dot(normal,l), 0.) * attenuation;\n'; + + if (hasSpecular) { + if (lightingModel === 'BLINN') { + fragmentLightingBlock += ' vec3 h = normalize(l + viewDir);\n'; + fragmentLightingBlock += ' float specularIntensity = max(0., pow(max(dot(normal, h), 0.), u_shininess)) * attenuation;\n'; + } + else { // PHONG + fragmentLightingBlock += ' vec3 reflectDir = reflect(-l, normal);\n'; + fragmentLightingBlock += ' float specularIntensity = max(0., pow(max(dot(reflectDir, viewDir), 0.), u_shininess)) * attenuation;\n'; + } + fragmentLightingBlock += ' specularLight += ' + lightColorName + ' * specularIntensity;\n'; + } + + if (lightType === 'spot') { + fragmentLightingBlock += ' }\n'; + } + } + fragmentLightingBlock += ' }\n'; + } + } + + if (!hasAmbientLights) { + // Add an ambient light if we don't have one + fragmentLightingBlock += ' ambientLight += vec3(0.1, 0.1, 0.1);\n'; + } + + if (!hasNonAmbientLights) { + fragmentLightingBlock += ' vec3 l = normalize(czm_sunDirectionEC);\n'; + fragmentLightingBlock += ' diffuseLight += vec3(1.0, 1.0, 1.0) * max(dot(normal,l), 0.);\n'; + + if (hasSpecular) { + if (lightingModel === 'BLINN') { + fragmentLightingBlock += ' vec3 h = normalize(l + viewDir);\n'; + fragmentLightingBlock += ' float specularIntensity = max(0., pow(max(dot(normal, h), 0.), u_shininess));\n'; + } + else { // PHONG + fragmentLightingBlock += ' vec3 reflectDir = reflect(-l, normal);\n'; + fragmentLightingBlock += ' float specularIntensity = max(0., pow(max(dot(reflectDir, viewDir), 0.), u_shininess));\n'; + } + + fragmentLightingBlock += ' specularLight += vec3(1.0, 1.0, 1.0) * specularIntensity;\n'; + } + } + + vertexShader += 'void main(void) {\n'; + vertexShader += vertexShaderMain; + vertexShader += '}\n'; + + fragmentShader += 'void main(void) {\n'; + var colorCreationBlock = ' vec3 color = vec3(0.0, 0.0, 0.0);\n'; + if (hasNormals) { + fragmentShader += ' vec3 normal = normalize(v_normal);\n'; + if (isDoubleSided) { + fragmentShader += ' if (gl_FrontFacing == false)\n'; + fragmentShader += ' {\n'; + fragmentShader += ' normal = -normal;\n'; + fragmentShader += ' }\n'; + } + } + + var finalColorComputation; + if (lightingModel !== 'CONSTANT') { + if (defined(techniqueParameters.diffuse)) { + if (techniqueParameters.diffuse.type === WebGLConstants.SAMPLER_2D) { + fragmentShader += ' vec4 diffuse = texture2D(u_diffuse, ' + v_texcoord + ');\n'; + } + else { + fragmentShader += ' vec4 diffuse = u_diffuse;\n'; + } + fragmentShader += ' vec3 diffuseLight = vec3(0.0, 0.0, 0.0);\n'; + colorCreationBlock += ' color += diffuse.rgb * diffuseLight;\n'; + } + + if (hasSpecular) { + if (techniqueParameters.specular.type === WebGLConstants.SAMPLER_2D) { + fragmentShader += ' vec3 specular = texture2D(u_specular, ' + v_texcoord + ');\n'; + } + else { + fragmentShader += ' vec3 specular = u_specular.rgb;\n'; + } + fragmentShader += ' vec3 specularLight = vec3(0.0, 0.0, 0.0);\n'; + colorCreationBlock += ' color += specular * specularLight;\n'; + } + + if (defined(techniqueParameters.transparency)) { + finalColorComputation = ' gl_FragColor = vec4(color * diffuse.a, diffuse.a * u_transparency);\n'; + } + else { + finalColorComputation = ' gl_FragColor = vec4(color * diffuse.a, diffuse.a);\n'; + } + } + else { + if (defined(techniqueParameters.transparency)) { + finalColorComputation = ' gl_FragColor = vec4(color, u_transparency);\n'; + } + else { + finalColorComputation = ' gl_FragColor = vec4(color, 1.0);\n'; + } + } + + if (defined(techniqueParameters.emission)) { + if (techniqueParameters.emission.type === WebGLConstants.SAMPLER_2D) { + fragmentShader += ' vec3 emission = texture2D(u_emission, ' + v_texcoord + ');\n'; + } + else { + fragmentShader += ' vec3 emission = u_emission.rgb;\n'; + } + colorCreationBlock += ' color += emission;\n'; + } + + if (defined(techniqueParameters.ambient)) { + if (techniqueParameters.ambient.type === WebGLConstants.SAMPLER_2D) { + fragmentShader += ' vec3 ambient = texture2D(u_ambient, ' + v_texcoord + ');\n'; + } + else { + fragmentShader += ' vec3 ambient = u_ambient.rgb;\n'; + } + } + else { + fragmentShader += ' vec3 ambient = diffuse.rgb;\n'; + } + fragmentShader += ' vec3 viewDir = -normalize(v_positionEC);\n'; + fragmentShader += ' vec3 ambientLight = vec3(0.0, 0.0, 0.0);\n'; + colorCreationBlock += ' color += ambient * ambientLight;\n'; + + // Add in light computations + fragmentShader += fragmentLightingBlock; + + fragmentShader += colorCreationBlock; + fragmentShader += finalColorComputation; + fragmentShader += '}\n'; + + // TODO: Handle texture transparency + var techniqueStates; + if (hasAlpha) { + techniqueStates = { + enable: [ + WebGLConstants.DEPTH_TEST, + WebGLConstants.BLEND + ], + depthMask: false, + functions: { + "blendEquationSeparate": [ + WebGLConstants.FUNC_ADD, + WebGLConstants.FUNC_ADD + ], + "blendFuncSeparate": [ + WebGLConstants.ONE, + WebGLConstants.ONE_MINUS_SRC_ALPHA, + WebGLConstants.ONE, + WebGLConstants.ONE_MINUS_SRC_ALPHA + ] + } + }; + } + else { + techniqueStates = { + enable: [ + WebGLConstants.CULL_FACE, + WebGLConstants.DEPTH_TEST + ] + }; + } + techniques[techniqueId] = { + attributes: techniqueAttributes, + parameters: techniqueParameters, + program: programId, + states: techniqueStates, + uniforms: techniqueUniforms + }; + + // Add shaders + shaders[vertexShaderId] = { + type: WebGLConstants.VERTEX_SHADER, + uri: '', + extras: { + source: vertexShader + } + }; + shaders[fragmentShaderId] = { + type: WebGLConstants.FRAGMENT_SHADER, + uri: '', + extras: { + source: fragmentShader + } + }; + + // Add program + var programAttributes = []; + for (i=0;i Date: Sun, 18 Oct 2015 23:33:54 -0400 Subject: [PATCH 2/2] Updated readme --- .gitignore | 13 ++----------- LICENSE.md | 7 +++++++ README.md | 25 ++++++++++++++++++++++++- doc/cesium.png | Bin 0 -> 4664 bytes 4 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 LICENSE.md create mode 100644 doc/cesium.png diff --git a/.gitignore b/.gitignore index f431c7b..31dde5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,3 @@ -*.sdf -*.opensdf -*.suo -*.csproj.user -*.vcxproj.user -*.log -x64 **/node_modules - -# WebStorm user-specific -.idea/ -data/ \ No newline at end of file +.DS_Store +Thumbs.db diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..6cb7cfd --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2015 Analytical Graphics, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index 30775bd..18570c2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,25 @@ # OBJ2GLTF -Convert OBJ assets to glTF + +Convert OBJ models to glTF + +## Getting Started + +Install [Node.js](https://nodejs.org/en/) if you don't already have it, clone this repo, and then: +``` +cd OBJ2GLTF +npm install +``` +Run `node ./bin/obj2gltf.js` and pass it the path to an OBJ file. + +## Limitations + +This tool is still in development. We plan on adding additional features like gzip compression, binary glTF export, normal generation, and a testing suite. + +Pull requests are appreciated. Please use the same [Contributor License Agreement (CLA)](https://github.com/AnalyticalGraphicsInc/cesium/blob/master/CONTRIBUTING.md) used for [Cesium](http://cesiumjs.org/). + +--- + +Developed by the Cesium team. +

+ +

diff --git a/doc/cesium.png b/doc/cesium.png new file mode 100644 index 0000000000000000000000000000000000000000..32db61698f08c8d7ef37b002e0ab294df1c34a37 GIT binary patch literal 4664 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