←Previous | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Next→
  } else if (fumendaiPhaK == fumendaiPhaN) {  //開いている
    mouseOpeningClosing = true;  //開閉中
    fumendaiPhaD = -1;  //閉じる
  }
}

//  譜面台の開閉タスク
function fumendaiTask () {
  //位置を更新する
  fumendaiPhaK = max2 (0, min2 (fumendaiPhaN, fumendaiPhaK + fumendaiPhaD));
  //譜面台と譜面板を動かす
  let pha1 = fumendaiPha1;  //コピー先
  let pha2 = fumendaiPhaA2[fumendaiPhaK];  //コピー元
  for (let i = 0; i < pha1.length; i++) {
    let ph1 = pha1[i];  //コピー先
    let ph2 = pha2[i];  //コピー元
    phLetCopy (ph1, ph2);
    ph1.ph$requested = 7;  //再計算リスエスト
  }
  projectRequested |= 7;  //再描画リクエスト。部分再計算
  //継続処理
  if (fumendaiPhaK == fumendaiPhaN) {  //開き終わった
    fumendaiPhaD = 0;
    mouseOpeningClosing = false;  //開閉終了
  } else if (fumendaiPhaK == 0) {  //閉じ終わった
    fumendaiPhaD = 0;
    mouseOpeningClosing = false;  //開閉終了
  }
}

//  屋根を開閉する
function yaneOpenClose () {
  if (fumendaiPhaK != 0) {  //譜面台が閉じていないときは屋根を閉じられない
    return;
  }
  //開閉タスクを起動する
  if (yanePhaK == 0) {  //閉じている
    mouseOpeningClosing = true;  //開閉中
    yanePhaD = 1;  //開く
  } else if (yanePhaK == yanePhaN) {  //開いている
    mouseOpeningClosing = true;  //開閉中
    yanePhaD = -1;  //閉じる
  }
}

//  屋根の開閉タスク
function yaneTask () {
  //位置を更新する
  yanePhaK = max2 (0, min2 (yanePhaN, yanePhaK + yanePhaD));
  //屋根を動かす
  let pha1 = yanePha1;  //コピー先
  let pha2 = yanePhaA2[yanePhaK];  //コピー元
  for (let i = 0; i < pha1.length; i++) {
    let ph1 = pha1[i];  //コピー先
    let ph2 = pha2[i];  //コピー元
    phLetCopy (ph1, ph2);
    ph1.ph$requested = 7;  //再計算リスエスト
  }
  projectRequested |= 7;  //再描画リクエスト。部分再計算
  //継続処理
  if (yanePhaK == yanePhaN) {  //開き終わった
    yanePhaD = 0;
    mouseOpeningClosing = false;  //開閉終了
  } else if (yanePhaK == 0) {  //閉じ終わった
    yanePhaD = 0;
    mouseOpeningClosing = false;  //開閉終了
  }
}

//  鍵盤蓋を開閉する
function kenbanfutaOpenClose () {
  if (kenbanfutaPhaK == 0) {  //閉じている
    mouseOpeningClosing = true;  //開閉中
    kenbanfutaPhaD = 1;  //開く
  } else if (kenbanfutaPhaK == kenbanfutaPhaN) {  //開いている
    mouseOpeningClosing = true;  //開閉中
    kenbanfutaPhaD = -1;  //閉じる
  }
}

//  鍵盤蓋の開閉タスク
function kenbanfutaTask () {
  //位置を更新する
  kenbanfutaPhaK = max2 (0, min2 (kenbanfutaPhaN, kenbanfutaPhaK + kenbanfutaPhaD));
  //鍵盤蓋を動かす
  let pha1 = kenbanfutaPha1;  //コピー先
  let pha2 = kenbanfutaPhaA2[kenbanfutaPhaK];  //コピー元
  for (let i = 0; i < pha1.length; i++) {
    let ph1 = pha1[i];  //コピー先
    let ph2 = pha2[i];  //コピー元
    phLetCopy (ph1, ph2);
    ph1.ph$requested = 7;  //再計算リスエスト
  }
  projectRequested |= 7;  //再描画リクエスト。部分再計算
  //継続処理
  if (kenbanfutaPhaK == kenbanfutaPhaN) {  //開き終わった
    kenbanfutaPhaD = 0;
    mouseOpeningClosing = false;  //開閉終了
  } else if (kenbanfutaPhaK == 0) {  //閉じ終わった
    kenbanfutaPhaD = 0;
    mouseOpeningClosing = false;  //開閉終了
  }
}


//  鍵の位置を下げる
function kenDown (ki) {
  if (!ki.ki$pressed) {  //押されていない
    ki.ki$pressed = true;  //押された
    ki.ki$sostenuto = false;  //ソステヌートはかかっていない
    let pha1 = ki.ki$pha1;  //コピー先
    let pha2 = ki.ki$phaA2[1];  //コピー元
    for (let i = 0; i < pha1.length; i++) {
      let ph1 = pha1[i];  //コピー先
      let ph2 = pha2[i];  //コピー元
      phLetCopy (ph1, ph2);
      ph1.ph$requested = 7;  //再計算リスエスト
    }
    projectRequested |= 7;  //再描画リクエスト。部分再計算
  }
}

//  鍵の位置を元に戻す
function kenUp (ki) {
  if (ki.ki$pressed) {  //押されている
    ki.ki$pressed = false;  //離された
    let pha1 = ki.ki$pha1;  //コピー先
    let pha2 = ki.ki$phaA2[0];  //コピー元
    for (let i = 0; i < pha1.length; i++) {
      let ph1 = pha1[i];  //コピー先
      let ph2 = pha2[i];  //コピー元
      phLetCopy (ph1, ph2);
      ph1.ph$requested = 7;  //再計算リスエスト
    }
    projectRequested |= 7;  //再描画リクエスト。部分再計算
  }
}


//  ペダルを動かす
//  num  0  左ペダル    ソフトペダル
//       1  中央ペダル  ソステヌートペダル
//       2  右ペダル    ラウドペダル
//  pos  0  離す
//       1  踏む
function movePedal (num, pos) {
  if (pedalPhaK[num] != pos) {
    pedalPhaK[num] = pos;
    let pha1 = pedalPha1[num];  //コピー先
    let pha2 = pedalPhaA2[num][pos];  //コピー元
    for (let i = 0; i < pha1.length; i++) {
      let ph1 = pha1[i];  //コピー先
      let ph2 = pha2[i];  //コピー元
      phLetCopy (ph1, ph2);
      ph1.ph$requested = 7;  //再計算リスエスト
    }
    projectRequested |= 7;  //再描画リクエスト。部分再計算
  }
}


function kenInfoMousedown (e) {
  mouseDownElementType = "ken";
  if (!this.ki$pressed) {  //押されていない
    kenKeyOn (this);  //キーオンする
    kenDown (this);  //押す
  }
  consumeEvent (e);
}

function kenInfoMouseup (e) {
  mouseDownElementType = "";
  if (this.ki$pressed) {  //押されている
    if (pedalPhaK[2] == 0 && !this.ki$sostenuto) {  //ラウドペダルが踏まれていなくてソステヌートがかかっていない
      kenKeyOff (this);  //キーオフする
    }
    kenUp (this);  //離す
  }
  consumeEvent (e);
}



const OPM_PORT_COUNT = 2;

let channelMask = 0;  //キーオンしているチャンネルのビットをセット
let channelQueue = [];  //キーオンまたはキーオフしたチャンネルのキュー。[0]は最初にキーオンまたはキーオフしたチャンネル
for (let channelNumber = 0; channelNumber < 8 * OPM_PORT_COUNT; channelNumber++) {
  channelQueue[channelNumber] = channelNumber;
}

//  キーオンする
function kenKeyOn (kenInfo) {
  if (kenInfo.ki$keyOn) {  //キーオンしている
    kenKeyOff (kenInfo);  //キーオフする
  }
  kenInfo.ki$keyOn = true;
  mousePlaying = true;  //演奏中
  //チャンネルを選ぶ
  let channelNumber = -1;  //チャンネル番号
  for (let i = 0; i < channelQueue.length; i++) {  //最初にキーオフしたチャンネルを探す
    if ((channelMask & (1 << channelQueue[i])) == 0) {  //キーオフしている
      channelNumber = channelQueue[i];
      channelQueue.splice (i, 1);
      break;
    }
  }
  if (channelNumber < 0) {  //キーオフしているチャンネルが見付からなかった
    channelNumber = channelQueue[0];  //最初にキーオンしたチャンネル
    channelQueue.splice (0, 1);
    let pn = channelNumber >> 3;
    let cn = channelNumber & 7;
    postOpmSet (pn, 0x08, cn);  //KON SLOT<<3|CH
  }
  channelMask |= 1 << channelNumber;
  channelQueue.push (channelNumber);
  kenInfo.ki$channelNumber = channelNumber;
  //ボリュームを設定する
  setVolume (calculateVolume ());
  //キーオンする
  {
    let pn = channelNumber >> 3;
    let cn = channelNumber & 7;
    postOpmSet (pn, 0x28 + cn, kenInfo.ki$keyCode);  //KC
    postOpmSet (pn, 0x30 + cn, kenInfo.ki$keyFraction);  //KF
    postOpmSet (pn, 0x08, ((currentNeiro[1] & 15) << 3) + cn);  //KON SLOT<<3|CH
  }
}

//  キーオフする
function kenKeyOff (kenInfo) {
  if (!kenInfo.ki$keyOn) {  //キーオンしていない
    return;
  }
  kenInfo.ki$keyOn = false;
  let channelNumber = kenInfo.ki$channelNumber;
  kenInfo.ki$channelNumber = -1;
  for (let i = 0; i < channelQueue.length; i++) {
    if (channelQueue[i] == channelNumber) {
      channelQueue.splice (i, 1);
      break;
    }
  }
  channelMask &= ~(1 << channelNumber);
  channelQueue.push (channelNumber);
  let pn = channelNumber >> 3;
  let cn = channelNumber & 7;
  postOpmSet (pn, 0x08, cn);  //KON SLOT<<3|CH
  if (channelMask == 0) {  //キーオンしているチャンネルが残っていない
    mousePlaying = false;  //演奏終了
  }
}

//========================================================================
let mouseElement = null;  //Object  ボタンが押された位置にあるDOM要素
let mousePressed = false;  //boolean  true=ボタンが押されている
let mouseMoved = false;  //boolean  true=最後にボタンが押されてからマウスが動いた
let mouseClickCount = 0;  //int  マウスを動かさずにボタンを押したまたは離した回数
let mouseShiftKey = false;  //boolean  true=マウスのボタンが押されたときShiftキーが押されていた
let mouseCtrlKey = false;  //boolean  true=マウスのボタンが押されたときCtrlキーが押されていた
let mouseAltKey = false;  //boolean  true=マウスのボタンが押されたときAltキーが押されていた
let mouseButton = 0;  //int  押されたボタンの番号
let mouseX = 0;  //double  マウスのX座標
let mouseY = 0;  //double  マウスのY座標
let mouseDx = 0;  //double  マウスのX方向の移動量
let mouseDy = 0;  //double  マウスのY方向の移動量
let mouseDw = 0;  //double  マウスホイールの移動量

let mousePlaying = false;  //true=演奏中
let mouseRolling = false;  //true=回転中
let mouseOpeningClosing = false;  //true=開閉中
let mouseDownElementType = "";  //mousedownが発生した要素の種類。"","yane","fumendai","kenbanfuta","ken","pedal_num"のいずれか。クリックされたとき開閉を開始する。"ken"かどうかでドラッグの動作が変わる

//========================================================================
//タイマー

let projectRequested = 7 << 3;  //再描画リクエスト。0=リクエストなし,7=部分再計算,7<<3=全体再計算

//startProject ()
//  強制的に描画して次回の描画を予約する
function startProject () {
  projectRequested = 7 << 3;  //再描画リクエスト。全体再計算
  project ();
  projectTimeoutId = setTimeout (tickProject, PROJECT_INTERVAL);
}

//stopProject ()
//  描画を停止する
function stopProject () {
  clearTimeout (projectTimeoutId);
  projectTimeoutId = 0;
}

//tickProject ()
//  再描画リクエストがあるときだけ描画して次回の描画を予約する
function tickProject () {
  if (fumendaiPhaD != 0) {  //譜面台を開閉中
    fumendaiTask ();
  }
  if (kenbanfutaPhaD != 0) {  //鍵盤蓋を開閉中
    kenbanfutaTask ();
  }
  if (yanePhaD != 0) {  //屋根を開閉中
    yaneTask ();
  }
  if (mouseDx || mouseDy || mouseDw) {
    projectRequested = 7 << 3;  //再描画リクエスト。全体再計算
  }
  if ((projectRequested & (9 << screenMode)) != 0) {  //全体再計算または部分再計算の再描画リクエストがある
    project ();
  }
  projectTimeoutId = setTimeout (tickProject, PROJECT_INTERVAL);
}

//========================================================================
//投影モード

//setScreenMode (mode)
//  画面モードを変更する
function setScreenMode (mode) {
  if (screenMode != mode) {
    stopProject ();
    screenMode = mode;
    if (mode == 0) {
      setDisplay (svgElementArray[1], "none");
      setDisplay (svgElementArray[2], "none");
      setDisplay (svgElementArray[0], "block");
    } else {
      setDisplay (svgElementArray[0], "none");
      setDisplay (svgElementArray[1], "block");
      setDisplay (svgElementArray[2], "block");
    }
    resetSize ();
    startProject ();
    setButtonActiveSelected (normalButtonArray, mode != 0, mode == 0);
    setButtonActiveSelected (parallelButtonArray, mode != 1, mode == 1);
    setButtonActiveSelected (crossButtonArray, mode != 2, mode == 2);
  }
}

//  床固定モードを切り替える
function toggleFloorFixedMode () {
  stopProject ();
  floorFixed = !floorFixed;
  if (floorFixed) {
    latitudeRad = max2 (-PI_2, min2 (PI_2, latitudeRad));
    nRotateYZ (nLetIRotateXY (rotationMatrix, -longitudeRad), latitudeRad);
  }
  startProject ();
  setButtonActiveSelected (floorFixedButtonArray, true, floorFixed);
}

//========================================================================
//描画

//project ()
//  カメラの位置の変化を処理してからすべてのスクリーンを描画する
function project () {
  let dx = mouseDx;  //移動量の合計
  let dy = mouseDy;
  let dw = mouseDw;
  if (dx) {
    mouseDx -= dx;  //移動量の合計をキャンセルする
    longitudeRad = frem (longitudeRad - dx * PI, TWOPI);
    if (floorFixed) {
      nRotateYZ (nLetIRotateXY (rotationMatrix, -longitudeRad), latitudeRad);
    } else {
      nRotateXY (rotationMatrix, dx * PI);
    }
  }
  if (dy) {
    mouseDy -= dy;
    if (floorFixed) {
      latitudeRad = max2 (-PI_2, min2 (PI_2, latitudeRad + dy * PI));
      nRotateYZ (nLetIRotateXY (rotationMatrix, -longitudeRad), latitudeRad);
    } else {
      latitudeRad = frem (latitudeRad + dy * PI + PI, TWOPI) - PI;
      nRotateYZ (rotationMatrix, dy * PI);
    }
  }
  if (dw) {
    mouseDw -= dw;
    cameraDistanceMm = max2 (minimumCameraDisatanceMm, cameraDistanceMm * (1 + 0.1 * dw));
  }
  if (screenMode == 0) {
    projectScreen (globalScreenArray[0]);
  } else {
    projectScreen (globalScreenArray[1]);
    projectScreen (globalScreenArray[2]);
  }
}



let ambient = 0;  //環境光
let lightPositionArray = [[0, 0, pianoZ1 + 4000],
                          [pianoX0 - 3000, pianoY0 - 2000, pianoZ0 - 1000],
                          [pianoX1 + 3000, pianoY0 - 2000, pianoZ0 - 1000],
                          [pianoX0 - 3000, pianoY1 + 2000, pianoZ0 - 1000],
                          [pianoX1 + 3000, pianoY1 + 2000, pianoZ0 - 1000]];
let lightColorArray = [[1, 1, 1],
                       [0.5, 0.5, 0.5],
                       [0.5, 0.5, 0.5],
                       [0.1, 0.1, 0.1],
                       [0.1, 0.1, 0.1]];

//projectScreen (screen)
//  Object screen
//  スクリーンを描画する
function projectScreen (screen) {

  let screenNumber = screen.sc$screenNumber;
  let screenMask = 1 << screenNumber;

  let savedProjectRequested = projectRequested;  //再描画リクエストを保存する。描画中にリクエストがあったとき取りこぼさないようにするため
  projectRequested &= ~(screenMask | (screenMask << 3));

  let svgElement = svgElementArray[screenNumber];

  let focalLengthPx = screen.sc$focalLengthPx;
  let ox = screen.sc$viewX + screen.sc$viewWidth * 0.5;
  let oy = screen.sc$viewY + screen.sc$viewHeight * 0.5;

  let shiftPx = (screenNumber == 0 ? 0 : screenNumber == screenMode ? 0.25 : -0.25) * pupillaryDistancePx;  //瞳孔間距離[px]/4
  let miniatureMatrix = nTranslateX (nScaleS (nNewTranslateY (rotationMatrix,  //経度と緯度を回転させる変換行列をコピーする
                                                              cameraDistanceMm),  //カメラを原点にする
                                              focalLengthPx / focalLengthMm),  //焦点距離[px]/焦点距離[mm]を掛けてmm単位からpx単位に変換する
                                     2 * shiftPx);  //立体視のとき瞳孔間距離[px]/2ずらす

  //光源
  let miniatureLightPositionArray = [];
  for (let i = 0; i < lightPositionArray.length; i++) {
    miniatureLightPositionArray[i] = wNewTransform (lightPositionArray[i], miniatureMatrix);
  }

  let hakoVisibleArrayArray = [];

  //箱ループ
  for (let hakoNumber = 0; hakoNumber < hakoArray.length; hakoNumber++) {
    let hako = hakoArray[hakoNumber];
    let hakoVisibleArray = hakoVisibleArrayArray[hakoNumber] = [];  //この箱で見えている多面体の配列

    //中身ループ
    for (let nakamiNumber = 0; nakamiNumber < hako.length; nakamiNumber++) {
      let polyhedron = hako[nakamiNumber];

      let faceArray = polyhedron.ph$faceArray;
      let polygonArray = polyhedron.ph$polygonArray;

      if ((savedProjectRequested & (screenMask << 3)) == 0 &&  //部分再計算で
          (polyhedron.ph$requested & screenMask) == 0 &&  //多面体の再計算がリクエストされておらず
          (polyhedron.ph$visible & screenMask) != 0) {  //前回表示したとき
        hakoVisibleArray.push (polyhedron);  //今回も表示する
        continue;
      }

      polyhedron.ph$requested &= ~screenMask;
      if (polyhedron.ph$isWaku) {
        polyhedron.ph$visible |= screenMask;  //枠は無条件に表示する
      } else {
        polyhedron.ph$visible &= ~screenMask;
      }

      let vertexArray = polyhedron.ph$vertexArray;  //頂点の配列
      let miniatureVertexArray = polyhedron.ph$miniatureVertexArray;  //投影する直前の頂点の配列
      let projectedVertexArray = polyhedron.ph$projectedVertexArray;  //投影された頂点の配列。[0]がNaNのとき範囲外

      //頂点を回転させて投影する
      for (let vn = 0; vn < vertexArray.length; vn++) {
        let v = vertexArray[vn];
        let mv = miniatureVertexArray[vn];
        let pv = projectedVertexArray[vn];
        wLetTransform (mv, v, miniatureMatrix);  //投影する直前の頂点
        if (mv[1] <= 0) {  //カメラの真横または後ろにある
          pv[0] = NaN;
        } else {  //カメラの前にある
          let s = focalLengthPx / mv[1];
          pv[0] = ox + s * mv[0] - shiftPx;  //立体視のとき瞳孔間距離[px]/4戻す
          pv[1] = oy - s * mv[2];
        }
      }

      let normalArray = polyhedron.ph$normalArray;
      let miniatureNormalArray = polyhedron.ph$miniatureNormalArray;

      let rgb = colorToRGB (polyhedron.ph$color);  //多面体の色

      let openArray = polyhedron.ph$openArray;

      //面ループ
    faceLoop:
      for (let faceNumber = 0; faceNumber < faceArray.length; faceNumber++) {
        if (openArray[faceNumber]) {  //閉じない
          continue;
        }

        let face = faceArray[faceNumber];
        let vertexCount = face.length;  //頂点の数

        let polygon = polygonArray[faceNumber];
        polygon.pg$visible &= ~screenMask;

        //法線ベクトル
        let miniatureNormal = wLetTransform (miniatureNormalArray[faceNumber], normalArray[faceNumber], rotationMatrix);

        //すべての頂点を投影できているか
        for (let i = 0; i < vertexCount; i++) {
          if (isNaN (projectedVertexArray[face[i]][0])) {  //カメラの真横または後ろにある
            continue faceLoop;
          }
        }

        //回転後の面の中心を求める
        let g = [0, 0, 0];
        if (false) {
          //  三角形のときは重心。頂点が多い方に偏る
          for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
            wTranslateW (g, miniatureVertexArray[face[vertexIndex]]);
          }
          g[0] /= vertexCount;
          g[1] /= vertexCount;
          g[2] /= vertexCount;
        } else {
          //  外接する矩形の中心。面に乗っているとは限らない
          let xmin = Infinity;
          let xmax = -Infinity;
          let ymin = Infinity;
          let ymax = -Infinity;
          let zmin = Infinity;
          let zmax = -Infinity;
          for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
            let w = miniatureVertexArray[face[vertexIndex]];
            if (w[0] < xmin) {
              xmin = w[0];
            }
            if (xmax < w[0]) {
              xmax = w[0];
            }
            if (w[1] < ymin) {
              ymin = w[1];
            }
            if (ymax < w[1]) {
              ymax = w[1];
            }
            if (w[2] < zmin) {
              zmin = w[2];
            }
            if (zmax < w[2]) {
              zmax = w[2];
            }
          }
          g[0] = (xmin + xmax) * 0.5;
          g[1] = (ymin + ymax) * 0.5;
          g[2] = (zmin + zmax) * 0.5;
        }
        //視点ベクトルを求める
        let geye = wSubtractW ([0, 0, 0], g);
        let cosAngle = wCosAngle (miniatureNormal, geye);
        if (cosAngle <= 0) {  //面の法線ベクトルと視点ベクトルのなす角が90度以上なので表面が見えていない
          continue faceLoop;
        }
        //形を決める
        let points = "";
        for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
          if (0 < vertexIndex) {
            points += " ";
          }
          let p = projectedVertexArray[face[vertexIndex]];
          points += p[0] + " " + p[1];
        }
        let element = polygon.pg$elementArray[screenNumber];
        movePolygon (element, points);
        //色を決める
        let red = ambient;
        let green = ambient;
        let blue = ambient;
        for (let i = 0; i < miniatureLightPositionArray.length; i++) {
          let lightColor = lightColorArray[i];
          let glight = wNewSubtractW (miniatureLightPositionArray[i], g);  //面の中心における光源ベクトル
          let zarazara = max2 (0, wCosAngle (miniatureNormal, glight));  //法線ベクトルと光源ベクトルのなす角のコサイン。0~1。ざらざらのとき大きく反映させる
          red += rgb[0] * zarazara * lightColor[0];
          green += rgb[1] * zarazara * lightColor[1];
          blue += rgb[2] * zarazara * lightColor[2];
        }
        let color = RGBtoColor ([red, green, blue]);
        setFill (element, color);
        setStroke (element, color);
        //今回表示する多角形のリストに加える
        polygon.pg$visible |= screenMask;  //今回表示する。動かなければ表示し続ける
        polyhedron.ph$visible |= screenMask;  //今回表示する。動かなければ表示し続ける
      }  //for faceNumber

      if ((polyhedron.ph$visible & screenMask) != 0) {  //今回表示する。動かなければ表示し続ける
        hakoVisibleArray.push (polyhedron);  //多面体を表示する
      }

    }  //for nakamiNumber

  }  //for hakoNumber

  //カメラを逆回転させる
  inversedCamera = wTransform ([0, 0, 0], nNewInverse (miniatureMatrix));

  //箱毎にソートする
  for (let hakoNumber = 0; hakoNumber < hakoArray.length; hakoNumber++) {
    inferentialSort (hakoVisibleArrayArray[hakoNumber], comparator);
  }

  //枠に箱をはめ込む
  let newVisibleArray = hakoVisibleArrayArray[0].concat ();  //全体
  for (let i = 0; i < newVisibleArray.length; i++) {
    let ph = newVisibleArray[i];
    if (ph.ph$isWaku) {  //枠のとき
      let hakoVisibleArray = hakoVisibleArrayArray[ph.ph$hakoNumber];
      Array.prototype.splice.apply (newVisibleArray, [i, 1, ...hakoVisibleArray]);
      //i += hakoVisibleArray.length - 1;
      i--;  //ネストできるようにする
    }
  }

  //コントロールパネルを消す
  removeNode (svgElement, controlPanelArray[screenNumber]);

  //前回表示した多面体を消す
  let oldVisibleArray = screen.sc$visibleArray[screenNumber];
  for (let k = oldVisibleArray.length - 1; 0 <= k; k--) {  //手前から消す
    let polyhedron = oldVisibleArray[k];
    let polygonArray = polyhedron.ph$polygonArray;
    for (let i = polygonArray.length - 1; 0 <= i; i--) {
      let polygon = polygonArray[i];
      if ((polygon.pg$displayed & screenMask) != 0) {  //現在表示されている
        removeNode (svgElement, polygon.pg$elementArray[screenNumber]);  //消す
        polygon.pg$displayed &= ~screenMask;
      }
    }
  }

  //今回表示する多面体を描く
  for (let k = 0; k < newVisibleArray.length; k++) {  //奥から描く
    let polyhedron = newVisibleArray[k];
    let polygonArray = polyhedron.ph$polygonArray;
    for (let i = polygonArray.length - 1; 0 <= i; i--) {
      let polygon = polygonArray[i];
      if ((polygon.pg$visible & screenMask) != 0) {  //今回表示する。動かなければ表示し続ける
        appendNode (svgElement, polygon.pg$elementArray[screenNumber]);
        polygon.pg$displayed |= screenMask;
      }
    }
  }
  screen.sc$visibleArray[screenNumber] = newVisibleArray;

  //コントロールパネルを描く
  appendNode (svgElement, controlPanelArray[screenNumber]);

}



//result = comparator (ph1, ph2)
//
//  機能
//    物体を奥→手前の順にソートするためのコンパレータ
//    視点から物体までの距離の降順に近いが同じとは限らない
//    物体のシルエットが重なっていないときはソーターに判断を委ねる
//
//  返却値
//    -1  <  ph1→ph2。ph1とph2のシルエットが重なっていて、ph1が奥、ph2が手前
//     0  ?  不明。ph1とph2のシルエットが重なっていない。ソーターに判断を委ねる
//     1  >  ph2→ph1。ph1とph2のシルエットが重なっていて、ph2が奥、ph1が手前
//
//  制約
//    どの2つの物体を選んでも、少なくとも一方の物体の外接直方体が他方の物体に食い込まないように配置すること
//
//  手順
//    ソートする前に、視点を逆回転させて物体を投影回転させる前の座標に移しておく
//    以下をph1とph2を入れ替えて2回行う
//      ph1の単位立方体変換行列を用いて、視点aを変換する
//      ph2の投影回転前の頂点毎に
//        ph1の単位立方体変換行列を用いて、ph2の投影回転前の頂点v[i]を変換する
//      ph2の面f2毎に
//        ph2の面f2が見えているとき(最初の頂点から見た視点ベクトルと法線ベクトルのドット積が正のとき)
//          ph2の面f2の頂点b毎に
//            ph1の単位立方体の面毎に
//              ph1の単位立方体の面が見えているとき(面の外側に視点aがあるとき)
//                視点aと、ph2の面f2の頂点bを、通る直線と、ph1の単位立方体の面の、交点cを求める
//                交点cが(0,1)-(0,1)-(0,1)の範囲にあるとき
//                  ph1の面の交点cから見て、視点aと、ph2の面f2の頂点bが、同じ側にあるとき(a-cとb-cのドット積が正のとき)
//                    ph1の面の交点c → ph2の面f2の頂点b → 視点a の順なので、-1を返す
//                  ph1の面の交点cから見て、視点aと、ph2の面f2の頂点bが、反対側にあるとき(a-cとb-cのドット積が負のとき)
//                    ph2の面f2の頂点b → ph1の面の交点c → 視点a の順なので、1を返す
//          ph2の面f2の辺(b,c)毎に
//            視点aと、ph2の面f2の辺(b,c)を、通る平面の方程式p*x+q*y+r*z+s=0を作る
//            ph1の単位立方体の辺毎に
//              ph1の単位立方体の辺が見えているとき(ph1の辺を挟む面のどちらかの外側に視点aがあるとき)
//                視点aと、ph2の面f2の辺(b,c)を、通る平面と、ph1の単位立方体の辺の、交点dを求める
//                交点dが(0,1)の範囲にあるとき
//                  視点aと、ph1の辺の交点dを、通る直線と、ph2の面f2の辺(b,c)の、交点eを求める
//                  交点eが(b,c)の範囲にあるとき
//                    ph2の面f2の辺(b,c)の交点eから見て、視点aと、ph1の辺の交点dが、同じ側にあるとき(a-eとd-eのドット積が正のとき)
//                      ph2の面f2の辺(b,c)の交点e → ph1の辺の交点d → 視点a の順なので、1を返す
//                    ph2の面f2の辺(b,c)の交点eから見て、視点aと、ph1の辺の交点dが、反対側にあるとき(a-eとd-eのドット積が負のとき)
//                      ph1の辺の交点d → ph2の面f2の辺(b,c)の交点e → 視点a の順なので、-1を返す
//
//  重なり判定
//    2つの物体のシルエットが重なっているかどうかをなるべく正確に判定する
//    見えている面、見えている辺、見えている頂点について、
//    一方の頂点と他方の外接直方体の面
//    一方の辺と他方の外接直方体の辺
//    を用いて重なり判定を行う
//            /\          /\    /\
//          /    \      /    \/    \
//        /  /\  \    \      \    /
//      /  /    \  \    \      \/
//      \  \    /  /    /\      \
//        \  \/  /    /    \      \
//          \    /      \    /\    /
//            \/          \/    \/
//             (A)               (B)
//    (A)は辺と辺の重なり判定だけでは重なっていることがわからない
//    (B)は頂点と面の重なり判定だけでは重なっていることがわからない
//
//  2つの物体が重なっていない場合について
//
//    2つの物体のシルエットが重なっていない場合は、どちらが手前にあるか判断することは困難
//      以下の配置でAとBを比較したとき、BはAよりも手前にあると判断しなければならないが、
//
//                            /\
//                          / C  \
//            /\        /      /
//          / A  \    /      /
//          \    /  /      /  /\
//            \/  /      /  / B  \
//                /      /    \    /
//              /      /        \/
//              \    /
//                \/
//
//
//                       ◎視点
//
//      AとBと視点の位置関係がまったく同じ以下の配置でAとBを比較したとき、BがAよりも手前にあると判断してしまうと、
//      Aより奥にあるCとBの比較が行われず、BがCの手前に表示されてしまう
//
//                /\
//              / C  \
//            /\      \
//          / A  \      \
//          \    /\      \    /\
//            \/    \      \/ B  \
//                      \      \    /
//                        \      \/
//                          \    /
//                            \/
//
//
//                       ◎視点
//
//      Cを半分に分ける方法はその場しのぎになるが根本的な解決にはならない
//
//                /\
//              / C1 \
//            /\      \
//          / A  \      \
//          \    /\    /\    /\
//            \/    \/ C2 \/ B  \
//                      \      \    /
//                        \      \/
//                          \    /
//                            \/
//
//
//                       ◎視点
//
//    ここでは、2つの物体のシルエットが重なっていないとわかった時点でどちらが手前か判断するのを諦め、ソーターに推論を委ねる
//    前述のように他の物体が挟まっていればソーターがどちらが手前か判断してソートする
//    他の物体が挟まっていないと視点からの距離の降順と異なる順序でソートしてしまう可能性があるが、
//    シルエットが重なっていないので逆順に表示しても見た目に問題はない
//
let comparatorWork1 = wNewZero ();
let comparatorWork2 = [];
let comparatorWork3 = wNewZero ();
let comparatorWork4 = nNewIdentity ();
function comparator (ph1, ph2) {
  const eps = 1e-4;
  let result = 0;
comparator:
  for (let sign = 1; -1 <= sign; sign -= 2) {
    //以下をph1とph2を入れ替えて2回行う
    let rm1 = ph1.ph$reciprocalMatrix;  //ph1の単位立方体変換行列
    let rmr1 = nLetRotationComponent (comparatorWork4, rm1);  //ph1の単位立方体変換行列の回転成分
    //ph1の単位立方体変換行列を用いて、視点aを変換する
    let a = wLetTransform (comparatorWork1, inversedCamera, rm1);  //視点a
    let ax = a[0];
    let ay = a[1];
    let az = a[2];
    let va2 = ph2.ph$vertexArray;
    let va2l = va2.length;
    let va = comparatorWork2;
    for (let i = va.length; i < va2l; i++) {
      va[i] = wNewZero ();
    }
    for (let i = 0; i < va2l; i++) {
      //ph2の投影回転前の頂点毎に
      //ph1の単位立方体変換行列を用いて、ph2の投影回転前の頂点v[i]を変換する
      wLetTransform (va[i], va2[i], rm1);
    }
    let fa2 = ph2.ph$faceArray;
    let fa2l = fa2.length;
    let na2 = ph2.ph$normalArray;
    for (let i = 0; i < fa2l; i++) {
      //ph2の面f2毎に
      let f2 = fa2[i];
      let n = wLetTransform (comparatorWork3, na2[i], rmr1);  //面f2の法線ベクトル
      let v0 = va[f2[0]];  //最初の頂点
      if ((ax - v0[0]) * n[0] + (ay - v0[1]) * n[1] + (az - v0[2]) * n[2] < eps) {
        continue;
      }
      //ph2の面f2が見えているとき(最初の頂点から見た視点ベクトルと法線ベクトルのドット積が正のとき)
      let f2l = f2.length;
      for (let j = 0; j < f2l; j++) {
        //ph2の面f2の頂点b毎に
        let b = va[f2[j]];
        let bx = b[0];
        let by = b[1];
        let bz = b[2];
        //ph1の単位立方体の面毎に
        //  x=0
        if (ax < -eps) {
          //ph1の単位立方体の面が見えているとき(面の外側に視点aがあるとき)
          //視点aと、ph2の面f2の頂点bを、通る直線と、ph1の単位立方体の面の、交点cを求める
          //  平面p*x+q*y+r*z+s=0と2点a,bを通る直線の交点c
          //    cx=((ay*bx-ax*by)*q+(az*bx-ax*bz)*r+(bx-ax)*s)/((ax-bx)*p+(ay-by)*q+(az-bz)*r)
          //    cy=((ax*by-ay*bx)*p+(az*by-ay*bz)*r+(by-ay)*s)/((ax-bx)*p+(ay-by)*q+(az-bz)*r)
          //    cz=((ax*bz-az*bx)*p+(ay*bz-az*by)*q+(bz-az)*s)/((ax-bx)*p+(ay-by)*q+(az-bz)*r)
          //  x=0(1*x+0*y+0*z+0=0)
          //    cx=0
          //    cy=(ax*by-ay*bx)/(ax-bx)  0<=cy<=1
          //    cz=(ax*bz-az*bx)/(ax-bx)  0<=cz<=1
          const cx = 0;
          let cy = ((ax - cx) * by - ay * (bx - cx)) / (ax - bx);
          let cz = ((ax - cx) * bz - az * (bx - cx)) / (ax - bx);
          if (-eps < cx && cx < 1 + eps &&
              -eps < cy && cy < 1 + eps &&
              -eps < cz && cz < 1 + eps) {
            //交点cが(0,1)-(0,1)-(0,1)の範囲にあるとき
            let dot = (ax - cx) * (bx - cx) + (ay - cy) * (by - cy) + (az - cz) * (bz - cz);
            if (eps < dot) {
              //ph1の面の交点cから見て、視点aと、ph2の面f2の頂点bが、同じ側にあるとき(a-cとb-cのドット積が正のとき)
              //ph1の面の交点c → ph2の面f2の頂点b → 視点a の順なので、-1を返す
              result = -sign;
              break comparator;
            }
            if (dot < -eps) {
              //ph1の面の交点cから見て、視点aと、ph2の面f2の頂点bが、反対側にあるとき(a-cとb-cのドット積が負のとき)
              //ph2の面f2の頂点b → ph1の面の交点c → 視点a の順なので、1を返す
              result = sign;
              break comparator;
            }
          }
        }
        //  x=1
        if (1 + eps < ax) {
          //  x=1(1*x+0*y+0*z-1=0)
          //    cx=1
          //    cy=((ax-1)*by-ay*(bx-1))/(ax-bx)  0<=cy<=1
          //    cz=((ax-1)*bz-az*(bx-1))/(ax-bx)  0<=cz<=1
          const cx = 1;
          let cy = ((ax - cx) * by - ay * (bx - cx)) / (ax - bx);
          let cz = ((ax - cx) * bz - az * (bx - cx)) / (ax - bx);
          if (-eps < cx && cx < 1 + eps &&
              -eps < cy && cy < 1 + eps &&
              -eps < cz && cz < 1 + eps) {
            let dot = (ax - cx) * (bx - cx) + (ay - cy) * (by - cy) + (az - cz) * (bz - cz);
            if (eps < dot) {
              result = -sign;
              break comparator;
            }
            if (dot < -eps) {
              result = sign;
              break comparator;
            }
          }
        }
        //  y=0
        if (ay < -eps) {
          //  y=0(0*x+1*y+0*z+0=0)
          //    cx=(ay*bx-ax*by)/(ay-by)
          //    cy=0
          //    cz=(ay*bz-az*by)/(ay-by)
          const cy = 0;
          let cz = ((ay - cy) * bz - az * (by - cy)) / (ay - by);
          let cx = ((ay - cy) * bx - ax * (by - cy)) / (ay - by);
          if (-eps < cx && cx < 1 + eps &&
              -eps < cy && cy < 1 + eps &&
              -eps < cz && cz < 1 + eps) {
            let dot = (ax - cx) * (bx - cx) + (ay - cy) * (by - cy) + (az - cz) * (bz - cz);
            if (eps < dot) {
              result = -sign;
              break comparator;
            }
            if (dot < -eps) {
              result = sign;
              break comparator;
            }
          }
        }
        //  y=1
        if (1 + eps < ay) {
          //  y=1(0*x+1*y+0*z-1=0)
          //    cx=((ay-1)*bx-ax*(by-1))/(ay-by)
          //    cy=1
          //    cz=((ay-1)*bz-az*(by-1))/(ay-by)
          const cy = 1;
          let cz = ((ay - cy) * bz - az * (by - cy)) / (ay - by);
          let cx = ((ay - cy) * bx - ax * (by - cy)) / (ay - by);
          if (-eps < cx && cx < 1 + eps &&
              -eps < cy && cy < 1 + eps &&
              -eps < cz && cz < 1 + eps) {
            let dot = (ax - cx) * (bx - cx) + (ay - cy) * (by - cy) + (az - cz) * (bz - cz);
            if (eps < dot) {
              result = -sign;
              break comparator;
            }
            if (dot < -eps) {
              result = sign;
              break comparator;
            }
          }
        }
        //  z=0
        if (az < -eps) {
          //  z=0(0*x+0*y+1*z+0=0)
          //    cx=(az*bx-ax*bz)/(az-bz)
          //    cy=(az*by-ay*bz)/(az-bz)
          //    cz=0
          const cz = 0;
          let cx = ((az - cz) * bx - ax * (bz - cz)) / (az - bz);
          let cy = ((az - cz) * by - ay * (bz - cz)) / (az - bz);
          if (-eps < cx && cx < 1 + eps &&
              -eps < cy && cy < 1 + eps &&
              -eps < cz && cz < 1 + eps) {
            let dot = (ax - cx) * (bx - cx) + (ay - cy) * (by - cy) + (az - cz) * (bz - cz);
            if (eps < dot) {
              result = -sign;
              break comparator;
            }
            if (dot < -eps) {
              result = sign;
              break comparator;
            }
          }
        }
        //  z=1
        if (1 + eps < az) {
          //  z=1(0*x+0*y+1*z-1=0)
          //    cx=((az-1)*bx-ax*(bz-1))/(az-bz)
          //    cy=((az-1)*by-ay*(bz-1))/(az-bz)
          //    cz=1
          const cz = 1;
          let cx = ((az - cz) * bx - ax * (bz - cz)) / (az - bz);
          let cy = ((az - cz) * by - ay * (bz - cz)) / (az - bz);
          if (-eps < cx && cx < 1 + eps &&
              -eps < cy && cy < 1 + eps &&
              -eps < cz && cz < 1 + eps) {
            let dot = (ax - cx) * (bx - cx) + (ay - cy) * (by - cy) + (az - cz) * (bz - cz);
            if (eps < dot) {
              result = -sign;
              break comparator;
            }
            if (dot < -eps) {
              result = sign;
              break comparator;
            }
          }
        }
      }  //for j
      for (let j = 0; j < f2l; j++) {
        //ph2の面f2の辺(b,c)毎に
        let b = va[f2[j]];
        let bx = b[0];
        let by = b[1];
        let bz = b[2];
        let c = va[f2[j + 1 < f2l ? j + 1 : 0]];
        let cx = c[0];
        let cy = c[1];
        let cz = c[2];
        //視点aと、ph2の面f2の辺(b,c)を、通る平面の方程式p*x+q*y+r*z+s=0を作る
        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);
        //ph1の単位立方体の辺毎に
        //
        //               +-------------------+
        //           (L)/|        (K)       /|
        //      x=0,z=1/ |(G)   y=1,z=1 (J)/ |
        //          z /  |x=0,y=1  x=1,z=1/  |
        //          |/   |               /   |(F)
        //         1+-------------------+    |x=1,y=1
        //          |    |   (I)        |    |
←Previous | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Next→