Published
Edited
Jun 20, 2021
2 forks
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
rotateInterface = {
let div = html`<div>
<style>
table.interface tr { margin:2px; border:0px;}
table.interface td.button {
cursor:pointer;
background:white;
border-radius:5px;
border:1px solid gray;
text-align:center;
}
button.interface {
cursor:pointer;
width:14em;
}
table.interface {
border-collapse:separate;
width:14em;
}
</style>
</div>`;
let reset = html`<button class=interface> Reset`;
reset.onclick = () => {
if (mutable rotInfo) return;
mutable rotInfo = { type: "reset", row: 0, col: 0 };
};
div.append(reset);
let table = html`<table class=interface>`;
div.append(table);
let buttonClick = rot => {
return () => {
if (mutable rotInfo) return;
mutable rotInfo = rot;
};
};

for (let i = -1; i <= 6; i++) {
let tr = html`<tr>`;
table.append(tr);
if (i == -1) {
tr.append(html`<td>`);
tr.append(html`<td>`);
let td = html`<td colspan=4 class=button>↓</td>`;
td.onclick = buttonClick({ type: "allcol", row: 0, col: 0, angle: 90 });
tr.append(td);
tr.append(html`<td>`);
tr.append(html`<td>`);
continue;
}
if (i == 6) {
tr.append(html`<td>`);
tr.append(html`<td>`);

let td = html`<td colspan=4 class=button>↑</td>`;
td.onclick = buttonClick({ type: "allcol", row: 0, col: 0, angle: -90 });
tr.append(td);
tr.append(html`<td>`);
tr.append(html`<td>`);
continue;
}

if (i == 1) {
let td = html`<td rowspan=4 class=button>→</td>`;
td.onclick = buttonClick({ type: "allrow", row: 0, col: 0, angle: 90 });
tr.append(td);
} else if (i == 0 || i == 5) {
tr.append(html`<td>`);
}
for (let j = 0; j <= 5; j++) {
if (i == j || i + j == 5) {
tr.append(html`<td>`);
continue;
}
let rot, sym;
if (i == 0)
[rot, sym] = [{ type: "col", row: 0, col: j - 1, angle: 90 }, "↓"];
else if (i == 5)
[rot, sym] = [{ type: "col", row: 0, col: j - 1, angle: -90 }, "↑"];
else if (j == 0)
[rot, sym] = [{ type: "row", row: 4 - i, col: 0, angle: 90 }, "→"];
else if (j == 5)
[rot, sym] = [{ type: "row", row: 4 - i, col: 0, angle: -90 }, "←"];
if (rot) {
let td = html`<td class=button>`;
tr.append(td);
td.onclick = buttonClick(rot);
td.append(sym);
} else tr.append(html`<td>`);
}
if (i == 1) {
let td = html`<td rowspan=4 class=button>←</td>`;
td.onclick = buttonClick({ type: "allrow", row: 0, col: 0, angle: -90 });
tr.append(td);
} else if (i == 0 || i == 5) {
tr.append(html`<td>`);
}
}
return div;
}
Insert cell
Insert cell
mutable rotInfo = null;
Insert cell
Insert cell
animation = {
if (rotInfo) {
let { type, row, col, angle } = rotInfo;
angle = (angle / 180) * Math.PI;
const rot = new THREE.Quaternion().setFromEuler(
type == "row" || type == "allrow"
? new THREE.Euler(0, angle, 0)
: new THREE.Euler(angle, 0, 0)
);
let anims = [];
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (
type === "allrow" ||
type === "allcol" ||
type === "reset" ||
(type === "row" && j === row) ||
(type === "col" && i === col)
) {
let c = cubes[i][j];
anims.push({
q: c.quaternion,
oldq: c.quaternion.clone(),
newq:
type === "reset"
? new THREE.Quaternion()
: c.quaternion.clone().premultiply(rot)
});
}
}
}
for (let i = 0; i < 60; i++) {
for (let { q, oldq, newq } of anims) {
q.slerpQuaternions(oldq, newq, i / 59);
}
render();
yield i;
}
mutable rotInfo = null;
}
}
Insert cell
Insert cell
THREE = {
const THREE = (window.THREE = await require('three@0.128'));
await require('three@0.128/examples/js/controls/OrbitControls.js').catch(
() => {}
);
return THREE;
}
Insert cell
Insert cell
renderer = new THREE.WebGLRenderer({
canvas: html`<canvas width=${width} height=${(width / 16) * 9}>`,
antialias: true
})
Insert cell
camera = {
let camera = new THREE.PerspectiveCamera(70, 16 / 9, 0.1, 100);
camera.position.set(0, 0, 8);
return camera;
}
Insert cell
light = {
let group = new THREE.Group();
const dirLight = new THREE.DirectionalLight({ color: 0x777777 }); //new THREE.HemisphereLight();
dirLight.position.set(10, 10, 100);
const ambLight = new THREE.AmbientLight(0x777777);
group.add(dirLight, ambLight);
return group;
}
Insert cell
Insert cell
function createCube(x = 0, y = 0, z = 0, l = 1) {
let geometry = new THREE.BoxGeometry(l, l, l);
let material = new THREE.MeshStandardMaterial({ color: 0xCC0000 });
let mesh = new THREE.Mesh(geometry, material);
mesh.position.set(x, y, z);
return mesh;
}
Insert cell
Insert cell
images = [
await FileAttachment("img1.jpg").image(),
await FileAttachment("img2.jpg").image(),
await FileAttachment("img3.jpg").image(),
await FileAttachment("img4.jpg").image(),
await FileAttachment("img5.jpg").image(),
await FileAttachment("img6.jpg").image()
]
Insert cell
function makeCubicTile(i, j, n = 4) {
const faceUV = [
[i / n, j / n], // Front
[(n - 1 - i) / n, j / n], // Back
[i / n, j / n], // Left
[i / n, j / n], // Right
[i / n, j / n], // Top
[i / n, j / n] // bottom
];
const { PI } = Math;
const faceRotation = [
[0, 0, 0], // front
[0, PI, 0], // back
[0, -PI / 2, 0], // left
[0, PI / 2, 0], // right
[-PI / 2, 0, 0], // top
[PI / 2, 0, 0]
];
const faceTranslation = [
[0, 0, 0.5], // Front
[0, 0, -0.5], // Back
[-0.5, 0, 0], // Left
[0.5, 0, 0], // Right
[0, 0.5, 0], // Top
[0, -0.5, 0] // Bottom
];

function makeFace(f) {
const tex = new THREE.CanvasTexture(images[f]);
tex.offset.set(...faceUV[f]);
tex.repeat.set(1 / n, 1 / n);
const mat = new THREE.MeshBasicMaterial({
color: 0xffffff,
side: THREE.DoubleSide,
map: tex
});
const geo = new THREE.PlaneGeometry();
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(...faceTranslation[f]);
mesh.rotation.set(...faceRotation[f]);
return mesh;
}
const group = new THREE.Object3D();
group.add(...[0, 1, 2, 3, 4, 5].map(f => makeFace(f)));
group.gridIndices = [i, j];
return group;
}
Insert cell
function createCubeTile(x = 0, y = 0, z = 0, l = 1, i = 0, j = 0) {
let mesh = makeCubicTile(i, j);
mesh.scale.set(l, l, l);
mesh.position.set(x, y, z);
return mesh;
}
Insert cell
Insert cell
function createCubeTex(x = 0, y = 0, z = 0, l = 1) {
let geometry = texCubeGeometry;
let map = new THREE.CanvasTexture(textureImage);
let material = new THREE.MeshStandardMaterial({
color: 0xffffff,
map
});
let mesh = new THREE.Mesh(geometry, material);
mesh.scale.set(l / 2, l / 2, l / 2);
mesh.position.set(x, y, z);
return mesh;
}
Insert cell
import { texCubeGeometry, textureImage } from "@esperanc/textured-box"
Insert cell
Insert cell
cubes = {
const m = 4;
const n = 4;
const w = 5;
const h = 5;
let cubes = [];
for (let i = 0; i < m; i++) {
cubes.push([]);
let x = w * (i / (m - 1) - 0.5);
for (let j = 0; j < n; j++) {
let y = h * (j / (n - 1) - 0.5);
cubes[i].push(
variant === "textured"
? createCubeTex(x, y, 0, w / (m - 1) - 0.04)
: variant === "plain"
? createCube(x, y, 0, w / (m - 1) - 0.04)
: createCubeTile(x, y, 0, w / (m - 1) - 0.04, i, j)
);
cubes[i].ij = [i, j];
}
}
return cubes;
}
Insert cell
cubeGroup = {
let cubeGroup = new THREE.Group();
for (let i = 0; i < cubes.length; i++)
for (let j = 0; j < cubes[0].length; j++) cubeGroup.add(cubes[i][j]);
return cubeGroup;
}
Insert cell
Insert cell
scene = {
let scene = new THREE.Scene();
scene.background = new THREE.Color(0xCCCCCC);
scene.add(cubeGroup)
scene.add(light)
return scene
}
Insert cell
Insert cell
controls = new THREE.OrbitControls(camera, renderer.domElement)
Insert cell
Insert cell
function render () {
controls.update();
renderer.render(scene, camera);
}
Insert cell
Insert cell
{
for (;;) {
render();
yield;
}
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more