данные пиксела рисования putimagedata 4 раза / не в масштабе - PullRequest
0 голосов
/ 15 января 2019

Недавно я смотрел некоторые потоки Нотча на дрожи и несколько лет назад интересовался одним из его методов рендеринга для задачи Ludum Dare. Я попытался преобразовать его Java-код в Javascript и столкнулся с некоторыми проблемами, и это потому, что я все еще плохо знаком с ctx.putimagedata из необработанных значений созданных пикселей.

Почему это приложение выводит намеченный результат 4 раза, а не масштабируется до окна? Есть ли что-то, чего мне не хватает, где я должен повторяться с умножением или делителем 4 из-за формы массива? Я в замешательстве, поэтому просто собираюсь опубликовать это здесь. Единственное исправление, которое я нашел, - это если я откорректирую this.width и this.height, чтобы он умножился на 4, но я считаю, что это выходит за границы холста и приводит к ужасной производительности, и это не совсем правильное решение проблема.

рассматриваемый класс:

document.addEventListener('DOMContentLoaded', () => {
    //setup
    document.body.style.margin = 0;
    document.body.style.overflow = `hidden`;
    const canvas = document.createElement('canvas');
    document.body.appendChild(canvas);
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    const ctx = canvas.getContext("2d");
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    //global helpers
    const randomint = (lower, upper) => {
        return Math.floor((Math.random() * upper+1) + lower);
    }
    const genrandomcolor = () => {
        return [randomint(0, 255), randomint(0, 255), randomint(0, 255), 1/randomint(1, 2)];
    }

    class App {
        constructor(){
            this.scale = 15;
            this.width = window.innerWidth;
            this.height = window.innerHeight;
            this.pixels = [];
            this.fov = 10;
            this.ub = 0;
            this.lr = 0;
            this.keys = {
              up: false,
              down: false,
              left: false,
              right: false
            }
            this.speed = 4;
        }
        update(){
            this.keys.up ? this.ub++ : null;
            this.keys.down ? this.ub-- : null;
            this.keys.left ? this.lr-- : null;
            this.keys.right ? this.lr++ : null;
        }
        draw(){
            this.drawspace()
        }
        drawspace(){
            for(let y = 0; y < this.height; y++){
                let yd = (y - this.height / 2) / this.height;
                yd < 0 ? yd = -yd : null;
                const z = this.fov / yd;
                for (let x = 0; x < this.width; x++){
                    let xd = (x - this.width /2) / this.height * z;
                    const xx = (xd+this.lr*this.speed) & this.scale;
                    const zz = (z+this.ub*this.speed) & this.scale;
                    this.pixels[x+y*this.width] = xx * this.scale | zz * this.scale;
                }
            }
            const screen = ctx.createImageData(this.width, this.height);
            for (let i = 0; i<this.width*this.height*4; i++){
                screen.data[i] = this.pixels[i]
            }
            ctx.putImageData(screen, 0, 0);
        }
    }

    const app = new App;

    window.addEventListener('resize', e => {
        canvas.width = app.width = window.innerWidth;
        canvas.height = app.height = window.innerHeight;
    })
  
    //events
    document.addEventListener("keydown", e => {
        e.keyCode == 37 ? app.keys.left = true : null;
        e.keyCode == 38 ? app.keys.up = true : null;
        e.keyCode == 39 ? app.keys.right = true : null;
        e.keyCode == 40 ? app.keys.down = true : null;
    })
    document.addEventListener("keyup", e => {
        e.keyCode == 37 ? app.keys.left = false : null;
        e.keyCode == 38 ? app.keys.up = false : null;
        e.keyCode == 39 ? app.keys.right = false : null;
        e.keyCode == 40 ? app.keys.down = false : null;
    })

    //game loop
    const fps = 60;
    const interval = 1000 / fps;
    let then = Date.now();
    let now;
    let delta;
    const animate = time => {
        window.requestAnimationFrame(animate);
        now = Date.now();
        delta = now - then;
        if (delta > interval) {
            then = now - (delta % interval)
            ctx.fillStyle = 'black';
            ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);
            app.update();
            app.draw();
        }
    }
    animate();
});

1 Ответ

0 голосов
/ 15 января 2019

Объект ImageData.data представляет собой массив Uint8ClampedArray, представляющий 4 канала Red, Green, Blue и Alpha каждого пикселя, каждый канал представлен в виде 8 бит (значения в диапазоне 0–255).

Это означает, что для установки пикселя вам необходимо установить 4 канала независимо друг от друга:

const r = data[0];
const g = data[1];
const b = data[2];
const a = data[3];

Это первый пиксель нашей ImageData (в левом верхнем углу).
Таким образом, чтобы иметь возможность проходить через все пиксели, нам нужно создать цикл, который позволит нам переходить от одного пикселя к другому. Это делается путем итерации 4 индексов одновременно:

for(
   let index = 0;
   index < data.length;
   index += 4 // increment by 4
) {
  const r = data[index + 0];
  const g = data[index + 1];
  const b = data[index + 2];
  const a = data[index + 3];
  ...
}

Теперь каждый пиксель будет проходить так, как он должен быть:

  //setup
  document.body.style.margin = 0;
  document.body.style.overflow = `hidden`;
  const canvas = document.createElement('canvas');
  document.body.appendChild(canvas);
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  const ctx = canvas.getContext("2d");
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  //global helpers
  const randomint = (lower, upper) => {
    return Math.floor((Math.random() * upper + 1) + lower);
  }
  const genrandomcolor = () => {
    return [randomint(0, 255), randomint(0, 255), randomint(0, 255), 1 / randomint(1, 2)];
  }

  class App {
    constructor() {
      this.scale = 15;
      this.width = window.innerWidth;
      this.height = window.innerHeight;
      this.pixels = [];
      this.fov = 10;
      this.ub = 0;
      this.lr = 0;
      this.keys = {
        up: false,
        down: false,
        left: false,
        right: false
      }
      this.speed = 4;
    }
    update() {
      this.keys.up ? this.ub++ : null;
      this.keys.down ? this.ub-- : null;
      this.keys.left ? this.lr-- : null;
      this.keys.right ? this.lr++ : null;
    }
    draw() {
      this.drawspace()
    }
    drawspace() {
      for (let y = 0; y < this.height; y++) {
        let yd = (y - this.height / 2) / this.height;
        yd < 0 ? yd = -yd : null;
        const z = this.fov / yd;
        for (let x = 0; x < this.width; x++) {
          let xd = (x - this.width / 2) / this.height * z;
          const xx = (xd + this.lr * this.speed) & this.scale;
          const zz = (z + this.ub * this.speed) & this.scale;
          this.pixels[x + y * this.width] = xx * this.scale | zz * this.scale;
        }
      }
      const screen = ctx.createImageData(this.width, this.height);
      for (let i = 0, j=0; i < screen.data.length; i += 4) {
        j++; // so we can iterate through this.pixels
        screen.data[i] = this.pixels[j]; // r
        screen.data[i + 1] = this.pixels[j], // g
        screen.data[i + 2] = this.pixels[j] // b
        screen.data[i + 3] = 255; // full opacity
      }
      ctx.putImageData(screen, 0, 0);
    }
  }

  const app = new App;

  window.addEventListener('resize', e => {
    canvas.width = app.width = window.innerWidth;
    canvas.height = app.height = window.innerHeight;
  })

  //events
  document.addEventListener("keydown", e => {
    e.keyCode == 37 ? app.keys.left = true : null;
    e.keyCode == 38 ? app.keys.up = true : null;
    e.keyCode == 39 ? app.keys.right = true : null;
    e.keyCode == 40 ? app.keys.down = true : null;
  })
  document.addEventListener("keyup", e => {
    e.keyCode == 37 ? app.keys.left = false : null;
    e.keyCode == 38 ? app.keys.up = false : null;
    e.keyCode == 39 ? app.keys.right = false : null;
    e.keyCode == 40 ? app.keys.down = false : null;
  })

  //game loop
  const fps = 60;
  const interval = 1000 / fps;
  let then = Date.now();
  let now;
  let delta;
  const animate = time => {
    window.requestAnimationFrame(animate);
    now = Date.now();
    delta = now - then;
    if (delta > interval) {
      then = now - (delta % interval)
      ctx.fillStyle = 'black';
      ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);
      app.update();
      app.draw();
    }
  }
  animate();

Но учтите, что вы также можете использовать другой вид поверх ArrayBuffer и работать с каждым пикселем непосредственно как 32-битные значения:

//setup
  document.body.style.margin = 0;
  document.body.style.overflow = `hidden`;
  const canvas = document.createElement('canvas');
  document.body.appendChild(canvas);
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  const ctx = canvas.getContext("2d");
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  //global helpers
  const randomint = (lower, upper) => {
    return Math.floor((Math.random() * upper + 1) + lower);
  }
  const genrandomcolor = () => {
    return [randomint(0, 255), randomint(0, 255), randomint(0, 255), 1 / randomint(1, 2)];
  }

  class App {
    constructor() {
      this.scale = 15;
      this.width = window.innerWidth;
      this.height = window.innerHeight;
      this.pixels = [];
      this.fov = 10;
      this.ub = 0;
      this.lr = 0;
      this.keys = {
        up: false,
        down: false,
        left: false,
        right: false
      }
      this.speed = 4;
    }
    update() {
      this.keys.up ? this.ub++ : null;
      this.keys.down ? this.ub-- : null;
      this.keys.left ? this.lr-- : null;
      this.keys.right ? this.lr++ : null;
    }
    draw() {
      this.drawspace()
    }
    drawspace() {
      for (let y = 0; y < this.height; y++) {
        let yd = (y - this.height / 2) / this.height;
        yd < 0 ? yd = -yd : null;
        const z = this.fov / yd;
        for (let x = 0; x < this.width; x++) {
          let xd = (x - this.width / 2) / this.height * z;
          const xx = (xd + this.lr * this.speed) & this.scale;
          const zz = (z + this.ub * this.speed) & this.scale;
          this.pixels[x + y * this.width] = xx * this.scale | zz * this.scale;
        }
      }
      const screen = ctx.createImageData(this.width, this.height);
      // use a 32bits view
      const data = new Uint32Array(screen.data.buffer);
      for (let i = 0, j=0; i < this.width * this.height; i ++) {
        // values are 0-255 range, we convert this to 0xFFnnnnnn 32bits
        data[i] = (this.pixels[i] / 255 * 0xFFFFFF) + 0xFF000000;
      }
      ctx.putImageData(screen, 0, 0);
    }
  }

  const app = new App;

  window.addEventListener('resize', e => {
    canvas.width = app.width = window.innerWidth;
    canvas.height = app.height = window.innerHeight;
  })

  //events
  document.addEventListener("keydown", e => {
    e.keyCode == 37 ? app.keys.left = true : null;
    e.keyCode == 38 ? app.keys.up = true : null;
    e.keyCode == 39 ? app.keys.right = true : null;
    e.keyCode == 40 ? app.keys.down = true : null;
  })
  document.addEventListener("keyup", e => {
    e.keyCode == 37 ? app.keys.left = false : null;
    e.keyCode == 38 ? app.keys.up = false : null;
    e.keyCode == 39 ? app.keys.right = false : null;
    e.keyCode == 40 ? app.keys.down = false : null;
  })

  //game loop
  const fps = 60;
  const interval = 1000 / fps;
  let then = Date.now();
  let now;
  let delta;
  const animate = time => {
    window.requestAnimationFrame(animate);
    now = Date.now();
    delta = now - then;
    if (delta > interval) {
      then = now - (delta % interval)
      ctx.fillStyle = 'black';
      ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);
      app.update();
      app.draw();
    }
  }
  animate();
...