Skip to content

Instantly share code, notes, and snippets.

@sketchpunk
Created May 16, 2020 03:18
Show Gist options
  • Select an option

  • Save sketchpunk/fab42db9d955915cb4a63d25be45d0cf to your computer and use it in GitHub Desktop.

Select an option

Save sketchpunk/fab42db9d955915cb4a63d25be45d0cf to your computer and use it in GitHub Desktop.
GLTF Geometry Loading
class Gltf{
static TYPE_BYTE = 5120;
static TYPE_UNSIGNED_BYTE = 5121;
static TYPE_SHORT = 5122;
static TYPE_UNSIGNED_SHORT = 5123;
static TYPE_UNSIGNED_INT = 5125;
static TYPE_FLOAT = 5126;
static MODE_POINTS = 0; // Mode Constants for GLTF and WebGL are identical
static MODE_LINES = 1; // https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Constants
static MODE_LINE_LOOP = 2;
static MODE_LINE_STRIP = 3;
static MODE_TRIANGLES = 4;
static MODE_TRIANGLE_STRIP = 5;
static MODE_TRIANGLE_FAN = 6;
static COMP_SCALAR = 1; // Component Length based on Type
static COMP_VEC2 = 2;
static COMP_VEC3 = 3;
static COMP_VEC4 = 4;
static COMP_MAT2 = 4;
static COMP_MAT3 = 9;
static COMP_MAT4 = 16;
static TARGET_ARY_BUF = 34962; // bufferview.target
static TARGET_ELM_ARY_BUF = 34963;
static parse_accessor( idx, json, bin, spec_only = false ){
let acc = json.accessors[ idx ], // Reference to Accessor JSON Element
bView = json.bufferViews[ acc.bufferView ], // Buffer Information
compLen = Gltf[ "COMP_" + acc.type ], // Component Length for Data Element
ary = null, // Final Type array that will be filled with data
byteStart = 0,
byteLen = 0,
TAry, // Reference to Type Array to create
DFunc; // Reference to GET function in Type Array
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Figure out which Type Array we need to save the data in
switch( acc.componentType ){
case Gltf.TYPE_FLOAT: TAry = Float32Array; DFunc = "getFloat32"; break;
case Gltf.TYPE_SHORT: TAry = Int16Array; DFunc = "getInt16"; break;
case Gltf.TYPE_UNSIGNED_SHORT: TAry = Uint16Array; DFunc = "getUint16"; break;
case Gltf.TYPE_UNSIGNED_INT: TAry = Uint32Array; DFunc = "getUint32"; break;
case Gltf.TYPE_UNSIGNED_BYTE: TAry = Uint8Array; DFunc = "getUint8"; break;
default: console.log("ERROR processAccessor","componentType unknown",a.componentType); return null; break;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let out = {
min : acc.min,
max : acc.max,
elm_cnt : acc.count,
comp_len : compLen
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Data is Interleaved
if( bView.byteStride ){
if( spec_only ) console.error( "GLTF STRIDE SPEC ONLY OPTION NEEDS TO BE IMPLEMENTED ");
/*
The RiggedSimple sample seems to be using stride the wrong way. The data all works out
but Weight and Joint indicate stride length BUT the data is not Interleaved in the buffer.
They both exists in their own individual block of data just like non-Interleaved data.
In the sample, Vertices and Normals ARE actually Interleaved. This make it a bit
difficult to parse when dealing with interlanced data with WebGL Buffers.
TODO: Can prob check if not interlanced by seeing if the Stride Length equals the length
of the data in question.
For example related to the RiggedSimple sample.
Stride Length == FloatByteLength(4) * Accessor.type's ComponentLength(Vec3||Vec4)
-- So if Stride is 16 Bytes
-- The data is grouped as Vec4 ( 4 Floats )
-- And Each Float = 4 bytes.
-- Then Stride 16 Bytes == Vec4 ( 4f loats * 4 Bytes )
-- So the stride length equals the data we're looking for, So the BufferView in question
IS NOT Interleaved.
By the looks of things. If the Accessor.bufferView number is shared between BufferViews
then there is a good chance its really Interleaved. Its ashame that things can be designed
to be more straight forward when it comes to Interleaved and Non-Interleaved data.
*/
// console.log("BView", bView );
// console.log("Accessor", acc );
let stride = bView.byteStride, // Stride Length in bytes
elmCnt = acc.count, // How many stride elements exist.
bOffset = (bView.byteOffset || 0), // Buffer Offset
sOffset = (acc.byteOffset || 0), // Stride Offset
bPer = TAry.BYTES_PER_ELEMENT, // How many bytes to make one value of the data type
aryLen = elmCnt * compLen, // How many "floats/ints" need for this array
dView = new DataView( bin ), // Access to Binary Array Buffer
p = 0, // Position Index of Byte Array
j = 0, // Loop Component Length ( Like a Vec3 at a time )
k = 0; // Position Index of new Type Array
ary = new TAry( aryLen ); //Final Array
//Loop for each element of by stride
for(var i=0; i < elmCnt; i++){
// Buffer Offset + (Total Stride * Element Index) + Sub Offset within Stride Component
p = bOffset + ( stride * i ) + sOffset; //Calc Starting position for the stride of data
//Then loop by compLen to grab stuff out of the DataView and into the Typed Array
for(j=0; j < compLen; j++) ary[ k++ ] = dView[ DFunc ]( p + (j * bPer) , true );
}
out.data = ary;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Data is NOT Interleaved
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
// TArray example from documentation works pretty well for data that is not interleaved.
}else{
if( spec_only ){
out.array_type = TAry.name.substring( 0, TAry.name.length - 5 );
out.byte_start = ( acc.byteOffset || 0 ) + ( bView.byteOffset || 0 );
out.byte_cnt = acc.count * compLen * TAry.BYTES_PER_ELEMENT;
//console.log( bin );
}else{
let bOffset = ( acc.byteOffset || 0 ) + ( bView.byteOffset || 0 );
out.data = new TAry( bin, bOffset, acc.count * compLen ); // ElementCount * ComponentLength
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
return out;
}
static get_mesh( name, json, bin, spec_only = false ){
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Find Mesh to parse out.
let i, n = null, mesh_idx = null;
for( i of json.nodes ){
if( i.name === name && i.mesh != undefined ){ n = i; mesh_idx = n.mesh; break; }
}
//No node Found, Try looking in mesh array for the name.
if( !n ){
for( i=0; i < json.meshes.length; i++ ) if( json.meshes[i].name == name ){ mesh_idx = i; break; }
}
if( mesh_idx == null ){
console.error( "Node or Mesh by the name", name, "not found in GLTF" );
return null;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Loop through all the primatives that make up a single mesh
let m = json.meshes[ mesh_idx ],
pLen = m.primitives.length,
ary = new Array( pLen ),
itm,
prim,
attr;
for( let i=0; i < pLen; i++ ){
//.......................................
// Setup some vars
prim = m.primitives[ i ];
attr = prim.attributes; // console.log( attr );
itm = {
name : name + (( pLen != 1 )? "_p" + i : ""),
mode : ( prim.mode != undefined )? prim.mode : Gltf.MODE_TRIANGLES
};
//.......................................
// Save Position, Rotation and Scale if Available.
if( n ){
if( n.translation ) itm.position = n.translation.slice( 0 );
if( n.rotation ) itm.rotation = n.rotation.slice( 0 );
if( n.scale ) itm.scale = n.scale.slice( 0 );
}
//.......................................
// Parse out all the raw Geometry Data from the Bin file
itm.vertices = Gltf.parse_accessor( attr.POSITION, json, bin, spec_only );
if( prim.indices != undefined ) itm.indices = Gltf.parse_accessor( prim.indices, json, bin, spec_only );
if( attr.NORMAL != undefined ) itm.normal = Gltf.parse_accessor( attr.NORMAL, json, bin, spec_only );
if( attr.TEXCOORD_0 != undefined ) itm.uv = Gltf.parse_accessor( attr.TEXCOORD_0, json, bin, spec_only );
if( attr.WEIGHTS_0 != undefined ) itm.weights = Gltf.parse_accessor( attr.WEIGHTS_0, json, bin, spec_only );
if( attr.JOINTS_0 != undefined ) itm.joints = Gltf.parse_accessor( attr.JOINTS_0, json, bin, spec_only );
if( attr.COLOR_0 != undefined ) itm.color = Gltf.parse_accessor( attr.COLOR_0, json, bin, spec_only );
//.......................................
// Save to return array
ary[ i ] = itm;
}
return ary;
}
static prim_to_geo( g ){
let geo = new THREE.BufferGeometry();
geo.setAttribute( "position", new THREE.BufferAttribute( g.vertices.data, g.vertices.comp_len ) );
if( g.indices ) geo.setIndex( new THREE.BufferAttribute( g.indices.data, 1 ) );
if( g.normal ) geo.setAttribute( "normal", new THREE.BufferAttribute( g.normal.data, g.normal.comp_len ) );
if( g.uv ) geo.setAttribute( "uv", new THREE.BufferAttribute( g.uv.data, g.uv.comp_len ) );
if( g.color ) geo.setAttribute( "color", new THREE.BufferAttribute( g.color.data, g.color.comp_len ) );
return geo;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment