// 多面体の面をx方向にp、y方向にq分割する function phSplitFaceNXY (ph, p, q) { return phSplitFaceNY (phSplitFaceNX (ph, p), q); } //gn = phSplitFaceNYZ (ph, q, r) // 多面体の面をy方向にq、z方向にr分割する function phSplitFaceNYZ (ph, q, r) { return phSplitFaceNZ (phSplitFaceNY (ph, q), r); } //gn = phSplitFaceNZX (ph, r, p) // 多面体の面をz方向にr、x方向にp分割する function phSplitFaceNZX (ph, r, p) { return phSplitFaceNX (phSplitFaceNZ (ph, r), p); } //gn = phSplitFaceNXYZ (ph, p, q, r) // 多面体の面をx方向にp、y方向にq、z方向にr分割する function phSplitFaceNXYZ (ph, p, q, r) { return phSplitFaceNZ (phSplitFaceNY (phSplitFaceNX (ph, p), q), r); } //gn = phSplitFaceNX (ph, p) // 多面体の面をx方向にp分割する function phSplitFaceNX (ph, p) { let va = ph.ph$vertexArray; let xmin = Infinity; let xmax = -Infinity; for (let i = 0; i < va.length; i++) { let v = va[i]; let x = v[0]; if (x < xmin) { xmin = x; } else if (xmax < x) { xmax = x; } } for (let i = 1; i < p; i++) { phSplitX (ph, (p - i) / p * xmin + i / p * xmax); } return ph; } //gn = phSplitFaceNY (ph, q) // 多面体の面をy方向にq分割する function phSplitFaceNY (ph, q) { let va = ph.ph$vertexArray; let ymin = Infinity; let ymax = -Infinity; for (let i = 0; i < va.length; i++) { let v = va[i]; let y = v[1]; if (y < ymin) { ymin = y; } else if (ymax < y) { ymax = y; } } for (let i = 1; i < q; i++) { phSplitY (ph, (q - i) / q * ymin + i / q * ymax); } return ph; } //gn = phSplitFaceNZ (ph, r) // 多面体の面をz方向にr分割する function phSplitFaceNZ (ph, r) { let va = ph.ph$vertexArray; let zmin = Infinity; let zmax = -Infinity; for (let i = 0; i < va.length; i++) { let v = va[i]; let z = v[2]; if (z < zmin) { zmin = z; } else if (zmax < z) { zmax = z; } } for (let i = 1; i < r; i++) { phSplitZ (ph, (r - i) / r * zmin + i / r * zmax); } return ph; } //gn = phSplitX (ph, s) // 多面体の面を平面x=sで分割する function phSplitX (ph, s) { return phSplitByPlane (ph, 1, 0, 0, -s); } //gn = phSplitY (ph, s) // 多面体の面を平面y=sで分割する function phSplitY (ph, s) { return phSplitByPlane (ph, 0, 1, 0, -s); } //gn = phSplitZ (ph, s) // 多面体の面を平面z=sで分割する function phSplitZ (ph, s) { return phSplitByPlane (ph, 0, 0, 1, -s); } //gn = phSplitByTriangle (ph, a, b, c) // 多面体の面を3点a,b,cを通る平面で分割する function phSplitByTriangle (ph, a, b, c) { //3点a,b,cを通る平面の方程式p*x+q*y+r*z+s=0を求める let ax = a[0]; let ay = a[1]; let az = a[2]; let bx = b[0]; let by = b[1]; let bz = b[2]; let cx = c[0]; let cy = c[1]; let cz = c[2]; let p = (by - ay) * (cz - az) - (cy - ay) * (bz - az); let q = (bz - az) * (cx - ax) - (cz - az) * (bx - ax); let r = (bx - ax) * (cy - ay) - (cx - ax) * (by - ay); let s = -(p * ax + q * ay + r * az); return phSplitByPlane (ph, p, q, r, s); } //gn = phSplitByPlane (ph, p, q, r, s) // 多面体の面を平面p*x+q*y+r*z+s=0で分割する function phSplitByPlane (ph, p, q, r, s) { let eps = 1e-8; let va = ph.ph$vertexArray; let fa = ph.ph$faceArray; let oa = ph.ph$openArray; let na = ph.ph$normalArray; //cprint(p+"*x+"+q+"*y+"+r+"*z+"+s); //面を平面で分割する for (let fn = 0; fn < fa.length; fn++) { //面の番号 let f = fa[fn]; //面 let ha = []; //頂点の判別式の符号の配列 for (let di = 0; di < f.length; di++) { //始点の、面の頂点の番号 let ei = di + 1 < f.length ? di + 1 : 0; //終点の、面の頂点の番号 let dn = f[di]; //始点の頂点の番号 let en = f[ei]; //終点の頂点の番号 let d = va[dn]; //始点 let e = va[en]; //終点 let dx = d[0]; //始点 let dy = d[1]; let dz = d[2]; let ex = e[0]; //終点 let ey = e[1]; let ez = e[2]; let dh = p * dx + q * dy + r * dz + s; //始点の判別式 let eh = p * ex + q * ey + r * ez + s; //終点の判別式 ha[di] = abs (dh) < eps ? 0 : dh < 0 ? -1 : 1; //始点の判別式の符号 ha[ei] = abs (eh) < eps ? 0 : eh < 0 ? -1 : 1; //終点の判別式の符号 if (0 <= ha[di] * ha[ei]) { //始点または終点が平面上にあるか、辺が平面を跨いでいない continue; } //辺と平面の交点を求める let den = (dx - ex) * p + (dy - ey) * q + (dz - ez) * r; let vx = ((dy * ex - dx * ey) * q + (dz * ex - dx * ez) * r + (ex - dx) * s) / den; let vy = ((dx * ey - dy * ex) * p + (dz * ey - dy * ez) * r + (ey - dy) * s) / den; let vz = ((dx * ez - dz * ex) * p + (dy * ez - dz * ey) * q + (ez - dz) * s) / den; //始点と終点は除いてあり、同じ辺は分割済みなので、同じ頂点はないはず //新しい頂点を追加する let vn = va.length; //新しい頂点の番号 va.push ([vx, vy, vz]); di++; f.splice (di, 0, vn); //面に新しい頂点を追加する ha.splice (di, 0, 0); //交点の判別式の符号は0 //残っている面から同じ辺を探して新しい頂点で分割しておく。後で平面上にある頂点として処理される for (let fn2 = fn + 1; fn2 < fa.length; fn2++) { //面の番号 let f2 = fa[fn2]; //面 for (let di2 = 0; di2 < f2.length; di2++) { let ei2 = di2 + 1 < f2.length ? di2 + 1 : 0; let dn2 = f2[di2]; let en2 = f2[ei2]; if (dn2 == dn && en2 == en || dn2 == en && en2 == dn) { //同じ辺 di2++; f2.splice (di2, 0, vn); //面に新しい頂点を追加する break; } } } } //for di //cprint("ha="+ha); //面を分割する // 頂点の判別式の符号の配列を使う // 判別式の符号が変わるところには必ず0が挟まっている // ただし、0があっても符号が変わるとは限らない // 0を挟んで符号が逆になっているところが面の境界になる // 挟まっている0は挟んでいる両方の面に分配する for (let i1 = f.length - 1, i2 = 0; i2 < f.length; i1 = i2, i2++) { //cprint("i1="+i1+",i2="+i2); if (!(ha[i1] != 0 && ha[i2] == 0)) { //判別式の符号が0以外から0に変化していない continue; } //判別式の符号が0以外から0に変化した // 0以外 0 // i1 i2 let i3 = i2; //i3以降はf.length以上になる場合がある let i4 = i3 + 1; while (ha[i4 % f.length] == 0) { i3 = i4; i4++; } //cprint("i1="+i1+",i2="+i2+",i3="+i3+",i4="+i4); // 0以外 0 … 0 0以外 // i1 i2 i3 i4 if (ha[i1] == ha[i4 % f.length]) { //符号が同じ continue; } // ±1 0 … 0 ∓1 // i1 i2 i3 i4 let i5 = i4; let i6 = i5 + 1; let i7; let i8; do { while (ha[i6 % f.length] != 0) { i5 = i6; i6++; } // ±1 0 … 0 ∓1 … ∓1 0 // i1 i2 i3 i4 i5 i6 i7 = i6; i8 = i7 + 1; while (ha[i8 % f.length] == 0) { i7 = i8; i8++; } // ±1 0 … 0 ∓1 … ∓1 0 … 0 0以外 // i1 i2 i3 i4 i5 i6 i7 i8 //cprint("i1="+i1+",i2="+i2+",i3="+i3+",i4="+i4+",i5="+i5+",i6="+i6+",i7="+i7+",i8="+i8); } while (ha[i1] != ha[i8 % f.length]); //cprint("i1="+i1+",i2="+i2+",i3="+i3+",i4="+i4+",i5="+i5+",i6="+i6+",i7="+i7+",i8="+i8); // ±1 0 … 0 ∓1 … ∓1 0 … 0 ±1 // i1 i2 i3 i4 i5 i6 i7 i8 // // -1 -1 0 +1 +1 +1 +1 +1 0 // 0 1 2 3 4 5 6 7 8 // 8 9 // i1 i2 // i3 i4 i5 i6 // i7 // i8 // // //i2…i7で新しい面を作る let f2 = []; for (let i = i2; i <= i7; i++) { f2.push (f[i % f.length]); } fa.splice (fn, 0, f2); //元の面の手前に挿入する oa.splice (fn, 0, oa[fn]); //開放フラグをコピーする na.splice (fn, 0, na[fn].concat ()); //法線ベクトルをコピーする fn++; //元の面が後ろにずれる //元の面のi4…i5を削除する if (f.length <= i4) { //削除する範囲の全体がはみ出している f.splice (i4 - f.length, i5 - i4 + 1); //2周目のi4からi5まで ha.splice (i4 - f.length, i5 - i4 + 1); } else if (f.length <= i5) { //削除する範囲の後半がはみ出している f.splice (i4, f.length - i4); //i4から末尾まで ha.splice (i4, f.length - i4); f.splice (0, i5 - f.length + 1); //先頭からi5まで ha.splice (0, i5 - f.length + 1); i2 -= i5 - f.length + 1; i1 = i2 == 0 ? f.length - 1 : i2 - 1; } else { //削除する範囲がはみ出していない f.splice (i4, i5 - i4 + 1); //i4からi5まで ha.splice (i4, i5 - i4 + 1); } } //for i1,i2 } //for fn return ph; } //[ph1, …] = phSplitKX (ph, p) // 多面体をx方向にp分割する function phSplitKX (ph, p) { let va = ph.ph$vertexArray; let xmin = Infinity; let xmax = -Infinity; for (let i = 0; i < va.length; i++) { let v = va[i]; let x = v[0]; if (x < xmin) { xmin = x; } else if (xmax < x) { xmax = x; } } let xa = []; for (let i = 1; i < p; i++) { xa.push ((p - i) / p * xmin + i / p * xmax); } return phSplitNX (ph, xa); } //[ph1, …] = phSplitKXY (ph, p, q) // 多面体をx方向にp分割、y方向にq分割する function phSplitKXY (ph, p, q) { let va = ph.ph$vertexArray; let xmin = Infinity; let xmax = -Infinity; let ymin = Infinity; let ymax = -Infinity; for (let i = 0; i < va.length; i++) { let v = va[i]; let x = v[0]; let y = v[1]; if (x < xmin) { xmin = x; } else if (xmax < x) { xmax = x; } if (y < ymin) { ymin = y; } else if (ymax < y) { ymax = y; } } let xa = []; for (let i = 1; i < p; i++) { xa.push ((p - i) / p * xmin + i / p * xmax); } let ya = []; for (let i = 1; i < q; i++) { ya.push ((q - i) / q * ymin + i / q * ymax); } return phSplitNXY (ph, xa, ya); } //[ph1, …] = phSplitNXY (ph, [x1, …], [y1, …]) //[ph1, …] = phSplitNYZ (ph, [y1, …], [z1, …]) //[ph1, …] = phSplitNZX (ph, [z1, …], [x1, …]) //[ph1, …] = phSplitNX (ph, [x1, …]) //[ph1, …] = phSplitNY (ph, [y1, …]) //[ph1, …] = phSplitNZ (ph, [z1, …]) //[ph1, ph2] = phSplit2X (ph, x) //[ph1, ph2] = phSplit2Y (ph, y) //[ph1, ph2] = phSplit2Z (ph, z) //[ph1, ph2] = phSplit2Plane (ph, p, q, r, s) // 多面体を平面で分割する // 切り口は塞がない function phSplitNXY (ph, xa, ya) { let pha = phSplitNX (ph, xa); let phaa = []; for (let i = 0; i < pha.length; i++) { phaa = phaa.concat (phSplitNY (pha[i], ya)); } return phaa; } function phSplitNYZ (ph, ya, za) { let pha = phSplitNY (ph, ya); let phaa = []; for (let i = 0; i < pha.length; i++) { phaa = phaa.concat (phSplitNZ (pha[i], za)); } return phaa; } function phSplitNZX (ph, za, xa) { let pha = phSplitNZ (ph, za); let phaa = []; for (let i = 0; i < pha.length; i++) { phaa = phaa.concat (phSplitNX (pha[i], xa)); } return phaa; } function phSplitNX (ph, xa) { let pha = []; for (let i = 0; i < xa.length; i++) { let t = phSplit2X (ph, xa[i]); pha.push (t[0]); ph = t[1]; } pha.push (ph); return pha; } function phSplitNY (ph, ya) { let pha = []; for (let i = 0; i < ya.length; i++) { let t = phSplit2Y (ph, ya[i]); pha.push (t[0]); ph = t[1]; } pha.push (ph); return pha; } function phSplitNZ (ph, za) { let pha = []; for (let i = 0; i < za.length; i++) { let t = phSplit2Z (ph, za[i]); pha.push (t[0]); ph = t[1]; } pha.push (ph); return pha; } function phSplit2X (ph, x) { return phSplit2Plane (ph, 1, 0, 0, -x); } function phSplit2Y (ph, y) { return phSplit2Plane (ph, 0, 1, 0, -y); } function phSplit2Z (ph, z) { return phSplit2Plane (ph, 0, 0, 1, -z); } function phSplit2Plane (ph, p, q, r, s) { let eps = 1e-8; let ph1 = phNewCopy (ph); phSplitByPlane (ph1, p, q, r, s); let ph2 = phNewCopy (ph1); let pha = [ph1, ph2]; for (let k = 0; k < 2; k++) { let ph = pha[k]; let va = ph.ph$vertexArray; let fa = ph.ph$faceArray; let oa = ph.ph$openArray; let na = ph.ph$normalArray; //頂点の判別式 let ha = []; for (let i = 0; i < va.length; i++) { let v = va[i]; let h = p * v[0] + q * v[1] + r * v[2] + s; ha[i] = abs (h) < eps ? 0 : h < 0 ? -1 : 1; } //判別式が+/-の頂点を含む面を取り除く for (let i = 0; i < fa.length; i++) { let f = fa[i]; for (let j = 0; j < f.length; j++) { if (k == 0 ? 0 < ha[f[j]] : ha[f[j]] < 0) { fa.splice (i, 1); oa.splice (i, 1); na.splice (i, 1); i--; break; } } } //使われている頂点を確認する for (let i = 0; i < va.length; i++) { ha[i] = -1; //使われていない } for (let i = 0; i < fa.length; i++) { let f = fa[i]; for (let j = 0; j < f.length; j++) { ha[f[j]] = 1; //使われている } } //使われている頂点の番号を詰める let n = 0; //使われている頂点の数 for (let i = 0; i < va.length; i++) { if (0 < ha[i]) { //使われている ha[i] = n; //新しい頂点の番号 n++; //使われている頂点の数 } } //面の頂点の番号を書き換える for (let i = 0; i < fa.length; i++) { let f = fa[i]; for (let j = 0; j < f.length; j++) { f[j] = ha[f[j]]; //新しい頂点の番号 } } //使われていない頂点を取り除く for (let i = va.length - 1; 0 <= i; i--) { if (ha[i] < 0) { //使われていない va.splice (i, 1); ha.splice (i, 1); } } ph.ph$reciprocalMatrix = null; phMakeFields (ph); //単位立方体変換行列を作り直す } return pha; } //------------------------------------------------------------------------ //その他 //i = phSearchFace (ph, f) // 多面体の面の番号を求める function phSearchFace (ph, f) { let fa = ph.ph$faceArray; for (let i = 0; i < fa.length; i++) { if (phIsSameFace (fa[i], f)) { return i; } } return -1; } //i = phSearchVertex (ph, v) // 多面体の頂点の番号を求める function phSearchVertex (ph, v) { let va = ph.ph$vertexArray; for (let i = 0; i < va.length; i++) { if (phIsSameVertex (va[i], v)) { return i; } } return -1; } //b = phIsReversedFace (f1, f2) // 裏返した面か function phIsReversedFace (f1, f2) { if (f1.length != f2.length) { //頂点の数が違う return false; } let i = f1.length - 1; let j = 0; while (j < f2.length && f1[i] != f2[j]) { //f1[f1.length]==f2[j]となるjを探す j++; } if (j == f2.length) { //最初の頂点が見付からない return false; } while (0 <= i && f1[i] == f2[j]) { //f1[i]==f2[j]とならないiを探す i--; j = j + 1 < f2.length ? j + 1 : 0; } if (0 <= i) { //一致しない頂点がある return false; } return true; } //b = phIsSameFace (f1, f2) // 同一面か function phIsSameFace (f1, f2) { if (f1.length != f2.length) { //頂点の数が違う return false; } let i = 0; let j = 0; while (j < f2.length && f1[i] != f2[j]) { //f1[0]==f2[j]となるjを探す j++; } if (j == f2.length) { //最初の頂点が見付からない return false; } while (i < f1.length && f1[i] == f2[j]) { //f1[i]==f2[j]とならないiを探す i++; j = j + 1 < f2.length ? j + 1 : 0; } if (i < f1.length) { //一致しない頂点がある return false; } return true; } //b = phIsSameNumber (x, y) // 同じ数か // 絶対誤差が1e-8未満ならば同じ数と見なす // 両方0に近いときは指数部が大きく違っていても同じ数と見なす // 本来0になるはずの値が誤差で0になり損なった場合の指数部は場合によって大きく異なるので相対誤差は適さない // 絶対誤差が1e-8以上かつ絶対値が大きい方を基準にした相対誤差が1e-8未満ならば同じ数と見なす // 絶対値が大きい方が0のときは絶対誤差が1e-8未満なので、ここでは絶対値が大きい方は0ではなく相対誤差を計算できる // 絶対値が小さい方が0のときは絶対値が大きい方がどんなに0に近くても相対誤差が1/1=1なので、ここでは同じ数と見なさない // それ以外は同じ数と見なさない function phIsSameNumber (x, y) { return (abs (y - x) < 1e-8 || //絶対誤差が1e-8未満 (abs (y) <= abs (x) ? abs (y - x) / abs (x) : abs (x - y) / abs (y)) < 1e-8); //絶対誤差が1e-8以上かつ絶対値が大きい方を基準にした相対誤差が1e-8未満 } //b = phIsSameVertex (v1, v2) // 同一頂点か function phIsSameVertex (v1, v2) { return (phIsSameNumber (v1[0], v2[0]) && phIsSameNumber (v1[1], v2[1]) && phIsSameNumber (v1[2], v2[2])); } //s = phToString (ph) // 文字列にする // 配列の配列を文字列にするとき内側の配列を囲む括弧がないと外側の配列の要素の区切りがどこだかわからない function phToString (ph) { let va = ph.ph$vertexArray; let fa = ph.ph$faceArray; let oa = ph.ph$openArray; let na = ph.ph$normalArray; let rm = ph.ph$reciprocalMatrix; let s = "{va:["; for (let i = 0; i < va.length; i++) { s += (i == 0 ? "[" : ",[") + va[i].join (",") + "]"; } s += "],fa:["; for (let i = 0; i < fa.length; i++) { s += (i == 0 ? "[" : ",[") + fa[i].join (",") + "]"; } s += "],oa:[" + oa.join (",") + "],na:["; for (let i = 0; i < na.length; i++) { s += (i == 0 ? "[" : ",[") + na[i].join (",") + "]"; } return s + "],rm:[" + rm.join (",") + "]}"; } //======================================================================================== // // メイン // //======================================================================================== //======================================================================== //座標系 // // 右が+x、奥が+y、上が+zの右手座標系を用いる // // +z // ↑ +y // │/ // -x ──┼─→ +x // /│ // -y │ // -z // //======================================================================== //投影 // // 定数 // focalLengthMm 焦点距離[mm] 画面の大きさが変わっても物体が画面を占める割合が変わらないようにする // pupillaryDistancePx 瞳孔間距離[px] // // 画面毎に存在し、画面の大きさが変わると変化する値 // 画面の幅[px] // 画面の高さ[px] // focalLengthPx 焦点距離[px] 画面に収まる最大の3:2の長方形の対角線の長さ[px]*(50/hypot2(36,24)) // // 物体が動くと変化する値 // x座標[mm] // y座標[mm] // z座標[mm] // // カメラが動くと変化する値 // longitudeRad 経度[rad] // latitudeRad 緯度[rad] // cameraDistanceMm カメラの距離[mm] 初期値は物体が画面に収まるように焦点距離[mm]に適当な値を掛けて決める // // 手順 // 物体をmm単位で構築する // 変換行列を用いて以下の計算をまとめて行う // y→xに経度[rad]回転させる // y座標[mm]=cos(経度[rad])*y座標[mm]-sin(経度[rad])*x座標[mm] // x座標[mm]=sin(経度[rad])*y座標[mm]+cos(経度[rad])*x座標[mm] // y→zに緯度[rad]回転させる // y座標[mm]=cos(緯度[rad])*y座標[mm]-sin(緯度[rad])*z座標[mm] // z座標[mm]=sin(緯度[rad])*y座標[mm]+cos(緯度[rad])*z座標[mm] // カメラを原点にする // y座標[mm]+=カメラの距離[mm] // 焦点距離[px]/焦点距離[mm]を掛けてmm単位からpx単位に変換する // x座標[px]=焦点距離[px]/焦点距離[mm]*x座標[mm] // y座標[px]=焦点距離[px]/焦点距離[mm]*y座標[mm] // z座標[px]=焦点距離[px]/焦点距離[mm]*z座標[mm] // 見えない頂点を取り除く // y座標[px]は0がカメラの位置、焦点距離[px]が画面の位置 // この時点でy座標[px]が0または負の頂点はカメラの真横または後ろにあって見えないので取り除く // 取り除かないと0除算やオーバーフローが発生する // 見えない頂点を含む面は見える頂点があっても表示できない // カメラを面に近付けすぎると面が消えてしまうことがある // 投影する // 立体視でないとき // x座標[px]=画面の幅[px]/2+焦点距離[px]/y座標[px]*x座標[px] // 立体視で左目のとき // x座標[px]=画面の幅[px]/2+焦点距離[px]/y座標[px]*(x座標[px]+瞳孔間距離[px]/2)-瞳孔間距離[px]/2 // 立体視で右目のとき // x座標[px]=画面の幅[px]/2+焦点距離[px]/y座標[px]*(x座標[px]-瞳孔間距離[px]/2)+瞳孔間距離[px]/2 // y座標[px]=画面の高さ[px]/2-焦点距離[px]/y座標[px]*z座標[px] // const focalLengthMm = 3000; //焦点距離[mm] const pupillaryDistancePx = 100; //瞳孔間距離[px]。0.264[mm/px]のとき66[mm] const minimumCameraDisatanceMm = 300; //カメラの距離の最小値[mm] //回転 // 床固定モードのとき // longitudeRadとlatitudeRadを更新する // rotationMatrixを作り直す // 床固定モードでないとき // longitudeRadとlatitudeRadを更新する。床固定モードに切り替えたときに使う // rotationMatrixを回転させる let longitudeRad = rad (0); //経度[rad] let latitudeRad = rad (15); //緯度[rad] let rotationMatrix = nRotateYZ (nNewIRotateXY (-longitudeRad), latitudeRad); //経度と緯度を回転させる変換行列 let cameraDistanceMm = focalLengthMm * 2; //カメラの距離[mm] //resetSize () // 画面の大きさが変わると変化する値を計算する function resetSize () { windowWidth = max2 (256, window.innerWidth || document.documentElement.offsetWidth || 0); //ウインドウの幅[px] windowHeight = max2 (256, window.innerHeight || document.documentElement.offsetHeight || 0); //ウインドウの高さ[px] windowHalfWidth = windowWidth >> 1; //ウインドウの幅の半分[px] for (let sn = 0; sn <= 2; sn++) { let screen = globalScreenArray[sn]; screen.sc$left = sn <= 1 ? 0 : windowHalfWidth; screen.sc$top = 0; screen.sc$width = sn == 0 ? windowWidth : windowHalfWidth; screen.sc$height = windowHeight; screen.sc$viewX = 0; screen.sc$viewY = 0; screen.sc$viewWidth = screen.sc$width; screen.sc$viewHeight = screen.sc$height; /* let diagonalLengthPx = (screen.sc$viewWidth * 2 <= screen.sc$viewHeight * 3 ? //3:2の長方形がぴったり収まるまたは3:2より縦長 hypot2 (2, 3) / 3 * screen.sc$viewWidth : //3:2より横長 hypot2 (2, 3) / 2 * screen.sc$viewHeight); //画面に収まる最大の3:2の長方形の対角線の長さ[px] */ let diagonalLengthPx = (windowWidth * 2 <= windowHeight * 3 ? //3:2の長方形がぴったり収まるまたは3:2より縦長 hypot2 (2, 3) / 3 * windowWidth //立体視のときも幅を半分にしないで計算する : //3:2より横長 hypot2 (2, 3) / 2 * windowHeight); //画面に収まる最大の3:2の長方形の対角線の長さ[px] screen.sc$focalLengthPx = diagonalLengthPx * (50 / hypot2 (36, 24)); //焦点距離[px] } } //======================================================================== //Zソート // // 重ね合わせはZソートのみで行い、Zバッファを用いない // // 1つの物体に1つの奥行きを持たせてソートして重ね合わせる方法は高速だが、 // 大きい物体の手前に小さい物体があると前後関係が崩れやすく、 // 3つ以上の物体が互いに他の物体を隠す位置にあると破綻する // 物体を分割して調整する // // ┌───┐ ┌───┐ // │ └─│ │ // │ │ │ // │ ┌─│ │ // └───┘ └┐ ┌┘ // │ │ │ │ // ┌┘ └┐ ┌───┐ // │ │─┘ │ // │ │ │ // │ │─┐ │ // └───┘ └───┘ // //======================================================================== //頂点の順序 // // 面の頂点の順序は表から見て左回りとする // //======================================================================== //隠面消去 // // 面は表から見えるが裏から見えない // 他の物体に隠れて見えない面はなるべく描かない // let floorFixed = true; //true=床固定モード let screenMode = 0; //画面モード。0=ノーマル,1=立体視(平行法),2=立体視(交差法) const PROJECT_DELAY = 1000; //描画遅延[ms] const PROJECT_INTERVAL = 50; //描画間隔[ms] let projectTimeoutId = 0; //======================================================================== //ウインドウ let windowWidth; //int ウインドウの幅[px] let windowHeight; //int ウインドウの高さ[px] let windowHalfWidth; //int ウインドウの幅の半分[px] //======================================================================== //screen スクリーン // 0 ノーマル // 1 裸眼立体視 交差法 左側 右目 // 2 裸眼立体視 交差法 右側 左目 // // sc$focalLengthPx 焦点距離[px] // sc$height スクリーンの高さ[px] // sc$left スクリーンの左の座標[px] // sc$matrix 拡大率と左右の目の間隔を含む変換マトリクス。カメラの位置は含まない // sc$screenNumber 0,1,2 // sc$top スクリーンの上の座標[px] // sc$viewHeight ビューの座標の高さ[px] // sc$viewWidth ビューの座標の幅[px] // sc$viewX ビューの左の座標[px] // sc$viewY ビューの上の座標[px] // sc$visibleArray[screenNumber] 表示されている多面体の配列 // sc$width スクリーンの幅[px] // let globalScreenArray; let svgElementArray; let inversedCamera; //逆回転させたカメラの座標 //枠と箱 // 鍵、YM2151、MSM6258Vを枠と箱に分けて別々にソートして表示するときに枠を箱で置き換える let zentaiHako; let hakoArray; //======================================================================== //polyhedron 多面体 // // ph$vertexArray[vertexNumber] 基本 頂点の配列 // ph$faceArray[faceNumber] 基本 面の配列 // ph$openArray[faceNumber] 基本 面の開放フラグの配列 // ph$normalArray[faceNumber] 基本 法線ベクトルの配列 // ph$reciprocalMatrix 基本 単位立方体変換行列 // // ph$name 拡張 名前。デバッグ用 // ph$color 拡張 色 // // ph$hakoNumber 拡張 枠のとき箱の番号 // ph$isWaku 拡張 true=枠 // ph$requested 拡張 再計算リクエスト。screenMask=部分再計算のときこの多面体を再計算する // ph$visible 拡張 screenMask=今回表示する。動かなければ表示し続ける // // ph$miniatureVertexArray[vertexNumber] 拡張 投影する直前の頂点の配列 // ph$projectedVertexArray[vertexNumber] 拡張 投影された頂点の配列。[0]がNaNのとき範囲外 // ph$miniatureNormalArray[faceNumber] 拡張 投影する直前の法線ベクトルの配列 // // ph$polygonArray[faceNumber] 拡張 この多面体に含まれる多角形の配列 // pg$displayed screenMask=現在表示されている // pg$elementArray[screenNumber] DOM要素の配列 // pg$visible screenMask=今回表示する。動かなければ表示し続ける //setupPolyhedron (ph, name, color) // 多面体の拡張要素を初期化する // 基本要素は設定済みであること function setupPolyhedron (ph, name, color) { let va = ph.ph$vertexArray; let fa = ph.ph$faceArray; ph.ph$name = name; ph.ph$color = color; ph.ph$hakoNumber = -1; ph.ph$isWaku = false; ph.ph$requested = 0; ph.ph$visible = 0; ph.ph$miniatureVertexArray = []; ph.ph$projectedVertexArray = []; ph.ph$miniatureNormalArray = []; ph.ph$polygonArray = []; for (let vn = 0; vn < va.length; vn++) { ph.ph$miniatureVertexArray[vn] = [0, 0, 0]; ph.ph$projectedVertexArray[vn] = [0, 0]; } for (let fn = 0; fn < fa.length; fn++) { ph.ph$miniatureNormalArray[fn] = [0, 0, 0]; let ea = []; //DOM要素の配列 for (let sn = 0; sn <= 2; sn++) { ea[sn] = setStrokeWidth (createPolygon ("0 0 1 0 0 1"), 1); } ph.ph$polygonArray[fn] = { pg$displayed: 0, pg$elementArray: ea, pg$visible: 0 }; } return ph; } //======================================================================== //kenInfo 鍵情報 // ki$channelNumber 最後に割り当てたチャンネル番号 // ki$kenNumber 鍵番号 // ki$keyCode キーコード // ki$keyFraction キーフラクション // ki$keyOn true=キーオンされている // ki$kokken true=黒鍵,false=白鍵 // ki$pha1 鍵を構成する多面体の配列 // ki$phaA2 鍵を構成する多面体の配列の配列。[0]=押されていない状態,[1]=押されている状態 // ki$pressed true=押されている // ki$sostenuto true=離したときキーオフしない let kenInfoArray; //鍵番号→鍵情報 //鍵情報を作る // 平均律で調律する // YM2151は3.58MHzで駆動したときオクターブ4のラの音が440Hzになるように設計されている // X68000はYM2151を4MHzで駆動しているのでおよそ2度高い音が出る // 1度を64分割するキーフラクションを123減らすと本来の周波数に近い音になる // KC,KFのペアを3個作る // ピアノは1つの鍵が押されると対応するハンマーが周波数のわずかに異なる3本の弦を叩く function newKenInfo (kn, pha1) { let oct = fdiv (kn + 9, 12); let note = frem (kn + 9, 12); let keyFraction = 64 * (12 * oct + note) - 123; let keyCode = -1; if (0 <= keyFraction && keyFraction < 768 * 8) { keyCode = keyFraction >> 6; keyFraction &= 63; keyCode += keyCode / 3 >> 0; } return { ki$channelNumber: -1, ki$kenNumber: kn, ki$keyCode: keyCode, ki$keyFraction: keyFraction, ki$keyOn: false, ki$isKokken: ((1 << note) & (1 << 1 | 1 << 3 | 1 << 6 | 1 << 8 | 1 << 10)) != 0, //false=白鍵,true=黒鍵 ki$pha1: pha1, ki$pressed: false, ki$sostenuto: false }; } //makePiano () // ピアノを作る // // ピアノのJIS規格 // http://kikakurui.com/s/S8507-1992-01.html // // ヤマハC5Xのサイズを参考にする // https://jp.yamaha.com/products/musical_instruments/pianos/grand_pianos/cx_series/index.html // 長さ200 // 幅149 // 前屋根14 // 大屋根56 // 閉めた屋根までの高さ101 // 棚板の底の高さ62 // // 楽器解体全書 // https://www.yamaha.com/ja/musical_instrument_guide/piano/ // 1.6セントずつずらして3音鳴らす // // 屋根の開け方 // https://www.youtube.com/watch?v=nY5ZE5hFDXE // // ベーゼンドルファー // モデル290インペリアルという97鍵の巨大なピアノを作っている // ベーゼンドルファー|Bösendorfer|製品情報|スタンダードモデル|Model 290 Imperial // http://boesendorfer.jp/products/standard/model290.html // 2700万円らしい // ベーゼンドルファー|株式会社 山野楽器 // https://www.yamano-music.co.jp/shops/ginza/contents/g-piano/grand_piano/boesendorfer/index.html // 1時間2000円で弾き放題らしい // ベーゼンドルファーを弾こう!! | 貸しホール貸会議室は三島駅から車で7分の長泉町文化センター // http://nagaizumi-culture-c.jp/jisyu/14/ // // 一般的なピアノは88鍵。白鍵が52、黒鍵が36 // // 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 // 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 黒鍵 // A# C#D# F#G#A# C#D# F#G#A# C#D# F#G#A# C#D# F#G#A# C#D# F#G#A# C#D# F#G#A# C#D# F#G#A# // ||B|||B|B|||B|B|B|||B|B|||B|B|B|||B|B|||B|B|B|||B|B|||B|B|B|||B|B|||B|B|B|||B|B|||B|B|B|||B|B|||B|B|B|| | // || ||| | ||| | | ||| | ||| | | ||| | ||| | | ||| | ||| | | ||| | ||| | | ||| | ||| | | ||| | ||| | | || | // | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | // |W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W|W| // A B C D E F G A B C D E F G A B C D E F G A B C D E F G A B C D E F G A B C D E F G A B C D E F G A B C // 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 // 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 白鍵 // 11111 11111 2222222 22233 3333333 34444 4444445 55555 5555666 66666 6677777 77777 8888888 8 // 012 34567 8901234 56789 0123456 78901 2345678 90123 4567890 12345 6789012 34567 8901234 56789 0123456 7 kn // 000 11111 1111111 22222 2222222 33333 3333333 44444 4444444 55555 5555555 66666 6666666 77777 7777777 8 oct // 11 11 11 11 11 11 11 11 // 901 01234 5678901 01234 5678901 01234 5678901 01234 5678901 01234 5678901 01234 5678901 01234 5678901 0 note // K K K K K K K K K K K K K K K K K K K K K K K K K K K K K K K K K K K K isKokken // 440 // // 黒鍵の間隔は白鍵よりも広い // // +----------+-----+------+-------+-----+-------+-----+-------+------+-----+-------+-----+-------+-----+------+ // | 20 | 13 | 14 | 15.5 | 13 | 13.5 | 13 | 15.5 | 14 | 13 | 13.5 | 13 | 13.5 | 13 | 14 | // | | | | | | | | | | | | | | | | // | | | | | | | | | | | | | | | |95 // | | | | | | | | | | | | | | | | // | | | | | | | | | | | | | | | | // | ++----+ | +---+-+ +-+---+ | +----++ +--+--+ ++----+ | // | 3.5 | 9.5 | 8 | 5 5 | 8 | 9.5 | 3.5 6.5 | 6.5 3.5 | 9.5 | // | | | | | | | | | |50 // | 23.5 | 23.5 | 23.5 | 23.5 | 23.5 | 23.5 | 23.5 | 23.5 | 23.5 | // +-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+ // 0 1 2 3 4 5 6 7 8 // // -----+------+-------+-----+-------+-----+-------+------+-----+-------+-----+-------+-----+------+-----------+ // 13 | 14 | 15.5 | 13 | 13.5 | 13 | 15.5 | 14 | 13 | 13.5 | 13 | 13.5 | 13 | 14 | 23.5 | // | | | | | | | | | | | | | | | // | | | | | | | | | | | | | | |95 // | | | | | | | | | | | | | | | // | | | | | | | | | | | | | | | // +----+ | +---+-+ +-+---+ | +----++ +--+--+ ++----+ | | // | 9.5 | 8 | 5 5 | 8 | 9.5 | 3.5 6.5 | 6.5 3.5 | 9.5 | | // | | | | | | | | | |50 // | 23.5 | 23.5 | 23.5 | 23.5 | 23.5 | 23.5 | 23.5 | 23.5 | 23.5 | // +-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+ // 43 44 45 46 47 48 49 50 51 // // 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 // +y 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 // ↑ 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 // 2000 +-------------------+-----------------------+---------------+ // 1950 | . | . | | // 1900 | | . | | // 1850 | . | . | | // 1800 | | | | // 1750 | | . | | // 1700 | . | | | // 1650 | | | | // 1600 |. | .| |