feat: create pucchinglgl

This commit is contained in:
Nebel 2020-12-29 03:33:50 +09:00
parent aae222df22
commit 21dfc3c19a
10 changed files with 2021 additions and 0 deletions

34
.github/workflows/github-pages.yml vendored Normal file
View file

@ -0,0 +1,34 @@
name: github-pages
on:
push:
branches: [master]
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: git config
run: |
git config user.name bot
git config user.email bot@example
- run: git subtree add -P build origin gh-pages
- id: yarn_cache
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
with:
path: ${{ steps.yarn_cache.outputs.dir }}
key: yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: yarn-
- run: yarn
- name: build
run: |
yarn build
touch build/.nojekyll
- id: git_status
run: echo "::set-output name=mod::$(git status --porcelain)"
- name: deploy
if: steps.git_status.outputs.mod != ''
run: |
git add -f build
git commit -m github-pages
git subtree push -P build origin gh-pages

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/node_modules/

17
package.json Normal file
View file

@ -0,0 +1,17 @@
{
"name": "pucchinglgl",
"version": "1.0.0",
"private": true,
"main": "build/main.js",
"repository": "git@github.com:kou029w/pucchinglgl.git",
"author": "Kohei Watanabe <kou029w@gmail.com>",
"license": "MIT",
"devDependencies": {
"snowpack": "^2.18.5",
"three": "^0.124.0"
},
"scripts": {
"dev": "snowpack dev",
"build": "snowpack build"
}
}

8
renovate.json Normal file
View file

@ -0,0 +1,8 @@
{
"extends": [
"config:base",
":preserveSemverRanges",
":maintainLockFilesWeekly"
],
"automerge": true
}

13
snowpack.config.json Normal file
View file

@ -0,0 +1,13 @@
{
"buildOptions": {
"clean": true
},
"mount": { "src": { "url": "/" } },
"experiments": {
"optimize": {
"bundle": true,
"minify": true,
"target": "esnext"
}
}
}

15
src/index.html Normal file
View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="ja" dir="ltr">
<head>
<meta charset="utf-8" />
<title>ぷっちんぐるぐる</title>
<meta name="viewport" content="width=device-width" />
<link rel="icon" href="data:," />
<style>
body {
margin: 0;
}
</style>
<script type="module" src="main.js"></script>
</head>
</html>

163
src/main.ts Normal file
View file

@ -0,0 +1,163 @@
import {
Color,
Mesh,
MeshBasicMaterial,
PerspectiveCamera,
PlaneGeometry,
Raycaster,
Scene,
Vector2,
WebGLRenderer,
} from "three";
import randomInt from "./randomInt";
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();
const material = new MeshBasicMaterial({
color: new Color("white"),
transparent: true,
});
const mesh = new Mesh(geometry, material);
return mesh;
});
const renderer = new WebGLRenderer();
const canvas = renderer.domElement;
const raycaster = new Raycaster();
function drawRect() {
initPanels(panels);
raycaster.setFromCamera(mouse, camera);
const [intersect] = raycaster.intersectObjects(panels);
if (!intersect) return;
(intersect.object as Mesh<
PlaneGeometry,
MeshBasicMaterial
>).material.opacity = 0.25;
const c16s = ["red", "green", "blue", "cyan", "magenta", "yellow"];
const c16 = c16s[randomInt(c16s.length - 1)];
const color = new Color(c16);
const geometry = new PlaneGeometry(1, 1);
const material = new MeshBasicMaterial({
color,
});
const mesh = new Mesh(geometry, material);
mesh.scale.set(0.1, 0.1, 1);
Object.assign(mesh.position, intersect.point);
scene.add(mesh);
}
function initPanels(panels: Mesh<PlaneGeometry, MeshBasicMaterial>[]) {
const width = camera.aspect / grid.colum;
const height = 1 / grid.row;
for (const [i, panel] of panels.entries()) {
panel.material.opacity = 0;
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),
});
}
}
const layouts = {
default: [
`\` 1 2 3 4 5 6 7 8 9 0 - = Backspace`,
`Tab q w e r t y u i o p [ ] \\`,
`CapsLock a s d f g h j k l ; ' Enter`,
`Shift z x c v b n m , . /`,
],
shift: [
`~ ! @ # $ % ^ & * ( ) _ + Backspace`,
`Tab Q W E R T Y U I O P { } |`,
`CapsLock A S D F G H J K L : " Enter`,
`Shift Z X C V B N M < > ?`,
],
} as const;
function position(
layout: readonly string[],
key: string
): [number, number] | [] {
const [x, y] = layout
.map((row) => row.split(" ").indexOf(key))
.flatMap((x, y) => (x >= 0 ? [x, y] : []));
return x >= 0 ? [x, y] : [];
}
function handleKeydown({ key }: KeyboardEvent) {
if (key === " ") {
scene.clear();
setup();
return;
}
const [x = randomInt(grid.colum - 1), y = randomInt(grid.row - 1)] = [
...position(layouts.default, key),
...position(layouts.shift, key),
];
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 }) {
mouse.x = (event.clientX / canvas.clientWidth) * 2 - 1;
mouse.y = -(event.clientY / canvas.clientHeight) * 2 + 1;
drawRect();
}
function handleTouchmove(event: TouchEvent) {
[...event.touches].map(handleMouseMove);
}
function handleMouseMoveEnd() {
mouse.set(NaN, NaN);
}
function setup() {
scene.background = new Color();
initPanels(panels);
scene.add(...panels);
camera.position.z = 1;
root.addEventListener("keydown", handleKeydown);
root.addEventListener("touchmove", handleTouchmove);
root.addEventListener("keyup", handleMouseMoveEnd);
root.addEventListener("touchend", handleMouseMoveEnd);
root.addEventListener("mousedown", () => {
root.addEventListener("mousemove", handleMouseMove);
});
for (const event of ["mouseup", "mouseleave"] as const) {
root.addEventListener(event, () => {
root.removeEventListener("mousemove", handleMouseMove);
handleMouseMoveEnd();
});
}
}
function tick() {
window.requestAnimationFrame(tick);
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}
function main() {
renderer.setSize(window.innerWidth, window.innerHeight);
Object.assign(canvas.style, { width: "100vw", height: "100vh" });
root.appendChild(canvas);
setup();
tick();
}
main();

8
src/randomInt.ts Normal file
View file

@ -0,0 +1,8 @@
const { floor, random } = Math;
/** 範囲 [0, high] のランダムな整数を返します */
function randomInt(high: number) {
return floor(random() * (high + 1));
}
export default randomInt;

1
tsconfig.json Normal file
View file

@ -0,0 +1 @@
{ "compilerOptions": { "target": "esnext", "moduleResolution": "node" } }

1761
yarn.lock Normal file

File diff suppressed because it is too large Load diff