<div class="scene"></div>
<script src="https://code.jquery.com/jquery-3.1.0.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js"></script>
<script src="https://ricostacruz.com/jquery.transit/jquery.transit.min.js"></script>
html, body {
  padding: 0;
  margin: 0;
  width: 100%;
  height: 100%;
}

body::after {
  font-family: "operator mono", "sans-serif";
  content: "Click and drag to rotate, or scroll mouse wheel to zoom...";
  position: absolute;
  top: 25px;
  left: 25px;
}

.scene {
  position: absolute;
  left: 50%;
  top: 50%;
  margin: -192px 0 0 -192px;
  width: 384px;
  height: 384px;
  background: rgba(100, 100, 255, 0.2);
  transform: rotateX(60deg) rotateZ(60deg);
  transform-style: preserve-3d;
  transform-origin: center;
}

.block {
  position: absolute;
  left: 0;
  top: 0;
  width: 64px;
  height: 64px;
  transform-style: preserve-3d;
  transform-origin: 50% 50% 50%;
}

.x-axis,
.y-axis,
.z-axis {
  position: absolute;
  left: 0;
  top: 0;
  width: 66px;
  height: 66px;
  transform-origin: 50% 50% 50%;
  pointer-events: none;
}

.x-axis {
  /* border: solid 2px rgba(255, 0, 0, 0.3); */
}

.y-axis {
  /* border: solid 2px rgba(0, 255, 0, 0.3); */
}

.z-axis {
  /* border: solid 2px rgba(0, 0, 255, 0.3); */
}

.side {
  position: absolute;
  left: 0;
  top: 0;
  width: 64px;
  height: 64px;
  backface-visibility: hidden;
  outline: 1px solid rgba(0, 0, 0, 0.3);
}

.block:hover .side {
  outline: 1px solid rgba(0, 255, 0, 0.5);
}

.ghost {
  pointer-events: none;
}

.ghost .side {
  opacity: 0.6;
  pointer-events: none;
  -webkit-filter: brightness(1.5);
}
"use strict"

class Block {
  constructor(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;

    this.build();
  }

  build() {
    const size = 64;
    const x = this.x * 64;
    const y = this.y * 64;
    const z = this.z * 64;

    const block = this.block = $(`<div class="block" />`)
      .css({
        transform: `
          translateX(${x}px)
          translateY(${y}px)
          translateZ(${z}px)
          scale(0.99)
        `
      });

    $(`<div class="x-axis" />`)
      .appendTo(block)
      .css({
        transform: `
          rotateX(90deg)
          rotateY(0deg)
          rotateZ(0deg)
        `
      });

    $(`<div class="y-axis" />`)
      .appendTo(block)
      .css({
        transform: `
          rotateX(0deg)
          rotateY(90deg)
          rotateZ(0deg)
        `
      });

    $(`<div class="z-axis" />`)
      .appendTo(block);
    
    this
      .createFace("top", 0, 0, size / 2, 0, 0, 0)
      .appendTo(block);

    this
      .createFace("side-1", 0, size / 2, 0, 270, 0, 0)
      .appendTo(block);

    this
      .createFace("side-2", size / 2, 0, 0, 0, 90, 0)
      .appendTo(block);

    this
      .createFace("side-3", 0, size / -2, 0, -270, 0, 0)
      .appendTo(block);

    this
      .createFace("side-4", size / -2, 0, 0, 0, -90, 0)
      .appendTo(block);

    this
      .createFace("bottom", 0, 0, size / -2, 0, 180, 0)
      .appendTo(block);
  }

  createFace(type, x, y, z, rx, ry, rz) {
    return $(`<div class="side side-${type}" />`)
      .css({
        transform: `
          translateX(${x}px)
          translateY(${y}px)
          translateZ(${z}px)
          rotateX(${rx}deg)
          rotateY(${ry}deg)
          rotateZ(${rz}deg)
        `,
        background: this.createTexture(type)
      })
      .data("block", this)
      .data("type", type);
  }

  createTexture(type) {
    return `rgba(100, 100, 255, 0.2)`;
  }
}

const DIRT_TEXTURES = {
  "top": [
    "https://github.com/assertchris-tutorials/tutorial-javascript-minecraft-editor/raw/master/textures/dirt-top-1.png",
    "https://github.com/assertchris-tutorials/tutorial-javascript-minecraft-editor/raw/master/textures/dirt-top-2.png",
    "https://github.com/assertchris-tutorials/tutorial-javascript-minecraft-editor/raw/master/textures/dirt-top-3.png"
  ],
  "side": [
    "https://github.com/assertchris-tutorials/tutorial-javascript-minecraft-editor/raw/master/textures/dirt-side-1.png",
    "https://github.com/assertchris-tutorials/tutorial-javascript-minecraft-editor/raw/master/textures/dirt-side-2.png",
    "https://github.com/assertchris-tutorials/tutorial-javascript-minecraft-editor/raw/master/textures/dirt-side-3.png",
    "https://github.com/assertchris-tutorials/tutorial-javascript-minecraft-editor/raw/master/textures/dirt-side-4.png",
    "https://github.com/assertchris-tutorials/tutorial-javascript-minecraft-editor/raw/master/textures/dirt-side-5.png"
  ]
};

class Dirt extends Block {
  createTexture(type) {
    if (type === "top" || type === "bottom") {
      const texture = DIRT_TEXTURES.top.random();

      return `url(${texture})`;
    }

    const texture = DIRT_TEXTURES.side.random();

    return `url(${texture})`;
  }
}

Block.Dirt = Dirt;

Array.prototype.random = function() {
  return this[Math.floor(Math.random() * this.length)];
};

const $scene = $(".scene");
const $body = $("body");

for (let x = 0; x < 6; x++) {
  for (let y = 0; y < 6; y++) {
    let next = new Block.Dirt(x, y, 0);
    next.block.appendTo($scene);
  }
}

function createCoordinatesFrom(side, x, y, z) {
  if (side == "top") {
    z += 1;
  }

  if (side == "side-1") {
    y += 1;
  }

  if (side == "side-2") {
    x += 1;
  }

  if (side == "side-3") {
    y -= 1;
  }

  if (side == "side-4") {
    x -= 1;
  }

  if (side == "bottom") {
    z -= 1;
  }

  return [x, y, z];
}

$body.on("click", ".side", function(e) {
  const $this = $(this);
  const previous = $this.data("block");

  const coordinates = createCoordinatesFrom(
    $this.data("type"),
    previous.x,
    previous.y,
    previous.z
  );

  const next = new Block.Dirt(...coordinates);

  next.block.appendTo($scene);
});

let ghost = null;

function removeGhost() {
  if (ghost) {
    ghost.block.remove();
    ghost = null;
  }
}

function createGhostAt(x, y, z) {
  const next = new Block.Dirt(x, y, z);

  next.block
    .addClass("ghost")
    .appendTo($scene);

  ghost = next;
}

$body.on("mouseenter", ".side", function(e) {
  removeGhost();

  const $this = jQuery(this);
  const previous = $this.data("block");

  const coordinates = createCoordinatesFrom(
    $this.data("type"),
    previous.x,
    previous.y,
    previous.z
  );

  createGhostAt(...coordinates);
});

$body.on("mouseleave", ".side", function(e) {
  removeGhost()
});

let lastMouseX = null;
let lastMouseY = null;

let sceneTransformX = 60;
let sceneTransformY = 0;
let sceneTransformZ = 60;
let sceneTransformScale = 1;

$body.on("mousewheel", function(event) {
  if (event.originalEvent.deltaY > 0) {
    sceneTransformScale -= 0.05;
  } else {
    sceneTransformScale += 0.05;
  }

  changeViewport();
});

Number.prototype.toInt = String.prototype.toInt = function() {
  return parseInt(this, 10);
};

$scene.on("mousedown", function(e) {
  e.stopPropagation();
});

$body.on("mousedown", function(e) {
  lastMouseX = e.clientX / 10;
  lastMouseY = e.clientY / 10;
});

$body.on("mousemove", function(e) {
  if (!lastMouseX) {
    return;
  }

  let nextMouseX = e.clientX / 10;
  let nextMouseY = e.clientY / 10;

  if (nextMouseX !== lastMouseX) {
    let deltaX = nextMouseX.toInt() - lastMouseX.toInt();
    let degrees = sceneTransformZ - deltaX;

    if (degrees > 360) {
        degrees -= 360;
    }

    if (degrees < 0) {
        degrees += 360;
    }

    sceneTransformZ = degrees;
    lastMouseX = nextMouseX;

    changeViewport();
  }

  if (nextMouseY !== lastMouseY) {
    let deltaY = nextMouseY.toInt() - lastMouseY.toInt();
    let degrees = sceneTransformX - deltaY;

    if (degrees > 360) {
        degrees -= 360;
    }

    if (degrees < 0) {
        degrees += 360;
    }

    sceneTransformX = degrees;
    lastMouseY = nextMouseY;

    changeViewport();
  }
});

$body.on("mouseup", function(e) {
  lastMouseX = null;
  lastMouseY = null;
});

function changeViewport() {
  $scene.css({
    "transform": `
      rotateX(${sceneTransformX}deg)
      rotateY(${sceneTransformY}deg)
      rotateZ(${sceneTransformZ}deg)
      scaleX(${sceneTransformScale})
      scaleY(${sceneTransformScale})
      scaleZ(${sceneTransformScale})
    `
  });
};
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.