myGLTF = loadJSON("https://inaridarkfox4231.github.io/resources/testSkinMesh_0.gltf");
createCanvas(640, 640, WEBGL);
const m0 = new p5.Matrix([1,1,1,1, 0,1,1,1, 0,0,1,1, 0,0,0,1]);
const m1 = new p5.Matrix([1,0,0,0, 1,1,0,0, 1,1,1,0, 1,1,1,1]);
const m2 = m0.copy().mult(m1);
const m3 = m1.copy().mult(m0);
nodes, arrayBuffers, objs, animations, skins
FRAME_NUM = animations[0].transform.frameNum;
const tfData = animations[0].transform.data;
deltaMatricesArray = new Array(FRAME_NUM);
jointMatricesArray = new Array(FRAME_NUM);
for(let i=0; i<FRAME_NUM; i++){
deltaMatricesArray[i] = [];
jointMatricesArray[i] = [];
for(let i=0; i<BONE_NUM; i++){
const nd = nodes[bone.node];
const tf = tfData[bone.node];
for(let k=0; k<FRAME_NUM;k++){
_t.push(...(nd.translation !== undefined ? nd.translation : [0,0,0]));
for(let k=0; k<FRAME_NUM;k++){
_r.push(...(nd.rotation !== undefined ? nd.rotation : [0,0,0,1]));
for(let k=0; k<FRAME_NUM;k++){
_s.push(...(nd.scale !== undefined ? nd.scale : [1,1,1]));
for(let k=0; k<FRAME_NUM; k++){
deltaMatricesArray[k].push(createTransform({
t:_t.slice(k*3,(k+1)*3), r:_r.slice(k*4,(k+1)*4), s:_s.slice(k*3,(k+1)*3)
const jointMap = new Array(nodes.length);
for(let i=0; i<BONE_NUM; i++){
jointMap[bones[i].node] = i;
for(let i=0; i<BONE_NUM; i++){
const nd = nodes[bone.node];
for(let k=0; k<FRAME_NUM; k++){
const deltaMatrices = deltaMatricesArray[k];
const m = ibm.copy().apply(deltaMatrices[i]);
while(currentNode.parent !== undefined){
const nextNode = nodes[currentNode.parent];
const jointIndex = jointMap[currentNode.parent];
if(jointIndex === undefined) break;
m.apply(deltaMatrices[jointIndex]);
jointMatricesArray[k].push(...m.mat4);
const _gl = this._renderer;
_gl.retainedMode.buffers.fill.push(new p5.RenderBuffer(4, "joints", "jointsdst", "aJoint", _gl));
_gl.retainedMode.buffers.fill.push(new p5.RenderBuffer(4, "weights", "weightsdst", "aWeight", _gl));
const mesh = objs[0].primitives[0];
const positionData = mesh.POSITION.data;
const normalData = mesh.NORMAL.data;
const faceData = mesh.indices.data;
const jointData = mesh.JOINTS_0.data;
const weightData = mesh.WEIGHTS_0.data;
geom = new p5.Geometry();
for(let i=0; i<positionData.length/3; i++){
geom.vertices.push(createVector(...positionData.slice(3*i, 3*i+3)));
geom.vertexNormals.push(createVector(...normalData.slice(3*i, 3*i+3)));
geom.joints.push(...jointData.slice(4*i, 4*i+4));
geom.weights.push(...weightData.slice(4*i, 4*i+4));
for(let k=0; k<faceData.length/3; k++){
geom.faces.push(faceData.slice(3*k, 3*(k+1)));
camera(12,12,16, 0,0,0, 0,1,0);
const eyeDist = dist(12,12,16, 0,0,0);
const nearPlaneHeightHalf = eyeDist*tan(PI/6)*0.1;
const nearPlaneWidthHalf = nearPlaneHeightHalf*width/height;
frustum(-nearPlaneWidthHalf, nearPlaneWidthHalf, nearPlaneHeightHalf, -nearPlaneHeightHalf, 0.1*eyeDist, 10*eyeDist);
const sh = baseMaterialShader();
uniform mat4 uJointMatrices[${BONE_NUM}];
skinMatrix = aWeight.x * uJointMatrices[int(aJoint.x)];
skinMatrix += aWeight.y * uJointMatrices[int(aJoint.y)];
skinMatrix += aWeight.z * uJointMatrices[int(aJoint.z)];
skinMatrix += aWeight.w * uJointMatrices[int(aJoint.w)];
'vec3 getLocalPosition': `(vec3 position) {
return (vec4(position, 1.0) * skinMatrix).xyz;
'vec3 getLocalNormal': `(vec3 normal){
return (vec4(normal, 0.0) * inverse(transpose(skinMatrix))).xyz;
const phaseIndex = (Math.floor(frameCount*0.5))%FRAME_NUM;
myShader.setUniform("uJointMatrices", jointMatricesArray[phaseIndex]);
function parseGLTF(data){
const nodes = setParents(data.nodes);
const arrayBuffers = getArrayBuffers(data);
const objs = getObjData(data, arrayBuffers);
const animations = getAnimationData(data, arrayBuffers);
const skins = getSkinsData(data, arrayBuffers);
return {nodes, arrayBuffers, objs, animations, skins};
function setParents(nodes){
for(let i=0; i<nodes.length; i++){
const eachNode = nodes[i];
if(eachNode.children === undefined)continue;
for(const child of eachNode.children){
function getArrayBuffers(data){
for(let i=0; i<data.buffers.length; i++){
const bin = data.buffers[i].uri.split(',')[1];
arrayBuffers.push(getArrayBuffer(bin));
function getObjData(data, buf){
const {meshes, accessors, bufferViews} = data;
for (const mesh of meshes) {
resultMesh.name = mesh.name;
resultMesh.primitives = [];
for (const prim of mesh.primitives) {
const attrs = prim.attributes;
for (const attrName of Object.keys(attrs)) {
const attrAccessor = accessors[attrs[attrName]];
const attrArrayData = getArrayData(
buf, attrAccessor, bufferViews[attrAccessor.bufferView]
data:attrArrayData, info:getInfo(attrAccessor)
const indexAccessor = data.accessors[prim.indices];
const indexArrayData = getArrayData(
buf, indexAccessor, bufferViews[indexAccessor.bufferView]
data:indexArrayData, info:getInfo(indexAccessor)
resultMesh.primitives.push(primitive);
function getAnimationData(data, buf){
const {animations, accessors, bufferViews} = data;
for (const animation of animations) {
const resultAnimation = {};
resultAnimation.useWeight = false;
resultAnimation.name = animation.name;
resultAnimation.data = [];
for(let k=0; k<animation.samplers.length; k++){
const sampler = animation.samplers[k];
const channel = animation.channels[k];
const resultSampler = {};
resultSampler.interpolation = sampler.interpolation;
const inputAccessor = accessors[sampler.input];
const outputAccessor = accessors[sampler.output];
const inputArrayData = getArrayData(
buf, inputAccessor, bufferViews[inputAccessor.bufferView]
const outputArrayData = getArrayData(
buf, outputAccessor, bufferViews[outputAccessor.bufferView]
data:inputArrayData, info:getInfo(inputAccessor)
data:outputArrayData, info:getInfo(outputAccessor)
resultData.sampler = resultSampler;
resultData.channel = channel;
resultData.node = channel.target.node;
resultData.path = channel.target.path;
if(resultData.path === "weight"){
resultAnimation.useWeight = true;
resultAnimation.data.push(resultData);
if(!resultAnimation.useWeight){
const transformData = [];
let maxFrame = -Infinity;
for(const data of resultAnimation.data){
if(transformData[data.node] === undefined){
transformData[data.node] = {scale:[], rotation:[], translation:[]};
maxFrame = Math.max(maxFrame, Math.round(24*data.sampler.input.info.max[0]));
for(const data of resultAnimation.data){
const normalizedArray = [];
const inputArray = data.sampler.input.data;
const outputArray = data.sampler.output.data;
case "scale": dataSize = 3; break;
case "rotation": dataSize = 4; break;
case "translation": dataSize = 3; break;
const minIndex = Math.round(24*inputArray[0]);
const maxIndex = Math.round(24*inputArray[inputArray.length-1]);
for(let i=0; i<=maxFrame; i++){
normalizedArray.push(...outputArray.slice(0, dataSize));
normalizedArray.push(...outputArray.slice(-dataSize, outputArray.length));
const properIndex = Math.floor(((i-minIndex)/(maxIndex-minIndex))*(inputArray.length-1));
normalizedArray.push(...outputArray.slice(properIndex*dataSize, (properIndex+1)*dataSize));
transformData[data.node][data.path] = normalizedArray;
resultAnimation.transform = {
data:transformData, frameNum: maxFrame+1
result.push(resultAnimation);
function getSkinsData(data, buf){
const {skins, accessors, bufferViews} = data;
for(const skin of skins){
const skinAccessor = accessors[skin.inverseBindMatrices];
const skinArrayData = getArrayData(
buf, skinAccessor, bufferViews[skinAccessor.bufferView]
for(let i=0; i<skin.joints.length; i++){
const m = skinArrayData.slice(i*16, (i+1)*16);
const ibm = new p5.Matrix([
m[2], m[6], m[10], m[14],
function getArrayBuffer(bin){
const byteString = atob(bin);
const byteStringLength = byteString.length;
const arrayBuffer = new ArrayBuffer(byteStringLength);
const intArray = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteStringLength; i++) {
intArray[i] = byteString.charCodeAt(i);
function getArrayData(buf, accessor, bufferView, arrayOutput = true){
const componentType = accessor.componentType;
case 5126: _size = 4; break;
case 5123: _size = 2; break;
case 5121: _size = 1; break;
const byteOffset = bufferView.byteOffset;
const byteLength = bufferView.byteLength;
const arrayLength = byteLength / _size;
const resultArray = createArrayData(buf[bufferView.buffer], componentType, byteOffset, arrayLength);
const outputArray = new Array(resultArray.length);
for(let i=0; i<outputArray.length; i++){
outputArray[i] = resultArray[i];
function createArrayData(buf, componentType, byteOffset, arrayLength) {
const f32Array = new Float32Array(buf, byteOffset, arrayLength);
const i16Array = new Uint16Array(buf, byteOffset, arrayLength);
const u8Array = new Uint8Array(buf, byteOffset, arrayLength);
function getComponentType(code){
case 5126: return "float32";
case 5123: return "uint16";
case 5121: return "uint8";
function getInfo(accessor){
componentType:getComponentType(accessor.componentType)
if (accessor.max !== undefined) { result.max = accessor.max; }
if (accessor.min !== undefined) { result.min = accessor.min; }
function createTransform(tf={}){
const s = createMatFromScale(tf.s);
const r = createMatFromRotation(tf.r);
const t = createMatFromTranslate(tf.t);
return s.apply(r).apply(t);
function createMatFromScale(q){
if(typeof q === 'number'){
}else if(Array.isArray(q)){
function createMatFromRotation(q){
if(typeof q === 'number'){
}else if(Array.isArray(q)){
w*w+x*x-y*y-z*z, 2.0*(x*y-z*w), 2.0*(x*z+y*w), 0.0,
2.0*(x*y+z*w), w*w+y*y-x*x-z*z, 2.0*(y*z-x*w), 0.0,
2.0*(x*z-y*w), 2.0*(y*z+x*w), w*w+z*z-x*x-y*y, 0.0,
function createMatFromTranslate(q){
if(typeof q === 'number'){
}else if(Array.isArray(q)){