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.
Loading scene…Key API excerpt
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);