mirror of
https://github.com/CesiumGS/obj2gltf.git
synced 2024-11-27 02:24:04 -05:00
Merge pull request #1 from lilleyse/master
Initial code for OBJ to glTF converter
This commit is contained in:
commit
82698ba8e4
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
**/node_modules
|
||||
.DS_Store
|
||||
Thumbs.db
|
7
LICENSE.md
Normal file
7
LICENSE.md
Normal file
@ -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.
|
25
README.md
25
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.
|
||||
<p align="center">
|
||||
<a href="http://cesiumjs.org/"><img src="doc/cesium.png" /></a>
|
||||
</p>
|
||||
|
61
bin/obj2gltf.js
Normal file
61
bin/obj2gltf.js
Normal file
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
BIN
doc/cesium.png
Normal file
BIN
doc/cesium.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
300
lib/WebGLConstants.js
Normal file
300
lib/WebGLConstants.js
Normal file
@ -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
|
||||
};
|
435
lib/gltf.js
Normal file
435
lib/gltf.js
Normal file
@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
44
lib/image.js
Normal file
44
lib/image.js
Normal file
@ -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);
|
||||
});
|
||||
}
|
750
lib/modelMaterialsCommon.js
Normal file
750
lib/modelMaterialsCommon.js
Normal file
@ -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<attributesCount;++i) {
|
||||
lowerCase = attributes[i].toLowerCase();
|
||||
techniqueAttributes['a_' + lowerCase] = lowerCase;
|
||||
}
|
||||
|
||||
var techniqueParameters = {
|
||||
// Add matrices
|
||||
modelViewMatrix: {
|
||||
semantic: 'MODELVIEW',
|
||||
type: WebGLConstants.FLOAT_MAT4
|
||||
},
|
||||
normalMatrix: {
|
||||
semantic: 'MODELVIEWINVERSETRANSPOSE',
|
||||
type: WebGLConstants.FLOAT_MAT3
|
||||
},
|
||||
projectionMatrix: {
|
||||
semantic: 'PROJECTION',
|
||||
type: WebGLConstants.FLOAT_MAT4
|
||||
}
|
||||
};
|
||||
|
||||
if (hasSkinning) {
|
||||
techniqueParameters.jointMatrix = {
|
||||
count: jointCount,
|
||||
semantic: 'JOINTMATRIX',
|
||||
type: WebGLConstants.FLOAT_MAT4
|
||||
};
|
||||
}
|
||||
|
||||
// Add material parameters
|
||||
var hasAlpha = false;
|
||||
var typeValue;
|
||||
for(var name in values) {
|
||||
if (values.hasOwnProperty(name)) {
|
||||
var value = values[name];
|
||||
var type = typeof value;
|
||||
typeValue = -1;
|
||||
switch (type) {
|
||||
case 'string':
|
||||
typeValue = WebGLConstants.SAMPLER_2D;
|
||||
break;
|
||||
case 'number':
|
||||
typeValue = WebGLConstants.FLOAT;
|
||||
if (!hasAlpha && (name === 'transparency')) {
|
||||
hasAlpha = (value !== 1.0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (Array.isArray(value)) {
|
||||
// 35664 (vec2), 35665 (vec3), 35666 (vec4)
|
||||
typeValue = 35662 + value.length;
|
||||
if (!hasAlpha && (typeValue === WebGLConstants.FLOAT_VEC4) && (name === 'diffuse')) {
|
||||
hasAlpha = (value[3] !== 1.0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (typeValue > 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<attributesCount;++i) {
|
||||
var attribute = attributes[i];
|
||||
typeValue = -1;
|
||||
if (attribute === 'POSITION') {
|
||||
typeValue = WebGLConstants.FLOAT_VEC3;
|
||||
vertexShader += 'attribute vec3 a_position;\n';
|
||||
vertexShader += 'varying vec3 v_positionEC;\n';
|
||||
if (hasSkinning) {
|
||||
vertexShaderMain += ' vec4 pos = u_modelViewMatrix * skinMat * vec4(a_position,1.0);\n';
|
||||
}
|
||||
else {
|
||||
vertexShaderMain += ' vec4 pos = u_modelViewMatrix * vec4(a_position,1.0);\n';
|
||||
}
|
||||
vertexShaderMain += ' v_positionEC = pos.xyz;\n';
|
||||
vertexShaderMain += ' gl_Position = u_projectionMatrix * pos;\n';
|
||||
fragmentShader += 'varying vec3 v_positionEC;\n';
|
||||
}
|
||||
else if (attribute === 'NORMAL') {
|
||||
typeValue = WebGLConstants.FLOAT_VEC3;
|
||||
vertexShader += 'attribute vec3 a_normal;\n';
|
||||
vertexShader += 'varying vec3 v_normal;\n';
|
||||
if (hasSkinning) {
|
||||
vertexShaderMain += ' v_normal = u_normalMatrix * mat3(skinMat) * a_normal;\n';
|
||||
}
|
||||
else {
|
||||
vertexShaderMain += ' v_normal = u_normalMatrix * a_normal;\n';
|
||||
}
|
||||
|
||||
fragmentShader += 'varying vec3 v_normal;\n';
|
||||
}
|
||||
else if (attribute.indexOf('TEXCOORD') === 0) {
|
||||
typeValue = WebGLConstants.FLOAT_VEC2;
|
||||
lowerCase = attribute.toLowerCase();
|
||||
var a_texcoord = 'a_' + lowerCase;
|
||||
v_texcoord = 'v_' + lowerCase;
|
||||
vertexShader += 'attribute vec2 ' + a_texcoord + ';\n';
|
||||
vertexShader += 'varying vec2 ' + v_texcoord + ';\n';
|
||||
vertexShaderMain += ' ' + v_texcoord + ' = ' + a_texcoord + ';\n';
|
||||
|
||||
fragmentShader += 'varying vec2 ' + v_texcoord + ';\n';
|
||||
}
|
||||
else if (attribute === 'JOINT') {
|
||||
typeValue = WebGLConstants.FLOAT_VEC4;
|
||||
vertexShader += 'attribute vec4 a_joint;\n';
|
||||
}
|
||||
else if (attribute === 'WEIGHT') {
|
||||
typeValue = WebGLConstants.FLOAT_VEC4;
|
||||
vertexShader += 'attribute vec4 a_weight;\n';
|
||||
}
|
||||
|
||||
if (typeValue > 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<attributesCount;++i) {
|
||||
programAttributes.push('a_' + attributes[i].toLowerCase());
|
||||
}
|
||||
|
||||
programs[programId] = {
|
||||
attributes: programAttributes,
|
||||
fragmentShader: fragmentShaderId,
|
||||
vertexShader: vertexShaderId
|
||||
};
|
||||
|
||||
return techniqueId;
|
||||
}
|
||||
|
||||
function getTechniqueKey(khrMaterialsCommon) {
|
||||
var techniqueKey = '';
|
||||
techniqueKey += 'technique:' + khrMaterialsCommon.technique + ';';
|
||||
|
||||
var values = khrMaterialsCommon.values;
|
||||
var keys = Object.keys(values).sort();
|
||||
var keysCount = keys.length;
|
||||
for (var i=0;i<keysCount;++i) {
|
||||
var name = keys[i];
|
||||
if (values.hasOwnProperty(name)) {
|
||||
var value = values[name];
|
||||
techniqueKey += name + ':';
|
||||
var type = typeof value;
|
||||
switch (type) {
|
||||
case 'string':
|
||||
techniqueKey += 'texture';
|
||||
break;
|
||||
case 'number':
|
||||
techniqueKey += 'float';
|
||||
break;
|
||||
default:
|
||||
if (Array.isArray(value)) {
|
||||
techniqueKey += 'vec' + value.length.toString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
techniqueKey += ';';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getPrimitiveInfo(materialId, gltf) {
|
||||
var meshes = gltf.meshes;
|
||||
for (var name in meshes) {
|
||||
if (meshes.hasOwnProperty(name)) {
|
||||
var mesh = meshes[name];
|
||||
var primitives = mesh.primitives;
|
||||
var primitivesCount = primitives.length;
|
||||
for (var i=0;i<primitivesCount;++i) {
|
||||
var primitive = primitives[i];
|
||||
if (primitive.material === materialId) {
|
||||
var jointCount = 0;
|
||||
if (defined(primitive.attributes.JOINT)) {
|
||||
var nodes = gltf.nodes;
|
||||
for (var nodeName in nodes) {
|
||||
if (nodes.hasOwnProperty(nodeName)) {
|
||||
var node = nodes[nodeName];
|
||||
// We have skinning for this node and it contains this mesh
|
||||
if (defined(node.skeletons) && defined(node.meshes) &&
|
||||
(node.meshes.indexOf(name) !== -1)) {
|
||||
jointCount = node.skeletons.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
attributes : Object.keys(primitive.attributes),
|
||||
jointCount : jointCount
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies gltf in place.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function modelMaterialsCommon(gltf) {
|
||||
if (!defined(gltf)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var hasExtension = false;
|
||||
var extensionsUsed = gltf.extensionsUsed;
|
||||
if (defined(extensionsUsed)) {
|
||||
var extensionsUsedCount = extensionsUsed.length;
|
||||
for(var i=0;i<extensionsUsedCount;++i) {
|
||||
if (extensionsUsed[i] === 'KHR_materials_common') {
|
||||
hasExtension = true;
|
||||
extensionsUsed.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasExtension) {
|
||||
if (!defined(gltf.programs)) {
|
||||
gltf.programs = {};
|
||||
}
|
||||
if (!defined(gltf.shaders)) {
|
||||
gltf.shaders = {};
|
||||
}
|
||||
if (!defined(gltf.techniques)) {
|
||||
gltf.techniques = {};
|
||||
}
|
||||
|
||||
var lightParameters = generateLightParameters(gltf);
|
||||
|
||||
var techniques = {};
|
||||
var materials = gltf.materials;
|
||||
var meshes = gltf.meshes;
|
||||
for (var name in materials) {
|
||||
if (materials.hasOwnProperty(name)) {
|
||||
var material = materials[name];
|
||||
if (defined(material.extensions) && defined(material.extensions.KHR_materials_common)) {
|
||||
var khrMaterialsCommon = material.extensions.KHR_materials_common;
|
||||
var techniqueKey = getTechniqueKey(khrMaterialsCommon);
|
||||
var technique = techniques[techniqueKey];
|
||||
if (!defined(technique)) {
|
||||
var primitiveInfo = getPrimitiveInfo(name, gltf);
|
||||
technique = generateTechnique(gltf, khrMaterialsCommon, primitiveInfo.attributes,
|
||||
lightParameters, primitiveInfo.jointCount);
|
||||
}
|
||||
|
||||
// Take advantage of the fact that we generate techniques that use the
|
||||
// same parameter names as the extension values.
|
||||
material.values = khrMaterialsCommon.values;
|
||||
material.technique = technique;
|
||||
|
||||
delete material.extensions.KHR_materials_common;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defined(gltf.extensions)) {
|
||||
delete gltf.extensions.KHR_materials_common;
|
||||
}
|
||||
|
||||
//var json = JSON.stringify(gltf, null, 4);
|
||||
//var a = document.createElement('a');
|
||||
//a.setAttribute('href', 'data:text;base64,' + btoa(json));
|
||||
//a.setAttribute('target', '_blank');
|
||||
//a.setAttribute('download', 'model.json');
|
||||
//a.click();
|
||||
}
|
||||
|
||||
return gltf;
|
||||
};
|
118
lib/mtl.js
Normal file
118
lib/mtl.js
Normal file
@ -0,0 +1,118 @@
|
||||
"use strict";
|
||||
var fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
getDefault : getDefault,
|
||||
parse : parse
|
||||
};
|
||||
|
||||
function createMaterial() {
|
||||
return {
|
||||
ambientColor : undefined, // Ka
|
||||
emissionColor : undefined, // Ke
|
||||
diffuseColor : undefined, // Kd
|
||||
specularColor : undefined, // Ks
|
||||
specularShininess : undefined, // Ns
|
||||
specularIntensity : undefined, // Ni
|
||||
alpha : undefined, // d
|
||||
ambientColorMap : undefined, // map_Ka
|
||||
emissionColorMap : undefined, // map_Ke
|
||||
diffuseColorMap : undefined, // map_Kd
|
||||
specularColorMap : undefined, // map_Ks
|
||||
specularShininessMap : undefined, // map_Ns
|
||||
specularIntensityMap : undefined, // map_Ni
|
||||
normalMap : undefined, // map_Bump
|
||||
alphaMap : undefined // map_d
|
||||
};
|
||||
}
|
||||
|
||||
function getDefault() {
|
||||
var material = createMaterial();
|
||||
material.diffuseColor = [0.5, 0.5, 0.5, 1.0];
|
||||
return material;
|
||||
}
|
||||
|
||||
function parse(mtlPath, done) {
|
||||
fs.readFile(mtlPath, 'utf-8', function (err, contents) {
|
||||
if (err) {
|
||||
console.log('Could not read material file at ' + mtlPath + '. Using default material instead.');
|
||||
done({});
|
||||
return;
|
||||
}
|
||||
|
||||
var materials = {};
|
||||
var material;
|
||||
|
||||
var values;
|
||||
var value;
|
||||
var lines = contents.split('\n');
|
||||
var length = lines.length;
|
||||
for (var i = 0; i < length; ++i) {
|
||||
var line = lines[i].trim();
|
||||
if (/^newmtl /i.test(line)) {
|
||||
var name = line.substring(7).trim();
|
||||
material = createMaterial();
|
||||
materials[name] = material;
|
||||
} else if (/^Ka /i.test(line)) {
|
||||
values = line.substring(3).trim().split(' ');
|
||||
material.ambientColor = [
|
||||
parseFloat(values[0]),
|
||||
parseFloat(values[1]),
|
||||
parseFloat(values[2]),
|
||||
1.0
|
||||
];
|
||||
} else if (/^Ke /i.test(line)) {
|
||||
values = line.substring(3).trim().split(' ');
|
||||
material.emissionColor = [
|
||||
parseFloat(values[0]),
|
||||
parseFloat(values[1]),
|
||||
parseFloat(values[2]),
|
||||
1.0
|
||||
];
|
||||
} else if (/^Kd /i.test(line)) {
|
||||
values = line.substring(3).trim().split(' ');
|
||||
material.diffuseColor = [
|
||||
parseFloat(values[0]),
|
||||
parseFloat(values[1]),
|
||||
parseFloat(values[2]),
|
||||
1.0
|
||||
];
|
||||
} else if (/^Ks /i.test(line)) {
|
||||
values = line.substring(3).trim().split(' ');
|
||||
material.specularColor = [
|
||||
parseFloat(values[0]),
|
||||
parseFloat(values[1]),
|
||||
parseFloat(values[2]),
|
||||
1.0
|
||||
];
|
||||
} else if (/^Ns /i.test(line)) {
|
||||
value = line.substring(3).trim();
|
||||
material.specularShininess = parseFloat(value);
|
||||
} else if (/^Ni /i.test(line)) {
|
||||
value = line.substring(3).trim();
|
||||
material.specularIntensity = parseFloat(value);
|
||||
} else if (/^d /i.test(line)) {
|
||||
value = line.substring(2).trim();
|
||||
material.alpha = parseFloat(value);
|
||||
} else if (/^map_Ka /i.test(line)) {
|
||||
material.ambientColorMap = line.substring(7).trim();
|
||||
} else if (/^map_Ke /i.test(line)) {
|
||||
material.emissionColorMap = line.substring(7).trim();
|
||||
} else if (/^map_Kd /i.test(line)) {
|
||||
material.diffuseColorMap = line.substring(7).trim();
|
||||
} else if (/^map_Ks /i.test(line)) {
|
||||
material.specularColorMap = line.substring(7).trim();
|
||||
} else if (/^map_Ns /i.test(line)) {
|
||||
material.specularShininessMap = line.substring(7).trim();
|
||||
} else if (/^map_Ni /i.test(line)) {
|
||||
material.specularIntensityMap = line.substring(7).trim();
|
||||
} else if (/^map_Bump /i.test(line)) {
|
||||
material.normalMap = line.substring(9).trim();
|
||||
} else if (/^map_d /i.test(line)) {
|
||||
material.alphaMap = line.substring(6).trim();
|
||||
}
|
||||
}
|
||||
|
||||
done(materials);
|
||||
});
|
||||
}
|
251
lib/obj.js
Normal file
251
lib/obj.js
Normal file
@ -0,0 +1,251 @@
|
||||
"use strict";
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var Material = require('./mtl');
|
||||
var util = require('./util');
|
||||
var defined = util.defined;
|
||||
var defaultValue = util.defaultValue;
|
||||
|
||||
module.exports = parseObj;
|
||||
|
||||
// Obj regex patterns are from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js)
|
||||
|
||||
function getMaterials(contents, inputPath, done) {
|
||||
var hasMaterialGroups = /^usemtl/gm.test(contents);
|
||||
if (!hasMaterialGroups) {
|
||||
done({});
|
||||
return;
|
||||
}
|
||||
|
||||
var mtllibMatches = contents.match(/^mtllib.*/gm);
|
||||
if (mtllibMatches === null) {
|
||||
done({});
|
||||
} else {
|
||||
var mtlFile = mtllibMatches[0].substring(7).trim();
|
||||
var mtlPath = path.join(inputPath, mtlFile);
|
||||
Material.parse(mtlPath, function (materials) {
|
||||
done(materials);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function parseObj(objFile, inputPath, done) {
|
||||
fs.readFile(objFile, 'utf-8', function (err, contents) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
getMaterials(contents, inputPath, function (materials) {
|
||||
// A vertex in a face is specified by indexes into each of the attribute arrays,
|
||||
// but these indexes may be different. This maps the separate indexes to a single index.
|
||||
var vertexCache = {};
|
||||
var vertexCount = 0;
|
||||
|
||||
var vertexArray = [];
|
||||
|
||||
var positions = [];
|
||||
var normals = [];
|
||||
var uvs = [];
|
||||
|
||||
var positionMin = [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE];
|
||||
var positionMax = [Number.MIN_VALUE, Number.MIN_VALUE, Number.MIN_VALUE];
|
||||
|
||||
var hasPositions = /^v\s/gm.test(contents);
|
||||
var hasNormals = /^vn/gm.test(contents);
|
||||
var hasUVs = /^vt/gm.test(contents);
|
||||
|
||||
if (!hasPositions) {
|
||||
console.log('Could not process obj file, no positions.')
|
||||
}
|
||||
|
||||
// Map material to index array
|
||||
var materialGroups = {};
|
||||
var matIndexArray;
|
||||
|
||||
// Switch to the material-specific index array, or create it if it doesn't exist
|
||||
function useMaterial(material) {
|
||||
if (!defined(materials[material])) {
|
||||
useDefaultMaterial();
|
||||
} else {
|
||||
matIndexArray = materialGroups[material];
|
||||
if (!defined(matIndexArray)) {
|
||||
matIndexArray = [];
|
||||
materialGroups[material] = matIndexArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function useDefaultMaterial() {
|
||||
var defaultMaterial = 'czmDefaultMat';
|
||||
if (!defined(materials[defaultMaterial])) {
|
||||
materials[defaultMaterial] = Material.getDefault();
|
||||
}
|
||||
useMaterial(defaultMaterial);
|
||||
}
|
||||
|
||||
var materialsLength = Object.keys(materials).length;
|
||||
if (materialsLength === 0) {
|
||||
useDefaultMaterial();
|
||||
}
|
||||
|
||||
// Sometimes the obj will have objects with different vertex formats
|
||||
// e.g. one object has uvs, the other does not.
|
||||
// In this case, use default values.
|
||||
function createVertex(p, u, n) {
|
||||
// Positions
|
||||
var pi = (parseInt(p) - 1) * 3;
|
||||
var px = positions[pi + 0];
|
||||
var py = positions[pi + 1];
|
||||
var pz = positions[pi + 2];
|
||||
positionMin[0] = Math.min(px, positionMin[0]);
|
||||
positionMin[1] = Math.min(py, positionMin[1]);
|
||||
positionMin[2] = Math.min(pz, positionMin[2]);
|
||||
positionMax[0] = Math.max(px, positionMax[0]);
|
||||
positionMax[1] = Math.max(py, positionMax[1]);
|
||||
positionMax[2] = Math.max(pz, positionMax[2]);
|
||||
vertexArray.push(px, py, pz);
|
||||
|
||||
// Normals
|
||||
var ni = (parseInt(n) - 1) * 3;
|
||||
var nx = defaultValue(normals[ni + 0], 0.0);
|
||||
var ny = defaultValue(normals[ni + 1], 0.0);
|
||||
var nz = defaultValue(normals[ni + 2], 0.0);
|
||||
vertexArray.push(nx, ny, nz);
|
||||
|
||||
// UVs
|
||||
if (hasUVs) {
|
||||
var ui = (parseInt(u) - 1) * 2;
|
||||
var ux = defaultValue(uvs[ui + 0], 0.0);
|
||||
var uy = defaultValue(uvs[ui + 1], 0.0);
|
||||
vertexArray.push(ux, uy);
|
||||
}
|
||||
}
|
||||
|
||||
function addVertex(v, p, u, n) {
|
||||
var index = vertexCache[v];
|
||||
if (!defined(index)) {
|
||||
index = vertexCount++;
|
||||
vertexCache[v] = index;
|
||||
createVertex(p, u, n);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
function addFace(v1, p1, u1, n1, v2, p2, u2, n2, v3, p3, u3, n3, v4, p4, u4, n4) {
|
||||
var index1 = addVertex(v1, p1, u1, n1);
|
||||
var index2 = addVertex(v2, p2, u2, n2);
|
||||
var index3 = addVertex(v3, p3, u3, n3);
|
||||
|
||||
matIndexArray.push(index1);
|
||||
matIndexArray.push(index2);
|
||||
matIndexArray.push(index3);
|
||||
|
||||
// Triangulate if the face is a quad
|
||||
if (defined(v4)) {
|
||||
var index4 = addVertex(v4, p4, u4, n4);
|
||||
matIndexArray.push(index1);
|
||||
matIndexArray.push(index3);
|
||||
matIndexArray.push(index4);
|
||||
}
|
||||
}
|
||||
|
||||
// v float float float
|
||||
var vertexPattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/;
|
||||
|
||||
// vn float float float
|
||||
var normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/;
|
||||
|
||||
// vt float float
|
||||
var uvPattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/;
|
||||
|
||||
// f vertex vertex vertex ...
|
||||
var facePattern1 = /f( +-?\d+)( +-?\d+)( +-?\d+)( +-?\d+)?/;
|
||||
|
||||
// f vertex/uv vertex/uv vertex/uv ...
|
||||
var facePattern2 = /f( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))?/;
|
||||
|
||||
// f vertex/uv/normal vertex/uv/normal vertex/uv/normal ...
|
||||
var facePattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/;
|
||||
|
||||
// f vertex//normal vertex//normal vertex//normal ...
|
||||
var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/;
|
||||
|
||||
var lines = contents.split('\n');
|
||||
var length = lines.length;
|
||||
for (var i = 0; i < length; ++i) {
|
||||
var line = lines[i].trim();
|
||||
var result;
|
||||
if ((line.length === 0) || (line.charAt(0) === '#')) {
|
||||
continue;
|
||||
} else if ((result = vertexPattern.exec(line)) !== null) {
|
||||
positions.push(
|
||||
parseFloat(result[1]),
|
||||
parseFloat(result[2]),
|
||||
parseFloat(result[3])
|
||||
);
|
||||
} else if ((result = normalPattern.exec(line) ) !== null) {
|
||||
// Normalize
|
||||
var nx = parseFloat(result[1]);
|
||||
var ny = parseFloat(result[2]);
|
||||
var nz = parseFloat(result[3]);
|
||||
var magnitude = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
||||
nx /= magnitude;
|
||||
ny /= magnitude;
|
||||
nz /= magnitude;
|
||||
normals.push(
|
||||
nx,
|
||||
ny,
|
||||
nz
|
||||
);
|
||||
} else if ((result = uvPattern.exec(line)) !== null) {
|
||||
uvs.push(
|
||||
parseFloat(result[1]),
|
||||
parseFloat(result[2])
|
||||
);
|
||||
|
||||
} else if ((result = facePattern1.exec(line)) !== null) {
|
||||
addFace(
|
||||
result[1], result[1], 0, 0,
|
||||
result[2], result[2], 0, 0,
|
||||
result[3], result[3], 0, 0,
|
||||
result[4], result[4], 0, 0
|
||||
);
|
||||
} else if ((result = facePattern2.exec(line)) !== null) {
|
||||
addFace(
|
||||
result[1], result[2], result[3], 0,
|
||||
result[4], result[5], result[6], 0,
|
||||
result[7], result[8], result[9], 0,
|
||||
result[10], result[11], result[12], 0
|
||||
);
|
||||
} else if ((result = facePattern3.exec(line)) !== null) {
|
||||
addFace(
|
||||
result[1], result[2], result[3], result[4],
|
||||
result[5], result[6], result[7], result[8],
|
||||
result[9], result[10], result[11], result[12],
|
||||
result[13], result[14], result[15], result[16]
|
||||
);
|
||||
} else if ((result = facePattern4.exec(line)) !== null) {
|
||||
addFace(
|
||||
result[1], result[2], 0, result[3],
|
||||
result[4], result[5], 0, result[6],
|
||||
result[7], result[8], 0, result[9],
|
||||
result[10], result[11], 0, result[12]
|
||||
);
|
||||
} else if (/^usemtl /.test(line)) {
|
||||
var materialName = line.substring(7).trim();
|
||||
useMaterial(materialName);
|
||||
}
|
||||
}
|
||||
|
||||
done({
|
||||
vertexCount: vertexCount,
|
||||
vertexArray: vertexArray,
|
||||
positionMin : positionMin,
|
||||
positionMax : positionMax,
|
||||
hasUVs: hasUVs,
|
||||
materialGroups: materialGroups,
|
||||
materials: materials
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
16
lib/util.js
Normal file
16
lib/util.js
Normal file
@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
module.exports = {
|
||||
defined : defined,
|
||||
defaultValue : defaultValue
|
||||
};
|
||||
|
||||
function defined(value) {
|
||||
return value !== undefined;
|
||||
}
|
||||
|
||||
function defaultValue(a, b) {
|
||||
if (a !== undefined) {
|
||||
return a;
|
||||
}
|
||||
return b;
|
||||
}
|
10
package.json
Normal file
10
package.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "obj2gltf",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"async": "^1.4.2",
|
||||
"fs-extra": "^0.24.0",
|
||||
"minimist": "^1.2.0",
|
||||
"path": "^0.12.7"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user