クオータニオンを使ってマウスで回転

    

クオータニオンのメモ

class Quaternion {
  constructor (w = 0, x = 0, y = 0, z = 0) {
    this.w = w; this.x = x; this.y = y; this.z = z;
  }

  add ({w, x, y, z}) {
    this.w += w; this.x += x; this.y += y; this.z += z;
    return this;
  }

  mult ({w, x, y, z}) {
    let {w:W, x:X, y:Y, z:Z} = this;
    this.w = W*w - X*x - Y*y - Z*z; this.x = W*x + X*w + Y*z - Z*y;
    this.y = W*y - X*z + Y*w + Z*x; this.z = W*z + X*y - Y*x + Z*w;
    return this;
  }
  
  norm () {
    return this.w**2 + this.x**2 + this.y**2 + this.z**2;
  }

  abs () {
    return Math.sqrt (this.norm ());
  }

  inverse () {
    let n = this.norm ();
    this.w /= n;
    this.x /= -n;
    this.y /= -n;
    this.z /= -n;
    return this;
  }
}

文字の色を虹色に変化させるスクリプトを書いてみた。またくだらないものを書いてしまった。
function getColor (n) {
  const M = 255;
  let c = n % 360 / 60 | 0;
  let x = n % 60 / 60 * M |0;
  let y = M - x;
  let [r,g,b]=[[M,0,y],[M,x,0],[y,M,0],[0,M,x],[0,y,M],[x,0,M]][c];
  return 'rgb(' + r + ',' + g + ',' + b +')';
}


{
  let e = document.querySelector('#hoge');
  let c = e.textContent.split('');
  e.innerText = null;
  c.forEach (c=>e.appendChild(document.createElement('span')).textContent=c);
  let ss = [...document.querySelectorAll ('span')];
  let col = 0;

  function loop () {
    for (let i = 0; i < ss.length; i += 1) {
      ss[i].style.color = getColor (col + i*2);
    }
    col = (col + 1) % 360;
  }
  setInterval (loop, 10);
}

Test

 

JavaScript 行列演算

let a =[[1,2],
        [3,4]];
       
let b =[[5,6],
        [7,8]];

let r =[[19,22]
        [43,50]];

//行列の加算        
function add (a, i) {
  let b = this[i];
  return a.map ((a, j) => a + b[j]);
}

//行列の減算
function sub (a, i) {
  let b = this[i];
  return a.map ((a, j) => a - b[j]);
}

//行列の積
function mult (a) {
  let b = this;
  return b[0].map ((_,j)=> a.reduce ((d,e,f)=> d + e * b[f][j], 0));
}

//転置
function transpose (_, i, b) {
  return b.map ((_, j)=> b[j][i])
}



let d = a.map (mult, b);

console.log (d);
//________________________________________________

class Matrix {
  //行列の加算        
  static add (a, b) { return a.map((a,i)=> b[i].map((b,j)=> a[j]+b)) }
  //行列の減算
  static sub (a, b) { return a.map((a,i)=> b[i].map((b,j)=> a[j]-b)) }
  //行列の積
  static mult (a, b) { return a.map(a=> a.map((_, i)=> b.reduce((g,_,j)=> g + a[j]*b[j][i],0))) }
  //転置
  static transpose (a) { return a[0].map((_,i)=> a.map ((_,j)=> a[j][i]))}
}

JavaScript: サーバーの時間を取得する

問題があるとすれば、

  1. 同期で行っていること
  2. リクエスト返ってくるまでの時間差を加味したこと
//サーバーの時間を取得する
function getUTCDateByServer () {
  let xhr = new XMLHttpRequest(), tm0, tm1, tm2;

  xhr.open("GET", "#", false);
  tm0 = new Date; xhr.send (null);
  tm1 = new Date (xhr.getResponseHeader('Date'));
  tm2 = new Date;

  return new Date ((+tm1) + ((+tm2)-(+tm0)) / 2);
}

JavaScript: innerHTML したときに中に含まれる script が実行されない件

function setInnerHTML (node, text) {
  node.innerHTML = text;
  let script = node.querySelectorAll ('script');

  if (script.length) {
    let
      doc = node.ownerDocument,
      org = doc.createElement ('script');

    for (let s of script) {
      let new_script = org.cloneNode (false);
      new_script.textContent = s.textContent;
      s.parentNode.replaceChild (new_script, s);
    }
    
    org = null;
  } 
}

ノードに innerHTML で挿入した後、その中から script ノードを抜き出す。
新しく作ったノードに中身をコピーして、元の script ノードと置き換える

ベジェ曲線を理解する

その前に線分を式にする。端点(p0, p1)とする。

L(t)=(1-t)P_0 + tP_1,  0 < t < 1

この式は実際の線分の長さはどうであれ t が0から1の範囲で線分上の任意の点を表せるようにするという意味である。
これをJavaScript で関数にすると以下のようになる。t の変化は 0.01 単位。

function line (p0, p1) {
  let rst = [ ];
  for (let t = 0; t <= 1; t += 0.01) {
    let pt = (1-t) * p0 + t * p1;
    rst.push (pt);
  }
  return rst;
}

これは関数の中で変数 t を変化させている。この t を関数の外から指定できるようにする

function line (p0, p1) {
  return function (t) {
    return (1-t) * p0 + t * p1;
  };
}
//__
let
  p0 = 0, p1 = 1,
  rst = [ ],
  p = line (p0, p1);

for (let t = 0; t <= 1; t += 0.01) {
  rst.push (p (t));
}

実際には平面上で直線を扱う場合、x座標とy座標の2つの次元が必要になるので
この関数の引数 a と b を配列にして n 次元の計算ができるように拡張する

function line ([...a], [...b]) {
  return  (t) => {
    let u = 1 - t;
    return a.map ((a,i) => u * a + b[i] * t);
  };
}
//__
let
  p0 = [0], p1 = [1],
  rst = [ ],
  p = line (p0, p1);

for (let t = 0; t <= 1; t += 0.01) {
  rst.push (p (t));
}

postd.cc

曲線を理解する

上の記事にあるSVGを丸パクリしたが、この図を見てベジェ曲線を理解する
点(p0)から点(p1)まで t で変化する点(pt0) と、
点(p1)から点(p2)まで t で変化する点(pt1) を
結ぶ直線を、これまた t で求めた点(Pt2)をなぞると2次ベジェ曲線となる


P 0 P 1 P 2 P t0 P t1 P t2

上の図をもとにプログラムを書く

const line =([...a],[...b])=>(t,u)=>(u=1-t,a.map((a,i)=>u*a+b[i]*t));
//__
let
  p0 = [0, 0], p1 = [550, 100], p2 = [550, 0];
  rst = [ ],
  L01 = line (p0, p1),
  L12 = line (p1, p2);

for (let t = 0; t <= 1; t += 0.01) {
  let
    pt0 = L01 (t), 
    pt1 = L12 (t), 
    pt2 = line (pt0, pt1) (t);
  rst.push (pt2);
}

3次のベジェ曲線

まず点(p0)、点(p1)、そして点(p2)をもとにベジェ曲線を描く(赤い点線)。
次に点(p1)、点(p2)、新たに追加した点(p3)をもとにベジェ曲線を描く(緑の点線)。
赤い点線上の点(pt2)と緑の点線上の点(pt3)と結ぶ直線(緑の線)を点(pt4)をとする
その点(pt4)の軌跡が、3次ベジェ曲線となる。


P 0 P 1 P 2 P 3 P t2 P t3 P t4

const line =([...a],[...b])=>(t,u)=>(u=1-t,a.map((a,i)=>u*a+b[i]*t));
//__
let
  [p0, p1, p2, p3] = [[50, 50], [50, 150], [550, 50], [550,150]],
  rst = [ ],
  L01 = line (p0, p1),
  L12 = line (p1, p2),
  L23 = line (p2, p3);

for (let t = 0; t <= 1; t += 0.01) {
  let
    pt01 = L01 (t), 
    pt12 = L12 (t),
    pt23 = L23 (t), 
    pt2 = line (pt01, pt12) (t),
    pt3 = line (pt12, pt23) (t),
    pt4 = line (pt2, pt3) (t); 
  rst.push (pt4);
}