xxxxxxxxxx
/*
gltfで頂点色、というかモデル構築
とりあえずモデルくらいは作りたいので
クォータニオンは....??
weightアニメーションに特化した内容で
ローダー作るのもいいかも?
つくづく思うけど
インスタンシングへの拡張何でこんな楽ちんなの
おしえておじいさん
おしえて~~~~
アルムの(以下略)
冗談は置いといて
どこまでメソッド化するかがかなり揉めそう
vとnのデータをshapeごとに取得するだけでも面の情報とか要るし割と大変なのよね
立方体とかならマージ要らんしそこら辺もな~
めんどくさ~~
まず条件として単一オブジェクト、アニメーションはシェイプキー、
そんなところね
VCとかね、これも存在前提で書いてるから無い場合は...んーまあ
たとえばだけど
Blenderサイドで独自にジオメトリー作ってみて
シェイプキーアニメも作ってみて
VCも付けてみて
それ動かせるかどうかで判断しましょ
あとunion-findで実績を上げたので
fisceToyBoxもあれしようかな...
rgba32fのtextureでやったんですけど
これalphaが乗算されてますね...
wを1にしたら通った
こっちとしてはx,y,z,wが入るようにしたいんだけど
なぜかxw,yw,zwみたいになっちゃってる(wはそのままみたい)
防ぐには...???
分かりませんでした
なのでとりあえずこのケースの場合RGBでいいんで
RGBフォーマットで対処しました
RGBAでもできるようにしたいんですけどね...今後の課題ですね。
たとえば4つの値をばらばらのfloat値にしたり、そういうことはできないわけ。
調べたら色でも同じ現象が生じるようです。めんどくさい...
fboに点描画で落とす場合は問題なく動作します(non-blend)。
たとえば邪道ですがwを1にしてRGBだけ落としてwだけあとで、
とかできるんだろうけどあほらしいからな。
原因分かったかも
p5.jsが
https://github.com/processing/p5.js/blob/2e112a4b3842489f8d016f590f26e568b2a98528/src/webgl/p5.RendererGL.js#L797
gl.UNPACK_PREMULTIPLY_ALPHA_WEBGLのデフォルトを
trueにしてるんよ
それが原因っぽい
これp5のレンダラーを流用してるから
そういうことみたいです
...
普通に困るんだが...まあp5はfloatTexture想定してないしな。仕方ないな。どこで使う?
これデフォルトはfalseなのよ。
daveのくそったれが
https://github.com/processing/p5.js/pull/6072/files#diff-1cce16cd40ae5c1b4197276831fbee402c44f3023dc40454941e11f3f337b8de
のときにこれをfalseからtrueに変えたらしい。くそったれ...くそったれが...
しかしどうするかね
fboから書き込む場合はこれの影響を受けないんよ。
あくまでテクスチャ格納時だけだから。これが悪さするのは。
そして今までテクスチャ格納時は基本的にalphaが1の画像しか使ってこなかったという
経緯がある
そうだよね?
あー...
もしfalseの場合、取得後にaをrgbに掛ける必要がある。具体的には透過画像。
後はまあこれ見てもわかるようにフォーマットがrgbaでなければ影響ゼロ
んー...
影響がテクスチャ格納時だけということですから、
texImage2Dの実行時のオプションとして用意すればいいのでは?となる。か?
格納時だけでなく更新時もだけど。
そうみたいです
ああー...
texture生成時にプロパティとしてmultiplyAlpha:true/falseしましょ
個別にしたい場合はmultiplyAlpha:falseで対応する
んでtexImage2Dの実行時に適用する
デフォルトは一応trueにするけど。
まあ透過画像のrgbやaがどうなってるかわかんないし
もしもともとrgbが調整されてる場合、aを掛けちゃうとまずいだろう。
そこら辺どうなんだろうね?
参考例1:https://openprocessing.org/sketch/1499154(双曲タイリング)
このときは透過画像に対してデフォルトのp5の処理を実行していますね
つまり透過画像そのものについてはalphaはrgbに掛けられていないみたいです
というかおそらくですけど2Dでそういうことをしているのかもしれないです
だから合わせる形にしたと
そう考えれば妥当性はあるわけですね
参考例2:https://openprocessing.org/sketch/2086015
(ビデオにrenderTextureで透過画像を落とす)
p5の枠組みから外れて実行していますね
つまり事前乗算無し
格納時にrgbにalphaが乗算されないわけ
それで今してるような「"blend"」をやると
事前乗算が前提として無いから描画結果が変になる
具体的には境界が
それでダイレクトブレンドしていますね
内部でrgbにalphaを掛けるなら問題ないと。んー...
しかもこの時の自分は事前乗算がないことに不満を持ってますね...まあ当然か。
選べるようにしよう
"blend"はもうそれ前提で作っちゃってあるから今更デフォルトは変えられない
でもfloat textureの格納時にそれやられちゃうと困るんだよ
だからプロパティをいじれるようにすればいい
どうせレアケースだから問題ない
*/
const ANIMAL_NAME = "Flamingo"; // Parrot, Horse, Stork.
function preload(){
// gltfで問題ないです
testGLTF = loadJSON(`https://inaridarkfox4231.github.io/resources/${ANIMAL_NAME}.gltf`);
}
let _node, LS;
const _timer = new p5wgex.Timer();
let VERT_NUM;
let FRAME_NUM;
function setup() {
createCanvas(window.innerWidth, window.innerHeight, WEBGL);
const result = analyzeGLTF(testGLTF);
//console.log(result.nodes);
//console.log(result.obj);
//console.log(result.animation);
// result.obj[0].primitives[0].(POSITION,NORMAL)
_node = new p5wgex.RenderNode(this._renderer.GL);
const mu = p5wgex.meshUtil;
const g = mu.create();
// 仕様変えたんでもう普通にsliceでOKっす
g.v = result.obj[0].primitives[0].POSITION.data.slice();
g.n = result.obj[0].primitives[0].NORMAL.data.slice();
g.f = result.obj[0].primitives[0].indices.data.slice();
g.vc = result.obj[0].primitives[0].COLOR_0.data.slice();
for(let i=0; i<g.vc.length; i++){
g.vc[i] /= 65535;
}
/*
mu.regist(_node, g, "flamingo",{otherAttrs:[{
size:4, data:g.vc, name:"aColor"
}]});
*/
// これでunion-findして親を使ってまとめればいい
const ufData=[];
for(let i=0;i<g.v.length;i+=3){
for(let k=i+3;k<g.v.length;k+=3){
if(g.v[i]===g.v[k]&&g.v[i+1]===g.v[k+1]&&g.v[i+2]===g.v[k+2]){
ufData.push([Math.floor(i/3),Math.floor(k/3)]);
}
}
}
//console.log(ufData);
// 親の個数が正式なvの個数になるんで
// それでfaceの数もいじればいいわけです
// nもそれを使って取り出せば問題ない
// vcも然り。以上。
// union-find.
const uf = fisceToyBox.getUnionFind(g.v.length/3, ufData);
console.log(`頂点数:${uf.count}`); // 273 vertices
const newV = new Array(uf.count*3);
for(let i=0; i<uf.count; i++){
const repIndex = uf.rep[i];
newV[3*i] = g.v[3*repIndex];
newV[3*i+1] = g.v[3*repIndex+1];
newV[3*i+2] = g.v[3*repIndex+2];
}
g.v = newV.slice();
const newVC = new Array(uf.count*4);
for(let i=0; i<uf.count; i++){
const repIndex = uf.rep[i];
newVC[4*i] = g.vc[4*repIndex];
newVC[4*i+1] = g.vc[4*repIndex+1];
newVC[4*i+2] = g.vc[4*repIndex+2];
newVC[4*i+3] = g.vc[4*repIndex+3];
}
g.vc = newVC.slice();
//console.log(uf.uf);
for(let i=0; i<g.f.length; i++){
g.f[i] = uf.uf[g.f[i]].lv;
}
g.calcNormal();
/*
// 今回はここでの登録はしません
mu.regist(_node, g, "flamingo",{otherAttrs:[{
size:4, data:g.vc, name:"aColor"
}]});
*/
// まず頂点について
// 14種類の位相が存在する
// それらについて頂点をいじるわけだがマージ前なので
// repを使ってrepIndexとし3倍して0,1,2を足してx,y,zを出して加えないといけない
const shapeData = result.obj[0].primitives[0].targetPositionData;
const shapes = []; // 0~14
for(let i=0; i<shapeData.length; i++){
const eachData = shapeData[i];
const shape = [];
for(let k=0; k<uf.count; k++){
const repIndex = uf.rep[k];
shape.push(g.v[3*k] + eachData[3*repIndex]);
shape.push(g.v[3*k+1] + eachData[3*repIndex + 1]);
shape.push(g.v[3*k+2] + eachData[3*repIndex + 2]);
}
shapes.push(shape);
}
// これと内積から各フレームにおけるモデルを生成する
// 実はここでも終わんなくて
// これだとインスタンシングできないので
// vとnのデータをtextureに焼いて
// VTFで参照することで
// 複数のモデルが異なる位相で動けるようにする
// すなわちmu.registに持っていくのではなくて
// 273x34のvec3のfloatTextureを2枚用意する
// ジオメトリーサイドはvcとfだけ流用してインスタンスアトリビュートはオフセットだけ
// とする
const weights = result.animation[0].data[0].weights; // 長さがフレーム数
console.log(`フレーム数:${weights.length}`);
VERT_NUM = uf.count;
FRAME_NUM = weights.length;
// FloatTextureを使う
// RGBフォーマットなので3倍
const vData = new Float32Array(3*VERT_NUM*FRAME_NUM);
const nData = new Float32Array(3*VERT_NUM*FRAME_NUM);
const indicesData = [];
// Flamingoの場合は34です。
for(let i=0; i<FRAME_NUM; i++){
const weight = weights[i];
const geom = mu.create();
//geom.vc = g.vc;
geom.f = g.f;
// 各々のvは
for(let k=0;k<g.v.length;k++)geom.v.push(0);
// weightが0でない場合にshapesの該当するshapeに係数を掛けて足す
for(let l=0; l<weight.length; l++){
if(weight[l] === 0) continue;
const eachWeight = weight[l];
for(let k=0; k<g.v.length; k++){
geom.v[k] += eachWeight * shapes[l][k];
}
}
// 終わったら法線
// geomは法線計算のために使うだけ
geom.calcNormal();
/*
// 今回は作らない!
mu.regist(_node, geom, `flamingo_${i}`, {otherAttrs:[{
name:"aColor",size:4,data:geom.vc
}]});
*/
for(let k=0; k<VERT_NUM; k++){
const offset = 3*VERT_NUM*i + 3*k;
vData[offset] = geom.v[3*k];
vData[offset+1] = geom.v[3*k+1];
vData[offset+2] = geom.v[3*k+2];
nData[offset] = geom.n[3*k];
nData[offset+1] = geom.n[3*k+1];
nData[offset+2] = geom.n[3*k+2];
}
}
// 欲しいのはRGBまでなのでRGBフォーマットでやります。
// RGBAだとalphaが掛け算されちゃうようです(原因究明済み)
_node.registTexture("verticeData", {
src:vData, w:VERT_NUM, h:FRAME_NUM,
internalFormat:"rgb32f", format:"rgb", type:"float",
magFilter:"nearest", minFilter:"nearest"
});
_node.registTexture("normalData", {
src:nData, w:VERT_NUM, h:FRAME_NUM,
internalFormat:"rgb32f", format:"rgb", type:"float",
magFilter:"nearest", minFilter:"nearest"
});
// あれですね
// わざわざvcだけのためのLS作るよりかは
// 上記のflamingoを流用して
// とりあえず現行のflamingoのpとnをいじる感じの方が
// 手っ取り早いですね
// そっすね。そっすね~
const accessor = [];
for(let i=0; i<VERT_NUM; i++)accessor.push((i+0.5)/VERT_NUM,0,0);
const g1 = mu.create();
g1.v = accessor;
g1.n = accessor;
g1.vc = g.vc;
g1.f = g.f;
const frameOffsets = [];
for(let k=0; k<7; k++){
frameOffsets.push(Math.floor(FRAME_NUM*k/7));
}
const offsetLength = 160;
const positionOffsets = [
0,0,0, offsetLength,0,0, -offsetLength,0,0, 0,offsetLength,0, 0,-offsetLength,0,
0,0,offsetLength, 0,0,-offsetLength
];
mu.regist(_node, g1, "flamingo_beta", {otherAttrs:[{
name:"aColor", data:g1.vc, size:4
},{
name:"aOffsetFrame", data:frameOffsets, size:1,divisor:1
},{
name:"aOffsetPosition", data:positionOffsets, size:3,divisor:1
}]});
// LSはインスタンシング前提なので他のことは考えてないです
// そもそもshaderってパイプラインごとやからね(基本ね)
LS = new p5wgex.StandardLightingSystem(_node);
LS.initialize({useColor:true});
LS.addAttr("float", "aOffsetFrame");
LS.addAttr("vec3", "aOffsetPosition");
LS.addUniform("sampler2D", "uVertices","vs");
LS.addUniform("sampler2D", "uNormals","vs");
LS.addUniform("float", "uFrame","vs");
LS.addUniform("float", "uMaxFrame","vs");
LS.addCode(`
// データ入力時に反転させてないので
// こっちでも反転は不要
float uvY = fract((aOffsetFrame + uFrame + 0.5)/uMaxFrame);
position = texture(uVertices, vec2(position.x, uvY)).xyz;
normal = texture(uNormals, vec2(normal.x, uvY)).xyz;
position += aOffsetPosition;
`,"preProcess", "vs");
LS.addCode(`
if(uMaterialFlag==1){
color = vColor;
}
`,"preProcess","fs");
LS.registPainter("light");
const cam = new p5wgex.PerspectiveCamera({w:width,h:height,eye:[200,200,200],top:[0,1,0],near:0.1,far:10000});
const CC = new p5wgex.CameraController(this.canvas,{dblclick:true});
CC.registCamera("cam",cam);LS.setController(CC);
_timer.initialize("clock");
}
function draw(){
_node.clear(1);
LS.update();
_node.use("light",`flamingo_beta`);
// textureをそのままセットする
_node.setTexture("uVertices", "verticeData");
_node.setTexture("uNormals", "normalData");
// 経過ミリ秒数を16msで割り、34で割って余りを取る
_node.setUniform("uFrame",_timer.getElapsedDiscrete("clock", 16, FRAME_NUM));
_node.setUniform("uMaxFrame", FRAME_NUM);
LS.easyLight();
LS.setFlag(1);
_node.bindIBO(`flamingo_betaIBO`).drawElementsInstanced("triangles",{instanceCount:7});
_node.unbind();
_node.flush();
}
// dataにjsonをぶちこむ
function analyzeGLTF(data){
const result = {};
result.nodes = data.nodes;
// とりまobjデータ...
const bin = data.buffers[0].uri.split(',')[1];
const arrayBuffer = getArrayBuffer(bin);
const objData = getObjData({
buf:arrayBuffer,
meshes:data.meshes,
accessors:data.accessors,
bufferViews:data.bufferViews
});
result.obj = objData;
if(data.animations !== undefined){
const animationData = getAnimationData({
buf:arrayBuffer,
animations:data.animations,
accessors:data.accessors,
bufferViews:data.bufferViews
});
result.animation = animationData;
}
if(data.skins !== undefined){
const skinsData = getSkinsData({
buf:arrayBuffer,
skins:data.skins,
accessors:data.accessors,
bufferViews:data.bufferViews
});
result.skins = skinsData;
}
// ここで一つの仮定をする
// skinsがundefinedならばmeshのanimationだけとみなして正規化
// skinsがundefinedでない場合はArmatureのanimationとみなす
if(data.skins !== undefined){
// boneがいくつあるか調べるのだ
// まあresult.skinsの配列の長さだけど
// これとtransformのdataから色々やる感じですね
// skinsは複数ある場合もあるので困るんですが
// そんなこと言ってたら話が進まないんだよな
// ああ、そうでもないか。
// 上記のskinsDataの番号が結局のところtransformで用いた番号と同期してるから
// (共にnode番号で共通)
// skinsをさらってskinsDataの番号、に対してtransformもさらってtransformの
// 番号の情報をぶち込んじゃえばいい
for(const skin of result.skins){
for(const anim of result.animation){
if(anim.transform === undefined) continue;
const tfData = anim.transform.data;
for(let k=0; k<tfData.length; k++){
if(tfData[k] !== undefined){
skin[k].transform = tfData[k];
}
}
}
}
}
// skinsでない通常のmeshActionの場合はtransformをそのまま使えばいいですね
// とりあえずこれで...skins[0]にはboneの情報が入ったことになる
// 各フレームの変換行列をboneごとに用意する....boneが10本なら160
// boneが40本でも640しかない
// 縦はフレーム数(30とか60)ですね
// 計算はibmを用意してibmの逆行列も用意して
// ibm * scale * rotation * translation * ibm^{-1} (想像)
// transformが無い場合は単位行列で埋める(フレーム数分)
// ここまでやるんなら
// transformをフレーム数分のtransform行列にしちゃってよくないか?
return result;
}
function getSkinsData(data){
// inverseBindMatricesという行列が入っている
// MAT4型で
// jointsにindexが入ってるんでそれに対応するように行列を当てはめればいい感じ
const result = [];
for(const skin of data.skins){
const boneData = [];
const skinAccessor = data.accessors[skin.inverseBindMatrices];
const skinArrayData = getArrayData(
data.buf, skinAccessor, data.bufferViews[skinAccessor.bufferView]
);
for(let i=0; i<skinArrayData.length; i+=16){
const jointIndex = Math.floor(i/16);
const matrixData = [];
for(let k=0; k<16; k++){
matrixData.push(skinArrayData[i+k]);
}
boneData[skin.joints[jointIndex]] = {ibm:matrixData};
}
result.push(boneData);
}
return result;
}
function getObjData(data){
// dataは上記のdataのdata.meshesの他に
// data.accessorsとdata.bufferViewsが入っています
// 加えて解読済みのarrayBufferが入っています(data.buf)
const result = [];
for (const mesh of data.meshes) {
const resultMesh = {};
resultMesh.name = mesh.name;
resultMesh.primitives = [];
for (const prim of mesh.primitives) {
const primitive = {};
const attrs = prim.attributes;
for (const attrName of Object.keys(attrs)) {
// POSITION:0, NORMAL:1, TEXCOORD_0:2
const attrAccessor = data.accessors[attrs[attrName]];
const attrArrayData = getArrayData(
data.buf, attrAccessor, data.bufferViews[attrAccessor.bufferView]
);
primitive[attrName] = {
data:attrArrayData, info:getInfo(attrAccessor)
}
}
const indexAccessor = data.accessors[prim.indices];
const indexArrayData = getArrayData(
data.buf, indexAccessor, data.bufferViews[indexAccessor.bufferView]
);
primitive.indices = {
data:indexArrayData, info:getInfo(indexAccessor)
}
if(prim.targets !== undefined){
const targetArray = [];
for(let i=0; i<prim.targets.length; i++){
const eachTarget = prim.targets[i];
const targetAccessor = data.accessors[eachTarget.POSITION];
const targetPositionData = getArrayData(
data.buf, targetAccessor, data.bufferViews[targetAccessor.bufferView]
);
targetArray.push(targetPositionData);
}
// 0~13のウェイトごとのデフォルトとの差分が入っているらしいです
primitive.targetPositionData = targetArray;
}
resultMesh.primitives.push(primitive);
// とりあえずこんなもんで
}
result.push(resultMesh);
}
return result;
}
function getAnimationData(data){
const result = [];
for (const animation of data.animations) {
const resultAnimation = {};
resultAnimation.useWeight = false;
resultAnimation.name = animation.name;
// samplersの各成分を解読して放り込んで
// resultAnimation.samplersとする。そのうえで
// nodesとchannelsからsamplersのどこを参照するか、
// あるいはnodesのどのメッシュを参照するか決める感じ。
resultAnimation.data = [];
// animationは複数存在する場合があるので。ここにぶちこんでいく。
for(let k=0; k<animation.samplers.length; k++){
const sampler = animation.samplers[k];
const channel = animation.channels[k];
const resultData = {};
const resultSampler = {};
resultSampler.interpolation = sampler.interpolation; // LINEARなど
const inputAccessor = data.accessors[sampler.input];
const outputAccessor = data.accessors[sampler.output];
const inputArrayData = getArrayData(
data.buf, inputAccessor, data.bufferViews[inputAccessor.bufferView]
);
const outputArrayData = getArrayData(
data.buf, outputAccessor, data.bufferViews[outputAccessor.bufferView]
);
resultSampler.input = {
data:inputArrayData, info:getInfo(inputAccessor)
}
resultSampler.output = {
data:outputArrayData, info:getInfo(outputAccessor)
}
resultData.sampler = resultSampler;
resultData.channel = channel;
resultData.node = channel.target.node; // nodeが分かんないと不便
resultData.path = channel.target.path; // pathが分かんないと不便
// weightsの場合はこれ
if(channel.target.path === "weights"){
resultAnimation.useWeight = true;
const animationFrameCount = resultSampler.input.info.count;
//console.log(animationFrameCount);
const outputArray = resultSampler.output.data;
const weightArray = [];
const weightNum = Math.floor(outputArray.length/animationFrameCount);
for(let i=0; i<outputArray.length; i+=weightNum){
const eachWeight = new Array(weightNum);
// スライスでいいじゃんとか言わないの
for(let k=0; k<weightNum; k++){
eachWeight[k] = outputArray[i+k];
}
weightArray.push(eachWeight);
}
resultData.weights = weightArray;
}
// で、translation,rotate,scaleの場合、は...
// minとmaxを24倍~roundで整数
// それと別にoutputをそのまま格納すればよいかと
// たとえばminとmaxが0と30なら[*,*,*,*,*,*,*,*](rotation case)
// でいい。後で何とかする。対象がboneかmeshかは...どうしようね
// 対象となるnodeがmeshを持ってるかどうかだと思う。持ってなければbone
// もっともboneとmesh以外に作用する対象がないことを想定してるけど
// 今はそれ以外は必要ないでしょう。
resultAnimation.data.push(resultData);
}
if(!resultAnimation.useWeight){
// weightでない場合にある程度扱いやすくする必要がある
// フレームを正規化して0~MAX-1としs,r,tそれぞれ配列とする
// まずscaleで[[],[],...] nodeで?jointと同じでnodeの番号に入れちゃえ
// たとえばCubeActionの場合は同じとこに全部入る
// weightとshapeKeyが併用されてる場合でもboneが優先して並ぶので問題ない
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;
let dataSize = 1;
switch(data.path){
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++){
// ここでclamp処理
if(i < minIndex){
normalizedArray.push(outputArray.slice(0, dataSize));
continue;
}
if(i > maxIndex){
normalizedArray.push(outputArray.slice(-dataSize, outputArray.length));
continue;
}
// それ以外の場合
// 面倒だったんですがまあこれでいいかなと
// ratioを出して長さ-1を掛ければ必然的に0~長さ-1にはなる
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;
}
// ここでtransformDataからframeNum個の行列の配列を作り、
// 計算しやすくしたいところ
for(const tf of transformData){
if(tf === undefined) continue;
const matArray = [];
for(let k=0; k<maxFrame+1; k++){
const _s = (tf.scale.length === 0 ? [1,1,1] : tf.scale.slice(3*k,3*(k+1)));
const _r = (tf.rotation.length === 0 ? [0,0,0,1] : tf.rotation.slice(4*k,4*(k+1)));
const _t = (tf.translation.length === 0 ? [0,0,0] : tf.translation.slice(3*k,3*(k+1)));
matArray.push(createTransform({s:_s, r:_r, t:_t}));
}
tf.mats = matArray;
}
resultAnimation.transform = {
data:transformData, frameNum: maxFrame+1
}
}
result.push(resultAnimation);
}
return result;
}
function getArrayBuffer(bin){
const byteString = atob(bin);
const byteStringLength = byteString.length;
const arrayBuffer = new ArrayBuffer(byteStringLength);
// このプロセスを挟むとうまくいく?
// 今朝はうまくいってなかったけど。おかしいね。
var intArray = new Uint8Array(arrayBuffer);
for (var i = 0; i < byteStringLength; i++) {
intArray[i] = byteString.charCodeAt(i);
}
return arrayBuffer;
}
// arrayOutputがfalseの場合はそのまま出力されるけども。
// まあjsのarrayのが便利やろ
function getArrayData(buf, accessor, bufferView, arrayOutput = true){
// それぞれのデータを取得する感じのあれ
const componentType = accessor.componentType;
//const _count = accessor.count; // 24とか30
const _type = accessor.type; // "VEC3"とか"SCALAR"
let _size;
switch(componentType) {
case 5126: _size = 4; break;
case 5123: _size = 2; break;
case 5121: _size = 1; break; // 追加(boneのJOINTが5121)
}
//const bufferView = data.bufferViews[accessor.bufferView];
//const bufferIndex = bufferView.buffer;
const byteOffset = bufferView.byteOffset;
const byteLength = bufferView.byteLength;
const arrayLength = byteLength / _size;
const resultArray = createArrayData(buf, componentType, byteOffset, arrayLength);
if(arrayOutput){
const outputArray = new Array(resultArray.length);
for(let i=0; i<outputArray.length; i++){
outputArray[i] = resultArray[i];
}
return outputArray;
}
return resultArray;
}
function createArrayData(buf, componentType, byteOffset, arrayLength) {
switch(componentType) {
case 5126:
const f32Array = new Float32Array(buf, byteOffset, arrayLength);
return f32Array;
case 5123:
const i16Array = new Uint16Array(buf, byteOffset, arrayLength);
return i16Array;
case 5121: // 追加
const u8Array = new Uint8Array(buf, byteOffset, arrayLength);
return u8Array;
}
return [];
}
function getComponentType(code){
switch(code){
case 5126: return "float32";
case 5123: return "uint16";
case 5121: return "uint8"; // 追加
}
return "";
}
// accessorそのままでもいいと思う
// 適宜内容変更して
function getInfo(accessor){
const result = {
type:accessor.type,
count:accessor.count,
componentType:getComponentType(accessor.componentType)
}
if (accessor.max !== undefined) { result.max = accessor.max; }
if (accessor.min !== undefined) { result.min = accessor.min; }
return result;
}
// gltfをJSONとして取得
async function getJSONData(url){
//const url = "https://inaridarkfox4231.github.io/resources/MyDog.json";
try{
const response = await fetch(url);
if(!response.ok){
throw new Error(`status:${response.status}`);
}
const json = response.json();
return json;
}catch(error){
console.error(error.message);
}
}
function loadingResources(){
if(resourcesAreLoaded) return;
resourcesAreLoaded = true;
const url = "https://inaridarkfox4231.github.io/resources/Flamingo.gltf";
getJSONData(url).then((result) => {
// resultはJSONオブジェクト。
console.log(result);
// ここでいろいろ処理する
});
}
function createTransform(tf={}){
const s = createMatFromScale(tf.s);
const r = createMatFromRotation(tf.r);
const t = createMatFromTranslate(tf.t);
const tmp = p5wgex.getMult4x4(s, r);
return p5wgex.getMult4x4(tmp, t);
}
function createMatFromTranslate(q){
let x,y,z;
if(typeof q === 'number'){
const args = arguments;
x = args[0] || 0;
y = args[1] || 0;
z = args[2] || 0;
}else if(Array.isArray(q)){
x = q[0] || 0;
y = q[1] || 0;
z = q[2] || 0;
}else{
x = q.x || 0;
y = q.y || 0;
z = q.z || 0;
}
return [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
x, y, z, 1
];
}
// 要するにこれらをまとめるってことかしら
// んー
// まずはあれですね
// ボーンごとに情報をまとめないと
function createMatFromScale(q){
let x,y,z;
if(typeof q === 'number'){
const args = arguments;
x = args[0] || 1;
y = args[1] || 1;
z = args[2] || 1;
}else if(Array.isArray(q)){
x = q[0] || 1;
y = q[1] || 1;
z = q[2] || 1;
}else{
x = q.x || 1;
y = q.y || 1;
z = q.z || 1;
}
return [
x, 0, 0, 0,
0, y, 0, 0,
0, 0, z, 0,
0, 0, 0, 1
];
}
function createMatFromRotation(q){
// まあそうね
// あれをそのまま、でいいよ。とりあえず。
let x,y,z,w;
if(typeof q === 'number'){
const args = arguments;
x = args[0] || 0;
y = args[1] || 0;
z = args[2] || 0;
w = args[3] || 1;
}else if(Array.isArray(q)){
x = q[0] || 0;
y = q[1] || 0;
z = q[2] || 0;
w = q[3] || 1;
}else{
x = q.x || 0;
y = q.y || 0;
z = q.z || 0;
w = q.w || 1;
}
return [
w*w+x*x-y*y-z*z, 2*(x*y+z*w), 2*(x*z-y*w), 0,
2*(x*y-z*w), w*w+y*y-x*x-z*z, 2*(x*w+y*z), 0,
2*(x*z+y*w), 2*(y*z-x*w), w*w+z*z-x*x-y*y, 0,
0, 0, 0, 1
];
}