2020-12-29 03:33:50 +09:00
|
|
|
import {
|
|
|
|
Color,
|
|
|
|
Mesh,
|
|
|
|
MeshBasicMaterial,
|
|
|
|
PerspectiveCamera,
|
|
|
|
PlaneGeometry,
|
|
|
|
Raycaster,
|
|
|
|
Scene,
|
|
|
|
Vector2,
|
|
|
|
WebGLRenderer,
|
|
|
|
} from "three";
|
2020-12-29 17:49:51 +09:00
|
|
|
import { Synth } from "tone";
|
2020-12-29 03:33:50 +09:00
|
|
|
import randomInt from "./randomInt";
|
2020-12-29 11:17:12 +09:00
|
|
|
import keyPosition from "./keyPosition";
|
|
|
|
import keyLayouts from "./config/keyLayouts";
|
2020-12-29 11:54:14 +09:00
|
|
|
import pallet from "./config/pallet";
|
2020-12-29 03:33:50 +09:00
|
|
|
|
|
|
|
const root = document.body;
|
|
|
|
const scene = new Scene();
|
|
|
|
const camera = new PerspectiveCamera();
|
|
|
|
const grid = { colum: 14, row: 4 } as const;
|
|
|
|
const mouse = new Vector2(NaN, NaN);
|
|
|
|
const panels = Array.from({ length: grid.colum * grid.row }, () => {
|
|
|
|
const geometry = new PlaneGeometry();
|
2020-12-29 11:46:13 +09:00
|
|
|
const material = new MeshBasicMaterial({ opacity: 0 });
|
2020-12-29 03:33:50 +09:00
|
|
|
const mesh = new Mesh(geometry, material);
|
|
|
|
return mesh;
|
|
|
|
});
|
|
|
|
const renderer = new WebGLRenderer();
|
|
|
|
const canvas = renderer.domElement;
|
|
|
|
const raycaster = new Raycaster();
|
2020-12-29 17:49:51 +09:00
|
|
|
const synth = new Synth({ oscillator: { type: "square" } }).toDestination();
|
2020-12-29 03:33:50 +09:00
|
|
|
|
2020-12-29 11:46:13 +09:00
|
|
|
function render() {
|
|
|
|
renderer.render(scene, camera);
|
|
|
|
}
|
|
|
|
|
2020-12-29 03:33:50 +09:00
|
|
|
function drawRect() {
|
|
|
|
raycaster.setFromCamera(mouse, camera);
|
|
|
|
const [intersect] = raycaster.intersectObjects(panels);
|
|
|
|
if (!intersect) return;
|
|
|
|
|
2020-12-29 18:33:10 +09:00
|
|
|
try {
|
|
|
|
// FIXME: Error: Start time must be strictly greater than previous start time.
|
|
|
|
synth.triggerAttackRelease("C4", "16n");
|
|
|
|
} catch {}
|
|
|
|
|
2020-12-29 11:54:14 +09:00
|
|
|
const color = pallet[randomInt(pallet.length - 1)];
|
2020-12-29 03:33:50 +09:00
|
|
|
const geometry = new PlaneGeometry(1, 1);
|
2020-12-29 11:46:13 +09:00
|
|
|
const material = new MeshBasicMaterial({ color });
|
2020-12-29 03:33:50 +09:00
|
|
|
const mesh = new Mesh(geometry, material);
|
2020-12-29 17:15:59 +09:00
|
|
|
const offset = () => 0.05 * (Math.random() - 0.5);
|
|
|
|
mesh.scale.set(0.075 + offset(), 0.075 + offset(), 0);
|
2020-12-29 03:33:50 +09:00
|
|
|
Object.assign(mesh.position, intersect.point);
|
|
|
|
scene.add(mesh);
|
2020-12-29 11:46:13 +09:00
|
|
|
render();
|
2020-12-29 16:49:48 +09:00
|
|
|
// NOTE: 1度だけだと正しく表示されないことがあるのでもう一度renderする。原因よくわからない。
|
|
|
|
window.requestAnimationFrame(render);
|
2020-12-29 03:33:50 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
function handleKeydown({ key }: KeyboardEvent) {
|
2020-12-29 11:46:13 +09:00
|
|
|
if (key === " ") return setup();
|
2020-12-29 03:33:50 +09:00
|
|
|
|
|
|
|
const [x = randomInt(grid.colum - 1), y = randomInt(grid.row - 1)] = [
|
2020-12-29 11:17:12 +09:00
|
|
|
...keyPosition(keyLayouts.default, key),
|
|
|
|
...keyPosition(keyLayouts.shift, key),
|
2020-12-29 03:33:50 +09:00
|
|
|
];
|
|
|
|
|
|
|
|
const offset = () => Math.random() - 0.5;
|
|
|
|
mouse.x = ((x + offset()) / (grid.colum - 1)) * 2 - 1;
|
|
|
|
mouse.y = -((y + offset()) / (grid.row - 1)) * 2 + 1;
|
|
|
|
|
|
|
|
drawRect();
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleMouseMove(event: { clientX: number; clientY: number }) {
|
2020-12-29 17:15:59 +09:00
|
|
|
const offset = () => 0.2 * (Math.random() - 0.5);
|
|
|
|
mouse.x = (event.clientX / canvas.clientWidth) * 2 - 1 + offset();
|
|
|
|
mouse.y = -(event.clientY / canvas.clientHeight) * 2 + 1 + offset();
|
2020-12-29 03:33:50 +09:00
|
|
|
|
|
|
|
drawRect();
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleTouchmove(event: TouchEvent) {
|
2020-12-29 14:17:05 +09:00
|
|
|
event.preventDefault();
|
2020-12-29 03:33:50 +09:00
|
|
|
[...event.touches].map(handleMouseMove);
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleMouseMoveEnd() {
|
|
|
|
mouse.set(NaN, NaN);
|
|
|
|
}
|
|
|
|
|
2020-12-29 11:46:13 +09:00
|
|
|
function adjustPanelSize() {
|
|
|
|
const width = camera.aspect / grid.colum;
|
|
|
|
const height = 1 / grid.row;
|
|
|
|
for (const [i, panel] of panels.entries()) {
|
|
|
|
panel.scale.set(width, height, 1);
|
|
|
|
Object.assign(panel.position, {
|
|
|
|
x: width * ((i % grid.colum) - (grid.colum - 1) / 2),
|
|
|
|
y: height * (-Math.floor(i / grid.colum) + (grid.row - 1) / 2),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-29 16:42:09 +09:00
|
|
|
function adjustRendererSize() {
|
|
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
2020-12-29 11:46:13 +09:00
|
|
|
camera.aspect = canvas.clientWidth / canvas.clientHeight;
|
|
|
|
camera.updateProjectionMatrix();
|
|
|
|
adjustPanelSize();
|
|
|
|
render();
|
|
|
|
}
|
|
|
|
|
2020-12-29 03:33:50 +09:00
|
|
|
function setup() {
|
|
|
|
scene.background = new Color();
|
2020-12-29 11:46:13 +09:00
|
|
|
scene.clear();
|
2020-12-29 03:33:50 +09:00
|
|
|
scene.add(...panels);
|
|
|
|
camera.position.z = 1;
|
2020-12-29 16:42:09 +09:00
|
|
|
adjustRendererSize();
|
2020-12-29 11:46:13 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
function main() {
|
2020-12-29 14:17:05 +09:00
|
|
|
Object.assign(canvas.style, { width: "100%", height: "100%" });
|
2020-12-29 03:33:50 +09:00
|
|
|
root.addEventListener("keydown", handleKeydown);
|
2020-12-29 16:32:50 +09:00
|
|
|
root.addEventListener("touchstart", handleTouchmove, { passive: false });
|
|
|
|
root.addEventListener("touchmove", handleTouchmove, { passive: false });
|
2020-12-29 16:44:11 +09:00
|
|
|
root.addEventListener("mousedown", handleMouseMove);
|
2020-12-29 03:33:50 +09:00
|
|
|
root.addEventListener("mousedown", () => {
|
|
|
|
root.addEventListener("mousemove", handleMouseMove);
|
|
|
|
});
|
|
|
|
for (const event of ["mouseup", "mouseleave"] as const) {
|
|
|
|
root.addEventListener(event, () => {
|
|
|
|
root.removeEventListener("mousemove", handleMouseMove);
|
|
|
|
});
|
|
|
|
}
|
2020-12-29 16:36:33 +09:00
|
|
|
for (const event of ["keyup", "mouseup", "mouseleave", "touchend"] as const) {
|
|
|
|
root.addEventListener(event, handleMouseMoveEnd);
|
|
|
|
}
|
2020-12-29 03:33:50 +09:00
|
|
|
root.appendChild(canvas);
|
2020-12-29 14:17:05 +09:00
|
|
|
Object.assign(root.style, { overflow: "hidden", overscrollBehavior: "none" });
|
2020-12-29 16:42:09 +09:00
|
|
|
window.addEventListener("resize", adjustRendererSize);
|
2020-12-29 03:33:50 +09:00
|
|
|
setup();
|
|
|
|
}
|
|
|
|
|
|
|
|
main();
|