dfsafsadfasdfdsf

🧩 Syntax:
/**
 * 2次元ベクトルのクラス
 */
class Vec2 {
  /**
   * @param {number} x成分
   * @param {number} y成分
   */
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  /**
   * @param {Vec2} b 足したいベクトル
   */
  add(b) {
    let a = this;
    return new Vec2(a.x+b.x, a.y+b.y);
  }
  /**
   * @param {Vec2} b 引きたいベクトル
   */
  sub(b) {
    let a = this;
    return new Vec2(a.x-b.x, a.y-b.y);
  }
  /**
   * @param {number} s ベクトルにかけたい実数
   */
  mul(s) {
    return new Vec2(s*this.x, s*this.y);
  }
  /**
   * @param {number} s この実数でベクトルを割る
   */
  div(s) {
    return new Vec2(this.x/s, this.y/s);
  }
  /**
   * @param {Vec2} v このベクトルとドット積をとる
   */
  dot(b) {
    let a = this;
    return a.x*b.x + a.y*b.y;
  }
  /**
   * @returns ベクトルの大きさ(成分のユークリッド距離)
   */
  mag() {
    return sqrt(this.x ** 2 + this.y ** 2);
  }
  /**
   * @param {number} s 大きさをsとしたベクトルを返す
   */
  magSet(s) {
    return this.mul(s/this.mag());
  }
  /**
   * @param {number} s 大きさにsを足したベクトルを返す
   */
  magAdded(s) {
    return this.mul(1+s/this.mag());
  }
  /**
   * @param {number} rad 回転させたい角度。単位はラジアン。
   */
  rotated(rad) {
    return new Vec2(
      this.x*cos(rad) - this.y*sin(rad),
      this.x*sin(rad) + this.y*cos(rad)
    );
  }
  /**
   * @returns 正規化されたベクトル
   */
  normalized() {
    return this.mul(1/this.mag());
  }
  copy() {
    return new Vec2(this.x, this.y);
  }
  /**
   * @param {Vec2} b このベクトルと成分が等しいか否かを返す
   */
  equals(b) {
    let a = this;
    return a.x === b.x && a.y === b.y;
  }
}

class Camera {
  constructor(pos, angle) {
    this.pos = pos;
    this.angle = angle;
  }
}

class SankakuTry{
  //sankakuX,sankakuY,sankakuZ,sankakuColor
  constructor(){
    this.sankakuX = 0;
    this.sankakuY = 1;//この場合は1以外はだめ
    this.sankakuZ = 0;
    //this.pos=[0,0,0];
    this.sankakuColor='red';
    this.V = [
      [0, 0, 0],
      [1, 0, 0],
      [0, 0, 1],
      [0, -1, 0]
    ];
    this.S = [];
    this.vertsInCamCoord = [];
    this.F = [
      [0, 2, 1],
      [0, 1, 3],
      [1, 2, 3],
      [0, 3, 2]
    ];

  }
  
  getZPosInCamCoord() {
    return this.vertsInCamCoord[0][2];
  }
  
  proc(){
    let ft = (A,x,y,z) => A.map(a => [a[0]+x, a[1]+y, a[2]+z]);
    let fs = (A,x,y,z) => A.map(a => [x*a[0], y*a[1], z*a[2]]);
    let fr = (A,r) => A.map(
      a => [
        a[0]*cos(r) + a[2]*sin(r),
        a[1],
        -a[0]*sin(r) + a[2]*cos(r)
      ]
    );
    let fp = (A) => A.map(a => [a[0]/a[2], a[1]/a[2]]);
    let fc = (A,w,h) => A.map(a => [width*a[0]+width/2, height*a[1]+height/2]);

    let t = millis()/1000;
    let S = this.V;
    
    // モデルを回転&平行移動
    S = fr(S, 2*t);
    S = ft(S,this.sankakuX,this.sankakuY,this.sankakuZ);//S = ft(S, 0, 1, 1);  5,1,1//this.sankakuX,this.sankakuY,this.sankakuZ
    
    // カメラに応じて平行移動&回転
    S = ft(S, -game.camera.pos[0], -game.camera.pos[1],-game.camera.pos[2]);
    S = fr(S, -game.camera.angle);
    
    this.vertsInCamCoord = ft(S, 0, 0, 0);
    
    // 投影面の後ろにあるときは描画しない
    if (S[0][2] < 1) return;

    // 投影
    S = fp(S);
    S = fc(S, width, height);
    
    this.S = S;
  }
  draw() {
    fill(this.sankakuColor);
    let S = this.S;
    if (S.length === 0) return;
    for(let i=0; i<this.F.length; i++) {
      triangle(
        ...S[this.F[i][0]], ...S[this.F[i][1]], ...S[this.F[i][2]]
      );
    }
  }
}

class ChickenDrawer{
  /**
   * ひよこ?を描画するコマンド
   * @param {Vec2} pos 足元の位置ベクトル
   * @param {number} angle Z軸の角度
   * @param {number} animFrame アニメーションの何フレーム目を再生するか
   * @param {number} depth
   */
  constructor(pos, angle, animFrame, depth) {
    this.pos = pos;
    this.angle = angle;
    this.animFrame = animFrame;
    this.depth = depth;
    this.zPosInCamCoord = 1;
  }

  getZPosInCamCoord() {
    return this.zPosInCamCoord;
  }
  
  draw() {
    let pos = this.pos;
    let angle = this.angle;
    let animFrame = this.animFrame;

    /** 角度を0~2πに正規化する。0.0001足しているのは誤差によるバグを避けるため */
    let normalized = a => ((a+0.0001+10000*PI) % (2*PI));
    let isFacingUs = a => (a = normalized(a), (a >= -PI/36 && a <= 37*PI/36));
    let isFacingUsNarrow = a => (a = normalized(a), (a >= PI/12 && a <= 11*PI/12));
    angle = normalized(angle);

    let shake = new Vec2(0, ((animFrame % 20) > 10) ? -2 : 0);
    let bodyCenter = pos.add(new Vec2(0,-16)).add(shake);
    let assCenter = isFacingUsNarrow(angle+PI) ? bodyCenter.add(new Vec2(12*cos(angle+PI), -4)) : null;
    let headCenter = bodyCenter.add(new Vec2(6*cos(angle),-16)).sub(shake);
    let beakCenter = isFacingUs(angle) ? headCenter.add(new Vec2(12*cos(angle),0)) : null;
    let eyes = [];
    if (isFacingUs(angle-PI/3)) eyes.push(headCenter.add(new Vec2(8*cos(angle-PI/3),-4)));
    if (isFacingUs(angle+PI/3)) eyes.push(headCenter.add(new Vec2(8*cos(angle+PI/3),-4)));

    push();
    stroke('black');
    strokeWeight(2);
    fill('yellow');
    let drawBody = _=>circle(bodyCenter.x, bodyCenter.y, 32);
    let drawHead = _=>circle(headCenter.x, headCenter.y, 24);
    if (isFacingUs(angle)) {
      drawBody();drawHead();
    } else {
      drawHead();drawBody();
    }
    fill('black');
    strokeWeight(0);
    for(let eye of eyes){
      circle(eye.x, eye.y, 3);
    }
    fill('orange')
    strokeWeight(1);
    if (beakCenter !== null) {
      circle(beakCenter.x, beakCenter.y, 8);
    }

    fill('black');
    textAlign(CENTER, CENTER);
    textSize(10);
    strokeWeight(1);
    if (assCenter !== null) {
      text('x', assCenter.x, assCenter.y);
    }
    pop();
  }
}

class Actor {
  constructor() {
    this.x = 0;
    this.y = 0;
    this.z = 0;
    this.angle = 0;
  }
}

class Game {
  constructor() {
    //----- ゲームの舞台を生成
    this.field = [
      [0,0,0,0,0,0,0,0,0,0,0,0,0],
      [0,0,1,1,1,0,0,0,0,0,0,0,0],
      [0,2,1,0,0,1,1,0,0,0,0,0,0],
      [0,1,3,0,0,0,0,1,1,1,0,0,0],
      [0,1,0,0,0,0,0,0,0,0,1,1,0],
      [0,1,0,0,0,0,0,1,0,0,0,1,0],
      [0,1,0,0,2,1,1,0,1,0,2,1,0],
      [0,1,1,1,1,3,0,0,0,1,1,3,0],
      [0,0,0,0,0,0,0,0,0,0,0,0,0],
    ];
    
    // 地形のカメラ計算が不完全(y座標が0のときだけ正しく動く)
    // ので注意
    this.camera = new Camera([3, 0, -0.75], 15*PI*2/16);
    
    this.hiyoko = new Actor();
    this.hiyoko.x = 1.5;
    this.hiyoko.y = 1;
    this.hiyoko.z = 1.5;
  }
  /**
   * 毎フレーム行う処理
   */
  proc(){
    // プログラム再生してからの経過秒数
    let t = millis()/1000;
    
    // WSADで操作
    if (key === 'a') {
      game.hiyoko.angle -= 1/10;
      
    }else if (key === 'd') {
      game.hiyoko.angle += 1/10;
      
    }else{
      let inputVec = (
        !keyIsPressed ? [0, 0]
        : key === 'w' ? [0, 1]
        : key === 's' ? [0, -1]
        : [0, 0]
      );
      let walkVec = inputVec;
      let x = inputVec[0];
      let z = inputVec[1];
      let r = game.hiyoko.angle;
      walkVec[0] = z*sin(r) + x*cos(r);
      walkVec[1] = z*cos(r) - x*sin(r);
      game.hiyoko.x += walkVec[0] / 5;
      game.hiyoko.z += walkVec[1] / 5;
    }
    
    // カメラをひよこの後ろへ
    let hiyokoPosX = game.hiyoko.x;
    let hiyokoPosZ = game.hiyoko.z;

    let hiyokoGyakuX = -3*sin(game.hiyoko.angle);
    let hiyokoGyakuZ = -3*cos(game.hiyoko.angle);

    let cameraPosX = hiyokoPosX + hiyokoGyakuX;
    let cameraPosZ = hiyokoPosZ + hiyokoGyakuZ;

    game.camera.pos = [cameraPosX, 0, cameraPosZ];
    game.camera.angle = game.hiyoko.angle;
    
    let colorDarkgreen = [22, 100, 45];
    let colorGreen = [22, 150, 45];
    let colorRoad = [236, 231, 195];

    // 投影面上の1点 [l,m] の明るさをの範囲で求める関数
    let briFilm = (l,m) => {
      // 床の平面上の点[x,z]のRGBを求める関数
      let briFloor = (x,z) => {
        let a = floor(x);
        let b = floor(z);
        
        if (a >= 0 && a < this.field.length && b >= 0 && b < this.field[0].length) {
          // フィールドの範囲内
          let tile = this.field[a][b];
          
          if(tile === 1) {
            return colorRoad;
          }else if (tile === 2) {
            let j = x%1;
            let k = z%1;
            if (j+k-1 > 0) return colorRoad;
          }else if (tile === 3) {
            let j = x%1;
            let k = z%1;
            if (j+k-1 < 0) return colorRoad;
          }
          return colorGreen;
        } 
        // フィールドの範囲外
        return colorDarkgreen;
      }
      // 床 y=1 との交点 p を求める
      let h = 1;
      let p = [l*h/m, h, h/m];
      
      //----- カメラ情報に応じて交点を操作
      let r = game.camera.angle; //y軸に対する回転量(ラジアン)
      
      // 回転させてから
      let x = p[0];
      let z = p[2];
      p[0] = z*sin(r) + x*cos(r);
      p[2] = z*cos(r) - x*sin(r);
      
      // 平行移動
      p[0] += this.camera.pos[0];
      p[2] += this.camera.pos[2];
      
      //-----
      // 交点pの床の模様を、関数の値(計算結果)とする
      return briFloor(p[0], p[2]);
    };
    
    //----- 描画
    background(12, 178, 227);
    noStroke();

    let pixelSize = 6;
    for(let y=height/2; y<height; y+=pixelSize) {
      for(let x=0; x<width; x+=pixelSize) {
        let l = x/width - 1/2;
        let m = y/height - 1/2;
        fill(...briFilm(l,m));
        rect(x, y, pixelSize, pixelSize);
      }
    }

    
    // 三角錐の配列
    let sankakuList = [];
    // 三角錐の配列
    let sankakuSitaList = [];
    // 影の配列
    let shadowList = [];
    for(let i=0; i<5; i++){
      let color;
      if(i%2==0){
        color='orange'
      }else{
        color='yellow'
      }
      // 三角錐を生成
      let sankaku = new SankakuTry();
      sankaku.sankakuX = 2 + 1.5*i;
      sankaku.sankakuY = 0 + sin(2*t);
      sankaku.sankakuZ = 3;
      sankaku.sankakuColor = color;
      sankaku.proc();
      sankakuList.push(sankaku);

      // 影を生成
      let shadow = new SankakuTry();
      shadow.sankakuX = 2 + 1.5*i;
      shadow.sankakuY = 1;
      shadow.sankakuZ = 3;
      shadow.sankakuColor = 'black';
      shadow.V = shadow.V.map(pos => [pos[0], 0, pos[2]]);
      shadow.proc();
      shadowList.push(shadow);
      
      // 三角錐下側_Aを生成
      let sankakuSitaA = new SankakuTry();
      sankakuSitaA.sankakuX = 2 + 1.5*i;
      sankakuSitaA.sankakuY = 1;//-1
      sankakuSitaA.sankakuZ = 9;
      sankakuSitaA.sankakuColor = color;
      sankakuSitaA.proc();
      sankakuList.push(sankakuSitaA);
    }
    
    //赤い三角錐
    let sankakuSitaC = new SankakuTry();
    sankakuSitaC.sankakuX = 2 + 1.5*3;
    sankakuSitaC.sankakuY = 1;
    sankakuSitaC.sankakuZ = 15 - t;
    sankakuSitaC.sankakuColor = 'red';
    //衝突判定のテスト
    for(let sankaku of sankakuList) {
      // 球の中心座標
      let v = [
        sankakuSitaC.sankakuX,
        sankakuSitaC.sankakuY,
        sankakuSitaC.sankakuZ
      ];
      let w = [
        sankaku.sankakuX,
        sankaku.sankakuY,
        sankaku.sankakuZ,
      ];
      // 半径(仮)
      let a = 0.5;
      let b = 0.5;
      // 2つの球の中心間の距離
      let d = sqrt((v[0]-w[0])**2 + (v[1]-w[1])**2 + (v[2]-w[2])**2);
      // 半径の和より小さいなら衝突している
      if (d < a+b) {
        sankakuSitaC.sankakuColor = 'blue';
        break;
      }
    }
    
    sankakuSitaC.proc();
    sankakuList.push(sankakuSitaC);
    
    
    // ひよこの描画コマンドを生成
    let x1 = game.hiyoko.x;
    let y1 = game.hiyoko.y;
    let z1 = game.hiyoko.z;
    
    // カメラの平行移動
    let x2 = x1 - this.camera.pos[0];
    let y2 = y1 - this.camera.pos[1];
    let z2 = z1 - this.camera.pos[2];
    
    // カメラの回転
    let r = -game.camera.angle;
    let x3 = z2*sin(r) + x2*cos(r);
    let y3 = y2;
    let z3 = z2*cos(r) - x2*sin(r);
    
    // 投影面の前方にあるときだけ描画
    if (z3 > 1) {
      let playerDrawer = new ChickenDrawer(
        new Vec2(width * x3/z3 + width/2, height * y3/z3 + height/2),
        game.hiyoko.angle - game.camera.angle - 2*PI / 4,
        0
      );
      playerDrawer.zPosInCamCoord = z3;
      sankakuList.push(playerDrawer);
    }
    
    // Zソートで三角錐の描画順を決定
    // カメラ座標系でのz座標でソートする
    sankakuList.sort(
      (san1, san2) => {
        let z1 = san1.getZPosInCamCoord();
        let z2 = san2.getZPosInCamCoord();
        return z2 - z1;
      }
    );
    
    // すべての影を描画
    for(let shadow of shadowList) {
      shadow.draw();
    }
    
    // すべての三角錐を描画
    for(let sankaku of sankakuList) {
      sankaku.draw();
    }
  }
}
//中途半端な衝突判定
/*
function hitThreeCircle() {
  background(220);
  
  let x0 = 120;
  let y0 = 180;
  let r0 = 60;
  
  let x1 = mouseX;
  let y1 = mouseY;
  let r1 = 120;
  
  let d = sqrt((x1-x0)**2+(y1-y0)**2);
  let hit = d < r0+r1;
  
  noFill();
  strokeWeight(4);
  
  circle(x0,y0,2*r0);
  circle(x1,y1,2*r1);
  
  stroke(0);
  textSize(32);
  text('d = '+nfc(d,2),30,420);
}
*/
let game;

function setup() {
  createCanvas(640,640);
  frameRate(15);
  game = new Game();
  
} 

function draw() {
  game.proc();
}