├── .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 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](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 | [![Deploy with Vercel](https://vercel.com/button)](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 | 3 | 4 | -------------------------------------------------------------------------------- /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 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](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 | [![Deploy with Vercel](https://vercel.com/button)](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 |
83 |
84 | 85 |
86 |
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 | 3 | 4 | -------------------------------------------------------------------------------- /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 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](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 | [![Deploy with Vercel](https://vercel.com/button)](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 | 3 | 4 | -------------------------------------------------------------------------------- /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 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](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 | [![Deploy with Vercel](https://vercel.com/button)](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 |
36 |
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 |
115 |
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 | 3 | 4 | -------------------------------------------------------------------------------- /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 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](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 | [![Deploy with Vercel](https://vercel.com/button)](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 | 3 | 4 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](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 | [![Deploy with Vercel](https://vercel.com/button)](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 | 3 | 4 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------