├── .gitignore
├── 01-solar-system
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── pages
│ ├── _app.jsx
│ ├── api
│ │ └── hello.js
│ ├── index.jsx
│ └── lib
│ │ ├── Planet.js
│ │ ├── Rotation.js
│ │ └── SceneInit.js
├── postcss.config.js
├── public
│ ├── earth.jpeg
│ ├── favicon.ico
│ ├── mars.jpeg
│ ├── mercury.png
│ ├── sun.jpeg
│ ├── venus.jpeg
│ └── vercel.svg
└── tailwind.config.js
├── 02-tic-tac-toe
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── pages
│ ├── _app.jsx
│ ├── api
│ │ └── hello.js
│ ├── cm.jsx
│ ├── index.jsx
│ └── lib
│ │ ├── SceneInit.js
│ │ └── TicTacToe.js
├── postcss.config.js
├── public
│ ├── favicon.ico
│ └── vercel.svg
└── tailwind.config.js
├── 03-tic-tac-toe-3d
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── pages
│ ├── _app.js
│ ├── api
│ │ └── hello.js
│ ├── index.js
│ └── lib
│ │ ├── SceneInit.js
│ │ └── TicTacToeCube.js
├── postcss.config.js
├── public
│ ├── favicon.ico
│ └── vercel.svg
└── tailwind.config.js
├── 04-audio-visualizer
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── pages
│ ├── _app.jsx
│ ├── components
│ │ └── CustomEditor.jsx
│ ├── index.jsx
│ └── lib
│ │ ├── SceneInit.js
│ │ └── Shaders.js
├── postcss.config.js
├── public
│ ├── favicon.ico
│ ├── fur_elise.mp3
│ └── vercel.svg
└── tailwind.config.js
├── 05-naruto-rasengan
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── pages
│ ├── _app.jsx
│ ├── api
│ │ └── hello.js
│ ├── index.jsx
│ └── lib
│ │ ├── SceneInit.js
│ │ └── Shaders.js
├── postcss.config.js
├── public
│ ├── favicon.ico
│ ├── hand.obj
│ └── vercel.svg
├── styles
│ └── globals.css
└── tailwind.config.js
├── 06-piano
├── .gitignore
├── .prettierrc.js
├── acoustic_grand_piano_mp3
│ ├── A0.mp3
│ ├── A1.mp3
│ ├── A2.mp3
│ ├── A3.mp3
│ ├── A4.mp3
│ ├── A5.mp3
│ ├── A6.mp3
│ ├── A7.mp3
│ ├── Ab1.mp3
│ ├── Ab2.mp3
│ ├── Ab3.mp3
│ ├── Ab4.mp3
│ ├── Ab5.mp3
│ ├── Ab6.mp3
│ ├── Ab7.mp3
│ ├── B0.mp3
│ ├── B1.mp3
│ ├── B2.mp3
│ ├── B3.mp3
│ ├── B4.mp3
│ ├── B5.mp3
│ ├── B6.mp3
│ ├── B7.mp3
│ ├── Bb0.mp3
│ ├── Bb1.mp3
│ ├── Bb2.mp3
│ ├── Bb3.mp3
│ ├── Bb4.mp3
│ ├── Bb5.mp3
│ ├── Bb6.mp3
│ ├── Bb7.mp3
│ ├── C1.mp3
│ ├── C2.mp3
│ ├── C3.mp3
│ ├── C4.mp3
│ ├── C5.mp3
│ ├── C6.mp3
│ ├── C7.mp3
│ ├── C8.mp3
│ ├── D1.mp3
│ ├── D2.mp3
│ ├── D3.mp3
│ ├── D4.mp3
│ ├── D5.mp3
│ ├── D6.mp3
│ ├── D7.mp3
│ ├── Db1.mp3
│ ├── Db2.mp3
│ ├── Db3.mp3
│ ├── Db4.mp3
│ ├── Db5.mp3
│ ├── Db6.mp3
│ ├── Db7.mp3
│ ├── Db8.mp3
│ ├── E1.mp3
│ ├── E2.mp3
│ ├── E3.mp3
│ ├── E4.mp3
│ ├── E5.mp3
│ ├── E6.mp3
│ ├── E7.mp3
│ ├── Eb1.mp3
│ ├── Eb2.mp3
│ ├── Eb3.mp3
│ ├── Eb4.mp3
│ ├── Eb5.mp3
│ ├── Eb6.mp3
│ ├── Eb7.mp3
│ ├── F1.mp3
│ ├── F2.mp3
│ ├── F3.mp3
│ ├── F4.mp3
│ ├── F5.mp3
│ ├── F6.mp3
│ ├── F7.mp3
│ ├── G1.mp3
│ ├── G2.mp3
│ ├── G3.mp3
│ ├── G4.mp3
│ ├── G5.mp3
│ ├── G6.mp3
│ ├── G7.mp3
│ ├── Gb1.mp3
│ ├── Gb2.mp3
│ ├── Gb3.mp3
│ ├── Gb4.mp3
│ ├── Gb5.mp3
│ ├── Gb6.mp3
│ └── Gb7.mp3
├── fonts
│ └── Helvetica-Bold.typeface.json
├── index.html
├── package-lock.json
├── package.json
├── pics
│ └── space.jpeg
├── src
│ ├── App.css
│ ├── App.jsx
│ ├── favicon.svg
│ ├── index.css
│ ├── lib
│ │ ├── Key.js
│ │ ├── Piano.js
│ │ └── SceneInit.js
│ ├── logo.svg
│ ├── main.jsx
│ └── test.js
└── vite.config.js
├── 07-wordle
├── .gitignore
├── .prettierrc.js
├── fonts
│ └── JetBrainsMonoExtraBold.ttf
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── App.jsx
│ ├── lib
│ │ ├── Block.js
│ │ ├── SceneInit.js
│ │ ├── Utils.js
│ │ └── Wordle.js
│ └── main.jsx
└── vite.config.js
├── 08-rubiks-cube
├── .gitignore
├── .prettierrc.js
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── App.jsx
│ ├── lib
│ │ ├── Cube.js
│ │ ├── RubiksCube.js
│ │ ├── SceneInit.js
│ │ └── Shaders.js
│ └── main.jsx
└── vite.config.js
├── 09-snake-retro
├── .gitignore
├── .prettierrc.js
├── assets
│ └── crt_monitor
│ │ ├── scene.bin
│ │ ├── scene.gltf
│ │ └── textures
│ │ ├── Material_baseColor.png
│ │ ├── Material_metallicRoughness.png
│ │ └── Material_normal.png
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── App.jsx
│ ├── lib
│ │ ├── SceneInit.js
│ │ ├── Shaders.js
│ │ └── SnakeGame.js
│ └── main.jsx
└── vite.config.js
├── 09-snake
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── pages
│ ├── _app.jsx
│ ├── api
│ │ └── hello.js
│ ├── index.jsx
│ └── lib
│ │ ├── SceneInit.js
│ │ └── SnakeGame.js
├── postcss.config.js
├── public
│ ├── favicon.ico
│ └── vercel.svg
└── tailwind.config.js
├── 10-frantic-architect
├── .gitignore
├── .prettierrc.js
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── App.css
│ ├── App.jsx
│ ├── index.css
│ ├── lib
│ │ ├── FranticArchitect.js
│ │ └── SceneInit.js
│ └── main.jsx
└── vite.config.js
├── LICENSE
├── README.md
├── _demos
├── audio-visualizer-1.png
├── audio-visualizer-2.png
├── fa-debug-1.png
├── fa-debug-2.png
├── frantic-architect-1.png
├── frantic-architect-2.png
├── frantic-architect-3.png
├── narutos-rasengan.png
├── piano.png
├── rubiks-cube-1.png
├── rubiks-cube-2.png
├── snake-retro-1.png
├── snake-retro-2.png
├── snakes-and-portals.png
├── solar-system.png
├── tic-tac-toe-1.png
├── tic-tac-toe-2.png
├── tic-tac-toe-3d-1.png
├── tic-tac-toe-3d-2.png
└── wordle.png
├── _reddit
├── audio-visualizer.png
├── narutos-rasengan.png
├── piano.png
├── rubiks-cube.png
├── snake-retro.png
├── solar-system.png
├── tic-tac-toe-3d.png
├── tic-tac-toe.png
└── wordle.png
└── _templates
├── README.md
└── three-vite-react
├── .gitignore
├── .prettierrc.js
├── index.html
├── package-lock.json
├── package.json
├── src
├── App.jsx
├── lib
│ └── SceneInit.js
└── main.jsx
└── vite.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | # MacOS
107 | *.DS*
--------------------------------------------------------------------------------
/01-solar-system/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/01-solar-system/README.md:
--------------------------------------------------------------------------------
1 | # Next.js + Tailwind CSS Example
2 |
3 | This example shows how to use [Tailwind CSS](https://tailwindcss.com/) [(v2.2)](https://blog.tailwindcss.com/tailwindcss-2-2) with Next.js. It follows the steps outlined in the official [Tailwind docs](https://tailwindcss.com/docs/guides/nextjs).
4 |
5 | It uses the new [`Just-in-Time Mode`](https://tailwindcss.com/docs/just-in-time-mode) for Tailwind CSS.
6 |
7 | ## Preview
8 |
9 | Preview the example live on [StackBlitz](http://stackblitz.com/):
10 |
11 | [](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-tailwindcss)
12 |
13 | ## Deploy your own
14 |
15 | Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
16 |
17 | [](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-tailwindcss&project-name=with-tailwindcss&repository-name=with-tailwindcss)
18 |
19 | ## How to use
20 |
21 | Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
22 |
23 | ```bash
24 | npx create-next-app --example with-tailwindcss with-tailwindcss-app
25 | # or
26 | yarn create next-app --example with-tailwindcss with-tailwindcss-app
27 | ```
28 |
29 | Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
30 |
--------------------------------------------------------------------------------
/01-solar-system/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "next dev",
5 | "build": "next build",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "dat.gui": "^0.7.7",
10 | "next": "latest",
11 | "react": "^17.0.2",
12 | "react-dom": "^17.0.2",
13 | "three": "^0.134.0"
14 | },
15 | "devDependencies": {
16 | "autoprefixer": "^10.2.6",
17 | "postcss": "^8.3.5",
18 | "tailwindcss": "^2.2.4"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/01-solar-system/pages/_app.jsx:
--------------------------------------------------------------------------------
1 | import 'tailwindcss/tailwind.css'
2 |
3 | function MyApp({ Component, pageProps }) {
4 | return
5 | }
6 |
7 | export default MyApp
8 |
--------------------------------------------------------------------------------
/01-solar-system/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function helloAPI(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/01-solar-system/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { useEffect } from "react";
3 | import SceneInit from "./lib/SceneInit";
4 | import Planet from "./lib/Planet";
5 | import Rotation from "./lib/Rotation";
6 |
7 | export default function Home() {
8 | let gui;
9 |
10 | const initGui = async () => {
11 | const dat = await import("dat.gui");
12 | gui = new dat.GUI();
13 | };
14 |
15 | useEffect(async () => {
16 | // TODO: Understand this code later.
17 | let test = new SceneInit();
18 | test.initScene();
19 | test.animate();
20 |
21 | const sunGeometry = new THREE.SphereGeometry(8);
22 | const sunTexture = new THREE.TextureLoader().load("sun.jpeg");
23 | const sunMaterial = new THREE.MeshBasicMaterial({ map: sunTexture });
24 | const sunMesh = new THREE.Mesh(sunGeometry, sunMaterial);
25 | const solarSystem = new THREE.Group();
26 | solarSystem.add(sunMesh);
27 | test.scene.add(solarSystem);
28 |
29 | const mercury = new Planet(2, 16, "mercury.png");
30 | const mercuryMesh = mercury.getMesh();
31 | let mercurySystem = new THREE.Group();
32 | mercurySystem.add(mercuryMesh);
33 |
34 | const venus = new Planet(3, 32, "venus.jpeg");
35 | const venusMesh = venus.getMesh();
36 | let venusSystem = new THREE.Group();
37 | venusSystem.add(venusMesh);
38 |
39 | const earth = new Planet(4, 48, "earth.jpeg");
40 | const earthMesh = earth.getMesh();
41 | let earthSystem = new THREE.Group();
42 | earthSystem.add(earthMesh);
43 |
44 | const mars = new Planet(3, 64, "mars.jpeg");
45 | const marsMesh = mars.getMesh();
46 | let marsSystem = new THREE.Group();
47 | marsSystem.add(marsMesh);
48 |
49 | solarSystem.add(mercurySystem, venusSystem, earthSystem, marsSystem);
50 |
51 | const mercuryRotation = new Rotation(mercuryMesh);
52 | const mercuryRotationMesh = mercuryRotation.getMesh();
53 | mercurySystem.add(mercuryRotationMesh);
54 | const venusRotation = new Rotation(venusMesh);
55 | const venusRotationMesh = venusRotation.getMesh();
56 | venusSystem.add(venusRotationMesh);
57 | const earthRotation = new Rotation(earthMesh);
58 | const earthRotationMesh = earthRotation.getMesh();
59 | earthSystem.add(earthRotationMesh);
60 | const marsRotation = new Rotation(marsMesh);
61 | const marsRotationMesh = marsRotation.getMesh();
62 | marsSystem.add(marsRotationMesh);
63 |
64 | // NOTE: Add solar system mesh GUI.
65 | await initGui();
66 | const solarSystemGui = gui.addFolder("solar system");
67 | solarSystemGui.add(mercuryRotationMesh, "visible").name("mercury").listen();
68 | solarSystemGui.add(venusRotationMesh, "visible").name("venus").listen();
69 | solarSystemGui.add(earthRotationMesh, "visible").name("earth").listen();
70 | solarSystemGui.add(marsRotationMesh, "visible").name("mars").listen();
71 |
72 | // NOTE: Animate solar system at 60fps.
73 | const EARTH_YEAR = 2 * Math.PI * (1 / 60) * (1 / 60);
74 | const animate = () => {
75 | sunMesh.rotation.y += 0.001;
76 | mercurySystem.rotation.y += EARTH_YEAR * 4;
77 | venusSystem.rotation.y += EARTH_YEAR * 2;
78 | earthSystem.rotation.y += EARTH_YEAR;
79 | marsSystem.rotation.y += EARTH_YEAR * 0.5;
80 | requestAnimationFrame(animate);
81 | };
82 | animate();
83 | }, []);
84 |
85 | return (
86 |
87 |
88 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/01-solar-system/pages/lib/Planet.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 |
3 | export default class Planet {
4 | constructor(radius, positionX, textureFile) {
5 | this.radius = radius;
6 | this.positionX = positionX;
7 | this.textureFile = textureFile;
8 | }
9 |
10 | getMesh() {
11 | if (this.mesh === undefined || this.mesh === null) {
12 | const geometry = new THREE.SphereGeometry(this.radius);
13 | const texture = new THREE.TextureLoader().load(this.textureFile);
14 | const material = new THREE.MeshBasicMaterial({ map: texture });
15 | this.mesh = new THREE.Mesh(geometry, material);
16 | this.mesh.position.x += this.positionX;
17 | }
18 | return this.mesh;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/01-solar-system/pages/lib/Rotation.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 |
3 | export default class Rotation {
4 | constructor(planetMesh, showRotation = false) {
5 | this.planetPositionX = planetMesh.position.x;
6 | this.y = 0.25;
7 | this.z = 0.25;
8 | this.showRotation = showRotation;
9 | }
10 |
11 | getMesh() {
12 | if (this.mesh === undefined || this.mesh === null) {
13 | const geometry = new THREE.BoxGeometry(this.planetPositionX, 0.25, 0.25);
14 | const material = new THREE.MeshNormalMaterial();
15 | this.mesh = new THREE.Mesh(geometry, material);
16 | this.mesh.position.x = this.planetPositionX / 2;
17 | this.mesh.visible = this.showRotation;
18 | }
19 | return this.mesh;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/01-solar-system/pages/lib/SceneInit.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
3 | import Stats from "three/examples/jsm/libs/stats.module";
4 |
5 | export default class SceneInit {
6 | constructor(fov = 36, camera, scene, stats, controls, renderer) {
7 | this.fov = fov;
8 | this.scene = scene;
9 | this.stats = stats;
10 | this.camera = camera;
11 | this.controls = controls;
12 | this.renderer = renderer;
13 | }
14 |
15 | initScene() {
16 | this.camera = new THREE.PerspectiveCamera(
17 | this.fov,
18 | window.innerWidth / window.innerHeight,
19 | 1,
20 | 1000
21 | );
22 | this.camera.position.z = 128;
23 |
24 | this.scene = new THREE.Scene();
25 |
26 | // const spaceTexture = new THREE.TextureLoader().load("space2.jpeg");
27 | // this.scene.background = spaceTexture;
28 |
29 | // specify a canvas which is already created in the HTML file and tagged by an id
30 | // aliasing enabled
31 | this.renderer = new THREE.WebGLRenderer({
32 | canvas: document.getElementById("myThreeJsCanvas"),
33 | antialias: true,
34 | });
35 | this.renderer.setSize(window.innerWidth, window.innerHeight);
36 | document.body.appendChild(this.renderer.domElement);
37 |
38 | this.controls = new OrbitControls(this.camera, this.renderer.domElement);
39 |
40 | this.stats = Stats();
41 | document.body.appendChild(this.stats.dom);
42 |
43 | // ambient light which is for the whole scene
44 | // let ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
45 | // ambientLight.castShadow = false;
46 | // this.scene.add(ambientLight);
47 |
48 | // spot light which is illuminating the chart directly
49 | // let spotLight = new THREE.SpotLight(0xffffff, 0.55);
50 | // spotLight.castShadow = true;
51 | // spotLight.position.set(0, 40, 10);
52 | // this.scene.add(spotLight);
53 |
54 | // if window resizes
55 | window.addEventListener("resize", () => this.onWindowResize(), false);
56 | }
57 |
58 | animate() {
59 | // requestAnimationFrame(this.animate.bind(this));
60 | window.requestAnimationFrame(this.animate.bind(this));
61 | this.render();
62 | this.stats.update();
63 | // this.controls.update();
64 | }
65 |
66 | render() {
67 | this.renderer.render(this.scene, this.camera);
68 | }
69 |
70 | onWindowResize() {
71 | this.camera.aspect = window.innerWidth / window.innerHeight;
72 | this.camera.updateProjectionMatrix();
73 | this.renderer.setSize(window.innerWidth, window.innerHeight);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/01-solar-system/postcss.config.js:
--------------------------------------------------------------------------------
1 | // If you want to use other PostCSS plugins, see the following:
2 | // https://tailwindcss.com/docs/using-with-preprocessors
3 | module.exports = {
4 | plugins: {
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/01-solar-system/public/earth.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/01-solar-system/public/earth.jpeg
--------------------------------------------------------------------------------
/01-solar-system/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/01-solar-system/public/favicon.ico
--------------------------------------------------------------------------------
/01-solar-system/public/mars.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/01-solar-system/public/mars.jpeg
--------------------------------------------------------------------------------
/01-solar-system/public/mercury.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/01-solar-system/public/mercury.png
--------------------------------------------------------------------------------
/01-solar-system/public/sun.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/01-solar-system/public/sun.jpeg
--------------------------------------------------------------------------------
/01-solar-system/public/venus.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/01-solar-system/public/venus.jpeg
--------------------------------------------------------------------------------
/01-solar-system/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/01-solar-system/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
4 | darkMode: false, // or 'media' or 'class'
5 | theme: {
6 | extend: {},
7 | },
8 | variants: {
9 | extend: {},
10 | },
11 | plugins: [],
12 | }
13 |
--------------------------------------------------------------------------------
/02-tic-tac-toe/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/02-tic-tac-toe/README.md:
--------------------------------------------------------------------------------
1 | # Next.js + Tailwind CSS Example
2 |
3 | This example shows how to use [Tailwind CSS](https://tailwindcss.com/) [(v2.2)](https://blog.tailwindcss.com/tailwindcss-2-2) with Next.js. It follows the steps outlined in the official [Tailwind docs](https://tailwindcss.com/docs/guides/nextjs).
4 |
5 | It uses the new [`Just-in-Time Mode`](https://tailwindcss.com/docs/just-in-time-mode) for Tailwind CSS.
6 |
7 | ## Preview
8 |
9 | Preview the example live on [StackBlitz](http://stackblitz.com/):
10 |
11 | [](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-tailwindcss)
12 |
13 | ## Deploy your own
14 |
15 | Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
16 |
17 | [](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-tailwindcss&project-name=with-tailwindcss&repository-name=with-tailwindcss)
18 |
19 | ## How to use
20 |
21 | Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
22 |
23 | ```bash
24 | npx create-next-app --example with-tailwindcss with-tailwindcss-app
25 | # or
26 | yarn create next-app --example with-tailwindcss with-tailwindcss-app
27 | ```
28 |
29 | Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
30 |
--------------------------------------------------------------------------------
/02-tic-tac-toe/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "next dev",
5 | "build": "next build",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "@codemirror/lang-javascript": "^0.19.3",
10 | "@uiw/react-codemirror": "^4.2.4",
11 | "codemirror": "^5.63.3",
12 | "next": "latest",
13 | "react": "^17.0.2",
14 | "react-dom": "^17.0.2",
15 | "three": "^0.134.0"
16 | },
17 | "devDependencies": {
18 | "autoprefixer": "^10.2.6",
19 | "postcss": "^8.3.5",
20 | "tailwindcss": "^2.2.4"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/02-tic-tac-toe/pages/_app.jsx:
--------------------------------------------------------------------------------
1 | import "tailwindcss/tailwind.css";
2 |
3 | function MyApp({ Component, pageProps }) {
4 | return ;
5 | }
6 |
7 | export default MyApp;
8 |
--------------------------------------------------------------------------------
/02-tic-tac-toe/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function helloAPI(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/02-tic-tac-toe/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 |
3 | import { useEffect } from "react";
4 | import SceneInit from "./lib/SceneInit";
5 | import TicTacToe from "./lib/TicTacToe";
6 | import CM from "./cm";
7 |
8 | export default function Home() {
9 | useEffect(() => {
10 | const test = new SceneInit("myThreeJsCanvas");
11 | test.initScene();
12 | test.animate();
13 |
14 | const ticTacToe = new TicTacToe();
15 | test.scene.add(ticTacToe.board);
16 |
17 | const mouse = new THREE.Vector2();
18 | const raycaster = new THREE.Raycaster();
19 |
20 | function onMouseDown(event) {
21 | // Half-screen
22 | const splitScreen = document.getElementById("splitScreen");
23 | mouse.x = (event.clientX / splitScreen.clientWidth) * 2 - 3;
24 | mouse.y = -(event.clientY / splitScreen.clientHeight) * 2 + 1;
25 | // Full-screen
26 | // mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
27 | // mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
28 | raycaster.setFromCamera(mouse, test.camera);
29 | const intersects = raycaster.intersectObjects(
30 | ticTacToe.hiddenTiles.children
31 | );
32 | console.log(intersects);
33 | if (intersects.length > 0) {
34 | const xOffset = intersects[0].object.position.x;
35 | const yOffset = intersects[0].object.position.y;
36 | ticTacToe.addCrossOrCircle(xOffset, yOffset);
37 | ticTacToe.checkWinConditions();
38 | const index = ticTacToe.hiddenTiles.children.findIndex(
39 | (c) => c.uuid === intersects[0].object.uuid
40 | );
41 | ticTacToe.hiddenTiles.children.splice(index, 1);
42 | }
43 | // NOTE: Demo ray being cast past objects.
44 | // for (let i = 0; i < intersects.length; i++) {
45 | // intersects[i].object.material.wireframe =
46 | // !intersects[i].object.material.wireframe;
47 | // }
48 | }
49 |
50 | window.addEventListener("mousedown", onMouseDown, false);
51 |
52 | const scaleUp = (obj) => {
53 | if (obj.scale.x < 1) {
54 | obj.scale.x += 0.04;
55 | }
56 | if (obj.scale.y < 1) {
57 | obj.scale.y += 0.04;
58 | }
59 | if (obj.scale.z < 1) {
60 | obj.scale.z += 0.04;
61 | }
62 | };
63 |
64 | // NOTE: Animate board and player moves.
65 | const animate = () => {
66 | ticTacToe.boardLines.children.forEach(scaleUp);
67 | ticTacToe.circles.children.forEach(scaleUp);
68 | ticTacToe.crosses.children.forEach(scaleUp);
69 | ticTacToe.winLine.children.forEach(scaleUp);
70 | // ticTacToe.board.rotation.y += 0.002;
71 | requestAnimationFrame(animate);
72 | };
73 | animate();
74 | });
75 |
76 | return (
77 |
78 | {/* Full-screen */}
79 | {/*
*/}
80 |
81 | {/* Half-screen */}
82 |
87 |
88 |
89 |
90 |
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/02-tic-tac-toe/pages/lib/SceneInit.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
3 | import Stats from "three/examples/jsm/libs/stats.module";
4 |
5 | export default class SceneInit {
6 | constructor(canvasID, camera, scene, stats, controls, renderer, fov = 36) {
7 | this.fov = fov;
8 | this.scene = scene;
9 | this.stats = stats;
10 | this.camera = camera;
11 | this.controls = controls;
12 | this.renderer = renderer;
13 | this.canvasID = canvasID;
14 | }
15 |
16 | initScene() {
17 | this.splitScreen = document.getElementById("splitScreen");
18 | this.camera = new THREE.PerspectiveCamera(
19 | this.fov,
20 | this.splitScreen.clientWidth / this.splitScreen.clientHeight,
21 | 1,
22 | 1000
23 | );
24 | // this.camera = new THREE.PerspectiveCamera(
25 | // this.fov,
26 | // window.innerWidth / window.innerHeight,
27 | // 1,
28 | // 1000
29 | // );
30 | this.camera.position.z = 128;
31 |
32 | this.scene = new THREE.Scene();
33 |
34 | // const spaceTexture = new THREE.TextureLoader().load("space2.jpeg");
35 | // this.scene.background = spaceTexture;
36 |
37 | // specify a canvas which is already created in the HTML file and tagged by an id
38 | // aliasing enabled
39 | const canvas = document.getElementById(this.canvasID);
40 | this.renderer = new THREE.WebGLRenderer({
41 | canvas,
42 | antialias: true,
43 | });
44 |
45 | // this.renderer.setSize(window.innerWidth, window.innerHeight);
46 | this.renderer.setSize(
47 | this.splitScreen.clientWidth,
48 | this.splitScreen.clientHeight
49 | );
50 | document.body.appendChild(this.renderer.domElement);
51 |
52 | this.controls = new OrbitControls(this.camera, this.renderer.domElement);
53 |
54 | // this.stats = Stats();
55 | // document.body.appendChild(this.stats.dom);
56 |
57 | // ambient light which is for the whole scene
58 | // let ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
59 | // ambientLight.castShadow = false;
60 | // this.scene.add(ambientLight);
61 |
62 | // spot light which is illuminating the chart directly
63 | // let spotLight = new THREE.SpotLight(0xffffff, 0.55);
64 | // spotLight.castShadow = true;
65 | // spotLight.position.set(0, 40, 10);
66 | // this.scene.add(spotLight);
67 |
68 | // if window resizes
69 | window.addEventListener("resize", () => this.onWindowResize(), false);
70 | }
71 |
72 | animate() {
73 | // NOTE: Window is implied.
74 | // requestAnimationFrame(this.animate.bind(this));
75 | window.requestAnimationFrame(this.animate.bind(this));
76 | this.render();
77 | // this.stats.update();
78 | this.controls.update();
79 | }
80 |
81 | render() {
82 | this.renderer.render(this.scene, this.camera);
83 | }
84 |
85 | onWindowResize() {
86 | // this.camera.aspect = window.innerWidth / window.innerHeight;
87 | this.camera.aspect =
88 | this.splitScreen.clientWidth / this.splitScreen.clientHeight;
89 | this.camera.updateProjectionMatrix();
90 | // this.renderer.setSize(window.innerWidth, window.innerHeight);
91 | this.renderer.setSize(
92 | this.splitScreen.clientWidth,
93 | this.splitScreen.clientHeight
94 | );
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/02-tic-tac-toe/postcss.config.js:
--------------------------------------------------------------------------------
1 | // If you want to use other PostCSS plugins, see the following:
2 | // https://tailwindcss.com/docs/using-with-preprocessors
3 | module.exports = {
4 | plugins: {
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/02-tic-tac-toe/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/02-tic-tac-toe/public/favicon.ico
--------------------------------------------------------------------------------
/02-tic-tac-toe/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/02-tic-tac-toe/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: "jit",
3 | purge: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"],
4 | darkMode: false, // or 'media' or 'class'
5 | theme: {
6 | extend: {},
7 | },
8 | variants: {
9 | extend: {
10 | backgroundOpacity: ["active"],
11 | },
12 | },
13 | plugins: [],
14 | };
15 |
--------------------------------------------------------------------------------
/03-tic-tac-toe-3d/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/03-tic-tac-toe-3d/README.md:
--------------------------------------------------------------------------------
1 | # Next.js + Tailwind CSS Example
2 |
3 | This example shows how to use [Tailwind CSS](https://tailwindcss.com/) [(v2.2)](https://blog.tailwindcss.com/tailwindcss-2-2) with Next.js. It follows the steps outlined in the official [Tailwind docs](https://tailwindcss.com/docs/guides/nextjs).
4 |
5 | It uses the new [`Just-in-Time Mode`](https://tailwindcss.com/docs/just-in-time-mode) for Tailwind CSS.
6 |
7 | ## Preview
8 |
9 | Preview the example live on [StackBlitz](http://stackblitz.com/):
10 |
11 | [](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-tailwindcss)
12 |
13 | ## Deploy your own
14 |
15 | Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
16 |
17 | [](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-tailwindcss&project-name=with-tailwindcss&repository-name=with-tailwindcss)
18 |
19 | ## How to use
20 |
21 | Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
22 |
23 | ```bash
24 | npx create-next-app --example with-tailwindcss with-tailwindcss-app
25 | # or
26 | yarn create next-app --example with-tailwindcss with-tailwindcss-app
27 | ```
28 |
29 | Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
30 |
--------------------------------------------------------------------------------
/03-tic-tac-toe-3d/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "next dev",
5 | "build": "next build",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "next": "latest",
10 | "react": "^17.0.2",
11 | "react-dom": "^17.0.2",
12 | "three": "^0.134.0"
13 | },
14 | "devDependencies": {
15 | "autoprefixer": "^10.2.6",
16 | "postcss": "^8.3.5",
17 | "tailwindcss": "^2.2.4"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/03-tic-tac-toe-3d/pages/_app.js:
--------------------------------------------------------------------------------
1 | import 'tailwindcss/tailwind.css'
2 |
3 | function MyApp({ Component, pageProps }) {
4 | return
5 | }
6 |
7 | export default MyApp
8 |
--------------------------------------------------------------------------------
/03-tic-tac-toe-3d/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function helloAPI(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/03-tic-tac-toe-3d/pages/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 |
3 | import { useEffect } from "react";
4 |
5 | import SceneInit from "./lib/SceneInit";
6 | import TicTacToeCube from "./lib/TicTacToeCube";
7 | import { WireframeGeometry } from "three";
8 |
9 | export default function Home() {
10 | const canvasId = "myThreeCanvas";
11 |
12 | useEffect(() => {
13 | const test = new SceneInit(canvasId);
14 | test.initScene();
15 | test.animate();
16 |
17 | const game = new TicTacToeCube();
18 | test.scene.add(game.board);
19 |
20 | const mouse = new THREE.Vector2();
21 | const raycaster = new THREE.Raycaster();
22 |
23 | const onMouseMove = (event) => {
24 | mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
25 | mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
26 | raycaster.setFromCamera(mouse, test.camera);
27 | const intersects = raycaster.intersectObjects(game.hiddenCubes.children);
28 |
29 | // NOTE: Hide all previous cubes.
30 | const hideCube = (hiddenCube) => {
31 | hiddenCube.material.wireframe = true;
32 | };
33 | game.hiddenCubes.children.forEach((hiddenCube) => hideCube(hiddenCube));
34 |
35 | if (intersects.length > 0) {
36 | const index = game.hiddenCubes.children.findIndex(
37 | (c) => c.uuid === intersects[0].object.uuid
38 | );
39 | game.hiddenCubes.children[index].material.wireframe = false;
40 | }
41 | };
42 |
43 | const onMouseDown = (event) => {
44 | mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
45 | mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
46 | raycaster.setFromCamera(mouse, test.camera);
47 | const intersects = raycaster.intersectObjects(game.hiddenCubes.children);
48 | console.log(intersects);
49 | if (intersects.length > 0) {
50 | const index = game.hiddenCubes.children.findIndex(
51 | (c) => c.uuid === intersects[0].object.uuid
52 | );
53 | game.hiddenCubes.children.splice(index, 1);
54 | const x = intersects[0].object.position.x;
55 | const y = intersects[0].object.position.y;
56 | const z = intersects[0].object.position.z;
57 | game.addSphereOrAsterisk({ x, y, z });
58 | game.checkWinConditions();
59 | }
60 | };
61 |
62 | const scaleUp = (obj) => {
63 | if (obj.scale.x < 1) {
64 | obj.scale.x += 0.04;
65 | }
66 | if (obj.scale.y < 1) {
67 | obj.scale.y += 0.04;
68 | }
69 | if (obj.scale.z < 1) {
70 | obj.scale.z += 0.04;
71 | }
72 | };
73 |
74 | const animate = () => {
75 | game.spheres.children.forEach((sphere) => scaleUp(sphere));
76 | game.asterisks.children.forEach((asterisk) => scaleUp(asterisk));
77 | game.winStrikes.children.forEach((strike) => scaleUp(strike));
78 | requestAnimationFrame(animate);
79 | };
80 |
81 | animate();
82 |
83 | window.addEventListener("mousedown", onMouseDown, false);
84 | window.addEventListener("mousemove", onMouseMove, false);
85 | }, []);
86 |
87 | return (
88 |
89 |
90 |
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/03-tic-tac-toe-3d/pages/lib/SceneInit.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
3 | import Stats from "three/examples/jsm/libs/stats.module";
4 |
5 | export default class SceneInit {
6 | constructor(canvasID, camera, scene, stats, controls, renderer, fov = 36) {
7 | this.fov = fov;
8 | this.scene = scene;
9 | this.stats = stats;
10 | this.camera = camera;
11 | this.controls = controls;
12 | this.renderer = renderer;
13 | this.canvasID = canvasID;
14 | }
15 |
16 | initScene() {
17 | this.camera = new THREE.PerspectiveCamera(
18 | this.fov,
19 | window.innerWidth / window.innerHeight,
20 | 1,
21 | 1000
22 | );
23 | this.camera.position.z = 196;
24 |
25 | this.clock = new THREE.Clock();
26 | this.scene = new THREE.Scene();
27 |
28 | this.uniforms = {
29 | u_time: { type: "f", value: 1.0 },
30 | colorB: { type: "vec3", value: new THREE.Color(0xfff000) },
31 | colorA: { type: "vec3", value: new THREE.Color(0xffffff) },
32 | };
33 |
34 | // specify a canvas which is already created in the HTML file and tagged by an id
35 | // aliasing enabled
36 | const canvas = document.getElementById(this.canvasID);
37 | this.renderer = new THREE.WebGLRenderer({
38 | canvas,
39 | antialias: true,
40 | });
41 |
42 | this.renderer.setSize(window.innerWidth, window.innerHeight);
43 | document.body.appendChild(this.renderer.domElement);
44 |
45 | this.controls = new OrbitControls(this.camera, this.renderer.domElement);
46 |
47 | this.stats = Stats();
48 | document.body.appendChild(this.stats.dom);
49 |
50 | // ambient light which is for the whole scene
51 | // let ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
52 | // ambientLight.castShadow = false;
53 | // this.scene.add(ambientLight);
54 |
55 | // spot light which is illuminating the chart directly
56 | // let spotLight = new THREE.SpotLight(0xffffff, 0.55);
57 | // spotLight.castShadow = true;
58 | // spotLight.position.set(0, 40, 10);
59 | // this.scene.add(spotLight);
60 |
61 | // if window resizes
62 | window.addEventListener("resize", () => this.onWindowResize(), false);
63 | }
64 |
65 | animate() {
66 | // NOTE: Window is implied.
67 | // requestAnimationFrame(this.animate.bind(this));
68 | window.requestAnimationFrame(this.animate.bind(this));
69 | this.render();
70 | this.stats.update();
71 | this.controls.update();
72 | }
73 |
74 | render() {
75 | this.uniforms.u_time.value += this.clock.getDelta();
76 | this.renderer.render(this.scene, this.camera);
77 | }
78 |
79 | onWindowResize() {
80 | this.camera.aspect = window.innerWidth / window.innerHeight;
81 | this.camera.updateProjectionMatrix();
82 | this.renderer.setSize(window.innerWidth, window.innerHeight);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/03-tic-tac-toe-3d/postcss.config.js:
--------------------------------------------------------------------------------
1 | // If you want to use other PostCSS plugins, see the following:
2 | // https://tailwindcss.com/docs/using-with-preprocessors
3 | module.exports = {
4 | plugins: {
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/03-tic-tac-toe-3d/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/03-tic-tac-toe-3d/public/favicon.ico
--------------------------------------------------------------------------------
/03-tic-tac-toe-3d/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/03-tic-tac-toe-3d/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
4 | darkMode: false, // or 'media' or 'class'
5 | theme: {
6 | extend: {},
7 | },
8 | variants: {
9 | extend: {},
10 | },
11 | plugins: [],
12 | }
13 |
--------------------------------------------------------------------------------
/04-audio-visualizer/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/04-audio-visualizer/README.md:
--------------------------------------------------------------------------------
1 | # Next.js + Tailwind CSS Example
2 |
3 | This example shows how to use [Tailwind CSS](https://tailwindcss.com/) [(v2.2)](https://blog.tailwindcss.com/tailwindcss-2-2) with Next.js. It follows the steps outlined in the official [Tailwind docs](https://tailwindcss.com/docs/guides/nextjs).
4 |
5 | It uses the new [`Just-in-Time Mode`](https://tailwindcss.com/docs/just-in-time-mode) for Tailwind CSS.
6 |
7 | ## Preview
8 |
9 | Preview the example live on [StackBlitz](http://stackblitz.com/):
10 |
11 | [](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-tailwindcss)
12 |
13 | ## Deploy your own
14 |
15 | Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
16 |
17 | [](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-tailwindcss&project-name=with-tailwindcss&repository-name=with-tailwindcss)
18 |
19 | ## How to use
20 |
21 | Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
22 |
23 | ```bash
24 | npx create-next-app --example with-tailwindcss with-tailwindcss-app
25 | # or
26 | yarn create next-app --example with-tailwindcss with-tailwindcss-app
27 | ```
28 |
29 | Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
30 |
--------------------------------------------------------------------------------
/04-audio-visualizer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "next dev",
5 | "build": "next build",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "@codemirror/lang-javascript": "^0.19.3",
10 | "@uiw/react-codemirror": "^4.2.4",
11 | "codemirror": "^5.63.3",
12 | "dat.gui": "^0.7.7",
13 | "next": "latest",
14 | "react": "^17.0.2",
15 | "react-dom": "^17.0.2",
16 | "three": "^0.135.0"
17 | },
18 | "devDependencies": {
19 | "autoprefixer": "^10.2.6",
20 | "postcss": "^8.3.5",
21 | "tailwindcss": "^2.2.4"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/04-audio-visualizer/pages/_app.jsx:
--------------------------------------------------------------------------------
1 | import 'tailwindcss/tailwind.css'
2 |
3 | function MyApp({ Component, pageProps }) {
4 | return
5 | }
6 |
7 | export default MyApp
8 |
--------------------------------------------------------------------------------
/04-audio-visualizer/pages/components/CustomEditor.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import CodeMirror from '@uiw/react-codemirror';
3 | import { javascript } from '@codemirror/lang-javascript';
4 |
5 | export default function CustomEditor() {
6 | const codeArray = [
7 | `
8 | // setup next.js + tailwind css + three.js
9 | // npx create-next-app -e with-tailwindcss audio-waveform
10 |
11 | // remove default code and add canvas
12 | return (
13 |
14 |
15 |
16 | )
17 |
18 | useEffect(() => {
19 | test = new SceneInit("myThreeJsCanvas");
20 | test.initScene();
21 | test.animate();
22 | }, []);
23 |
24 | const planeGeometry = new THREE.PlaneGeometry(64, 64, 64, 64);
25 | const planeMaterial = new THREE.MeshNormalMaterial({ wireframe: true });
26 | const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
27 | planeMesh.rotation.x = -Math.PI / 2 + Math.PI / 4;
28 | planeMesh.scale.x = 2;
29 | planeMesh.scale.y = 2;
30 | planeMesh.scale.z = 2;
31 | planeMesh.position.y = 8;
32 | test.scene.add(planeMesh);
33 | `,
34 | `
35 |
45 |
46 | const setupAudioContext = () => {
47 | audioContext = new window.AudioContext();
48 | audioElement = document.getElementById("myAudio");
49 | source = audioContext.createMediaElementSource(audioElement);
50 | analyser = audioContext.createAnalyser();
51 | source.connect(analyser);
52 | analyser.connect(audioContext.destination);
53 | analyser.fftSize = 1024;
54 | dataArray = new Uint8Array(analyser.frequencyBinCount);
55 | };
56 |
57 | const render = () => {
58 | // note: update audio data
59 | analyser.getByteFrequencyData(dataArray);
60 |
61 | // note: call render function on every animation frame
62 | requestAnimationFrame(render);
63 | };
64 |
65 | render();
66 | `,
67 | `
68 |
69 | // const planeGeometry = new THREE.PlaneGeometry(64, 64, 64, 64);
70 | // const planeMaterial = new THREE.MeshNormalMaterial({ wireframe: true });
71 | // const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
72 |
73 | const planeGeometry = new THREE.PlaneGeometry(64, 64, 64, 64);
74 | const planeCustomMaterial = new THREE.ShaderMaterial({
75 | // note: this is where the magic happens
76 | uniforms: uniforms, // dataArray, time
77 | vertexShader: vertexShader(),
78 | fragmentShader: fragmentShader(),
79 | wireframe: true,
80 | });
81 | const planeMesh = new THREE.Mesh(planeGeometry, planeCustomMaterial);
82 | `,
83 | `
84 | // vertex shader 1
85 | // void main() {
86 | // gl_Position = projectionMatrix
87 | // * modelViewMatrix
88 | // * vec4(position.x, position.y, position.z, 1.0);
89 | // }
90 |
91 | // vertex shader 2
92 | // void main() {
93 | // float z = abs(position.x) + abs(position.y);
94 | // gl_Position = projectionMatrix
95 | // * modelViewMatrix
96 | // * vec4(position.x, position.y, z, 1.0);
97 | // }
98 |
99 | // vertex shader 3
100 | // void main() {
101 | // float z = sin(abs(position.x) + abs(position.y));
102 | // gl_Position = projectionMatrix
103 | // * modelViewMatrix
104 | // * vec4(position.x, position.y, z, 1.0);
105 | // }
106 |
107 | // vertex shader 4
108 | void main() {
109 | float z = sin(abs(position.x) + abs(position.y) + u_time * .005);
110 | gl_Position = projectionMatrix
111 | * modelViewMatrix
112 | * vec4(position.x, position.y, z, 1.0);
113 | }
114 | `,
115 | `
116 | // vertex shader
117 | varying float x;
118 | varying float y;
119 | varying float z;
120 | varying vec3 vUv;
121 |
122 | uniform float u_time;
123 | uniform float u_amplitude;
124 | uniform float[64] u_data_arr;
125 |
126 | void main() {
127 | vUv = position;
128 |
129 | x = abs(position.x);
130 | y = abs(position.y);
131 |
132 | float floor_x = round(x);
133 | float floor_y = round(y);
134 |
135 | float x_multiplier = (32.0 - x) / 8.0;
136 | float y_multiplier = (32.0 - y) / 8.0;
137 |
138 | z = sin(u_data_arr[int(floor_x)] / 50.0 + u_data_arr[int(floor_y)] / 50.0) * u_amplitude;
139 |
140 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position.x, position.y, z, 1.0);
141 | }
142 | `,
143 | `
144 | // fragment shader 1
145 | void main() {
146 | // red color
147 | gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
148 | }
149 |
150 | // fragment shader 2
151 | void main() {
152 | if (position.x < 0.0) {
153 | gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
154 | } else {
155 | gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
156 | }
157 | }
158 |
159 | // fragment shader 3
160 | uniform float u_time;
161 | void main() {
162 | gl_FragColor = vec4(sin(u_time * .0001), 0.0, 0.0, 1.0);
163 | }
164 | `,
165 | `
166 | // fragment shader
167 | varying float x;
168 | varying float y;
169 | varying float z;
170 | varying vec3 vUv;
171 |
172 | uniform float u_time;
173 |
174 | void main() {
175 | gl_FragColor = vec4((32.0 - abs(x)) / 32.0, (32.0 - abs(y)) / 32.0, (abs(x + y) / 2.0) / 32.0, 1.0);
176 | }
177 | `,
178 | ];
179 |
180 | const [index, setIndex] = useState(0);
181 | const [code, setCode] = useState(codeArray[index]);
182 |
183 | const nextCodeBlock = () => {
184 | if (index < codeArray.length - 1) {
185 | setCode(codeArray[index + 1]);
186 | setIndex(index + 1);
187 | }
188 | };
189 |
190 | const prevCodeBlock = () => {
191 | if (index >= 1) {
192 | setCode(codeArray[index - 1]);
193 | setIndex(index - 1);
194 | }
195 | };
196 |
197 | return (
198 |
199 |
203 |
204 |
{
210 | setCode(value);
211 | }}
212 | className="h-full w-11/12"
213 | />
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 | );
224 | }
225 |
--------------------------------------------------------------------------------
/04-audio-visualizer/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import * as THREE from "three";
3 | import SceneInit from "./lib/SceneInit";
4 | import CustomEditor from "./components/CustomEditor";
5 | import { vertexShader, fragmentShader } from "./lib/Shaders";
6 |
7 | export default function Home() {
8 | let test, audioContext, audioElement, dataArray, analyser, source;
9 |
10 | let gui;
11 | const initGui = async () => {
12 | const dat = await import("dat.gui");
13 | gui = new dat.GUI();
14 | };
15 |
16 | const setupAudioContext = () => {
17 | audioContext = new window.AudioContext();
18 | audioElement = document.getElementById("myAudio");
19 | source = audioContext.createMediaElementSource(audioElement);
20 | analyser = audioContext.createAnalyser();
21 | source.connect(analyser);
22 | analyser.connect(audioContext.destination);
23 | analyser.fftSize = 1024;
24 | dataArray = new Uint8Array(analyser.frequencyBinCount);
25 | };
26 |
27 | const play = async () => {
28 | if (audioContext === undefined) {
29 | setupAudioContext();
30 | }
31 |
32 | const uniforms = {
33 | u_time: {
34 | type: "f",
35 | value: 1.0,
36 | },
37 | u_amplitude: {
38 | type: "f",
39 | value: 3.0,
40 | },
41 | u_data_arr: {
42 | type: "float[64]",
43 | value: dataArray,
44 | },
45 | // u_black: { type: "vec3", value: new THREE.Color(0x000000) },
46 | // u_white: { type: "vec3", value: new THREE.Color(0xffffff) },
47 | };
48 |
49 | // note: uncomment these geometries to see different visualizations
50 | // const planeGeometry = new THREE.BoxGeometry(64, 64, 8, 64, 64, 8);
51 | // const planeGeometry = new THREE.SphereGeometry(16, 64, 64);
52 |
53 | // note: set up plane mesh and add it to the scene
54 | const planeGeometry = new THREE.PlaneGeometry(64, 64, 64, 64);
55 | // const planeMaterial = new THREE.MeshNormalMaterial({ wireframe: true });
56 | // const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
57 | const planeCustomMaterial = new THREE.ShaderMaterial({
58 | // note: this is where the magic happens
59 | uniforms: uniforms,
60 | vertexShader: vertexShader(),
61 | fragmentShader: fragmentShader(),
62 | wireframe: true,
63 | });
64 | const planeMesh = new THREE.Mesh(planeGeometry, planeCustomMaterial);
65 | planeMesh.rotation.x = -Math.PI / 2 + Math.PI / 4;
66 | planeMesh.scale.x = 2;
67 | planeMesh.scale.y = 2;
68 | planeMesh.scale.z = 2;
69 | planeMesh.position.y = 8;
70 | test.scene.add(planeMesh);
71 |
72 | if (gui === undefined) {
73 | await initGui();
74 | const audioWaveGui = gui.addFolder("audio waveform");
75 | audioWaveGui
76 | .add(planeCustomMaterial, "wireframe")
77 | .name("wireframe")
78 | .listen();
79 | audioWaveGui
80 | .add(uniforms.u_amplitude, "value", 1.0, 8.0)
81 | .name("amplitude")
82 | .listen();
83 | }
84 |
85 | const render = (time) => {
86 | // note: update audio data
87 | analyser.getByteFrequencyData(dataArray);
88 |
89 | // note: update uniforms
90 | uniforms.u_time.value = time;
91 | uniforms.u_data_arr.value = dataArray;
92 |
93 | // note: call render function on every animation frame
94 | requestAnimationFrame(render);
95 | };
96 |
97 | render();
98 | };
99 |
100 | useEffect(() => {
101 | test = new SceneInit("myThreeJsCanvas");
102 | test.initScene();
103 | test.animate();
104 | }, []);
105 |
106 | // note: Custom editor helpers.
107 | // const [showCustomEditor, setShowCustomEditor] = useState(false);
108 | // const toggleCustomEditor = () => {
109 | // setShowCustomEditor(!showCustomEditor);
110 | // };
111 |
112 | return (
113 |
114 |
124 | {/*
125 |
128 |
*/}
129 |
130 | {/* {showCustomEditor ?
: null} */}
131 |
132 | );
133 | }
134 |
--------------------------------------------------------------------------------
/04-audio-visualizer/pages/lib/SceneInit.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
3 | import Stats from "three/examples/jsm/libs/stats.module";
4 |
5 | export default class SceneInit {
6 | constructor(canvasID, camera, scene, stats, controls, renderer, fov = 36) {
7 | this.fov = fov;
8 | this.scene = scene;
9 | this.stats = stats;
10 | this.camera = camera;
11 | this.controls = controls;
12 | this.renderer = renderer;
13 | this.canvasID = canvasID;
14 | }
15 |
16 | initScene() {
17 | this.camera = new THREE.PerspectiveCamera(
18 | this.fov,
19 | window.innerWidth / window.innerHeight,
20 | 1,
21 | 1000
22 | );
23 | this.camera.position.z = 196;
24 |
25 | this.clock = new THREE.Clock();
26 | this.scene = new THREE.Scene();
27 |
28 | this.uniforms = {
29 | u_time: { type: "f", value: 1.0 },
30 | colorB: { type: "vec3", value: new THREE.Color(0xfff000) },
31 | colorA: { type: "vec3", value: new THREE.Color(0xffffff) },
32 | };
33 |
34 | // specify a canvas which is already created in the HTML file and tagged by an id
35 | // aliasing enabled
36 | const canvas = document.getElementById(this.canvasID);
37 | this.renderer = new THREE.WebGLRenderer({
38 | canvas,
39 | antialias: true,
40 | });
41 |
42 | this.renderer.setSize(window.innerWidth, window.innerHeight);
43 | document.body.appendChild(this.renderer.domElement);
44 |
45 | this.controls = new OrbitControls(this.camera, this.renderer.domElement);
46 |
47 | this.stats = Stats();
48 | document.body.appendChild(this.stats.dom);
49 |
50 | // ambient light which is for the whole scene
51 | let ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
52 | ambientLight.castShadow = false;
53 | this.scene.add(ambientLight);
54 |
55 | // spot light which is illuminating the chart directly
56 | let spotLight = new THREE.SpotLight(0xffffff, 0.55);
57 | spotLight.castShadow = true;
58 | spotLight.position.set(0, 80, 10);
59 | this.scene.add(spotLight);
60 |
61 | // if window resizes
62 | window.addEventListener("resize", () => this.onWindowResize(), false);
63 | }
64 |
65 | animate() {
66 | // NOTE: Window is implied.
67 | // requestAnimationFrame(this.animate.bind(this));
68 | window.requestAnimationFrame(this.animate.bind(this));
69 | this.render();
70 | this.stats.update();
71 | this.controls.update();
72 | }
73 |
74 | render() {
75 | this.uniforms.u_time.value += this.clock.getDelta();
76 | this.renderer.render(this.scene, this.camera);
77 | }
78 |
79 | onWindowResize() {
80 | this.camera.aspect = window.innerWidth / window.innerHeight;
81 | this.camera.updateProjectionMatrix();
82 | this.renderer.setSize(window.innerWidth, window.innerHeight);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/04-audio-visualizer/pages/lib/Shaders.js:
--------------------------------------------------------------------------------
1 | const vertexShader = () => {
2 | return `
3 | varying float x;
4 | varying float y;
5 | varying float z;
6 | varying vec3 vUv;
7 |
8 | uniform float u_time;
9 | uniform float u_amplitude;
10 | uniform float[64] u_data_arr;
11 |
12 | void main() {
13 | vUv = position;
14 |
15 | x = abs(position.x);
16 | y = abs(position.y);
17 |
18 | float floor_x = round(x);
19 | float floor_y = round(y);
20 |
21 | float x_multiplier = (32.0 - x) / 8.0;
22 | float y_multiplier = (32.0 - y) / 8.0;
23 |
24 | // z = position.z;
25 | // z = abs(position.x) + abs(position.y);
26 | // z = sin(abs(position.x) + abs(position.y));
27 | // z = sin(abs(position.x) + abs(position.y) + u_time * .005);
28 | z = sin(u_data_arr[int(floor_x)] / 50.0 + u_data_arr[int(floor_y)] / 50.0) * u_amplitude;
29 | // z = (u_data_arr[int(floor_x)] / 50.0 + u_data_arr[int(floor_y)] / 50.0) * 2.0;
30 |
31 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position.x, position.y, z, 1.0);
32 | }
33 | `;
34 | };
35 |
36 | const fragmentShader = () => {
37 | return `
38 | varying float x;
39 | varying float y;
40 | varying float z;
41 | varying vec3 vUv;
42 |
43 | uniform float u_time;
44 | // uniform vec3 u_black;
45 | // uniform vec3 u_white;
46 |
47 | void main() {
48 | // old
49 | // gl_FragColor = vec4(mix(u_black, u_white, vUv.x), 1.0);
50 |
51 | // gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
52 | // if (vUv.x < 0.0) {
53 | // gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
54 | // } else {
55 | // gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
56 | // }
57 | // gl_FragColor = vec4(abs(sin(u_time * .001)), 0.0, 0.0, 1.0);
58 | gl_FragColor = vec4((32.0 - abs(x)) / 32.0, (32.0 - abs(y)) / 32.0, (abs(x + y) / 2.0) / 32.0, 1.0);
59 | }
60 | `;
61 | };
62 |
63 | export { vertexShader, fragmentShader };
64 |
--------------------------------------------------------------------------------
/04-audio-visualizer/postcss.config.js:
--------------------------------------------------------------------------------
1 | // If you want to use other PostCSS plugins, see the following:
2 | // https://tailwindcss.com/docs/using-with-preprocessors
3 | module.exports = {
4 | plugins: {
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/04-audio-visualizer/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/04-audio-visualizer/public/favicon.ico
--------------------------------------------------------------------------------
/04-audio-visualizer/public/fur_elise.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/04-audio-visualizer/public/fur_elise.mp3
--------------------------------------------------------------------------------
/04-audio-visualizer/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/04-audio-visualizer/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
4 | darkMode: false, // or 'media' or 'class'
5 | theme: {
6 | extend: {},
7 | },
8 | variants: {
9 | extend: {},
10 | },
11 | plugins: [],
12 | }
13 |
--------------------------------------------------------------------------------
/05-naruto-rasengan/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/05-naruto-rasengan/README.md:
--------------------------------------------------------------------------------
1 | # Next.js + Tailwind CSS Example
2 |
3 | This example shows how to use [Tailwind CSS](https://tailwindcss.com/) [(v3.0)](https://tailwindcss.com/blog/tailwindcss-v3) with Next.js. It follows the steps outlined in the official [Tailwind docs](https://tailwindcss.com/docs/guides/nextjs).
4 |
5 | ## Preview
6 |
7 | Preview the example live on [StackBlitz](http://stackblitz.com/):
8 |
9 | [](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-tailwindcss)
10 |
11 | ## Deploy your own
12 |
13 | Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
14 |
15 | [](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-tailwindcss&project-name=with-tailwindcss&repository-name=with-tailwindcss)
16 |
17 | ## How to use
18 |
19 | Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
20 |
21 | ```bash
22 | npx create-next-app --example with-tailwindcss with-tailwindcss-app
23 | # or
24 | yarn create next-app --example with-tailwindcss with-tailwindcss-app
25 | ```
26 |
27 | Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
28 |
--------------------------------------------------------------------------------
/05-naruto-rasengan/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "next dev",
5 | "build": "next build",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "dat.gui": "^0.7.7",
10 | "next": "latest",
11 | "react": "^17.0.2",
12 | "react-dom": "^17.0.2",
13 | "three": "^0.135.0"
14 | },
15 | "devDependencies": {
16 | "autoprefixer": "^10.4.0",
17 | "postcss": "^8.4.4",
18 | "tailwindcss": "^3.0.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/05-naruto-rasengan/pages/_app.jsx:
--------------------------------------------------------------------------------
1 | import '../styles/globals.css'
2 |
3 | function MyApp({ Component, pageProps }) {
4 | return
5 | }
6 |
7 | export default MyApp
8 |
--------------------------------------------------------------------------------
/05-naruto-rasengan/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function helloAPI(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/05-naruto-rasengan/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 |
3 | import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
4 |
5 | import { useEffect } from "react";
6 | import SceneInit from "./lib/SceneInit";
7 | import {
8 | sinWaveVertexShader2,
9 | sinWaveVertexShader,
10 | fragmentShader,
11 | sphereVertexShader,
12 | auraFragmentShader,
13 | rasenganFragmentShader,
14 | } from "./lib/Shaders";
15 |
16 | export default function Home() {
17 | useEffect(() => {
18 | const test = new SceneInit("myThreeJsCanvas");
19 | test.initScene();
20 | test.animate();
21 |
22 | // test.scene.scale.x = 2.0;
23 | // test.scene.scale.y = 2.0;
24 | // test.scene.scale.z = 2.0;
25 |
26 | const loader = new OBJLoader();
27 | loader.load(
28 | "./hand.obj",
29 | function (obj) {
30 | obj.scale.x = 4;
31 | obj.scale.y = 4;
32 | obj.scale.z = 4;
33 | obj.rotation.x = -Math.PI / 2;
34 | obj.position.z = -28;
35 | obj.position.y = -50;
36 | obj.position.x = 6;
37 | test.scene.position.y = 20;
38 | test.scene.add(obj);
39 | },
40 | function (xhr) {
41 | console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
42 | },
43 | function (error) {
44 | console.log("An error happened");
45 | }
46 | );
47 |
48 | const boxGeometry = new THREE.BoxGeometry(0.5, 0.5, 36, 4, 4, 16);
49 | // const boxGeometry = new THREE.BoxGeometry(4, 4, 36, 1, 1, 16);
50 | const boxMaterial = new THREE.ShaderMaterial({
51 | vertexShader: sinWaveVertexShader(),
52 | fragmentShader: fragmentShader(),
53 | // wireframe: true,
54 | });
55 | const bm = new THREE.Mesh(boxGeometry, boxMaterial);
56 | bm.rotation.x = Math.PI / 16;
57 | bm.rotation.y = Math.PI / 16;
58 | test.scene.add(bm);
59 |
60 | const bm2 = new THREE.Mesh(boxGeometry, boxMaterial);
61 | bm2.rotation.x = Math.PI / 8;
62 | const bm3 = new THREE.Mesh(boxGeometry, boxMaterial);
63 | bm3.rotation.x = Math.PI / 2;
64 | const bm4 = new THREE.Mesh(boxGeometry, boxMaterial);
65 | bm4.rotation.x = Math.PI / 4;
66 | const bm5 = new THREE.Mesh(boxGeometry, boxMaterial);
67 | bm5.rotation.x = Math.PI / 6;
68 | const bm6 = new THREE.Mesh(boxGeometry, boxMaterial);
69 | bm6.rotation.x = Math.PI / 3;
70 | const bm7 = new THREE.Mesh(boxGeometry, boxMaterial);
71 | bm7.rotation.x = Math.PI / 5;
72 | const bm8 = new THREE.Mesh(boxGeometry, boxMaterial);
73 | bm8.rotation.x = Math.PI / 7;
74 | const bm9 = new THREE.Mesh(boxGeometry, boxMaterial);
75 | bm9.rotation.x = Math.PI / 2.5;
76 | const bm10 = new THREE.Mesh(boxGeometry, boxMaterial);
77 | bm10.rotation.x = Math.PI / 3.5;
78 | const bm11 = new THREE.Mesh(boxGeometry, boxMaterial);
79 | bm11.rotation.x = Math.PI / 4.5;
80 | const bm12 = new THREE.Mesh(boxGeometry, boxMaterial);
81 | bm12.rotation.x = Math.PI / 5.5;
82 | test.scene.add(bm2, bm3, bm4, bm5, bm6, bm7, bm8, bm9, bm10, bm11, bm12);
83 |
84 | const sphereGeometry = new THREE.SphereGeometry(20, 80, 80);
85 | const sphereMaterial = new THREE.ShaderMaterial({
86 | vertexShader: sphereVertexShader(),
87 | fragmentShader: rasenganFragmentShader(),
88 | transparent: true,
89 | opacity: 0.5,
90 | });
91 | const sm = new THREE.Mesh(sphereGeometry, sphereMaterial);
92 | test.scene.add(sm);
93 |
94 | const sphereGeometry2 = new THREE.SphereGeometry(20, 20, 20);
95 | const sphereMaterial2 = new THREE.ShaderMaterial({
96 | vertexShader: sphereVertexShader(),
97 | fragmentShader: auraFragmentShader(),
98 | blending: THREE.AdditiveBlending,
99 | side: THREE.BackSide,
100 | });
101 | const sm2 = new THREE.Mesh(sphereGeometry2, sphereMaterial2);
102 | sm2.scale.x = 1.25;
103 | sm2.scale.y = 1.25;
104 | sm2.scale.z = 1.25;
105 | test.scene.add(sm2);
106 |
107 | const render = () => {
108 | bm.rotation.y += 0.02;
109 | bm2.rotation.y += 0.024;
110 | bm3.rotation.y += 0.028;
111 | bm4.rotation.y += 0.032;
112 | bm5.rotation.y += 0.036;
113 | bm6.rotation.y += 0.04;
114 | bm7.rotation.y += 0.044;
115 | bm8.rotation.y += 0.048;
116 | bm9.rotation.y += 0.052;
117 | bm10.rotation.y += 0.056;
118 | bm11.rotation.y += 0.056;
119 | bm12.rotation.y += 0.056;
120 | window.requestAnimationFrame(render);
121 | };
122 |
123 | render();
124 | }, []);
125 |
126 | return (
127 |
128 |
129 |
130 | );
131 | }
132 |
--------------------------------------------------------------------------------
/05-naruto-rasengan/pages/lib/SceneInit.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
3 | import Stats from "three/examples/jsm/libs/stats.module";
4 |
5 | export default class SceneInit {
6 | constructor(canvasID, camera, scene, stats, controls, renderer, fov = 36) {
7 | this.fov = fov;
8 | this.scene = scene;
9 | this.stats = stats;
10 | this.camera = camera;
11 | this.controls = controls;
12 | this.renderer = renderer;
13 | this.canvasID = canvasID;
14 | }
15 |
16 | initScene() {
17 | this.camera = new THREE.PerspectiveCamera(
18 | this.fov,
19 | window.innerWidth / window.innerHeight,
20 | 1,
21 | 1000
22 | );
23 | this.camera.position.z = 196;
24 |
25 | this.clock = new THREE.Clock();
26 | this.scene = new THREE.Scene();
27 |
28 | this.uniforms = {
29 | u_time: { type: "f", value: 1.0 },
30 | colorB: { type: "vec3", value: new THREE.Color(0xfff000) },
31 | colorA: { type: "vec3", value: new THREE.Color(0xffffff) },
32 | };
33 |
34 | // specify a canvas which is already created in the HTML file and tagged by an id
35 | // aliasing enabled
36 | const canvas = document.getElementById(this.canvasID);
37 | this.renderer = new THREE.WebGLRenderer({
38 | canvas,
39 | antialias: true,
40 | });
41 |
42 | this.renderer.setSize(window.innerWidth, window.innerHeight);
43 | document.body.appendChild(this.renderer.domElement);
44 |
45 | this.controls = new OrbitControls(this.camera, this.renderer.domElement);
46 |
47 | this.stats = Stats();
48 | document.body.appendChild(this.stats.dom);
49 |
50 | // ambient light which is for the whole scene
51 | let ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
52 | ambientLight.castShadow = false;
53 | this.scene.add(ambientLight);
54 |
55 | // spot light which is illuminating the chart directly
56 | let spotLight = new THREE.SpotLight(0xffffff, 0.55);
57 | spotLight.castShadow = true;
58 | spotLight.position.set(0, 100, 100);
59 | this.scene.add(spotLight);
60 |
61 | // if window resizes
62 | window.addEventListener("resize", () => this.onWindowResize(), false);
63 | }
64 |
65 | animate() {
66 | // NOTE: Window is implied.
67 | // requestAnimationFrame(this.animate.bind(this));
68 | window.requestAnimationFrame(this.animate.bind(this));
69 | this.render();
70 | this.stats.update();
71 | this.controls.update();
72 | }
73 |
74 | render() {
75 | this.uniforms.u_time.value += this.clock.getDelta();
76 | this.renderer.render(this.scene, this.camera);
77 | }
78 |
79 | onWindowResize() {
80 | this.camera.aspect = window.innerWidth / window.innerHeight;
81 | this.camera.updateProjectionMatrix();
82 | this.renderer.setSize(window.innerWidth, window.innerHeight);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/05-naruto-rasengan/pages/lib/Shaders.js:
--------------------------------------------------------------------------------
1 | const sinWaveVertexShader2 = () => {
2 | return `
3 | varying vec3 vUv;
4 |
5 | void main() {
6 | vUv = position;
7 |
8 | if (position.z == 0.0) {
9 | gl_Position = projectionMatrix
10 | * modelViewMatrix
11 | * vec4(
12 | position.x,
13 | position.y,
14 | position.z,
15 | 1.0
16 | );
17 | } else {
18 | gl_Position = projectionMatrix
19 | * modelViewMatrix
20 | * vec4(
21 | position.x/ abs(position.z) ,
22 | position.y/ abs(position.z),
23 | position.z,
24 | 1.0
25 | );
26 | }
27 | }
28 | `;
29 | };
30 |
31 | const sinWaveVertexShader = () => {
32 | return `
33 | varying vec3 vUv;
34 |
35 | void main() {
36 | vUv = position;
37 |
38 | if (position.z == 0.0) {
39 | gl_Position = projectionMatrix
40 | * modelViewMatrix
41 | * vec4(
42 | position.x * 0.5,
43 | position.y * 0.5,
44 | position.z,
45 | 1.0
46 | );
47 | } else {
48 | gl_Position = projectionMatrix
49 | * modelViewMatrix
50 | * vec4(
51 | sin(position.z / 8.0) * 8.0 + position.x * 0.5 / abs(position.z),
52 | position.y * 0.5 / abs(position.z),
53 | position.z,
54 | 1.0
55 | );
56 | }
57 | }
58 | `;
59 | };
60 |
61 | const fragmentShader = () => {
62 | return `
63 | varying vec3 vUv;
64 |
65 | void main() {
66 | gl_FragColor = vec4(max(0.6, abs(sin(vUv.z / 20.0))), max(0.8, abs(sin(vUv.z / 20.0))), 1.0, 1.0);
67 | }
68 | `;
69 | };
70 |
71 | const sphereVertexShader = () => {
72 | return `
73 | varying vec2 vertexUv;
74 | varying vec3 vertexNormal;
75 |
76 | void main() {
77 | vertexUv = uv;
78 | vertexNormal = normalize(normalMatrix * normal);
79 |
80 | gl_Position = projectionMatrix
81 | * modelViewMatrix
82 | * vec4(position.x, position.y, position.z, 1.0);
83 | }
84 | `;
85 | };
86 |
87 | const auraFragmentShader = () => {
88 | return `
89 | varying vec2 vertexUv;
90 | varying vec3 vertexNormal;
91 |
92 | void main() {
93 | float intensity = pow(0.5 - dot(vertexNormal, vec3(0.0, 0.0, 1.0)), 2.0);
94 | gl_FragColor = vec4(0.5, 0.75, 1.0, 1.0) * intensity;
95 | }
96 | `;
97 | };
98 |
99 | const rasenganFragmentShader = () => {
100 | return `
101 | varying vec2 vertexUv;
102 | varying vec3 vertexNormal;
103 |
104 | void main() {
105 | float intensity = pow(0.9 - dot(vertexNormal, vec3(0.0, 0.0, 1.0)), 2.0);
106 | gl_FragColor = vec4(0.5, 0.75, 1.0, 1.0) * intensity;
107 | }
108 | `;
109 | };
110 |
111 | export {
112 | sinWaveVertexShader2,
113 | sinWaveVertexShader,
114 | fragmentShader,
115 | sphereVertexShader,
116 | auraFragmentShader,
117 | rasenganFragmentShader,
118 | };
119 |
--------------------------------------------------------------------------------
/05-naruto-rasengan/postcss.config.js:
--------------------------------------------------------------------------------
1 | // If you want to use other PostCSS plugins, see the following:
2 | // https://tailwindcss.com/docs/using-with-preprocessors
3 | module.exports = {
4 | plugins: {
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/05-naruto-rasengan/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/05-naruto-rasengan/public/favicon.ico
--------------------------------------------------------------------------------
/05-naruto-rasengan/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/05-naruto-rasengan/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/05-naruto-rasengan/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: [
3 | './pages/**/*.{js,ts,jsx,tsx}',
4 | './components/**/*.{js,ts,jsx,tsx}',
5 | ],
6 | theme: {
7 | extend: {},
8 | },
9 | plugins: [],
10 | }
11 |
--------------------------------------------------------------------------------
/06-piano/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
--------------------------------------------------------------------------------
/06-piano/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | tabWidth: 2,
4 | printWidth: 80,
5 | singleQuote: true,
6 | trailingComma: 'es5',
7 | arrowParens: 'always',
8 | };
9 |
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/A0.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/A0.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/A1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/A1.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/A2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/A2.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/A3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/A3.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/A4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/A4.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/A5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/A5.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/A6.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/A6.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/A7.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/A7.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Ab1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Ab1.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Ab2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Ab2.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Ab3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Ab3.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Ab4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Ab4.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Ab5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Ab5.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Ab6.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Ab6.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Ab7.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Ab7.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/B0.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/B0.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/B1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/B1.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/B2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/B2.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/B3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/B3.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/B4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/B4.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/B5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/B5.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/B6.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/B6.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/B7.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/B7.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Bb0.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Bb0.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Bb1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Bb1.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Bb2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Bb2.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Bb3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Bb3.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Bb4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Bb4.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Bb5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Bb5.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Bb6.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Bb6.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Bb7.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Bb7.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/C1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/C1.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/C2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/C2.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/C3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/C3.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/C4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/C4.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/C5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/C5.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/C6.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/C6.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/C7.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/C7.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/C8.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/C8.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/D1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/D1.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/D2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/D2.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/D3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/D3.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/D4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/D4.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/D5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/D5.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/D6.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/D6.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/D7.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/D7.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Db1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Db1.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Db2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Db2.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Db3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Db3.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Db4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Db4.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Db5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Db5.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Db6.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Db6.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Db7.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Db7.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Db8.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Db8.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/E1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/E1.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/E2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/E2.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/E3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/E3.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/E4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/E4.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/E5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/E5.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/E6.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/E6.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/E7.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/E7.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Eb1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Eb1.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Eb2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Eb2.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Eb3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Eb3.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Eb4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Eb4.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Eb5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Eb5.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Eb6.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Eb6.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Eb7.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Eb7.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/F1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/F1.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/F2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/F2.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/F3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/F3.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/F4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/F4.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/F5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/F5.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/F6.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/F6.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/F7.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/F7.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/G1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/G1.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/G2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/G2.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/G3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/G3.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/G4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/G4.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/G5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/G5.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/G6.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/G6.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/G7.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/G7.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Gb1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Gb1.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Gb2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Gb2.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Gb3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Gb3.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Gb4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Gb4.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Gb5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Gb5.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Gb6.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Gb6.mp3
--------------------------------------------------------------------------------
/06-piano/acoustic_grand_piano_mp3/Gb7.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/acoustic_grand_piano_mp3/Gb7.mp3
--------------------------------------------------------------------------------
/06-piano/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/06-piano/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "piano-3d-test",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build",
7 | "preview": "vite preview"
8 | },
9 | "dependencies": {
10 | "dat.gui": "^0.7.7",
11 | "howler": "^2.2.3",
12 | "react": "^17.0.2",
13 | "react-dom": "^17.0.2",
14 | "three": "^0.136.0"
15 | },
16 | "devDependencies": {
17 | "@vitejs/plugin-react": "^1.0.7",
18 | "vite": "^2.7.2"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/06-piano/pics/space.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/06-piano/pics/space.jpeg
--------------------------------------------------------------------------------
/06-piano/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
40 | button {
41 | font-size: calc(10px + 2vmin);
42 | }
43 |
--------------------------------------------------------------------------------
/06-piano/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | import { GUI } from 'dat.gui';
4 | import { FontLoader } from 'three/examples/jsm/loaders/FontLoader';
5 |
6 | import SceneInit from './lib/SceneInit';
7 | import Piano from './lib/Piano';
8 |
9 | function App() {
10 | useEffect(() => {
11 | const test = new SceneInit('myThreeJsCanvas');
12 | test.initScene();
13 | test.animate();
14 |
15 | const p = new Piano();
16 | test.scene.add(p.getPianoGroup());
17 |
18 | const fontLoader = new FontLoader();
19 | fontLoader.load('./fonts/Helvetica-Bold.typeface.json', (font) => {
20 | p.renderText(font);
21 | });
22 |
23 | const gui = new GUI();
24 | const cameraFolder = gui.addFolder('Camera');
25 | cameraFolder.add(test.camera.position, 'z', 100, 200);
26 | cameraFolder.open();
27 |
28 | // NOTE: UI bug caused by importing tailwind css.
29 | const pianoFolder = gui.addFolder('Piano');
30 | pianoFolder.addColor(p, 'highlightColor').name('Highlight Color');
31 | pianoFolder
32 | .add(p, 'displayText')
33 | .name('Display Text')
34 | .onChange((value) => {
35 | if (value) {
36 | p.renderText();
37 | } else {
38 | p.hideText();
39 | }
40 | });
41 | pianoFolder.open();
42 |
43 | const onKeyDown = (event) => {
44 | if (event.repeat) {
45 | return;
46 | }
47 | p.maybePlayNote(event.key);
48 | };
49 |
50 | const onKeyUp = (event) => {
51 | p.maybeStopPlayingNote(event.key);
52 | };
53 |
54 | window.addEventListener('keyup', onKeyUp);
55 | window.addEventListener('keydown', onKeyDown);
56 |
57 | return () => {
58 | window.removeEventListener('keyup', onKeyUp);
59 | window.removeEventListener('keydown', onKeyDown);
60 | };
61 |
62 | // NOTE: Play piano with mouse.
63 | // const mouse = new THREE.Vector2();
64 | // const raycaster = new THREE.Raycaster();
65 | // function onMouseDown(event) {
66 | // mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
67 | // mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
68 | // raycaster.setFromCamera(mouse, test.camera);
69 | // const intersects = raycaster.intersectObjects(pianoObject.children);
70 | // console.log(intersects);
71 | // if (intersects.length > 0) {
72 | // intersects.forEach((note) =>
73 | // console.log((note.object.position.z = -10))
74 | // );
75 | // }
76 | // }
77 | });
78 |
79 | return (
80 |
81 |
82 |
83 | );
84 | }
85 |
86 | export default App;
87 |
--------------------------------------------------------------------------------
/06-piano/src/favicon.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/06-piano/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/06-piano/src/lib/Key.js:
--------------------------------------------------------------------------------
1 | import { Howl } from 'howler';
2 | import * as THREE from 'three';
3 | import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';
4 |
5 | export default class Key {
6 | constructor(note, inputKey, xOffset) {
7 | this.note = note;
8 | this.inputKey = inputKey;
9 | this.isFlat = note.length === 3;
10 |
11 | this.sound = new Howl({
12 | src: [`./acoustic_grand_piano_mp3/${note}.mp3`],
13 | });
14 |
15 | this.theta = Math.PI / 32;
16 | this.axis = new THREE.Vector3(1, 0, 0);
17 | this.point = new THREE.Vector3(0, 20, 0);
18 |
19 | if (this.isFlat) {
20 | const geometry = new THREE.BoxGeometry(4.5, 26, 4);
21 | const material = new THREE.MeshBasicMaterial({ color: '#0f0f0f' });
22 | this.keyMesh = new THREE.Mesh(geometry, material);
23 | this.keyMesh.position.z = 4;
24 | this.keyMesh.position.y = 7;
25 | } else {
26 | const geometry = new THREE.BoxGeometry(9, 40, 4);
27 | const material = new THREE.MeshStandardMaterial({ color: '#ffffff' });
28 | this.keyMesh = new THREE.Mesh(geometry, material);
29 | }
30 |
31 | this.keyGroup = new THREE.Group();
32 | this.keyGroup.position.x = xOffset;
33 | this.keyGroup.add(this.keyMesh);
34 | }
35 |
36 | hideKeyText() {
37 | this.textMesh.visible = false;
38 | }
39 |
40 | renderKeyText(font) {
41 | if (this.textMesh) {
42 | this.textMesh.visible = true;
43 | } else {
44 | const geometry = new TextGeometry(this.note[0], {
45 | font,
46 | size: 4,
47 | height: 2,
48 | });
49 | const material = new THREE.MeshNormalMaterial();
50 | this.textMesh = new THREE.Mesh(geometry, material);
51 | this.textMesh.position.z = 2;
52 | this.textMesh.position.x = -1.5;
53 | this.textMesh.position.y = -18;
54 | this.keyGroup.add(this.textMesh);
55 | }
56 | }
57 |
58 | rotateAroundWorldAxis(rotation) {
59 | // remove the offset
60 | this.keyGroup.position.sub(this.point);
61 |
62 | // rotate the POSITION
63 | this.keyGroup.position.applyAxisAngle(this.axis, this.theta * rotation);
64 |
65 | // re-add the offset
66 | this.keyGroup.position.add(this.point);
67 |
68 | // rotate the OBJECT
69 | this.keyGroup.rotateOnAxis(this.axis, this.theta * rotation);
70 | }
71 |
72 | play(highlightColor) {
73 | this.rotateAroundWorldAxis(1);
74 | this.sound.play();
75 | this.keyMesh.material.color.set(highlightColor);
76 | this.sound.fade(1, 0, 1000);
77 | }
78 |
79 | stopPlaying() {
80 | if (this.isFlat) {
81 | this.keyMesh.material.color.set('#000000');
82 | } else {
83 | this.keyMesh.material.color.set('#ffffff');
84 | }
85 | this.rotateAroundWorldAxis(-1);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/06-piano/src/lib/Piano.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | import Key from './Key';
4 |
5 | export default class Piano {
6 | constructor() {
7 | this.flatKeys = [
8 | new Key('Db3', '2', 5),
9 | new Key('Eb3', '3', 15),
10 | new Key('Gb3', '5', 35),
11 | new Key('Ab3', '6', 45),
12 | new Key('Bb3', '7', 55),
13 |
14 | new Key('Db4', 's', 75),
15 | new Key('Eb4', 'd', 85),
16 | new Key('Gb4', 'g', 105),
17 | new Key('Ab4', 'h', 115),
18 | new Key('Bb4', 'j', 125),
19 | ];
20 |
21 | this.naturalKeys = [
22 | new Key('C3', 'q', 0),
23 | new Key('D3', 'w', 10),
24 | new Key('E3', 'e', 20),
25 | new Key('F3', 'r', 30),
26 | new Key('G3', 't', 40),
27 | new Key('A3', 'y', 50),
28 | new Key('B3', 'u', 60),
29 |
30 | new Key('C4', 'z', 70),
31 | new Key('D4', 'x', 80),
32 | new Key('E4', 'c', 90),
33 | new Key('F4', 'v', 100),
34 | new Key('G4', 'b', 110),
35 | new Key('A4', 'n', 120),
36 | new Key('B4', 'm', 130),
37 | ];
38 |
39 | this.displayText = true;
40 | this.highlightColor = '#61DBFB';
41 |
42 | this.pianoGroup = new THREE.Group();
43 | this.pianoGroup.position.x = -65;
44 | this.pianoGroup.rotation.x = -Math.PI / 4;
45 | this.pianoGroup.add(
46 | ...this.flatKeys.map((key) => key.keyGroup),
47 | ...this.naturalKeys.map((key) => key.keyGroup)
48 | );
49 | }
50 |
51 | hideText() {
52 | this.naturalKeys.forEach((key) => {
53 | key.hideKeyText();
54 | });
55 | }
56 |
57 | renderText(font) {
58 | this.naturalKeys.forEach((key) => {
59 | key.renderKeyText(font);
60 | });
61 | }
62 |
63 | getPianoGroup() {
64 | return this.pianoGroup;
65 | }
66 |
67 | getKeyFromInput(inputKey) {
68 | const flatKey = this.flatKeys.find((k) => k.inputKey === inputKey);
69 | const naturalKey = this.naturalKeys.find((k) => k.inputKey === inputKey);
70 | return flatKey || naturalKey || undefined;
71 | }
72 |
73 | maybePlayNote(eventKey) {
74 | const key = this.getKeyFromInput(eventKey);
75 | if (key !== undefined) {
76 | key.play(this.highlightColor);
77 | }
78 | }
79 |
80 | maybeStopPlayingNote(eventKey) {
81 | const key = this.getKeyFromInput(eventKey);
82 | if (key !== undefined) {
83 | key.stopPlaying();
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/06-piano/src/lib/SceneInit.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
3 | import Stats from 'three/examples/jsm/libs/stats.module';
4 |
5 | export default class SceneInit {
6 | constructor(canvasID, camera, scene, stats, controls, renderer, fov = 36) {
7 | this.fov = fov;
8 | this.scene = scene;
9 | this.stats = stats;
10 | this.camera = camera;
11 | this.controls = controls;
12 | this.renderer = renderer;
13 | this.canvasID = canvasID;
14 | }
15 |
16 | initScene() {
17 | this.camera = new THREE.PerspectiveCamera(
18 | this.fov,
19 | window.innerWidth / window.innerHeight,
20 | 1,
21 | 1000
22 | );
23 | this.camera.position.z = 196;
24 | // this.camera.position.x = 196;
25 |
26 | this.clock = new THREE.Clock();
27 | this.scene = new THREE.Scene();
28 |
29 | // NOTE: Load space background.
30 | // this.loader = new THREE.TextureLoader();
31 | // this.scene.background = this.loader.load('./pics/space.jpeg');
32 |
33 | // NOTE: Declare uniforms to pass into glsl shaders.
34 | this.uniforms = {
35 | u_time: { type: 'f', value: 1.0 },
36 | colorB: { type: 'vec3', value: new THREE.Color(0xfff000) },
37 | colorA: { type: 'vec3', value: new THREE.Color(0xffffff) },
38 | };
39 |
40 | // specify a canvas which is already created in the HTML file and tagged by an id
41 | // aliasing enabled
42 | const canvas = document.getElementById(this.canvasID);
43 | this.renderer = new THREE.WebGLRenderer({
44 | canvas,
45 | antialias: true,
46 | });
47 |
48 | this.renderer.setSize(window.innerWidth, window.innerHeight);
49 | document.body.appendChild(this.renderer.domElement);
50 |
51 | this.controls = new OrbitControls(this.camera, this.renderer.domElement);
52 |
53 | this.stats = Stats();
54 | document.body.appendChild(this.stats.dom);
55 |
56 | // ambient light which is for the whole scene
57 | let ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
58 | ambientLight.castShadow = true;
59 | this.scene.add(ambientLight);
60 |
61 | // spot light which is illuminating the chart directly
62 | let spotLight = new THREE.SpotLight(0xffffff, 1);
63 | spotLight.castShadow = true;
64 | spotLight.position.set(0, 64, 32);
65 | this.scene.add(spotLight);
66 |
67 | // if window resizes
68 | window.addEventListener('resize', () => this.onWindowResize(), false);
69 | }
70 |
71 | animate() {
72 | // NOTE: Window is implied.
73 | // requestAnimationFrame(this.animate.bind(this));
74 | window.requestAnimationFrame(this.animate.bind(this));
75 | this.render();
76 | this.stats.update();
77 | this.controls.update();
78 | }
79 |
80 | render() {
81 | // NOTE: Update uniform data on each render.
82 | this.uniforms.u_time.value += this.clock.getDelta();
83 | this.renderer.render(this.scene, this.camera);
84 | }
85 |
86 | onWindowResize() {
87 | this.camera.aspect = window.innerWidth / window.innerHeight;
88 | this.camera.updateProjectionMatrix();
89 | this.renderer.setSize(window.innerWidth, window.innerHeight);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/06-piano/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/06-piano/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import './index.css'
4 | import App from './App'
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | )
12 |
--------------------------------------------------------------------------------
/06-piano/src/test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * NOTE:
3 | * Some of the features of the Web Audio API currently only work on Chrome, hence the 'webkit' prefix!
4 | *
5 | * REFERENCE NOTE:
6 | * https://codepen.io/noogn/pen/LAiDz?editors=0010
7 | */
8 |
9 | (function () {
10 | // Create audio (context) container
11 | var audioCtx = new (AudioContext || webkitAudioContext)();
12 | // Audio context must be enabled via user gesture
13 | document.querySelector('button').addEventListener('click', function (event) {
14 | audioCtx.resume().then(() => {
15 | event.target.innerText = 'Sound enabled';
16 | console.log('Playback resumed successfully');
17 | });
18 | });
19 |
20 | // Table of notes with correspending keyboard codes. Frequencies are in hertz.
21 | // The notes start from middle C
22 | var notesByKeyCode = {
23 | 65: { noteName: 'c4', frequency: 261.6, keyName: 'a' },
24 | 83: { noteName: 'd4', frequency: 293.7, keyName: 's' },
25 | 68: { noteName: 'e4', frequency: 329.6, keyName: 'd' },
26 | 70: { noteName: 'f4', frequency: 349.2, keyName: 'f' },
27 | 71: { noteName: 'g4', frequency: 392, keyName: 'g' },
28 | 72: { noteName: 'a4', frequency: 440, keyName: 'h' },
29 | 74: { noteName: 'b4', frequency: 493.9, keyName: 'j' },
30 | 75: { noteName: 'c5', frequency: 523.3, keyName: 'k' },
31 | 76: { noteName: 'd5', frequency: 587.3, keyName: 'l' },
32 | 186: { noteName: 'e5', frequency: 659.3, keyName: ';' },
33 | };
34 |
35 | function Key(keyCode, noteName, keyName, frequency) {
36 | var keyHTML = document.createElement('div');
37 | var keySound = new Sound(frequency, 'triangle');
38 |
39 | /* Cheap way to map key on touch screens */
40 | keyHTML.setAttribute('data-key', keyCode);
41 |
42 | /* Style the key */
43 | keyHTML.className = 'key';
44 | keyHTML.innerHTML = noteName + '
' + keyName + '';
45 |
46 | return {
47 | html: keyHTML,
48 | sound: keySound,
49 | };
50 | }
51 |
52 | // function Sound(frequency, type) {
53 | // this.osc = audioctx.createoscillator(); // create oscillator node
54 | // this.pressed = false; // flag to indicate if sound is playing
55 | // /* set default configuration for sound */
56 | // if (typeof frequency !== 'undefined') {
57 | // /* set frequency. if it's not set, the default is used (440hz) */
58 | // this.osc.frequency.value = frequency;
59 | // }
60 | // /* set waveform type. default is actually 'sine' but triangle sounds better :) */
61 | // this.osc.type = type || 'triangle';
62 | // /* start playing the sound. you won't hear it yet as the oscillator node needs to be
63 | // piped to output (aka your speakers). */
64 | // this.osc.start(0);
65 | // }
66 |
67 | function createKeyboard(notes, containerId) {
68 | var sortedKeys = []; // Placeholder for keys to be sorted
69 | var waveFormSelector = document.getElementById('soundType');
70 |
71 | for (var keyCode in notes) {
72 | var note = notes[keyCode];
73 |
74 | /* Generate playable key */
75 | note.key = new Key(keyCode, note.noteName, note.keyName, note.frequency);
76 |
77 | /* Add new key to array to be sorted */
78 | sortedKeys.push(notes[keyCode]);
79 | }
80 |
81 | /* Sort keys by frequency so that they'll be added to the DOM in the correct order */
82 | sortedKeys = sortedKeys.sort(function (note1, note2) {
83 | if (note1.frequency < note2.frequency) return -1;
84 | if (note1.frequency > note2.frequency) return 1;
85 |
86 | return 0;
87 | });
88 |
89 | // Add those sorted keys to DOM
90 | for (var i = 0; i < sortedKeys.length; i++) {
91 | document.getElementById(containerId).appendChild(sortedKeys[i].key.html);
92 | }
93 |
94 | var playNote = function (event) {
95 | event.preventDefault();
96 |
97 | var keyCode = event.keyCode || event.target.getAttribute('data-key');
98 |
99 | if (typeof notesByKeyCode[keyCode] !== 'undefined') {
100 | // Pipe sound to output (AKA speakers)
101 | notesByKeyCode[keyCode].key.sound.play();
102 |
103 | // Highlight key playing
104 | notesByKeyCode[keyCode].key.html.className = 'key playing';
105 | }
106 | };
107 |
108 | var endNote = function (event) {
109 | var keyCode = event.keyCode || event.target.getAttribute('data-key');
110 |
111 | if (typeof notesByKeyCode[keyCode] !== 'undefined') {
112 | // Kill connection to output
113 | notesByKeyCode[keyCode].key.sound.stop();
114 |
115 | // Remove key highlight
116 | notesByKeyCode[keyCode].key.html.className = 'key';
117 | }
118 | };
119 |
120 | var setWaveform = function (event) {
121 | for (var keyCode in notes) {
122 | notes[keyCode].key.sound.osc.type = this.value;
123 | }
124 |
125 | // Unfocus selector so value is not accidentally updated again while playing keys
126 | this.blur();
127 | };
128 |
129 | // Check for changes in the waveform selector and update all oscillators with the selected type
130 | waveFormSelector.addEventListener('change', setWaveform);
131 |
132 | window.addEventListener('keydown', playNote);
133 | window.addEventListener('keyup', endNote);
134 | window.addEventListener('touchstart', playNote);
135 | window.addEventListener('touchend', endNote);
136 | }
137 |
138 | window.addEventListener('load', function () {
139 | createKeyboard(notesByKeyCode, 'keyboard');
140 | });
141 | })();
142 |
--------------------------------------------------------------------------------
/06-piano/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()]
7 | })
8 |
--------------------------------------------------------------------------------
/07-wordle/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
--------------------------------------------------------------------------------
/07-wordle/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | tabWidth: 2,
4 | printWidth: 80,
5 | singleQuote: true,
6 | trailingComma: "es5",
7 | arrowParens: "always",
8 | };
9 |
--------------------------------------------------------------------------------
/07-wordle/fonts/JetBrainsMonoExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/07-wordle/fonts/JetBrainsMonoExtraBold.ttf
--------------------------------------------------------------------------------
/07-wordle/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/07-wordle/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "three-vite-react",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build",
7 | "preview": "vite preview"
8 | },
9 | "dependencies": {
10 | "react": "^17.0.2",
11 | "react-dom": "^17.0.2",
12 | "three": "^0.136.0"
13 | },
14 | "devDependencies": {
15 | "@vitejs/plugin-react": "^1.0.7",
16 | "vite": "^2.7.2"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/07-wordle/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | import SceneInit from './lib/SceneInit';
4 | import Wordle from './lib/Wordle';
5 |
6 | function App() {
7 | useEffect(() => {
8 | const test = new SceneInit('myThreeJsCanvas');
9 | test.initScene();
10 | test.animate();
11 |
12 | const w = new Wordle();
13 | test.scene.add(w.wordleGroup);
14 |
15 | const onKeyDown = (event) => {
16 | if (event.repeat) {
17 | return;
18 | }
19 | w.addLetter(event);
20 | };
21 |
22 | window.addEventListener('keydown', onKeyDown);
23 | }, []);
24 | return (
25 |
26 |
27 |
28 | );
29 | }
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/07-wordle/src/lib/Block.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';
4 | import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry';
5 |
6 | export default class Block {
7 | constructor(letter, xOffset, yOffset) {
8 | this.animate = false;
9 | this.letter = letter;
10 | this.xOffset = xOffset;
11 | this.yOffset = yOffset;
12 | this.blockGroup = new THREE.Group();
13 | this.blockGroup.position.x = xOffset;
14 | this.blockGroup.position.y = yOffset;
15 | this.addBlock();
16 |
17 | const animate = (t) => {
18 | if (this.animate) {
19 | this.blockGroup.rotation.z =
20 | (Math.sin(Date.now() * 0.01) * Math.PI) / 32;
21 | }
22 | requestAnimationFrame(animate);
23 | };
24 | animate();
25 | }
26 |
27 | setFont(parsedFont) {
28 | this.parsedFont = parsedFont;
29 | }
30 |
31 | checkLetter(word, letter) {
32 | if (this.letter === letter) {
33 | this.block.material.color.set('#008000');
34 | this.animate = true;
35 | } else if (word.includes(this.letter)) {
36 | this.block.material.color.set('#f7df1e');
37 | }
38 | }
39 |
40 | addBlock() {
41 | const geometry = new RoundedBoxGeometry(8, 8, 8, 4, 1);
42 | const material = new THREE.MeshPhongMaterial({
43 | color: '#fafafa',
44 | transparent: true,
45 | opacity: 0.25,
46 | });
47 | this.block = new THREE.Mesh(geometry, material);
48 | this.blockGroup.add(this.block);
49 | }
50 |
51 | removeLetter() {
52 | this.blockGroup.remove(this.letterMesh);
53 | this.block.material.opacity = 0.25;
54 | }
55 |
56 | addLetter(letter) {
57 | this.letter = letter;
58 | const letterGeometry = new TextGeometry(letter, {
59 | font: this.parsedFont,
60 | size: 5,
61 | height: 2,
62 | });
63 | const letterMaterial = new THREE.MeshNormalMaterial({});
64 | this.letterMesh = new THREE.Mesh(letterGeometry, letterMaterial);
65 | this.letterMesh.position.x = -2;
66 | this.letterMesh.position.y = -2;
67 | this.letterMesh.position.z = -1;
68 | this.blockGroup.add(this.letterMesh);
69 | this.block.material.opacity = 0.5;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/07-wordle/src/lib/SceneInit.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
3 | import Stats from 'three/examples/jsm/libs/stats.module';
4 |
5 | export default class SceneInit {
6 | constructor(canvasID, camera, scene, stats, controls, renderer, fov = 36) {
7 | this.fov = fov;
8 | this.scene = scene;
9 | this.stats = stats;
10 | this.camera = camera;
11 | this.controls = controls;
12 | this.renderer = renderer;
13 | this.canvasID = canvasID;
14 | }
15 |
16 | initScene() {
17 | this.camera = new THREE.PerspectiveCamera(
18 | this.fov,
19 | window.innerWidth / window.innerHeight,
20 | 1,
21 | 1000
22 | );
23 | this.camera.position.z = 96;
24 |
25 | this.clock = new THREE.Clock();
26 | this.scene = new THREE.Scene();
27 |
28 | // NOTE: Load space background.
29 | // this.loader = new THREE.TextureLoader();
30 | // this.scene.background = this.loader.load('./pics/space.jpeg');
31 |
32 | // NOTE: Declare uniforms to pass into glsl shaders.
33 | // this.uniforms = {
34 | // u_time: { type: 'f', value: 1.0 },
35 | // colorB: { type: 'vec3', value: new THREE.Color(0xfff000) },
36 | // colorA: { type: 'vec3', value: new THREE.Color(0xffffff) },
37 | // };
38 |
39 | // specify a canvas which is already created in the HTML file and tagged by an id
40 | // aliasing enabled
41 | const canvas = document.getElementById(this.canvasID);
42 | this.renderer = new THREE.WebGLRenderer({
43 | canvas,
44 | antialias: true,
45 | });
46 |
47 | this.renderer.setSize(window.innerWidth, window.innerHeight);
48 | document.body.appendChild(this.renderer.domElement);
49 |
50 | this.controls = new OrbitControls(this.camera, this.renderer.domElement);
51 |
52 | this.stats = Stats();
53 | document.body.appendChild(this.stats.dom);
54 |
55 | // ambient light which is for the whole scene
56 | let ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
57 | ambientLight.castShadow = true;
58 | this.scene.add(ambientLight);
59 |
60 | // spot light which is illuminating the chart directly
61 | let spotLight = new THREE.SpotLight(0xffffff, 1);
62 | spotLight.castShadow = true;
63 | spotLight.position.set(0, 64, 32);
64 | this.scene.add(spotLight);
65 |
66 | // if window resizes
67 | window.addEventListener('resize', () => this.onWindowResize(), false);
68 | }
69 |
70 | animate() {
71 | // NOTE: Window is implied.
72 | // requestAnimationFrame(this.animate.bind(this));
73 | window.requestAnimationFrame(this.animate.bind(this));
74 | this.render();
75 | this.stats.update();
76 | this.controls.update();
77 | }
78 |
79 | render() {
80 | // NOTE: Update uniform data on each render.
81 | // this.uniforms.u_time.value += this.clock.getDelta();
82 | this.renderer.render(this.scene, this.camera);
83 | }
84 |
85 | onWindowResize() {
86 | this.camera.aspect = window.innerWidth / window.innerHeight;
87 | this.camera.updateProjectionMatrix();
88 | this.renderer.setSize(window.innerWidth, window.innerHeight);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/07-wordle/src/lib/Utils.js:
--------------------------------------------------------------------------------
1 | const validKeys = [
2 | 'a',
3 | 'b',
4 | 'c',
5 | 'd',
6 | 'e',
7 | 'f',
8 | 'g',
9 | 'h',
10 | 'i',
11 | 'j',
12 | 'k',
13 | 'l',
14 | 'm',
15 | 'n',
16 | 'o',
17 | 'p',
18 | 'q',
19 | 'r',
20 | 's',
21 | 't',
22 | 'u',
23 | 'v',
24 | 'w',
25 | 'x',
26 | 'y',
27 | 'z',
28 | ];
29 |
30 | export { validKeys };
31 |
--------------------------------------------------------------------------------
/07-wordle/src/lib/Wordle.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | import { TTFLoader } from 'three/examples/jsm/loaders/TTFLoader';
4 | import { FontLoader } from 'three/examples/jsm/loaders/FontLoader';
5 |
6 | import Block from './Block';
7 | import { validKeys } from './Utils';
8 |
9 | export default class Wordle {
10 | constructor() {
11 | this.level = 0;
12 | this.letterIndex = 0;
13 | this.currentWord = '';
14 | this.word = 'debug';
15 |
16 | this.wordleGroup = new THREE.Group();
17 | this.wordleGroup.position.x = -20;
18 | this.wordleGroup.position.y = -20;
19 | this.createBoard();
20 | this.setUpFont();
21 | }
22 |
23 | createBoard() {
24 | this.blocks = [
25 | new Block('', 0, 40),
26 | new Block('', 10, 40),
27 | new Block('', 20, 40),
28 | new Block('', 30, 40),
29 | new Block('', 40, 40),
30 |
31 | new Block('', 0, 30),
32 | new Block('', 10, 30),
33 | new Block('', 20, 30),
34 | new Block('', 30, 30),
35 | new Block('', 40, 30),
36 |
37 | new Block('', 0, 20),
38 | new Block('', 10, 20),
39 | new Block('', 20, 20),
40 | new Block('', 30, 20),
41 | new Block('', 40, 20),
42 |
43 | new Block('', 0, 10),
44 | new Block('', 10, 10),
45 | new Block('', 20, 10),
46 | new Block('', 30, 10),
47 | new Block('', 40, 10),
48 |
49 | new Block('', 0, 0),
50 | new Block('', 10, 0),
51 | new Block('', 20, 0),
52 | new Block('', 30, 0),
53 | new Block('', 40, 0),
54 | ];
55 |
56 | this.blocks.forEach((block) => this.wordleGroup.add(block.blockGroup));
57 | }
58 |
59 | addLetter(event) {
60 | if (event.key === 'Enter') {
61 | // NOTE: Only allow user to press 'Enter' if the
62 | // word is 5 characters long.
63 | if (this.currentWord.length === 5) {
64 | for (let i = 0; i < 5; i++) {
65 | const letter = this.word[i];
66 | const block = this.blocks[i + this.level * 5];
67 | block.checkLetter(this.word, letter);
68 | }
69 | this.level += 1;
70 | this.currentWord = '';
71 | }
72 | } else if (event.key === 'Backspace') {
73 | this.letterIndex -= 1;
74 | this.currentWord = this.currentWord.slice(0, -1);
75 | const block = this.blocks[this.letterIndex];
76 | block.removeLetter();
77 | } else if (validKeys.includes(event.key)) {
78 | if (this.currentWord.length === 5) {
79 | return;
80 | }
81 | const block = this.blocks[this.letterIndex];
82 | block.addLetter(event.key);
83 | this.letterIndex += 1;
84 | this.currentWord += event.key;
85 | }
86 | }
87 |
88 | setUpFont() {
89 | this.ttfLoader = new TTFLoader();
90 | this.fontLoader = new FontLoader();
91 | this.ttfLoader.load(
92 | './fonts/JetBrainsMonoExtraBold.ttf',
93 | (unparsedFont) => {
94 | this.parsedFont = this.fontLoader.parse(unparsedFont);
95 | this.blocks.forEach((block) => block.setFont(this.parsedFont));
96 | }
97 | );
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/07-wordle/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(
6 |
7 |
8 | ,
9 | document.getElementById('root')
10 | );
11 |
--------------------------------------------------------------------------------
/07-wordle/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()]
7 | })
8 |
--------------------------------------------------------------------------------
/08-rubiks-cube/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
--------------------------------------------------------------------------------
/08-rubiks-cube/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | tabWidth: 2,
4 | printWidth: 80,
5 | singleQuote: true,
6 | trailingComma: "es5",
7 | arrowParens: "always",
8 | };
9 |
--------------------------------------------------------------------------------
/08-rubiks-cube/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/08-rubiks-cube/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "three-vite-react",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build",
7 | "preview": "vite preview"
8 | },
9 | "dependencies": {
10 | "@tweenjs/tween.js": "^18.6.4",
11 | "dat.gui": "^0.7.7",
12 | "react": "^17.0.2",
13 | "react-dom": "^17.0.2",
14 | "three": "^0.136.0"
15 | },
16 | "devDependencies": {
17 | "@vitejs/plugin-react": "^1.0.7",
18 | "vite": "^2.7.2"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/08-rubiks-cube/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | import { GUI } from 'dat.gui';
4 | import * as THREE from 'three';
5 |
6 | import SceneInit from './lib/SceneInit';
7 | import RubiksCube from './lib/RubiksCube';
8 |
9 | function App() {
10 | useEffect(() => {
11 | const test = new SceneInit('myThreeJsCanvas');
12 | test.initScene();
13 | test.animate();
14 |
15 | const r = new RubiksCube();
16 | test.scene.add(r.rubiksCubeGroup);
17 |
18 | const mouse = new THREE.Vector2();
19 | const raycaster = new THREE.Raycaster();
20 |
21 | function onMouseDown(event) {
22 | mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
23 | mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
24 | raycaster.setFromCamera(mouse, test.camera);
25 | const objects = raycaster.intersectObjects(r.rubiksCubeGroup.children);
26 | const cubeObjects = objects.filter((c) => {
27 | return c.object.type === 'Mesh';
28 | });
29 | if (cubeObjects.length > 0) {
30 | r.highlightCubes(cubeObjects[0].object);
31 | }
32 | }
33 |
34 | const onKeyDown = (event) => {
35 | if (event.repeat) {
36 | return;
37 | }
38 | r.onKeyDown(event);
39 | };
40 |
41 | window.addEventListener('keydown', onKeyDown);
42 | window.addEventListener('mousedown', onMouseDown);
43 |
44 | // const planeGeometry = new THREE.PlaneGeometry(2, 2);
45 | // const planeMaterial = new THREE.MeshPhongMaterial({ color: '#ff0000' });
46 | // const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
47 | // planeMesh.position.z = -2;
48 | // group.add(planeMesh);
49 |
50 | const gui = new GUI();
51 | const folder = gui.addFolder("Rubik's Cube");
52 | folder.add(r, 'epsilon', 0.5, 3.5, 0.5);
53 | folder.add(r, 'consoleDebug');
54 | folder.open();
55 | }, []);
56 | return (
57 |
58 |
59 |
60 | );
61 | }
62 |
63 | export default App;
64 |
--------------------------------------------------------------------------------
/08-rubiks-cube/src/lib/Cube.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | import { vertexShader, fragmentShader } from './Shaders';
4 |
5 | export default class Cube {
6 | constructor(xOffset, yOffset, zOffset) {
7 | this.cubeGroup = new THREE.Group();
8 | this.uniforms = {
9 | opacity: {
10 | type: 'f',
11 | value: 1.0,
12 | },
13 | };
14 |
15 | const geometry = new THREE.BoxGeometry(1, 1, 1);
16 | const material = new THREE.ShaderMaterial({
17 | transparent: true,
18 | uniforms: this.uniforms,
19 | vertexShader: vertexShader(),
20 | fragmentShader: fragmentShader(),
21 | });
22 | this.cubeMesh = new THREE.Mesh(geometry, material);
23 |
24 | const lineEdges = new THREE.EdgesGeometry(this.cubeMesh.geometry);
25 | const lineMaterial = new THREE.LineBasicMaterial({ color: '#000000' });
26 | this.lineMesh = new THREE.LineSegments(lineEdges, lineMaterial);
27 |
28 | this.cubeGroup.add(this.cubeMesh);
29 | this.cubeGroup.add(this.lineMesh);
30 | this.cubeGroup.position.x = xOffset;
31 | this.cubeGroup.position.y = yOffset;
32 | this.cubeGroup.position.z = zOffset;
33 | }
34 |
35 | // highlight() {
36 | // this.cubeMesh.material.transparent = true;
37 | // this.cubeMesh.material.opacity = 0.5;
38 | // }
39 | }
40 |
--------------------------------------------------------------------------------
/08-rubiks-cube/src/lib/SceneInit.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
3 | import Stats from 'three/examples/jsm/libs/stats.module';
4 |
5 | export default class SceneInit {
6 | constructor(canvasID, camera, scene, stats, controls, renderer, fov = 36) {
7 | this.fov = fov;
8 | this.scene = scene;
9 | this.stats = stats;
10 | this.camera = camera;
11 | this.controls = controls;
12 | this.renderer = renderer;
13 | this.canvasID = canvasID;
14 | }
15 |
16 | initScene() {
17 | this.camera = new THREE.PerspectiveCamera(
18 | this.fov,
19 | window.innerWidth / window.innerHeight,
20 | 1,
21 | 1000
22 | );
23 | this.camera.position.z = 196;
24 |
25 | this.clock = new THREE.Clock();
26 | this.scene = new THREE.Scene();
27 |
28 | // NOTE: Load space background.
29 | // this.loader = new THREE.TextureLoader();
30 | // this.scene.background = this.loader.load('./pics/space.jpeg');
31 |
32 | // NOTE: Declare uniforms to pass into glsl shaders.
33 | this.uniforms = {
34 | u_time: { type: 'f', value: 1.0 },
35 | colorB: { type: 'vec3', value: new THREE.Color(0xfff000) },
36 | colorA: { type: 'vec3', value: new THREE.Color(0xffffff) },
37 | };
38 |
39 | // specify a canvas which is already created in the HTML file and tagged by an id
40 | // aliasing enabled
41 | const canvas = document.getElementById(this.canvasID);
42 | this.renderer = new THREE.WebGLRenderer({
43 | canvas,
44 | antialias: true,
45 | });
46 |
47 | this.renderer.setSize(window.innerWidth, window.innerHeight);
48 | document.body.appendChild(this.renderer.domElement);
49 |
50 | this.controls = new OrbitControls(this.camera, this.renderer.domElement);
51 |
52 | this.stats = Stats();
53 | document.body.appendChild(this.stats.dom);
54 |
55 | // ambient light which is for the whole scene
56 | let ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
57 | ambientLight.castShadow = true;
58 | this.scene.add(ambientLight);
59 |
60 | // spot light which is illuminating the chart directly
61 | let spotLight = new THREE.SpotLight(0xffffff, 1);
62 | spotLight.castShadow = true;
63 | spotLight.position.set(0, 64, 32);
64 | this.scene.add(spotLight);
65 |
66 | // if window resizes
67 | window.addEventListener('resize', () => this.onWindowResize(), false);
68 | }
69 |
70 | animate() {
71 | // NOTE: Window is implied.
72 | // requestAnimationFrame(this.animate.bind(this));
73 | window.requestAnimationFrame(this.animate.bind(this));
74 | this.render();
75 | this.stats.update();
76 | this.controls.update();
77 | }
78 |
79 | render() {
80 | // NOTE: Update uniform data on each render.
81 | this.uniforms.u_time.value += this.clock.getDelta();
82 | this.renderer.render(this.scene, this.camera);
83 | }
84 |
85 | onWindowResize() {
86 | this.camera.aspect = window.innerWidth / window.innerHeight;
87 | this.camera.updateProjectionMatrix();
88 | this.renderer.setSize(window.innerWidth, window.innerHeight);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/08-rubiks-cube/src/lib/Shaders.js:
--------------------------------------------------------------------------------
1 | const vertexShader = () => {
2 | return `
3 | uniform float opacity;
4 |
5 | varying vec3 pos;
6 |
7 | void main() {
8 | pos = position;
9 |
10 | gl_Position = projectionMatrix
11 | * modelViewMatrix
12 | * vec4(
13 | position.x,
14 | position.y,
15 | position.z,
16 | 1.0
17 | );
18 | }
19 | `;
20 | };
21 |
22 | const fragmentShader = () => {
23 | return `
24 | uniform float opacity;
25 |
26 | varying vec3 pos;
27 |
28 | void main() {
29 | vec4 red = vec4(1.0, 0.0, 0.0, opacity);
30 | vec4 white = vec4(1.0, 1.0, 1.0, opacity);
31 | vec4 blue = vec4(0.0, 0.0, 1.0, opacity);
32 | vec4 yellow = vec4(1.0, 1.0, 0.0, opacity);
33 | vec4 green = vec4(0.0, 1.0, 0.0, opacity);
34 | vec4 orange = vec4(1.0, 0.65, 0.0, opacity);
35 |
36 | vec4 black = vec4(0.0, 0.0, 0.0, opacity);
37 |
38 | float scale = 0.499;
39 |
40 | bool front = pos.z > scale;
41 | bool back = pos.z < -1.0 * scale;
42 | bool top = pos.y > scale;
43 | bool bottom = pos.y < -1.0 * scale;
44 | bool right = pos.x > scale;
45 | bool left = pos.x < -1.0 * scale;
46 |
47 | if (front) {
48 | gl_FragColor = red;
49 | } else if (back) {
50 | gl_FragColor = orange;
51 | } else if (top) {
52 | gl_FragColor = white;
53 | } else if (bottom) {
54 | gl_FragColor = yellow;
55 | } else if (right) {
56 | gl_FragColor = blue;
57 | } else if (left) {
58 | gl_FragColor = green;
59 | } else {
60 | gl_FragColor = black;
61 | }
62 | }
63 | `;
64 | };
65 |
66 | export { vertexShader, fragmentShader };
67 |
--------------------------------------------------------------------------------
/08-rubiks-cube/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(
6 |
7 |
8 | ,
9 | document.getElementById('root')
10 | );
11 |
--------------------------------------------------------------------------------
/08-rubiks-cube/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()]
7 | })
8 |
--------------------------------------------------------------------------------
/09-snake-retro/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
--------------------------------------------------------------------------------
/09-snake-retro/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | tabWidth: 2,
4 | printWidth: 80,
5 | singleQuote: true,
6 | trailingComma: "es5",
7 | arrowParens: "always",
8 | };
9 |
--------------------------------------------------------------------------------
/09-snake-retro/assets/crt_monitor/scene.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/09-snake-retro/assets/crt_monitor/scene.bin
--------------------------------------------------------------------------------
/09-snake-retro/assets/crt_monitor/scene.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "accessors": [
3 | {
4 | "bufferView": 2,
5 | "componentType": 5126,
6 | "count": 766,
7 | "max": [
8 | 0.47237420082092285,
9 | 1,
10 | 0.93256908655166626
11 | ],
12 | "min": [
13 | -1.7092454433441162,
14 | -1,
15 | -1.3454774618148804
16 | ],
17 | "type": "VEC3"
18 | },
19 | {
20 | "bufferView": 2,
21 | "byteOffset": 9192,
22 | "componentType": 5126,
23 | "count": 766,
24 | "max": [
25 | 1,
26 | 1,
27 | 1
28 | ],
29 | "min": [
30 | -1,
31 | -1,
32 | -1
33 | ],
34 | "type": "VEC3"
35 | },
36 | {
37 | "bufferView": 3,
38 | "componentType": 5126,
39 | "count": 766,
40 | "max": [
41 | 1,
42 | 0.99584060907363892,
43 | 0.99916386604309082,
44 | 1
45 | ],
46 | "min": [
47 | -0.99928092956542969,
48 | -0.99999868869781494,
49 | -1,
50 | 1
51 | ],
52 | "type": "VEC4"
53 | },
54 | {
55 | "bufferView": 1,
56 | "componentType": 5126,
57 | "count": 766,
58 | "max": [
59 | 0.99967515468597412,
60 | 0.99810218811035156
61 | ],
62 | "min": [
63 | 0.00032479004585184157,
64 | 0.00032479004585184157
65 | ],
66 | "type": "VEC2"
67 | },
68 | {
69 | "bufferView": 0,
70 | "componentType": 5125,
71 | "count": 3318,
72 | "max": [
73 | 765
74 | ],
75 | "min": [
76 | 0
77 | ],
78 | "type": "SCALAR"
79 | }
80 | ],
81 | "asset": {
82 | "extras": {
83 | "author": "James.Harness (https://sketchfab.com/James.Harness)",
84 | "license": "CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)",
85 | "source": "https://sketchfab.com/3d-models/crt-monitor-e2dd2887a8904e4fa3d5a32e2935adb9",
86 | "title": "CRT Monitor"
87 | },
88 | "generator": "Sketchfab-8.64.0",
89 | "version": "2.0"
90 | },
91 | "bufferViews": [
92 | {
93 | "buffer": 0,
94 | "byteLength": 13272,
95 | "byteOffset": 0,
96 | "name": "floatBufferViews",
97 | "target": 34963
98 | },
99 | {
100 | "buffer": 0,
101 | "byteLength": 6128,
102 | "byteOffset": 13272,
103 | "byteStride": 8,
104 | "name": "floatBufferViews",
105 | "target": 34962
106 | },
107 | {
108 | "buffer": 0,
109 | "byteLength": 18384,
110 | "byteOffset": 19400,
111 | "byteStride": 12,
112 | "name": "floatBufferViews",
113 | "target": 34962
114 | },
115 | {
116 | "buffer": 0,
117 | "byteLength": 12256,
118 | "byteOffset": 37784,
119 | "byteStride": 16,
120 | "name": "floatBufferViews",
121 | "target": 34962
122 | }
123 | ],
124 | "buffers": [
125 | {
126 | "byteLength": 50040,
127 | "uri": "scene.bin"
128 | }
129 | ],
130 | "images": [
131 | {
132 | "uri": "textures/Material_baseColor.png"
133 | },
134 | {
135 | "uri": "textures/Material_metallicRoughness.png"
136 | },
137 | {
138 | "uri": "textures/Material_normal.png"
139 | }
140 | ],
141 | "materials": [
142 | {
143 | "doubleSided": true,
144 | "emissiveFactor": [
145 | 0,
146 | 0,
147 | 0
148 | ],
149 | "name": "Material",
150 | "normalTexture": {
151 | "index": 2,
152 | "scale": 1,
153 | "texCoord": 0
154 | },
155 | "occlusionTexture": {
156 | "index": 1,
157 | "strength": 1,
158 | "texCoord": 0
159 | },
160 | "pbrMetallicRoughness": {
161 | "baseColorFactor": [
162 | 1,
163 | 1,
164 | 1,
165 | 1
166 | ],
167 | "baseColorTexture": {
168 | "index": 0,
169 | "texCoord": 0
170 | },
171 | "metallicFactor": 1,
172 | "metallicRoughnessTexture": {
173 | "index": 1,
174 | "texCoord": 0
175 | },
176 | "roughnessFactor": 1
177 | }
178 | }
179 | ],
180 | "meshes": [
181 | {
182 | "name": "Cube_Material_0",
183 | "primitives": [
184 | {
185 | "attributes": {
186 | "NORMAL": 1,
187 | "POSITION": 0,
188 | "TANGENT": 2,
189 | "TEXCOORD_0": 3
190 | },
191 | "indices": 4,
192 | "material": 0,
193 | "mode": 4
194 | }
195 | ]
196 | }
197 | ],
198 | "nodes": [
199 | {
200 | "children": [
201 | 1
202 | ],
203 | "name": "RootNode (gltf orientation matrix)",
204 | "rotation": [
205 | -0.70710678118654746,
206 | -0,
207 | -0,
208 | 0.70710678118654757
209 | ]
210 | },
211 | {
212 | "children": [
213 | 2
214 | ],
215 | "name": "RootNode (model correction matrix)"
216 | },
217 | {
218 | "children": [
219 | 3
220 | ],
221 | "matrix": [
222 | 1,
223 | 0,
224 | 0,
225 | 0,
226 | 0,
227 | 0,
228 | 1,
229 | 0,
230 | 0,
231 | -1,
232 | 0,
233 | 0,
234 | 0,
235 | 0,
236 | 0,
237 | 1
238 | ],
239 | "name": "computer.fbx"
240 | },
241 | {
242 | "children": [
243 | 4
244 | ],
245 | "name": "RootNode"
246 | },
247 | {
248 | "children": [
249 | 5
250 | ],
251 | "matrix": [
252 | 100,
253 | 0,
254 | 0,
255 | 0,
256 | -0,
257 | -1.6292067939183141e-05,
258 | -99.999999999998678,
259 | 0,
260 | 0,
261 | 99.999999999998678,
262 | -1.6292067939183141e-05,
263 | 0,
264 | -10.030162811279297,
265 | 0.13394522666931152,
266 | 1.7996770296235809e-08,
267 | 1
268 | ],
269 | "name": "Cube"
270 | },
271 | {
272 | "mesh": 0,
273 | "name": "Cube_Material_0"
274 | }
275 | ],
276 | "samplers": [
277 | {
278 | "magFilter": 9729,
279 | "minFilter": 9987,
280 | "wrapS": 10497,
281 | "wrapT": 10497
282 | }
283 | ],
284 | "scene": 0,
285 | "scenes": [
286 | {
287 | "name": "OSG_Scene",
288 | "nodes": [
289 | 0
290 | ]
291 | }
292 | ],
293 | "textures": [
294 | {
295 | "sampler": 0,
296 | "source": 0
297 | },
298 | {
299 | "sampler": 0,
300 | "source": 1
301 | },
302 | {
303 | "sampler": 0,
304 | "source": 2
305 | }
306 | ]
307 | }
308 |
309 |
--------------------------------------------------------------------------------
/09-snake-retro/assets/crt_monitor/textures/Material_baseColor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/09-snake-retro/assets/crt_monitor/textures/Material_baseColor.png
--------------------------------------------------------------------------------
/09-snake-retro/assets/crt_monitor/textures/Material_metallicRoughness.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/09-snake-retro/assets/crt_monitor/textures/Material_metallicRoughness.png
--------------------------------------------------------------------------------
/09-snake-retro/assets/crt_monitor/textures/Material_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/09-snake-retro/assets/crt_monitor/textures/Material_normal.png
--------------------------------------------------------------------------------
/09-snake-retro/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/09-snake-retro/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "three-vite-react",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build",
7 | "preview": "vite preview"
8 | },
9 | "dependencies": {
10 | "@tweenjs/tween.js": "^18.6.4",
11 | "dat.gui": "^0.7.7",
12 | "react": "^17.0.2",
13 | "react-dom": "^17.0.2",
14 | "three": "^0.136.0"
15 | },
16 | "devDependencies": {
17 | "@vitejs/plugin-react": "^1.0.7",
18 | "vite": "^2.7.2"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/09-snake-retro/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | import { GUI } from 'dat.gui';
4 |
5 | import SceneInit from './lib/SceneInit';
6 | import SnakeGame from './lib/SnakeGame';
7 |
8 | function App() {
9 | useEffect(() => {
10 | const test = new SceneInit('myThreeJsCanvas');
11 | test.initScene();
12 | test.animate();
13 |
14 | const snakeGame = new SnakeGame();
15 | // test.scene.add(snakeGame.sgg);
16 | test.rtScene.add(snakeGame.sgg);
17 |
18 | const animate = (t) => {
19 | snakeGame.loop(t);
20 | requestAnimationFrame(animate);
21 | };
22 | animate();
23 |
24 | const onKeyDown = (event) => {
25 | if (event.repeat) {
26 | return;
27 | }
28 | snakeGame.pressKey(event);
29 | };
30 |
31 | // NOTE: Add board gui.
32 | const gui = new GUI();
33 | const gameDifficulty = gui.addFolder('Difficulty');
34 | gameDifficulty.add(snakeGame, 'loopTimeStep', 150, 500);
35 | gameDifficulty.add(snakeGame, 'tweenTimeStep', 150, 500);
36 | gameDifficulty.open();
37 | const boardFolder = gui.addFolder('Board');
38 | boardFolder
39 | .add(snakeGame, 'gameScale', 4, 6)
40 | .onChange(() => snakeGame.updateScale());
41 | boardFolder
42 | .add(snakeGame, 'boardSize', 6, 12)
43 | .step(2)
44 | .onChange(() => {
45 | snakeGame.resetBoard();
46 | });
47 | boardFolder.open();
48 |
49 | window.addEventListener('keydown', onKeyDown);
50 | return () => {
51 | window.removeEventListener('keydown', onKeyDown);
52 | };
53 | }, []);
54 | return (
55 |
56 |
57 |
58 | );
59 | }
60 |
61 | export default App;
62 |
--------------------------------------------------------------------------------
/09-snake-retro/src/lib/SceneInit.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | import Stats from 'three/examples/jsm/libs/stats.module';
4 |
5 | import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
6 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
7 |
8 | import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
9 | import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
10 | import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass';
11 | import { FilmPass } from 'three/examples/jsm/postprocessing/FilmPass';
12 |
13 | import {
14 | planeVertexShader,
15 | planeFragmentShader,
16 | customVignetteVertexShader,
17 | customVignetteFragmentShader,
18 | } from './Shaders';
19 |
20 | export default class SceneInit {
21 | constructor(canvasID, camera, scene, stats, controls, renderer, fov = 36) {
22 | this.fov = fov;
23 | this.scene = scene;
24 | this.stats = stats;
25 | this.camera = camera;
26 | this.controls = controls;
27 | this.renderer = renderer;
28 | this.canvasID = canvasID;
29 | }
30 |
31 | initScene() {
32 | this.clock = new THREE.Clock();
33 | this.scene = new THREE.Scene();
34 | this.camera = new THREE.PerspectiveCamera(
35 | this.fov,
36 | window.innerWidth / window.innerHeight,
37 | 1,
38 | 1000
39 | );
40 | this.camera.position.z = 96;
41 |
42 | // specify a canvas which is already created in the HTML file and tagged by an id
43 | // aliasing enabled
44 | const canvas = document.getElementById(this.canvasID);
45 | this.renderer = new THREE.WebGLRenderer({
46 | canvas,
47 | antialias: true,
48 | });
49 |
50 | this.renderer.setSize(window.innerWidth, window.innerHeight);
51 | document.body.appendChild(this.renderer.domElement);
52 |
53 | this.controls = new OrbitControls(this.camera, this.renderer.domElement);
54 |
55 | this.stats = Stats();
56 | document.body.appendChild(this.stats.dom);
57 |
58 | // ambient light which is for the whole scene
59 | let ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
60 | ambientLight.castShadow = true;
61 | this.scene.add(ambientLight);
62 |
63 | // spot light which is illuminating the chart directly
64 | let spotLight = new THREE.SpotLight(0xffffff, 0.5);
65 | spotLight.castShadow = true;
66 | spotLight.position.set(0, 48, 48);
67 | this.scene.add(spotLight);
68 |
69 | const loader = new GLTFLoader();
70 | loader.load('./assets/crt_monitor/scene.gltf', (gltf) => {
71 | gltf.scene.scale.set(0.25, 0.25, 0.25);
72 | gltf.scene.rotation.y = -Math.PI / 2;
73 | gltf.scene.position.y = 5;
74 | gltf.scene.position.z = -8;
75 | this.scene.add(gltf.scene);
76 | });
77 |
78 | // if window resizes
79 | window.addEventListener('resize', () => this.onWindowResize(), false);
80 |
81 | // NOTE: Render target.
82 | const rtFov = 75;
83 | const rtNear = 0.1;
84 | const rtFar = 100;
85 | const rtWidth = 1024;
86 | const rtHeight = 1024;
87 | const rtAspect = rtWidth / rtHeight;
88 | this.rtScene = new THREE.Scene();
89 | this.renderTarget = new THREE.WebGLRenderTarget(rtWidth, rtHeight);
90 | this.rtCamera = new THREE.PerspectiveCamera(rtFov, rtAspect, rtNear, rtFar);
91 | this.rtCamera.position.z = 36;
92 |
93 | const color = 0xffffff;
94 | const intensity = 1;
95 | const light = new THREE.DirectionalLight(color, intensity);
96 | light.position.set(0, 0, 200);
97 | this.rtScene.add(light);
98 | this.rtScene.background = new THREE.Color(0xfafafa);
99 |
100 | const planeGeometry = new THREE.PlaneGeometry(32, 32, 32, 32);
101 | const planeMaterial = new THREE.ShaderMaterial({
102 | side: THREE.DoubleSide,
103 | uniforms: {
104 | time: { type: 'f', value: 1.0 },
105 | uTexture: { value: this.renderTarget.texture },
106 | },
107 | vertexShader: planeVertexShader(),
108 | fragmentShader: planeFragmentShader(),
109 | });
110 | const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
111 | planeMesh.position.y = 5;
112 | planeMesh.position.z = -2.4;
113 | planeMesh.rotation.x = -Math.PI / 48;
114 | planeMesh.scale.set(1.2, 1.2, 1.2);
115 | this.scene.add(planeMesh);
116 |
117 | this.customVignetteShader = {
118 | uniforms: {
119 | tDiffuse: { value: null },
120 | offset: { value: 1.0 },
121 | darkness: { value: 3.0 },
122 | time: { type: 'f', value: 1.0 },
123 | },
124 | vertexShader: customVignetteVertexShader(),
125 | fragmentShader: customVignetteFragmentShader(),
126 | };
127 |
128 | this.composer = new EffectComposer(this.renderer, this.renderTarget);
129 | const renderPass = new RenderPass(this.rtScene, this.rtCamera);
130 | const filmPass = new FilmPass(0.35, 0.025, 648, false);
131 | const customVignettePass = new ShaderPass(
132 | new THREE.ShaderMaterial(this.customVignetteShader)
133 | );
134 | this.composer.addPass(renderPass);
135 | this.composer.addPass(customVignettePass);
136 | this.composer.addPass(filmPass);
137 | }
138 |
139 | animate() {
140 | // NOTE: Window is implied.
141 | // requestAnimationFrame(this.animate.bind(this));
142 | window.requestAnimationFrame(this.animate.bind(this));
143 | this.render();
144 | this.stats.update();
145 | this.controls.update();
146 | }
147 |
148 | render() {
149 | // NOTE: Update uniform data on each render.
150 | // this.uniforms.u_time.value += this.clock.getDelta();
151 | this.composer.render();
152 | this.renderer.render(this.scene, this.camera);
153 | }
154 |
155 | onWindowResize() {
156 | this.camera.aspect = window.innerWidth / window.innerHeight;
157 | this.camera.updateProjectionMatrix();
158 | this.renderer.setSize(window.innerWidth, window.innerHeight);
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/09-snake-retro/src/lib/Shaders.js:
--------------------------------------------------------------------------------
1 | const planeVertexShader = () => {
2 | return `
3 | uniform float time;
4 | varying vec2 vUv;
5 | void main() {
6 | vUv = uv;
7 | gl_Position = projectionMatrix
8 | * modelViewMatrix
9 | * vec4(
10 | position.x,
11 | position.y,
12 | 2.0 * cos((abs(position.x) + abs(position.y)) / 16.0),
13 | 1.0
14 | );
15 | }
16 | `;
17 | };
18 |
19 | const planeFragmentShader = () => {
20 | return `
21 | uniform sampler2D uTexture;
22 | varying vec2 vUv;
23 | void main() {
24 | vec3 t = texture2D(uTexture, vUv).rgb;
25 | gl_FragColor = vec4( t, 1.0 );
26 | }
27 | `;
28 | };
29 |
30 | const customVignetteVertexShader = () => {
31 | return `
32 | uniform float time;
33 | varying vec2 vUv;
34 | void main() {
35 | vUv = uv;
36 | gl_Position = projectionMatrix
37 | * modelViewMatrix
38 | * vec4(
39 | position.x,
40 | position.y,
41 | position.z,
42 | 1.0
43 | );
44 | }
45 | `;
46 | };
47 |
48 | const customVignetteFragmentShader = () => {
49 | return `
50 | uniform float time;
51 | uniform float offset;
52 | uniform float darkness;
53 | uniform sampler2D tDiffuse;
54 | varying vec2 vUv;
55 | void main() {
56 | // Eskil's vignette
57 | vec4 texel = texture2D( tDiffuse, vUv );
58 | vec2 uv = ( vUv - vec2( 0.5 ) ) * vec2( offset );
59 | gl_FragColor = vec4( mix( texel.rgb, vec3( 1.0 - darkness ), dot( uv, uv ) ), texel.a );
60 | }
61 | `;
62 | };
63 |
64 | export {
65 | planeVertexShader,
66 | planeFragmentShader,
67 | customVignetteVertexShader,
68 | customVignetteFragmentShader,
69 | };
70 |
--------------------------------------------------------------------------------
/09-snake-retro/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(
6 |
7 |
8 | ,
9 | document.getElementById('root')
10 | );
11 |
--------------------------------------------------------------------------------
/09-snake-retro/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()]
7 | })
8 |
--------------------------------------------------------------------------------
/09-snake/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/09-snake/README.md:
--------------------------------------------------------------------------------
1 | # Next.js + Tailwind CSS Example
2 |
3 | This example shows how to use [Tailwind CSS](https://tailwindcss.com/) [(v2.2)](https://blog.tailwindcss.com/tailwindcss-2-2) with Next.js. It follows the steps outlined in the official [Tailwind docs](https://tailwindcss.com/docs/guides/nextjs).
4 |
5 | It uses the new [`Just-in-Time Mode`](https://tailwindcss.com/docs/just-in-time-mode) for Tailwind CSS.
6 |
7 | ## Preview
8 |
9 | Preview the example live on [StackBlitz](http://stackblitz.com/):
10 |
11 | [](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-tailwindcss)
12 |
13 | ## Deploy your own
14 |
15 | Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
16 |
17 | [](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-tailwindcss&project-name=with-tailwindcss&repository-name=with-tailwindcss)
18 |
19 | ## How to use
20 |
21 | Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
22 |
23 | ```bash
24 | npx create-next-app --example with-tailwindcss with-tailwindcss-app
25 | # or
26 | yarn create next-app --example with-tailwindcss with-tailwindcss-app
27 | ```
28 |
29 | Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
30 |
--------------------------------------------------------------------------------
/09-snake/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "next dev",
5 | "build": "next build",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "@tweenjs/tween.js": "^18.6.4",
10 | "dat.gui": "^0.7.7",
11 | "next": "latest",
12 | "react": "^17.0.2",
13 | "react-dom": "^17.0.2",
14 | "three": "^0.135.0"
15 | },
16 | "devDependencies": {
17 | "autoprefixer": "^10.2.6",
18 | "postcss": "^8.3.5",
19 | "tailwindcss": "^2.2.4"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/09-snake/pages/_app.jsx:
--------------------------------------------------------------------------------
1 | import 'tailwindcss/tailwind.css'
2 |
3 | function MyApp({ Component, pageProps }) {
4 | return
5 | }
6 |
7 | export default MyApp
8 |
--------------------------------------------------------------------------------
/09-snake/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function helloAPI(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/09-snake/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import SceneInit from "./lib/SceneInit";
3 | import SnakeGame from "./lib/SnakeGame";
4 |
5 | export default function Home() {
6 | useEffect(() => {
7 | const test = new SceneInit("myThreeJsCanvas");
8 | test.initScene();
9 | test.animate();
10 |
11 | const snakeGame = new SnakeGame(test.scene);
12 |
13 | const animate = (t) => {
14 | snakeGame.loop(t);
15 | requestAnimationFrame(animate);
16 | };
17 | animate();
18 |
19 | const initDatGui = async () => {
20 | const dat = await import("dat.gui");
21 | const gui = new dat.GUI();
22 | const snakeGameFolder = gui.addFolder("Snake Game");
23 | snakeGameFolder.add(snakeGame, "loopTimeStep", 256, 1024);
24 | };
25 | initDatGui();
26 |
27 | const onKeyDown = (e) => {
28 | snakeGame.prevPressedKey = snakeGame.lastPressedKey;
29 | snakeGame.lastPressedKey = e.key;
30 | };
31 | document.addEventListener("keydown", onKeyDown);
32 | return () => {
33 | document.removeEventListener("keydown", onKeyDown);
34 | };
35 | }, []);
36 |
37 | return (
38 |
39 | {/* {!isPlaying && (
40 |
41 |
42 |
48 |
49 |
50 | )} */}
51 |
52 |
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/09-snake/pages/lib/SceneInit.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
3 | import Stats from "three/examples/jsm/libs/stats.module";
4 |
5 | export default class SceneInit {
6 | constructor(canvasID, camera, scene, stats, controls, renderer, fov = 36) {
7 | this.fov = fov;
8 | this.scene = scene;
9 | this.stats = stats;
10 | this.camera = camera;
11 | this.controls = controls;
12 | this.renderer = renderer;
13 | this.canvasID = canvasID;
14 | }
15 |
16 | initScene() {
17 | this.camera = new THREE.PerspectiveCamera(
18 | this.fov,
19 | window.innerWidth / window.innerHeight,
20 | 1,
21 | 1000
22 | );
23 | this.camera.position.z = 32;
24 |
25 | this.clock = new THREE.Clock();
26 | this.scene = new THREE.Scene();
27 |
28 | this.uniforms = {
29 | u_time: { type: "f", value: 1.0 },
30 | colorB: { type: "vec3", value: new THREE.Color(0xfff000) },
31 | colorA: { type: "vec3", value: new THREE.Color(0xffffff) },
32 | };
33 |
34 | // specify a canvas which is already created in the HTML file and tagged by an id
35 | // aliasing enabled
36 | const canvas = document.getElementById(this.canvasID);
37 | this.renderer = new THREE.WebGLRenderer({
38 | canvas,
39 | antialias: true,
40 | });
41 |
42 | this.renderer.setSize(window.innerWidth, window.innerHeight);
43 | document.body.appendChild(this.renderer.domElement);
44 |
45 | this.controls = new OrbitControls(this.camera, this.renderer.domElement);
46 |
47 | this.stats = Stats();
48 | document.body.appendChild(this.stats.dom);
49 |
50 | // ambient light which is for the whole scene
51 | let ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
52 | ambientLight.castShadow = false;
53 | this.scene.add(ambientLight);
54 |
55 | // spot light which is illuminating the chart directly
56 | let spotLight = new THREE.SpotLight(0xffffff, 0.55);
57 | spotLight.castShadow = true;
58 | spotLight.position.set(0, 80, 10);
59 | this.scene.add(spotLight);
60 |
61 | // if window resizes
62 | window.addEventListener("resize", () => this.onWindowResize(), false);
63 | }
64 |
65 | animate() {
66 | // NOTE: Window is implied.
67 | // requestAnimationFrame(this.animate.bind(this));
68 | window.requestAnimationFrame(this.animate.bind(this));
69 | this.render();
70 | this.stats.update();
71 | this.controls.update();
72 | }
73 |
74 | render() {
75 | this.uniforms.u_time.value += this.clock.getDelta();
76 | this.renderer.render(this.scene, this.camera);
77 | }
78 |
79 | onWindowResize() {
80 | this.camera.aspect = window.innerWidth / window.innerHeight;
81 | this.camera.updateProjectionMatrix();
82 | this.renderer.setSize(window.innerWidth, window.innerHeight);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/09-snake/pages/lib/SnakeGame.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import * as TWEEN from "@tweenjs/tween.js";
3 |
4 | export default class SnakeGame {
5 | constructor(testScene) {
6 | this.boardSize = 16;
7 | this.snakeSpeed = 1;
8 |
9 | this.lastTimeStamp = 0;
10 | this.loopTimeStep = 512;
11 | this.tweenTimeStep = 256;
12 | this.lastPressedKey = "w";
13 |
14 | this.snake = new THREE.Group();
15 | this.snacks = new THREE.Group();
16 | this.tileMap = new THREE.Group();
17 |
18 | this._addSnack();
19 | this._createSnake();
20 | this._createTileMap();
21 |
22 | testScene.add(this.snake);
23 | testScene.add(this.snacks);
24 | testScene.add(this.tileMap);
25 | }
26 |
27 | loop(t) {
28 | TWEEN.update(t);
29 | const timeStep = t - this.lastTimeStamp;
30 | if (timeStep > this.loopTimeStep) {
31 | this._moveSnake();
32 | this._updateSnack();
33 | this.lastTimeStamp = t;
34 | }
35 | }
36 |
37 | _updateSnack() {
38 | const snackCoords = {
39 | x: this.snacks.children[0].position.x,
40 | y: this.snacks.children[0].position.y,
41 | };
42 | const snakeHeadCoords = {
43 | x: this.snake.children[0].position.x,
44 | y: this.snake.children[0].position.y,
45 | };
46 | if (
47 | snakeHeadCoords.x === snackCoords.x &&
48 | snakeHeadCoords.y === snackCoords.y
49 | ) {
50 | // TODO: What is the best way to remove a mesh?
51 | // this.snacks.children = []
52 | this.snacks.remove(this.snacks.children[0]);
53 | this._addSnack();
54 | }
55 | }
56 |
57 | _addSnack() {
58 | const snackGeometry = new THREE.BoxGeometry(1, 1, 1);
59 | const snackMaterial = new THREE.MeshNormalMaterial();
60 | const snackMesh = new THREE.Mesh(snackGeometry, snackMaterial);
61 | let x = Math.round((Math.random() * this.boardSize) / 2);
62 | let y = Math.round((Math.random() * this.boardSize) / 2);
63 | x = Math.random() < 0.5 ? x * -1 : x * -1;
64 | y = Math.random() < 0.5 ? y * -1 : y;
65 | snackMesh.position.x = x;
66 | snackMesh.position.y = y;
67 | snackMesh.position.z = 1;
68 | this.snacks.add(snackMesh);
69 | }
70 |
71 | _animateSnakeMovement(oldCoords, newCoords) {
72 | for (let i = 0; i < this.snake.children.length; i++) {
73 | // note: head of snake is pre-determined from user input
74 | if (i !== 0) {
75 | newCoords = { x: oldCoords.x, y: oldCoords.y };
76 | oldCoords = {
77 | x: this.snake.children[i].position.x,
78 | y: this.snake.children[i].position.y,
79 | };
80 | }
81 | const tween = new TWEEN.Tween(oldCoords)
82 | .to(newCoords, this.tweenTimeStep)
83 | .easing(TWEEN.Easing.Sinusoidal.Out)
84 | .onUpdate(({ x, y }) => {
85 | this.snake.children[i].position.x = x;
86 | this.snake.children[i].position.y = y;
87 | });
88 | tween.start();
89 | }
90 | }
91 |
92 | _moveSnake() {
93 | const lastPressedKey = this.lastPressedKey;
94 |
95 | const oldHeadXCoord = this.snake.children[0].position.x;
96 | const oldHeadYCoord = this.snake.children[0].position.y;
97 | const oldCoords = {
98 | x: oldHeadXCoord,
99 | y: oldHeadYCoord,
100 | };
101 | const newCoords = {
102 | x: oldHeadXCoord,
103 | y: oldHeadYCoord,
104 | };
105 |
106 | const upKeys = ["w", "ArrowUp"];
107 | const leftKeys = ["a", "ArrowLeft"];
108 | const downKeys = ["s", "ArrowDown"];
109 | const rightKeys = ["d", "ArrowRight"];
110 |
111 | if (upKeys.includes(lastPressedKey)) {
112 | newCoords.y = oldHeadYCoord + this.snakeSpeed;
113 | this._animateSnakeMovement(oldCoords, newCoords);
114 | } else if (leftKeys.includes(lastPressedKey)) {
115 | newCoords.x = oldHeadXCoord - this.snakeSpeed;
116 | this._animateSnakeMovement(oldCoords, newCoords);
117 | } else if (downKeys.includes(lastPressedKey)) {
118 | newCoords.y = oldHeadYCoord - this.snakeSpeed;
119 | this._animateSnakeMovement(oldCoords, newCoords);
120 | } else if (rightKeys.includes(lastPressedKey)) {
121 | newCoords.x = oldHeadXCoord + this.snakeSpeed;
122 | this._animateSnakeMovement(oldCoords, newCoords);
123 | }
124 | }
125 |
126 | _createSnake() {
127 | const snakeGeometry = new THREE.BoxGeometry(1, 1, 1);
128 | const snakeMaterial = new THREE.MeshNormalMaterial();
129 | const snakeMesh = new THREE.Mesh(snakeGeometry, snakeMaterial);
130 | snakeMesh.position.z = 1;
131 | const sm = new THREE.Mesh(snakeGeometry, snakeMaterial);
132 | sm.position.z = 1;
133 | sm.position.x = -1;
134 | const sm2 = new THREE.Mesh(snakeGeometry, snakeMaterial);
135 | sm2.position.z = 1;
136 | sm2.position.x = -2;
137 | const sm3 = new THREE.Mesh(snakeGeometry, snakeMaterial);
138 | sm3.position.z = 1;
139 | sm3.position.x = -3;
140 | this.snake.add(snakeMesh, sm, sm2, sm3);
141 | }
142 |
143 | _createTileMap() {
144 | for (let i = -this.boardSize / 2; i < this.boardSize / 2; i++) {
145 | for (let j = -this.boardSize / 2; j < this.boardSize / 2; j++) {
146 | this.tileMap.add(this._newTileMesh(i, j));
147 | }
148 | }
149 | }
150 |
151 | _newTileMesh(i, j) {
152 | const tileGeometry = new THREE.BoxGeometry(1, 1, 1);
153 | const tileMaterial = new THREE.MeshPhongMaterial({
154 | // wireframe: true,
155 | });
156 | const tileMesh = new THREE.Mesh(tileGeometry, tileMaterial);
157 | tileMesh.position.x = j;
158 | tileMesh.position.y = i;
159 | return tileMesh;
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/09-snake/postcss.config.js:
--------------------------------------------------------------------------------
1 | // If you want to use other PostCSS plugins, see the following:
2 | // https://tailwindcss.com/docs/using-with-preprocessors
3 | module.exports = {
4 | plugins: {
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/09-snake/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/09-snake/public/favicon.ico
--------------------------------------------------------------------------------
/09-snake/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/09-snake/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
4 | darkMode: false, // or 'media' or 'class'
5 | theme: {
6 | extend: {},
7 | },
8 | variants: {
9 | extend: {},
10 | },
11 | plugins: [],
12 | }
13 |
--------------------------------------------------------------------------------
/10-frantic-architect/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/10-frantic-architect/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | tabWidth: 2,
4 | printWidth: 80,
5 | singleQuote: true,
6 | trailingComma: 'es5',
7 | arrowParens: 'always',
8 | };
9 |
--------------------------------------------------------------------------------
/10-frantic-architect/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/10-frantic-architect/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00-setup-guide",
3 | "private": true,
4 | "version": "0.0.0",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "cannon-es": "^0.19.0",
12 | "cannon-es-debugger": "^1.0.0",
13 | "dat.gui": "^0.7.9",
14 | "react": "^17.0.2",
15 | "react-dom": "^17.0.2",
16 | "three": "^0.138.3"
17 | },
18 | "devDependencies": {
19 | "@vitejs/plugin-react": "^1.0.7",
20 | "vite": "^2.8.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/10-frantic-architect/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
40 | button {
41 | font-size: calc(10px + 2vmin);
42 | }
43 |
--------------------------------------------------------------------------------
/10-frantic-architect/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | import { GUI } from 'dat.gui';
4 | import * as THREE from 'three';
5 | import CannonDebugger from 'cannon-es-debugger';
6 |
7 | import SceneInit from './lib/SceneInit';
8 | import FranticArchitect from './lib/FranticArchitect';
9 |
10 | function App() {
11 | useEffect(() => {
12 | const test = new SceneInit('myThreeJsCanvas');
13 | test.initialize();
14 |
15 | const franticArchitect = new FranticArchitect();
16 | test.scene.add(franticArchitect.gg);
17 | const cannonDebugger = new CannonDebugger(
18 | test.scene,
19 | franticArchitect.world
20 | );
21 |
22 | const gui = new GUI();
23 | gui
24 | .add(test, 'cameraRotationDepth', 5, 100)
25 | .name('Camera Distance')
26 | .onChange((value) => {
27 | // TODO: Change camera position every 10 units.
28 | // const newY = Math.round((value / 10) % 5) + 5;
29 | // if (test.camera.position.y !== newY) {
30 | // test.camera.lookAt(new THREE.Vector3(0, newY, 0));
31 | // test.camera.position.y = newY;
32 | // }
33 | });
34 |
35 | const animate = () => {
36 | const dt = test.clock.getDelta();
37 |
38 | test.render();
39 | test.stats.update();
40 | cannonDebugger.update();
41 | franticArchitect.update(dt);
42 | franticArchitect.animatePhantomGroup();
43 | franticArchitect.animateCompoundShapeGroup();
44 |
45 | // NOTE: Don't allow user to control camera.
46 | // test.controls.update();
47 | test.udpateCameraPosition();
48 |
49 | requestAnimationFrame(animate);
50 | };
51 | animate();
52 |
53 | const onClick = (event) => {
54 | franticArchitect.acceptPhantomBlock();
55 | };
56 |
57 | const onKeyDown = (event) => {
58 | if (event.code === 'Space') {
59 | franticArchitect.acceptPhantomBlock();
60 | }
61 | };
62 |
63 | window.addEventListener('click', onClick);
64 | window.addEventListener('keydown', onKeyDown);
65 |
66 | return () => {
67 | window.removeEventListener('click', onClick);
68 | window.removeEventListener('keydown', onKeyDown);
69 | };
70 | }, []);
71 |
72 | return (
73 |
74 |
75 |
76 | );
77 | }
78 |
79 | export default App;
80 |
--------------------------------------------------------------------------------
/10-frantic-architect/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/10-frantic-architect/src/lib/SceneInit.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import Stats from 'three/examples/jsm/libs/stats.module';
3 |
4 | // NOTE: Don't allow user to control camera.
5 | // import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
6 |
7 | export default class SceneInit {
8 | constructor(canvasId) {
9 | // NOTE: Core components to initialize Three.js app.
10 | this.scene = undefined;
11 | this.camera = undefined;
12 | this.renderer = undefined;
13 | this.cameraRotationDepth = 12;
14 |
15 | // NOTE: Camera params;
16 | this.fov = 45;
17 | this.nearPlane = 1;
18 | this.farPlane = 1000;
19 | this.canvasId = canvasId;
20 |
21 | // NOTE: Additional components.
22 | this.clock = undefined;
23 | this.stats = undefined;
24 |
25 | // NOTE: Don't allow user to control camera.
26 | // this.controls = undefined;
27 |
28 | // NOTE: Lighting is basically required.
29 | this.ambientLight = undefined;
30 | this.directionalLight = undefined;
31 | }
32 |
33 | initialize() {
34 | this.scene = new THREE.Scene();
35 | this.camera = new THREE.PerspectiveCamera(
36 | this.fov,
37 | window.innerWidth / window.innerHeight,
38 | 1,
39 | 1000
40 | );
41 | this.camera.position.y = 4;
42 |
43 | // NOTE: Specify a canvas which is already created in the HTML.
44 | const canvas = document.getElementById(this.canvasId);
45 | this.renderer = new THREE.WebGLRenderer({
46 | canvas,
47 | // NOTE: Anti-aliasing smooths out the edges.
48 | antialias: true,
49 | });
50 | this.renderer.setSize(window.innerWidth, window.innerHeight);
51 | // this.renderer.shadowMap.enabled = true;
52 | document.body.appendChild(this.renderer.domElement);
53 |
54 | this.clock = new THREE.Clock();
55 | this.stats = Stats();
56 | document.body.appendChild(this.stats.dom);
57 |
58 | // NOTE: Don't allow user to control camera.
59 | // this.controls = new OrbitControls(this.camera, this.renderer.domElement);
60 |
61 | // ambient light which is for the whole scene
62 | this.ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
63 | this.ambientLight.castShadow = true;
64 | this.scene.add(this.ambientLight);
65 |
66 | // directional light - parallel sun rays
67 | this.directionalLight = new THREE.DirectionalLight(0xffffff, 1);
68 | // this.directionalLight.castShadow = true;
69 | this.directionalLight.position.set(0, 32, 64);
70 | this.scene.add(this.directionalLight);
71 |
72 | // if window resizes
73 | window.addEventListener('resize', () => this.onWindowResize(), false);
74 |
75 | // NOTE: Load space background.
76 | // this.loader = new THREE.TextureLoader();
77 | // this.scene.background = this.loader.load('./pics/space.jpeg');
78 |
79 | // NOTE: Declare uniforms to pass into glsl shaders.
80 | // this.uniforms = {
81 | // u_time: { type: 'f', value: 1.0 },
82 | // colorB: { type: 'vec3', value: new THREE.Color(0xfff000) },
83 | // colorA: { type: 'vec3', value: new THREE.Color(0xffffff) },
84 | // };
85 | }
86 |
87 | animate() {
88 | // NOTE: Window is implied.
89 | // requestAnimationFrame(this.animate.bind(this));
90 | window.requestAnimationFrame(this.animate.bind(this));
91 | this.render();
92 | this.stats.update();
93 |
94 | // NOTE: Don't allow user to control camera.
95 | // this.controls.update();
96 | }
97 |
98 | render() {
99 | // NOTE: Update uniform data on each render.
100 | // this.uniforms.u_time.value += this.clock.getDelta();
101 | this.renderer.render(this.scene, this.camera);
102 | }
103 |
104 | udpateCameraPosition() {
105 | const sinX = Math.sin(0.25 * this.clock.getElapsedTime());
106 | const cosZ = Math.cos(0.25 * this.clock.getElapsedTime());
107 | this.camera.position.x = this.cameraRotationDepth * sinX;
108 | this.camera.position.z = this.cameraRotationDepth * cosZ;
109 | this.camera.lookAt(new THREE.Vector3(0, 2, 0));
110 | }
111 |
112 | onWindowResize() {
113 | this.camera.aspect = window.innerWidth / window.innerHeight;
114 | this.camera.updateProjectionMatrix();
115 | this.renderer.setSize(window.innerWidth, window.innerHeight);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/10-frantic-architect/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import './index.css'
4 | import App from './App'
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | )
12 |
--------------------------------------------------------------------------------
/10-frantic-architect/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()]
7 | })
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 👾 Three.js Games
2 |
3 | Hello world! My internet name is Suboptimal and I am an Indian software engineer. This is the repo where I keep track of my Three.js game dev experiments. I also post the demos on [twitter](https://www.twitter.com/SuboptimalEng) so feel free to follow me there!
4 |
5 | All of these experiments are built with React.js + Three.js so running them locally is super easy. Here's an example of how you can run the `solar-system` animation:
6 |
7 | ```
8 | git clone https://github.com/SuboptimalEng/three-js-games.git
9 | cd three-js-games/solar-system/
10 | npm install
11 | npm run dev
12 | ```
13 |
14 | ## Snakes and Portals - [Demo](https://twitter.com/SuboptimalEng/status/1555563350423126017?s=20&t=6nL7HzevakKsCmC6ysqFPw)
15 |
16 | - The game is playable on my [website](https://suboptimaleng.github.io).
17 | - The code is available in this [repository](https://github.com/SuboptimalEng/suboptimaleng.github.io).
18 |
19 |
20 |
21 | ## Frantic Architect - [Demo](https://twitter.com/SuboptimalEng/status/1514638959439228932?s=20&t=RpkyLBOetRIdc2PSdgLzmQ)
22 |
23 | - This is a clone of Frantic Architect - a mobile game made by [Will Kwan](https://twitter.com/_willkwan).
24 |
25 |
26 |
27 |
28 | ## Retro Snake - [Demo](https://twitter.com/SuboptimalEng/status/1492319950849155073?s=20&t=vPsvcyCzeLn53K8Ogi8E5Q)
29 |
30 | - ["CRT Monitor"](https://sketchfab.com/3d-models/crt-monitor-e2dd2887a8904e4fa3d5a32e2935adb9) by [James.Harness](https://sketchfab.com/James.Harness) is licensed under [CC Attribution](https://creativecommons.org/licenses/by/4.0/).
31 |
32 |
33 |
34 |
35 | ## Rubik's Cube - [Demo](https://twitter.com/SuboptimalEng/status/1489659085238775817?s=20&t=0fMein5vltFc2_8Tso335g)
36 |
37 |
38 |
39 |
40 |
41 | ## Wordle - [Demo](https://twitter.com/SuboptimalEng/status/1486504856403824643?s=20&t=0tpf5oFjMqcWj6O-DB6txQ)
42 |
43 |
44 |
45 | ## Piano - [Demo](https://twitter.com/SuboptimalEng/status/1484201522951032833?s=20&t=0tpf5oFjMqcWj6O-DB6txQ)
46 |
47 | - Grand piano music by [Borja Morales (reality3d)](https://github.com/reality3d/3d-piano-player) is licensed under [MIT](https://github.com/reality3d/3d-piano-player/blob/master/LICENSE).
48 |
49 |
50 |
51 | ## Naruto's Rasengan - [Demo](https://twitter.com/SuboptimalEng/status/1471878925584322562?s=20&t=0tpf5oFjMqcWj6O-DB6txQ)
52 |
53 | - ["Hand Anatomy"](https://sketchfab.com/3d-models/hand-anatomy-ada8498be9754e9f90b2eecc1b4ef8c5) by [Caterina Zamai](https://www.artstation.com/zaccate) is licensed under [CC Attribution](https://creativecommons.org/licenses/by/4.0/).
54 |
55 |
56 |
57 | ## Audio Visualizer - [Demo](https://twitter.com/SuboptimalEng/status/1466441813867302918?s=20&t=0tpf5oFjMqcWj6O-DB6txQ)
58 |
59 |
60 |
61 |
62 | ## 3D Tic-Tac-Toe - [Demo](https://twitter.com/SuboptimalEng/status/1463510451338321931?s=20&t=0tpf5oFjMqcWj6O-DB6txQ)
63 |
64 |
65 |
66 |
67 | ## Tic-Tac-Toe - [Demo](https://twitter.com/SuboptimalEng/status/1461358957935484945?s=20&t=0tpf5oFjMqcWj6O-DB6txQ)
68 |
69 |
70 |
71 |
72 | ## Solar System - [Demo](https://twitter.com/SuboptimalEng/status/1458473664442142725?s=20&t=0tpf5oFjMqcWj6O-DB6txQ)
73 |
74 |
75 |
76 | ## License
77 |
78 | Shield: [![CC BY-NC-SA 4.0][cc-by-nc-sa-shield]][cc-by-nc-sa]
79 |
80 | This work is licensed under a
81 | [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License][cc-by-nc-sa].
82 |
83 | [![CC BY-NC-SA 4.0][cc-by-nc-sa-image]][cc-by-nc-sa]
84 |
85 | [cc-by-nc-sa]: http://creativecommons.org/licenses/by-nc-sa/4.0/
86 | [cc-by-nc-sa-image]: https://licensebuttons.net/l/by-nc-sa/4.0/88x31.png
87 | [cc-by-nc-sa-shield]: https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg
88 |
--------------------------------------------------------------------------------
/_demos/audio-visualizer-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/audio-visualizer-1.png
--------------------------------------------------------------------------------
/_demos/audio-visualizer-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/audio-visualizer-2.png
--------------------------------------------------------------------------------
/_demos/fa-debug-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/fa-debug-1.png
--------------------------------------------------------------------------------
/_demos/fa-debug-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/fa-debug-2.png
--------------------------------------------------------------------------------
/_demos/frantic-architect-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/frantic-architect-1.png
--------------------------------------------------------------------------------
/_demos/frantic-architect-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/frantic-architect-2.png
--------------------------------------------------------------------------------
/_demos/frantic-architect-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/frantic-architect-3.png
--------------------------------------------------------------------------------
/_demos/narutos-rasengan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/narutos-rasengan.png
--------------------------------------------------------------------------------
/_demos/piano.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/piano.png
--------------------------------------------------------------------------------
/_demos/rubiks-cube-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/rubiks-cube-1.png
--------------------------------------------------------------------------------
/_demos/rubiks-cube-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/rubiks-cube-2.png
--------------------------------------------------------------------------------
/_demos/snake-retro-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/snake-retro-1.png
--------------------------------------------------------------------------------
/_demos/snake-retro-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/snake-retro-2.png
--------------------------------------------------------------------------------
/_demos/snakes-and-portals.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/snakes-and-portals.png
--------------------------------------------------------------------------------
/_demos/solar-system.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/solar-system.png
--------------------------------------------------------------------------------
/_demos/tic-tac-toe-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/tic-tac-toe-1.png
--------------------------------------------------------------------------------
/_demos/tic-tac-toe-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/tic-tac-toe-2.png
--------------------------------------------------------------------------------
/_demos/tic-tac-toe-3d-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/tic-tac-toe-3d-1.png
--------------------------------------------------------------------------------
/_demos/tic-tac-toe-3d-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/tic-tac-toe-3d-2.png
--------------------------------------------------------------------------------
/_demos/wordle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_demos/wordle.png
--------------------------------------------------------------------------------
/_reddit/audio-visualizer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_reddit/audio-visualizer.png
--------------------------------------------------------------------------------
/_reddit/narutos-rasengan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_reddit/narutos-rasengan.png
--------------------------------------------------------------------------------
/_reddit/piano.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_reddit/piano.png
--------------------------------------------------------------------------------
/_reddit/rubiks-cube.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_reddit/rubiks-cube.png
--------------------------------------------------------------------------------
/_reddit/snake-retro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_reddit/snake-retro.png
--------------------------------------------------------------------------------
/_reddit/solar-system.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_reddit/solar-system.png
--------------------------------------------------------------------------------
/_reddit/tic-tac-toe-3d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_reddit/tic-tac-toe-3d.png
--------------------------------------------------------------------------------
/_reddit/tic-tac-toe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_reddit/tic-tac-toe.png
--------------------------------------------------------------------------------
/_reddit/wordle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuboptimalEng/three-js-games/b9a56f9885af5ad781859bf9d72cc37718235417/_reddit/wordle.png
--------------------------------------------------------------------------------
/_templates/README.md:
--------------------------------------------------------------------------------
1 | # 📦 Minimal Project Templates
2 |
3 | This folder contains templates for quickly running game dev experiments.
4 |
--------------------------------------------------------------------------------
/_templates/three-vite-react/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
--------------------------------------------------------------------------------
/_templates/three-vite-react/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | tabWidth: 2,
4 | printWidth: 80,
5 | singleQuote: true,
6 | trailingComma: "es5",
7 | arrowParens: "always",
8 | };
9 |
--------------------------------------------------------------------------------
/_templates/three-vite-react/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/_templates/three-vite-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "three-vite-react",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build",
7 | "preview": "vite preview"
8 | },
9 | "dependencies": {
10 | "react": "^17.0.2",
11 | "react-dom": "^17.0.2",
12 | "three": "^0.136.0"
13 | },
14 | "devDependencies": {
15 | "@vitejs/plugin-react": "^1.0.7",
16 | "vite": "^2.7.2"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/_templates/three-vite-react/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | import * as THREE from 'three';
4 |
5 | import SceneInit from './lib/SceneInit';
6 |
7 | function App() {
8 | useEffect(() => {
9 | const test = new SceneInit('myThreeJsCanvas');
10 | test.initScene();
11 | test.animate();
12 |
13 | const geometry = new THREE.BoxGeometry(16, 16, 16);
14 | const material = new THREE.MeshNormalMaterial();
15 | const mesh = new THREE.Mesh(geometry, material);
16 |
17 | test.scene.add(mesh);
18 | }, []);
19 | return (
20 |
21 |
22 |
23 | );
24 | }
25 |
26 | export default App;
27 |
--------------------------------------------------------------------------------
/_templates/three-vite-react/src/lib/SceneInit.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
3 | import Stats from 'three/examples/jsm/libs/stats.module';
4 |
5 | export default class SceneInit {
6 | constructor(canvasID, camera, scene, stats, controls, renderer, fov = 36) {
7 | this.fov = fov;
8 | this.scene = scene;
9 | this.stats = stats;
10 | this.camera = camera;
11 | this.controls = controls;
12 | this.renderer = renderer;
13 | this.canvasID = canvasID;
14 | }
15 |
16 | initScene() {
17 | this.camera = new THREE.PerspectiveCamera(
18 | this.fov,
19 | window.innerWidth / window.innerHeight,
20 | 1,
21 | 1000
22 | );
23 | this.camera.position.z = 196;
24 |
25 | this.clock = new THREE.Clock();
26 | this.scene = new THREE.Scene();
27 |
28 | // NOTE: Load space background.
29 | // this.loader = new THREE.TextureLoader();
30 | // this.scene.background = this.loader.load('./pics/space.jpeg');
31 |
32 | // NOTE: Declare uniforms to pass into glsl shaders.
33 | this.uniforms = {
34 | u_time: { type: 'f', value: 1.0 },
35 | colorB: { type: 'vec3', value: new THREE.Color(0xfff000) },
36 | colorA: { type: 'vec3', value: new THREE.Color(0xffffff) },
37 | };
38 |
39 | // specify a canvas which is already created in the HTML file and tagged by an id
40 | // aliasing enabled
41 | const canvas = document.getElementById(this.canvasID);
42 | this.renderer = new THREE.WebGLRenderer({
43 | canvas,
44 | antialias: true,
45 | });
46 |
47 | this.renderer.setSize(window.innerWidth, window.innerHeight);
48 | document.body.appendChild(this.renderer.domElement);
49 |
50 | this.controls = new OrbitControls(this.camera, this.renderer.domElement);
51 |
52 | this.stats = Stats();
53 | document.body.appendChild(this.stats.dom);
54 |
55 | // ambient light which is for the whole scene
56 | let ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
57 | ambientLight.castShadow = true;
58 | this.scene.add(ambientLight);
59 |
60 | // spot light which is illuminating the chart directly
61 | let spotLight = new THREE.SpotLight(0xffffff, 1);
62 | spotLight.castShadow = true;
63 | spotLight.position.set(0, 64, 32);
64 | this.scene.add(spotLight);
65 |
66 | // if window resizes
67 | window.addEventListener('resize', () => this.onWindowResize(), false);
68 | }
69 |
70 | animate() {
71 | // NOTE: Window is implied.
72 | // requestAnimationFrame(this.animate.bind(this));
73 | window.requestAnimationFrame(this.animate.bind(this));
74 | this.render();
75 | this.stats.update();
76 | this.controls.update();
77 | }
78 |
79 | render() {
80 | // NOTE: Update uniform data on each render.
81 | this.uniforms.u_time.value += this.clock.getDelta();
82 | this.renderer.render(this.scene, this.camera);
83 | }
84 |
85 | onWindowResize() {
86 | this.camera.aspect = window.innerWidth / window.innerHeight;
87 | this.camera.updateProjectionMatrix();
88 | this.renderer.setSize(window.innerWidth, window.innerHeight);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/_templates/three-vite-react/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(
6 |
7 |
8 | ,
9 | document.getElementById('root')
10 | );
11 |
--------------------------------------------------------------------------------
/_templates/three-vite-react/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()]
7 | })
8 |
--------------------------------------------------------------------------------