Source: primitives/SceneParser.js

/**
 *  @namespace SceneParser
 *  @memberof SQR
 *
 *  @description Utility to load scenes from J3D/Unity exported JSON files.
 *
 */
SQR.SceneParser = (function() {

	// var skinnedMeshLayout = function() { return { aPosition: 3, aNormal: 3, aUV: 2, aWeight: 4, aIndex: 4 } };

	var arrayToObject = function(a, v) {
		v.x = a[0];
		v.y = a[1];
		v.z = a[2];
		if(v.w) v.w = a[3];
	}

	return {

		/**
		 *	@method fromJSON
		 *	@memberof SQR.SceneParser
		 *
		 *	@description Parses the J3D JSON scene data format and creates a SQR.Buffer out of it.
		 *
		 *	@param {Object} assets The assets loaded with SQR.Loader or otherwise. 
		 *	This method will expect to find all the 3 exported files in there, named 'scene', 'mesh' and 'anim'. 
		 *	Alternatively a prefix can be provided in the seconds, options argument.
		 *
		 *	@param {Object} options Options on how to parse the scene.
		 */	
		parse: function(assets, options) {

			options = options || {};

			var prefix = options.prefix || '';
			var scene = assets[prefix + 'scene'];
			var meshes = assets[prefix + 'mesh'];
			var anim = assets[prefix + 'anim'];

			if(options.context) {
				var bc = scene.background;
				options.context.clearColor(bc.r, bc.g, bc.b);
			};

			var getDefaultShader = (function() {
				var d;
				return function() {
					if(!d) d = options.shader.setUniform ? options.shader : SQR.Shader(options.shader);
					return d;
				}
			})();

			// If this is a scene coming from unity we need to flip the matrix
			// TODO: this doesn't play well with the WebVR thing btw....
			SQR.flipMatrix = (options && options.flipMatrix) ? options.flipMatrix : false;

			var buffers = {}, bufferByName = {};
			var skinnedMeshes = [];
			var animations = {};

			for(var n in meshes) {
				var md = meshes[n];

				var layout = SQR.v3n3u2();

				if(md.boneWeights) {
					 layout.aWeight = 4;
					 layout.aIndex = 4;
				}

				if(md.uv2) {
					layout.aUV2 = 2;
				}

				var b = SQR.Mesh.fromJSON(md, null, { layout: layout });
				buffers[n] = b;
				bufferByName[md.name] = b;
			}

			for(var m in scene.materials) {
				var mat = scene.materials[m];
				mat.uColor = SQR.Color().setRGB(mat.color.r, mat.color.g, mat.color.b);
			}

			var root = new SQR.Transform();
			var camera;
			var ts = scene.transforms;

			ts.forEach(function(td) {
				var t = new SQR.Transform(td.name, td.uid);
				t.useQuaternion = true;
				arrayToObject(td.position, t.position);
				arrayToObject(td.rotation, t.quaternion);
				if(td.scale) arrayToObject(td.scale, t.scale);

				t.data = td;
				if(td.bones) skinnedMeshes.push(t);

				if(td.camera) {
					camera = t;
					var cd = scene.cameras[td.camera];

					// var resize = function() {
						var w = window.innerWidth, h = window.innerHeight, aspect = w/h;
						camera.projection = new SQR.ProjectionMatrix().perspective(cd.fov, aspect, cd.near, cd.far);
					// }

					// window.addEventListener('resize', resize);
					// resize();
				}

				if(td.lightmapTileOffset) {
					t.uniforms = t.uniforms || {};
					t.uniforms.uLightmapTileOffset = td.lightmapTileOffset;
				}

				if(td.mesh) {
					t.buffer = buffers[td.meshId];
					
				}

				if(td.collider) {
					var c;
					switch(td.collider.type) {
						case 'sphere':
							c = SQR.Collider.Sphere();
							c.radius = td.collider.radius;
							arrayToObject(td.collider.center, c.center);
							
							break;
						case 'box':
							c = SQR.Collider.Box();
							var cn = td.collider.center, si = td.collider.size;
							c.box = {
								maxX: cn[0] + si[0]/2, minX: cn[0] - si[0]/2,
								maxY: cn[1] + si[1]/2, minY: cn[1] - si[1]/2,
								maxZ: cn[2] + si[2]/2, minZ: cn[2] - si[2]/2,
							};
							break;
						case 'mesh':
							c = SQR.Collider.Mesh(buffers[td.meshId]);
							break;
					}
					t.collider = c;
				}

				
				if(td.renderer) {

					// we will deal with skinned meshes below
					if(!td.bones) t.shader = getDefaultShader();

					t.uniforms = t.uniforms || {};
					t.uniforms.uColor = scene.materials[td.renderer].color;
					
				}

				if(td.parent) {
					root.findById(td.parent).add(t);
				} else {
					root.add(t);
				}
			});

			skinnedMeshes.forEach(function(s) {
				var bs = s.data.bones, bst = [], numBones = bs.length;
				
				bs.forEach(function(id) {
					bst.push(root.findById(id));
				});

				bst[0].setAsBoneRoot();

				var boneMatrices = [];

				s.shader = SQR.Shader(options.shader, {
					directives: [
						{ name: 'NUM_BONES', value: numBones },
						{ name: 'BONE_PER_VERTEX', value: 4 }
					]
				});

				s.beforeDraw = function() {

					for(var i = 0; i < numBones; i++) {
						boneMatrices[i] = bst[i].computeBoneMatrix();
					}

					s.shader.use().setUniform('uBones', boneMatrices);
				}
			});

			for(var id in anim) {

				var data = anim[id];

				animations[id] = {
					duration: data.length,
					transforms: {}
				};

				for(var c in data.curves) {

					animations[id].transforms[c] = {
						properties: {}
					};

					for(var p in data.curves[c]) {
						var keyframes = [];
						var d = data.curves[c][p];

						if(options.linearAnimation) {
							for(var i = 0; i < d.keys.length; i += 4) {
								var a = d.keys[i + 0];
								var b = d.keys[i + 1];
								keyframes.push(new SQR.V2(a, b));								
							}
						} else {
							for(var i = 0; i < d.keys.length - 4; i += 4) {
								var k1t = d.keys[i+0];
								var k1v = d.keys[i+1];
								var k1o = d.keys[i+3];

								var k2t = d.keys[i+4];
								var k2v = d.keys[i+5];
								var k2i = d.keys[i+6];
								
								var start = new SQR.V2(k1t, k1v);
								var end = new SQR.V2(k2t, k2v);

								var dt = (end.x - start.x) / 3.0;
								var st = new SQR.V2( dt,  dt * k1o).add(start);
								var et = new SQR.V2(-dt, -dt * k2i).add(end);

								keyframes.push(new SQR.Bezier(start, st, et, end));
							}
						}

						animations[id].transforms[c].properties[p] = keyframes;
					}
				}
			}

			root.recurse(function(t) {
				if(t.data && t.data.animationId) {
					var id = t.data.animationId;
					var data = animations[id];

					// Aniation file is missing or was not exported, abort.
					if(!data) return;

					t.animation = SQR.Animation(data.duration);

					for(var cn in data.transforms) {
						var c = t.findByPath(cn);
						c.clip = SQR.Clip(data.duration);
						t.animation.addClip(c.clip);

						for(var p in data.transforms[cn].properties) {
							c.clip.addProperty(p, data.transforms[cn].properties[p]);
						}
					}

					if(options.autoPlay) t.animation.play();
				};
			}, true);

			return {
				root: root, camera: camera, buffers: bufferByName
			};
		}

	}

})();