-
Notifications
You must be signed in to change notification settings - Fork 140
/
Copy pathfungi.gltf.js
332 lines (263 loc) · 11.7 KB
/
fungi.gltf.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/*NOTES
- Each primitive corresponds to one draw call
- Min/Max can be used to create the bound box.
- When a primitive's indices property is defined, it references the accessor to use for index data, and GL's drawElements function should be used. When the indices property is not defined, GL's drawArrays function should be used with a count equal to the count property of any of the accessors referenced by the attributes property (they are all equal for a given primitive).
- JavaScript client implementations should convert JSON-parsed floating-point doubles to single precision, when componentType is 5126 (FLOAT). This could be done with Math.fround function.
- The offset of an accessor into a bufferView (i.e., accessor.byteOffset) and the offset of an accessor into a buffer (i.e., accessor.byteOffset + bufferView.byteOffset) must be a multiple of the size of the accessor's component type.
- byteStride must be defined, when two or more accessors use the same bufferView.
- Each accessor must fit its bufferView, i.e., accessor.byteOffset + STRIDE * (accessor.count - 1) + SIZE_OF_ELEMENT must be less than or equal to bufferView.length.
*/
//https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0
//https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
//https://github.com/aframevr/aframe/blob/master/docs/components/vive-controls.md
//https://raw.githubusercontent.com/javagl/JglTF/master/images/gltfOverview-0.2.0.png
//https://github.com/KhronosGroup/glTF-Blender-Exporter/issues/39
class GLTFLoader{
constructor(){
this.json = null;
this.skeletons = []; //
this.meshes = []; // VAOs
this.nodes = []; // Renderable, references back to mesh
}
get version(){ return this.json.asset.version; }
loadFromDom(elmID,processNow){
//TODO: Validation of element, text and json parsing.
var txt = document.getElementById(elmID).text;
this.json = JSON.parse(txt);
if(processNow == true) this.processScene();
return this;
}
load(jsObj,processNow){
this.json = jsObj;
//.....................................
//Go through Skins and make all nodes as joints for later processing.
//Joint data never exports well, there is usually garbage. Documentation
//Suggests that developer pre process nodes to make them as joints and
//it does help weed out bad data
if(this.json.skins){
var j, //loop index
s = this.json.skins, //alias for skins
complete = []; //list of skeleton root nodes, prevent prcessing duplicate data that can exist in file
for(var i=0; i < s.length; i++){
if( complete.indexOf(s[i].skeleton) != -1) continue; //If already processed, go to next skin
//Loop through all specified joints and mark the nodes as joints.
for(j in s[i].joints) this.json.nodes[ s[i].joints[j] ].isJoint = true;
complete.push(s[i].skeleton); //push root node index to complete list.
}
}
//.....................................
if(processNow == true) this.processScene();
return this;
}
processScene(sceneNum){
//TODO process skin first to mark nodes as joints since spec does not
//https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins
if(sceneNum == undefined) sceneNum = 0; //If not specify, get first scene
if(this.json.scenes[sceneNum].nodes.length == 0) return;
var sceneNodes = this.json.scenes[sceneNum].nodes,
nStack = [],
node,
idx,
i;
//Setup Initial Stack
for(i=0; i < sceneNodes.length; i++) nStack.push( sceneNodes[i] );
//Process Stack of nodes, check for children to add to stack
while(nStack.length > 0){
idx = nStack.pop();
node = this.json.nodes[idx];
//Add More Nodes to the stack
if(node.children != undefined)
for(i=0; i < node.children.length; i++) nStack.push(node.children[i]);
this.processNode( idx );
}
}
//
processNode(idx){
var n = this.json.nodes[idx];
//n.children = [nodeIndex,nodeIndex,etc]
//n.skin = Defines skeleton
//n.weights
//TODO - Need to handle Node Heirarchy
//if there is n.camera, its a camera.
//if there is no camera or mesh, then its an empty that may get a mesh node as a child.
//Handle Mesh
if(n.mesh != undefined){
var m = {
name: (n.name)? n.name : "untitled",
rotate: n.rotation || null,
scale: n.scale || null,
position: n.translation || null,
matrix: n.matrix || null,
meshes: this.processMesh(n.mesh)
};
if(n.skin != undefined) m.skeleton = this.processSkin(n.skin);
this.nodes.push(m);
}
}
//TODO Make sure not to process the same mesh twice incase different nodes reference same mesh data.
processMesh(idx){
var m = this.json.meshes[idx];
var meshName = m.name || "unnamed"
//m.weights = for morph targets
//m.name
//p.attributes.TANGENT = vec4
//p.attributes.TEXCOORD_1 = vec2
//p.attributes.COLOR_0 = vec3 or vec4
//p.material
//p.targets = Morph Targets
//.....................................
var p, //Alias for primative element
a, //Alias for primative's attributes
itm,
mesh = [];
for(var i=0; i < m.primitives.length; i++){
p = m.primitives[i];
a = p.attributes;
itm = {
name: meshName + "_p" + i,
mode: (p.mode != undefined)? p.mode : GLTFLoader.MODE_TRIANGLES,
indices: null, //p.indices
vertices: null, //p.attributes.POSITION = vec3
normals: null, //p.attributes.NORMAL = vec3
texcoord: null, //p.attributes.TEXCOORD_0 = vec2
joints: null, //p.attributes.JOINTS_0 = vec4
weights: null //p.attributes.WEIGHTS_0 = vec4
};
//Get Raw Data
itm.vertices = this.processAccessor(a.POSITION);
if(p.indices != undefined) itm.indices = this.processAccessor(p.indices);
if(a.NORMAL != undefined) itm.normals = this.processAccessor(a.NORMAL);
if(a.WEIGHTS_0 != undefined) itm.weights = this.processAccessor(a.WEIGHTS_0);
if(a.JOINTS_0 != undefined) itm.joints = this.processAccessor(a.JOINTS_0);
//Save Data
this.meshes.push(itm); //Each Primitive is its own draw call, so its really just a mesh
mesh.push(this.meshes.length-1); //Save index to new mesh so nodes can reference the mesh
}
return mesh;
}
//Decodes the binary buffer data into a Type Array that is webgl friendly.
processAccessor(idx){
var a = this.json.accessors[idx], //Accessor Alias Ref
bView = this.json.bufferViews[ a.bufferView ], //bufferView Ref
buf = this.prepareBuffer(bView.buffer), //Buffer Data decodes into a ArrayBuffer/DataView
bOffset = (a.byteOffset || 0) + (bView.byteOffset || 0), //Starting point for reading.
bLen = 0,//a.count,//bView.byteLength, //Byte Length for this Accessor
TAry = null, //Type Array Ref
DFunc = null; //DateView Function name
//Figure out which Type Array we need to save the data in
switch(a.componentType){
case GLTFLoader.TYPE_FLOAT: TAry = Float32Array; DFunc = "getFloat32"; break;
case GLTFLoader.TYPE_SHORT: TAry = Int16Array; DFunc = "getInt16"; break;
case GLTFLoader.TYPE_UNSIGNED_SHORT: TAry = Uint16Array; DFunc = "getUint16"; break;
case GLTFLoader.TYPE_UNSIGNED_INT: TAry = Uint32Array; DFunc = "getUint32"; break;
case GLTFLoader.TYPE_UNSIGNED_BYTE: TAry = Uint8Array; DFunc = "getUint8"; break;
default: console.log("ERROR processAccessor","componentType unknown",a.componentType); return null; break;
}
//When more then one accessor shares a buffer, The BufferView length is the whole section
//but that won't work, so you need to calc the partition size of that whole chunk of data
//The math in the spec about stride doesn't seem to work, it goes over bounds, what Im using works.
//https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
if(bView.byteStride != undefined) bLen = bView.byteStride * a.count;
else bLen = a.count * GLTFLoader["COMP_"+a.type] * TAry.BYTES_PER_ELEMENT; //elmCnt * compCnt * compByteSize)
//Pull the data out of the dataView based on the Type.
var bPer = TAry.BYTES_PER_ELEMENT, //How many Bytes needed to make a single element
aLen = bLen / bPer, //Final Array Length
ary = new TAry(aLen), //Final Array
p = 0; //Starting position in DataView
for(var i=0; i < aLen; i++){
p = bOffset + i * bPer;
ary[i] = buf.dView[DFunc](p,true);
}
//console.log(a.type,GLTFLoader["COMP_"+a.type],"offset",bOffset, "bLen",bLen, "aLen", aLen, ary);
return { data:ary, max:a.max, min:a.min, count:a.count, compLen:GLTFLoader["COMP_"+a.type] };
}
//Get the buffer data ready to be parsed threw by the Accessor
prepareBuffer(idx){
var buf = this.json.buffers[idx];
if(buf.dView != undefined) return buf;
if(buf.uri.substr(0,5) != "data:"){
//TODO Get Bin File
return buf;
}
//Create and Fill DataView with buffer data
var pos = buf.uri.indexOf("base64,") + 7,
blob = window.atob(buf.uri.substr(pos)),
dv = new DataView( new ArrayBuffer(blob.length) );
for(var i=0; i < blob.length; i++) dv.setUint8(i,blob.charCodeAt(i));
buf.dView = dv;
//console.log("buffer len",buf.byteLength,dv.byteLength);
//var fAry = new Float32Array(blob.length/4);
//for(var j=0; j < fAry.length; j++) fAry[j] = dv.getFloat32(j*4,true);
//console.log(fAry);
return buf;
}
processSkin(idx){
//Check if the skin has already processed skeleton info
var i,s = this.json.skins[idx]; //skin reference
for(i=0; i < this.skeletons.length; i++){
if(this.skeletons[i].nodeIdx == s.skeleton) return i; //Find a root bone that matches the skin's.
}
console.log("ProcessSkin",idx, s.skeleton, this.skeletons.length);
//skeleton not processed, do it now.
var stack = [], //Queue
final = [], //Flat array of joints for skeleton
n, //Node reference
itm, //popped queue tiem
pIdx; //parent index
if(s.joints.indexOf(s.skeleton) != -1){
stack.push([s.skeleton,null]); //Add Root bone Node Index, final index ofParent
}else{
var cAry = this.json.nodes[s.skeleton].children;
for(var c=0; c < cAry.length; c++){
stack.push([cAry[c],null]);
}
}
while(stack.length > 0){
itm = stack.pop(); //Pop off the list
n = this.json.nodes[itm[0]]; //Get node info for joint
if(n.isJoint != true) continue; //Check preprocessing to make sure its actually a used node.
//Save copy of data : Ques? Are bones's joint number always in a linear fashion where parents have
//a lower index then the children;
final.push({
jointNum : s.joints.indexOf(itm[0]),
name : n.name || null,
position : n.translation || null,
scale : n.scale || null,
rotation : n.rotation || null,
matrix : n.matrix || null,
parent : itm[1],
nodeIdx : itm[0]
});
//Save the the final index for this joint for children reference
pIdx = final.length - 1;
//Add children to stack
if(n.children != undefined){
for(i=0; i < n.children.length; i++) stack.push([n.children[i],pIdx]);
}
}
final.nodeIdx = s.skeleton; //Save root node index to make sure we dont process the same skeleton twice.
this.skeletons.push(final);
return this.skeletons.length - 1;
}
}
//CONSTANTS
GLTFLoader.MODE_POINTS = 0; //Mode Constants for GLTF and WebGL are identical
GLTFLoader.MODE_LINES = 1; //https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Constants
GLTFLoader.MODE_LINE_LOOP = 2;
GLTFLoader.MODE_LINE_STRIP = 3;
GLTFLoader.MODE_TRIANGLES = 4;
GLTFLoader.MODE_TRIANGLE_STRIP = 5;
GLTFLoader.MODE_TRIANGLE_FAN = 6;
GLTFLoader.TYPE_BYTE = 5120;
GLTFLoader.TYPE_UNSIGNED_BYTE = 5121;
GLTFLoader.TYPE_SHORT = 5122;
GLTFLoader.TYPE_UNSIGNED_SHORT = 5123;
GLTFLoader.TYPE_UNSIGNED_INT = 5125;
GLTFLoader.TYPE_FLOAT = 5126;
GLTFLoader.COMP_SCALAR = 1;
GLTFLoader.COMP_VEC2 = 2;
GLTFLoader.COMP_VEC3 = 3;
GLTFLoader.COMP_VEC4 = 4;
GLTFLoader.COMP_MAT2 = 4;
GLTFLoader.COMP_MAT3 = 9;
GLTFLoader.COMP_MAT4 = 16;