canvas

interactive / voxelpainter

What this shows

Click the grid to stack textured orange voxels where a translucent red roll-over cube snaps to the picked face; Shift-click removes a voxel.

Side by side with the three.js r186 original: webgl_interactive_voxelpainter.html

Differences from three.js

  • The upstream page tracks Shift with document keydown and keyup listeners; both ports read shiftKey from the pointerdown event so every listener stays on the canvas.
  • EASEL's TextureLoader.load returns nothing, so the port assigns the square-outline map in the load callback, where it also sets colorSpace and re-caches the already decoded texels with update(); voxels placed before the data-URL PNG is decoded draw untextured until it arrives.
  • The 16x16 square-outline texture is sampled nearest-neighbor with affine warping instead of being filtered and perspective-correct.
  • EASEL has discrete opacity levels, so the roll-over cube's 0.5 opacity becomes level 4 of 8.
EASEL (Canvas2D)
This example needs a canvas-capable browser.

Loading scene…

three.js r186 (WebGL)
This example needs a canvas-capable browser.

Loading scene…

Loading scene…

Key API excerpt

import * as EASEL from "@xsyetopz/easel";


const rollOverMaterial = new EASEL.BasicMaterial({ color: 0xff0000, opacity: 4, transparent: true, vertexColors: false });
const cubeMaterial = new EASEL.LambertMaterial({ color: 0xfeb74c, vertexColors: false });
new EASEL.TextureLoader().load(squareOutlineUrl, (map) => {
  map.colorSpace = EASEL.SRGBColorSpace;
  map.update().buildBrightnessLevels();
  cubeMaterial.map = map;
});

const plane = new EASEL.Mesh(geometry, new EASEL.BasicMaterial({ visible: false }));
objects.push(plane);

raycaster.setFromCamera(pointer, camera);
const intersect = raycaster.intersectObjects(objects, false)[0];

if (intersect) {
  const voxel = new EASEL.Mesh(cubeGeo, cubeMaterial);
  voxel.position.copy(intersect.point).add(intersect.face.normal);
  voxel.position.divideScalar(50).floor().multiplyScalar(50).addScalar(25);

  scene.add(voxel);
  objects.push(voxel);
}
renderer.prepare(scene, camera);
renderer.render(scene, camera);