mirror of
https://github.com/kou029w/pucchinglgl.git
synced 2025-01-18 08:15:02 +00:00
feat: create pucchinglgl
This commit is contained in:
parent
aae222df22
commit
21dfc3c19a
10 changed files with 2021 additions and 0 deletions
34
.github/workflows/github-pages.yml
vendored
Normal file
34
.github/workflows/github-pages.yml
vendored
Normal 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
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/node_modules/
|
17
package.json
Normal file
17
package.json
Normal 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
8
renovate.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": [
|
||||
"config:base",
|
||||
":preserveSemverRanges",
|
||||
":maintainLockFilesWeekly"
|
||||
],
|
||||
"automerge": true
|
||||
}
|
13
snowpack.config.json
Normal file
13
snowpack.config.json
Normal 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
15
src/index.html
Normal 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
163
src/main.ts
Normal 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
8
src/randomInt.ts
Normal 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
1
tsconfig.json
Normal file
|
@ -0,0 +1 @@
|
|||
{ "compilerOptions": { "target": "esnext", "moduleResolution": "node" } }
|
Loading…
Add table
Reference in a new issue