mirror of
https://github.com/kou029w/pucchinglgl.git
synced 2025-01-18 16:18:00 +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