commit 2d21a06262a1af07662c7be50d5fa1b9adea60d9 Author: Florian Sylvain Date: Wed Apr 12 12:18:18 2023 +0200 Initial commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..82a8b08 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db4c6d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +dist +node_modules \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..4a5bda6 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "useTabs": true, + "semi": false, + "printWidth": 90 +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2290b81 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,619 @@ +{ + "name": "threejs-audio-starter", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "threejs-audio-starter", + "version": "0.0.0", + "dependencies": { + "lil-gui": "^0.17.0", + "three": "^0.148.0" + }, + "devDependencies": { + "vite": "^4.0.4" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.15.tgz", + "integrity": "sha512-sRSOVlLawAktpMvDyJIkdLI/c/kdRTOqo8t6ImVxg8yT7LQDUYV5Rp2FKeEosLr6ZCja9UjYAzyRSxGteSJPYg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.15.tgz", + "integrity": "sha512-0kOB6Y7Br3KDVgHeg8PRcvfLkq+AccreK///B4Z6fNZGr/tNHX0z2VywCc7PTeWp+bPvjA5WMvNXltHw5QjAIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.15.tgz", + "integrity": "sha512-MzDqnNajQZ63YkaUWVl9uuhcWyEyh69HGpMIrf+acR4otMkfLJ4sUCxqwbCyPGicE9dVlrysI3lMcDBjGiBBcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.15.tgz", + "integrity": "sha512-7siLjBc88Z4+6qkMDxPT2juf2e8SJxmsbNVKFY2ifWCDT72v5YJz9arlvBw5oB4W/e61H1+HDB/jnu8nNg0rLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.15.tgz", + "integrity": "sha512-NbImBas2rXwYI52BOKTW342Tm3LTeVlaOQ4QPZ7XuWNKiO226DisFk/RyPk3T0CKZkKMuU69yOvlapJEmax7cg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.15.tgz", + "integrity": "sha512-Xk9xMDjBVG6CfgoqlVczHAdJnCs0/oeFOspFap5NkYAmRCT2qTn1vJWA2f419iMtsHSLm+O8B6SLV/HlY5cYKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.15.tgz", + "integrity": "sha512-3TWAnnEOdclvb2pnfsTWtdwthPfOz7qAfcwDLcfZyGJwm1SRZIMOeB5FODVhnM93mFSPsHB9b/PmxNNbSnd0RQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.15.tgz", + "integrity": "sha512-MLTgiXWEMAMr8nmS9Gigx43zPRmEfeBfGCwxFQEMgJ5MC53QKajaclW6XDPjwJvhbebv+RzK05TQjvH3/aM4Xw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.15.tgz", + "integrity": "sha512-T0MVnYw9KT6b83/SqyznTs/3Jg2ODWrZfNccg11XjDehIved2oQfrX/wVuev9N936BpMRaTR9I1J0tdGgUgpJA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.15.tgz", + "integrity": "sha512-wp02sHs015T23zsQtU4Cj57WiteiuASHlD7rXjKUyAGYzlOKDAjqK6bk5dMi2QEl/KVOcsjwL36kD+WW7vJt8Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.15.tgz", + "integrity": "sha512-k7FsUJjGGSxwnBmMh8d7IbObWu+sF/qbwc+xKZkBe/lTAF16RqxRCnNHA7QTd3oS2AfGBAnHlXL67shV5bBThQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.15.tgz", + "integrity": "sha512-ZLWk6czDdog+Q9kE/Jfbilu24vEe/iW/Sj2d8EVsmiixQ1rM2RKH2n36qfxK4e8tVcaXkvuV3mU5zTZviE+NVQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.15.tgz", + "integrity": "sha512-mY6dPkIRAiFHRsGfOYZC8Q9rmr8vOBZBme0/j15zFUKM99d4ILY4WpOC7i/LqoY+RE7KaMaSfvY8CqjJtuO4xg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.15.tgz", + "integrity": "sha512-EcyUtxffdDtWjjwIH8sKzpDRLcVtqANooMNASO59y+xmqqRYBBM7xVLQhqF7nksIbm2yHABptoioS9RAbVMWVA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.15.tgz", + "integrity": "sha512-BuS6Jx/ezxFuHxgsfvz7T4g4YlVrmCmg7UAwboeyNNg0OzNzKsIZXpr3Sb/ZREDXWgt48RO4UQRDBxJN3B9Rbg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.15.tgz", + "integrity": "sha512-JsdS0EgEViwuKsw5tiJQo9UdQdUJYuB+Mf6HxtJSPN35vez1hlrNb1KajvKWF5Sa35j17+rW1ECEO9iNrIXbNg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.15.tgz", + "integrity": "sha512-R6fKjtUysYGym6uXf6qyNephVUQAGtf3n2RCsOST/neIwPqRWcnc3ogcielOd6pT+J0RDR1RGcy0ZY7d3uHVLA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.15.tgz", + "integrity": "sha512-mVD4PGc26b8PI60QaPUltYKeSX0wxuy0AltC+WCTFwvKCq2+OgLP4+fFd+hZXzO2xW1HPKcytZBdjqL6FQFa7w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.15.tgz", + "integrity": "sha512-U6tYPovOkw3459t2CBwGcFYfFRjivcJJc1WC8Q3funIwX8x4fP+R6xL/QuTPNGOblbq/EUDxj9GU+dWKX0oWlQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.15.tgz", + "integrity": "sha512-W+Z5F++wgKAleDABemiyXVnzXgvRFs+GVKThSI+mGgleLWluv0D7Diz4oQpgdpNzh4i2nNDzQtWbjJiqutRp6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.15.tgz", + "integrity": "sha512-Muz/+uGgheShKGqSVS1KsHtCyEzcdOn/W/Xbh6H91Etm+wiIfwZaBn1W58MeGtfI8WA961YMHFYTthBdQs4t+w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.15.tgz", + "integrity": "sha512-DjDa9ywLUUmjhV2Y9wUTIF+1XsmuFGvZoCmOWkli1XcNAh5t25cc7fgsCx4Zi/Uurep3TTLyDiKATgGEg61pkA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild": { + "version": "0.17.15", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.15.tgz", + "integrity": "sha512-LBUV2VsUIc/iD9ME75qhT4aJj0r75abCVS0jakhFzOtR7TQsqQA5w0tZ+KTKnwl3kXE0MhskNdHDh/I5aCR1Zw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.15", + "@esbuild/android-arm64": "0.17.15", + "@esbuild/android-x64": "0.17.15", + "@esbuild/darwin-arm64": "0.17.15", + "@esbuild/darwin-x64": "0.17.15", + "@esbuild/freebsd-arm64": "0.17.15", + "@esbuild/freebsd-x64": "0.17.15", + "@esbuild/linux-arm": "0.17.15", + "@esbuild/linux-arm64": "0.17.15", + "@esbuild/linux-ia32": "0.17.15", + "@esbuild/linux-loong64": "0.17.15", + "@esbuild/linux-mips64el": "0.17.15", + "@esbuild/linux-ppc64": "0.17.15", + "@esbuild/linux-riscv64": "0.17.15", + "@esbuild/linux-s390x": "0.17.15", + "@esbuild/linux-x64": "0.17.15", + "@esbuild/netbsd-x64": "0.17.15", + "@esbuild/openbsd-x64": "0.17.15", + "@esbuild/sunos-x64": "0.17.15", + "@esbuild/win32-arm64": "0.17.15", + "@esbuild/win32-ia32": "0.17.15", + "@esbuild/win32-x64": "0.17.15" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/lil-gui": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz", + "integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==" + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "3.20.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.20.2.tgz", + "integrity": "sha512-3zwkBQl7Ai7MFYQE0y1MeQ15+9jsi7XxfrqwTb/9EK8D9C9+//EBR4M+CuA1KODRaNbFez/lWxA5vhEGZp4MUg==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/three": { + "version": "0.148.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.148.0.tgz", + "integrity": "sha512-8uzVV+qhTPi0bOFs/3te3RW6hb3urL8jYEl6irjCWo/l6sr8MPNMcClFev/MMYeIxr0gmDcoXTy/8LXh/LXkfw==" + }, + "node_modules/vite": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==", + "dev": true, + "dependencies": { + "esbuild": "^0.17.5", + "postcss": "^8.4.21", + "resolve": "^1.22.1", + "rollup": "^3.18.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8bceb4f --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "threejs-audio-starter", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "devDependencies": { + "vite": "^4.0.4" + }, + "dependencies": { + "lil-gui": "^0.17.0", + "three": "^0.148.0" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..ee09a25 --- /dev/null +++ b/readme.md @@ -0,0 +1,14 @@ +## Setup +Download [Node.js](https://nodejs.org/en/download/). +Run this followed commands: + +``` bash +# Install dependencies (only the first time) +npm install + +# Run the local server at localhost:8080 +npm run dev + +# Build for production in the dist/ directory +npm run build +``` diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..8388977 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/Experience/.DS_Store b/src/Experience/.DS_Store new file mode 100644 index 0000000..ca477ee Binary files /dev/null and b/src/Experience/.DS_Store differ diff --git a/src/Experience/Camera.js b/src/Experience/Camera.js new file mode 100644 index 0000000..6868c2e --- /dev/null +++ b/src/Experience/Camera.js @@ -0,0 +1,41 @@ +import * as THREE from "three" +import Experience from "./Experience.js" +import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js" + +export default class Camera { + constructor() { + this.experience = new Experience() + this.sizes = this.experience.sizes + this.scene = this.experience.scene + this.canvas = this.experience.canvas + + this.setInstance() + // this.setControls() + } + + setInstance() { + this.instance = new THREE.PerspectiveCamera( + 35, + this.sizes.width / this.sizes.height, + 0.1, + 100 + ) + this.instance.position.set(0, 0, 20) + + this.scene.add(this.instance) + } + + setControls() { + this.controls = new OrbitControls(this.instance, this.canvas) + this.controls.enableDamping = true + } + + resize() { + this.instance.aspect = this.sizes.width / this.sizes.height + this.instance.updateProjectionMatrix() + } + + update() { + // this.controls.update() + } +} diff --git a/src/Experience/Experience.js b/src/Experience/Experience.js new file mode 100644 index 0000000..005de05 --- /dev/null +++ b/src/Experience/Experience.js @@ -0,0 +1,93 @@ +import * as THREE from "three" + +import Debug from "./Utils/Debug.js" +import Sizes from "./Utils/Sizes.js" +import Time from "./Utils/Time.js" +import Camera from "./Camera.js" +import Renderer from "./Renderer.js" +import World from "./World/World.js" +import Resources from "./Utils/Resources.js" +import AudioAnalyser from "./Utils/AudioAnalyser.js" +import Interface from "./Interface.js" + +import sources from "./sources.js" + +let instance = null + +export default class Experience { + constructor(_canvas) { + // Singleton + if (instance) { + return instance + } + instance = this + + // Global access + window.experience = this + + // Options + this.canvas = _canvas + + // Setup + this.debug = new Debug() + this.sizes = new Sizes() + this.time = new Time() + this.scene = new THREE.Scene() + this.resources = new Resources(sources) + this.interface = new Interface() + this.camera = new Camera() + this.renderer = new Renderer() + this.world = new World() + this.audio = new AudioAnalyser("audio/onemoretime.mp3") + + // Resize event + this.sizes.on("resize", () => { + this.resize() + }) + + // Time tick event + this.time.on("tick", () => { + this.update() + }) + } + + resize() { + this.camera.resize() + this.renderer.resize() + } + + update() { + this.camera.update() + this.world.update() + this.renderer.update() + this.audio.update() + } + + destroy() { + this.sizes.off("resize") + this.time.off("tick") + + // Traverse the whole scene + this.scene.traverse((child) => { + // Test if it's a mesh + if (child instanceof THREE.Mesh) { + child.geometry.dispose() + + // Loop through the material properties + for (const key in child.material) { + const value = child.material[key] + + // Test if there is a dispose function + if (value && typeof value.dispose === "function") { + value.dispose() + } + } + } + }) + + this.camera.controls.dispose() + this.renderer.instance.dispose() + + if (this.debug.active) this.debug.ui.destroy() + } +} diff --git a/src/Experience/Interface.js b/src/Experience/Interface.js new file mode 100644 index 0000000..8ab1101 --- /dev/null +++ b/src/Experience/Interface.js @@ -0,0 +1,27 @@ +import EventEmitter from './Utils/EventEmitter.js' +import Experience from './Experience.js' + +export default class Interface extends EventEmitter +{ + constructor() + { + super() + + this.experience = new Experience() + this.resources = this.experience.resources + + this.$loader = document.querySelector('.loader') + this.$startBtn = document.querySelector('.start-btn') + + // Resources loaded + this.resources.on('ready', () => { + this.$loader.classList.remove('is-active') + this.$startBtn.classList.add('is-active') + + this.$startBtn.addEventListener('click', () => { + this.trigger('startExperience') + this.$startBtn.classList.remove('is-active') + }) + }) + } +} \ No newline at end of file diff --git a/src/Experience/Renderer.js b/src/Experience/Renderer.js new file mode 100644 index 0000000..5b379f7 --- /dev/null +++ b/src/Experience/Renderer.js @@ -0,0 +1,44 @@ +import * as THREE from 'three' +import Experience from './Experience.js' + +export default class Renderer +{ + constructor() + { + this.experience = new Experience() + this.canvas = this.experience.canvas + this.sizes = this.experience.sizes + this.scene = this.experience.scene + this.camera = this.experience.camera + + this.setInstance() + } + + setInstance() + { + this.instance = new THREE.WebGLRenderer({ + canvas: this.canvas, + antialias: true + }) + this.instance.physicallyCorrectLights = true + this.instance.outputEncoding = THREE.sRGBEncoding + this.instance.toneMapping = THREE.CineonToneMapping + this.instance.toneMappingExposure = 1.75 + this.instance.shadowMap.enabled = true + this.instance.shadowMap.type = THREE.PCFSoftShadowMap + this.instance.setClearColor('#ffffff') + this.instance.setSize(this.sizes.width, this.sizes.height) + this.instance.setPixelRatio(Math.min(this.sizes.pixelRatio, 2)) + } + + resize() + { + this.instance.setSize(this.sizes.width, this.sizes.height) + this.instance.setPixelRatio(Math.min(this.sizes.pixelRatio, 2)) + } + + update() + { + this.instance.render(this.scene, this.camera.instance) + } +} \ No newline at end of file diff --git a/src/Experience/Utils/.DS_Store b/src/Experience/Utils/.DS_Store new file mode 100644 index 0000000..ce096b4 Binary files /dev/null and b/src/Experience/Utils/.DS_Store differ diff --git a/src/Experience/Utils/AudioAnalyser.js b/src/Experience/Utils/AudioAnalyser.js new file mode 100644 index 0000000..d10d9ea --- /dev/null +++ b/src/Experience/Utils/AudioAnalyser.js @@ -0,0 +1,119 @@ +import EventEmitter from "./EventEmitter.js" +import Experience from "../Experience.js" + +export default class AudioAnalyser extends EventEmitter { + constructor(audioPath, fftSize = 1024) { + super() + + this.experience = new Experience() + this.scene = this.experience.scene + this.resources = this.experience.resources + this.interface = this.experience.interface + + this.averageFrequencies = 0 + this.averageLowFrequencies = 0 + this.averageMidFrequencies = 0 + this.averageHighFrequencies = 0 + + this.interface.on("startExperience", () => { + this.audioContext = new AudioContext() + this.analyser = this.audioContext.createAnalyser() + this.analyser.fftSize = fftSize + this.frequencyData = new Uint8Array(this.analyser.frequencyBinCount) + this.timeData = new Uint8Array(this.analyser.fftSize) + + this.isPlaying = false + this.gainNode = this.audioContext.createGain() + this.gainNode.gain.value = 0.1 + + this.audio = new Audio(audioPath) + this.source = this.audioContext.createMediaElementSource(this.audio) + this.source.connect(this.analyser) + this.source.connect(this.gainNode) + this.gainNode.connect(this.audioContext.destination) + this.play() + }) + } + + play() { + if (this.isPlaying) return + + this.isPlaying = true + this.audio.play() + } + + pause() { + if (!this.isPlaying) return + + this.isPlaying = false + this.audio.pause() + } + + update() { + if (!this.isPlaying) return + + this.analyser.getByteFrequencyData(this.frequencyData) + this.analyser.getByteTimeDomainData(this.timeData) + + this.setAverageLowFrequencies() + this.setAverageMediumFrequencies() + this.setAverageHighFrequencies() + this.setAverageFrequencies() + } + + setVolume(gain) { + this.gainNode.gain.value = gain + } + + setAverageFrequencies() { + // on add tous les éléments de l'array entre eux + const total = this.frequencyData.reduce((acc, cur) => acc + cur) + // on fait la moyenne des fréquences + const average = total / this.frequencyData.length + + this.averageFrequencies = average / 255 + } + + setAverageLowFrequencies() { + // On divise l'array de toutes les frequences par 3 et on prend la première partie (les fréquences les plus hautes) + + const third = Math.round(this.frequencyData.length / 3) + const lowFrequencies = this.frequencyData.slice(0, third) + + // on add tous les éléments de l'array entre eux + const total = lowFrequencies.reduce((acc, cur) => acc + cur) + // on fait la moyenne des fréquences + const average = total / lowFrequencies.length + + this.averageLowFrequencies = average / 255 + } + + setAverageMediumFrequencies() { + // On divise l'array de toutes les frequences par 3 et on prend la deuxieme partie (les fréquences mid) + const third = Math.round(this.frequencyData.length / 3) + const mediumFrequencies = this.frequencyData.slice(third, third * 2) + + // on add tous les éléments de l'array entre eux + const total = mediumFrequencies.reduce((acc, cur) => acc + cur) + // on fait la moyenne des fréquences + const average = total / mediumFrequencies.length + + this.averageMidFrequencies = average / 255 + } + + setAverageHighFrequencies() { + // On divise l'array de toutes les frequences par 3 et on prend la troiseme partie (les fréquences hautes) + const third = Math.round(this.frequencyData.length / 3) + const highFrequencies = this.frequencyData.slice( + third * 2, + this.frequencyData.length - 1 + ) + + // on add tous les éléments de l'array entre eux + const total = highFrequencies.reduce((acc, cur) => acc + cur) + // on fait la moyenne des fréquences + const average = total / highFrequencies.length + + this.averageHighFrequencies = average / 255 + } +} diff --git a/src/Experience/Utils/Debug.js b/src/Experience/Utils/Debug.js new file mode 100644 index 0000000..cee3465 --- /dev/null +++ b/src/Experience/Utils/Debug.js @@ -0,0 +1,14 @@ +import * as dat from 'lil-gui' + +export default class Debug +{ + constructor() + { + this.active = window.location.hash === '#debug' + + if(this.active) + { + this.ui = new dat.GUI() + } + } +} \ No newline at end of file diff --git a/src/Experience/Utils/EventEmitter.js b/src/Experience/Utils/EventEmitter.js new file mode 100644 index 0000000..216b423 --- /dev/null +++ b/src/Experience/Utils/EventEmitter.js @@ -0,0 +1,197 @@ +export default class EventEmitter +{ + constructor() + { + this.callbacks = {} + this.callbacks.base = {} + } + + on(_names, callback) + { + // Errors + if(typeof _names === 'undefined' || _names === '') + { + console.warn('wrong names') + return false + } + + if(typeof callback === 'undefined') + { + console.warn('wrong callback') + return false + } + + // Resolve names + const names = this.resolveNames(_names) + + // Each name + names.forEach((_name) => + { + // Resolve name + const name = this.resolveName(_name) + + // Create namespace if not exist + if(!(this.callbacks[ name.namespace ] instanceof Object)) + this.callbacks[ name.namespace ] = {} + + // Create callback if not exist + if(!(this.callbacks[ name.namespace ][ name.value ] instanceof Array)) + this.callbacks[ name.namespace ][ name.value ] = [] + + // Add callback + this.callbacks[ name.namespace ][ name.value ].push(callback) + }) + + return this + } + + off(_names) + { + // Errors + if(typeof _names === 'undefined' || _names === '') + { + console.warn('wrong name') + return false + } + + // Resolve names + const names = this.resolveNames(_names) + + // Each name + names.forEach((_name) => + { + // Resolve name + const name = this.resolveName(_name) + + // Remove namespace + if(name.namespace !== 'base' && name.value === '') + { + delete this.callbacks[ name.namespace ] + } + + // Remove specific callback in namespace + else + { + // Default + if(name.namespace === 'base') + { + // Try to remove from each namespace + for(const namespace in this.callbacks) + { + if(this.callbacks[ namespace ] instanceof Object && this.callbacks[ namespace ][ name.value ] instanceof Array) + { + delete this.callbacks[ namespace ][ name.value ] + + // Remove namespace if empty + if(Object.keys(this.callbacks[ namespace ]).length === 0) + delete this.callbacks[ namespace ] + } + } + } + + // Specified namespace + else if(this.callbacks[ name.namespace ] instanceof Object && this.callbacks[ name.namespace ][ name.value ] instanceof Array) + { + delete this.callbacks[ name.namespace ][ name.value ] + + // Remove namespace if empty + if(Object.keys(this.callbacks[ name.namespace ]).length === 0) + delete this.callbacks[ name.namespace ] + } + } + }) + + return this + } + + trigger(_name, _args) + { + // Errors + if(typeof _name === 'undefined' || _name === '') + { + console.warn('wrong name') + return false + } + + let finalResult = null + let result = null + + // Default args + const args = !(_args instanceof Array) ? [] : _args + + // Resolve names (should on have one event) + let name = this.resolveNames(_name) + + // Resolve name + name = this.resolveName(name[ 0 ]) + + // Default namespace + if(name.namespace === 'base') + { + // Try to find callback in each namespace + for(const namespace in this.callbacks) + { + if(this.callbacks[ namespace ] instanceof Object && this.callbacks[ namespace ][ name.value ] instanceof Array) + { + this.callbacks[ namespace ][ name.value ].forEach(function(callback) + { + result = callback.apply(this, args) + + if(typeof finalResult === 'undefined') + { + finalResult = result + } + }) + } + } + } + + // Specified namespace + else if(this.callbacks[ name.namespace ] instanceof Object) + { + if(name.value === '') + { + console.warn('wrong name') + return this + } + + this.callbacks[ name.namespace ][ name.value ].forEach(function(callback) + { + result = callback.apply(this, args) + + if(typeof finalResult === 'undefined') + finalResult = result + }) + } + + return finalResult + } + + resolveNames(_names) + { + let names = _names + names = names.replace(/[^a-zA-Z0-9 ,/.]/g, '') + names = names.replace(/[,/]+/g, ' ') + names = names.split(' ') + + return names + } + + resolveName(name) + { + const newName = {} + const parts = name.split('.') + + newName.original = name + newName.value = parts[ 0 ] + newName.namespace = 'base' // Base namespace + + // Specified namespace + if(parts.length > 1 && parts[ 1 ] !== '') + { + newName.namespace = parts[ 1 ] + } + + return newName + } +} \ No newline at end of file diff --git a/src/Experience/Utils/Resources.js b/src/Experience/Utils/Resources.js new file mode 100644 index 0000000..2894a6d --- /dev/null +++ b/src/Experience/Utils/Resources.js @@ -0,0 +1,89 @@ +import * as THREE from 'three' +import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' +import EventEmitter from './EventEmitter.js' + +export default class Resources extends EventEmitter +{ + constructor(sources) + { + super() + + this.sources = sources + + this.items = {} + this.toLoad = this.sources.length + this.loaded = 0 + + this.setLoaders() + this.startLoading() + } + + setLoaders() + { + this.loaders = {} + this.loaders.audioLoader = new THREE.AudioLoader() + this.loaders.gltfLoader = new GLTFLoader() + this.loaders.textureLoader = new THREE.TextureLoader() + this.loaders.cubeTextureLoader = new THREE.CubeTextureLoader() + } + + startLoading() + { + // Load each source + for(const source of this.sources) + { + if(source.type === 'gltfModel') + { + this.loaders.gltfLoader.load( + source.path, + (file) => + { + this.sourceLoaded(source, file) + } + ) + } + else if (source.type === 'audio') + { + this.loaders.audioLoader.load( + source.path, + (file) => + { + this.sourceLoaded(source, file) + } + ) + } + else if(source.type === 'texture') + { + this.loaders.textureLoader.load( + source.path, + (file) => + { + this.sourceLoaded(source, file) + } + ) + } + else if(source.type === 'cubeTexture') + { + this.loaders.cubeTextureLoader.load( + source.path, + (file) => + { + this.sourceLoaded(source, file) + } + ) + } + } + } + + sourceLoaded(source, file) + { + this.items[source.name] = file + + this.loaded++ + + if(this.loaded === this.toLoad) + { + this.trigger('ready') + } + } +} \ No newline at end of file diff --git a/src/Experience/Utils/Sizes.js b/src/Experience/Utils/Sizes.js new file mode 100644 index 0000000..01f4b02 --- /dev/null +++ b/src/Experience/Utils/Sizes.js @@ -0,0 +1,24 @@ +import EventEmitter from './EventEmitter.js' + +export default class Sizes extends EventEmitter +{ + constructor() + { + super() + + // Setup + this.width = window.innerWidth + this.height = window.innerHeight + this.pixelRatio = Math.min(window.devicePixelRatio, 2) + + // Resize event + window.addEventListener('resize', () => + { + this.width = window.innerWidth + this.height = window.innerHeight + this.pixelRatio = Math.min(window.devicePixelRatio, 2) + + this.trigger('resize') + }) + } +} \ No newline at end of file diff --git a/src/Experience/Utils/Time.js b/src/Experience/Utils/Time.js new file mode 100644 index 0000000..139c1b5 --- /dev/null +++ b/src/Experience/Utils/Time.js @@ -0,0 +1,35 @@ +import EventEmitter from './EventEmitter.js' + +export default class Time extends EventEmitter +{ + constructor() + { + super() + + // Setup + this.start = Date.now() + this.current = this.start + this.elapsed = 0 + this.delta = 16 + + window.requestAnimationFrame(() => + { + this.tick() + }) + } + + tick() + { + const currentTime = Date.now() + this.delta = currentTime - this.current + this.current = currentTime + this.elapsed = this.current - this.start + + this.trigger('tick') + + window.requestAnimationFrame(() => + { + this.tick() + }) + } +} \ No newline at end of file diff --git a/src/Experience/World/.DS_Store b/src/Experience/World/.DS_Store new file mode 100644 index 0000000..9c9dd1c Binary files /dev/null and b/src/Experience/World/.DS_Store differ diff --git a/src/Experience/World/Cube.js b/src/Experience/World/Cube.js new file mode 100644 index 0000000..eac27d4 --- /dev/null +++ b/src/Experience/World/Cube.js @@ -0,0 +1,71 @@ +import * as THREE from "three" +import Experience from "../Experience.js" + +export default class Cube { + constructor(position) { + this.experience = new Experience() + this.scene = this.experience.scene + + this.initialPosition = { + x: position.x, + y: position.y, + z: position.z, + } + + this.setGeometry() + this.setMaterial() + this.setMesh() + + this.rotation = { + speed: { + x: Math.random() * 2 - 1, + y: Math.random() * 2 - 1, + z: Math.random() * 2 - 1, + }, + } + + this.randomDirection = { + x: Math.random() * 2 - 1, + y: Math.random() * 2 - 1, + } + } + + setGeometry() { + this.geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2) + } + + setMaterial() { + this.material = new THREE.MeshStandardMaterial({ + color: new THREE.Color( + Math.round(Math.random()), + Math.round(Math.random()), + Math.round(Math.random()) + ), + }) + } + + setMesh() { + this.mesh = new THREE.Mesh(this.geometry, this.material) + + this.mesh.position.x = this.initialPosition.x + this.mesh.position.y = this.initialPosition.y + this.mesh.position.z = this.initialPosition.z + + this.mesh.receiveShadow = true + this.mesh.castShadow = true + this.scene.add(this.mesh) + } + + update() { + this.mesh.rotation.x += this.rotation.speed.x + this.mesh.rotation.y += this.rotation.speed.y + this.mesh.rotation.z += this.rotation.speed.z + + this.mesh.position.x += this.randomDirection.x * 0.1 + this.mesh.position.y += this.randomDirection.y * 0.1 + } + + destroy() { + this.scene.remove(this.mesh) + } +} diff --git a/src/Experience/World/Environment.js b/src/Experience/World/Environment.js new file mode 100644 index 0000000..bdab038 --- /dev/null +++ b/src/Experience/World/Environment.js @@ -0,0 +1,94 @@ +import * as THREE from "three" +import Experience from "../Experience.js" + +export default class Environment { + constructor() { + this.experience = new Experience() + this.scene = this.experience.scene + this.resources = this.experience.resources + this.debug = this.experience.debug + + // Debug + if (this.debug.active) { + this.debugFolder = this.debug.ui.addFolder("environment") + } + + this.setSunLight() + this.setEnvironmentMap() + } + + setSunLight() { + this.sunLight = new THREE.DirectionalLight("#ffffff", 4) + this.sunLight.castShadow = true + this.sunLight.shadow.camera.far = 15 + this.sunLight.shadow.mapSize.set(1024, 1024) + this.sunLight.shadow.normalBias = 0.05 + this.sunLight.position.set(3.5, 2, -1.25) + this.scene.add(this.sunLight) + + // Debug + if (this.debug.active) { + this.debugFolder + .add(this.sunLight, "intensity") + .name("sunLightIntensity") + .min(0) + .max(10) + .step(0.001) + + this.debugFolder + .add(this.sunLight.position, "x") + .name("sunLightX") + .min(-5) + .max(5) + .step(0.001) + + this.debugFolder + .add(this.sunLight.position, "y") + .name("sunLightY") + .min(-5) + .max(5) + .step(0.001) + + this.debugFolder + .add(this.sunLight.position, "z") + .name("sunLightZ") + .min(-5) + .max(5) + .step(0.001) + } + } + + setEnvironmentMap() { + this.environmentMap = {} + this.environmentMap.intensity = 0.4 + this.environmentMap.texture = this.resources.items.environmentMapTexture + this.environmentMap.texture.encoding = THREE.sRGBEncoding + + this.scene.environment = this.environmentMap.texture + + this.environmentMap.updateMaterials = () => { + this.scene.traverse((child) => { + if ( + child instanceof THREE.Mesh && + child.material instanceof THREE.MeshStandardMaterial + ) { + child.material.envMap = this.environmentMap.texture + child.material.envMapIntensity = this.environmentMap.intensity + child.material.needsUpdate = true + } + }) + } + this.environmentMap.updateMaterials() + + // Debug + if (this.debug.active) { + this.debugFolder + .add(this.environmentMap, "intensity") + .name("envMapIntensity") + .min(0) + .max(4) + .step(0.001) + .onChange(this.environmentMap.updateMaterials) + } + } +} diff --git a/src/Experience/World/Sphere.js b/src/Experience/World/Sphere.js new file mode 100644 index 0000000..b832c3a --- /dev/null +++ b/src/Experience/World/Sphere.js @@ -0,0 +1,31 @@ +import * as THREE from "three" +import Experience from "../Experience.js" + +export default class Sphere { + constructor() { + this.experience = new Experience() + this.scene = this.experience.scene + this.resources = this.experience.resources + this.debug = this.experience.debug + + this.setSphere() + } + + setSphere() { + this.sphere = new THREE.Mesh( + new THREE.SphereGeometry(0.1, 16, 16), + new THREE.MeshStandardMaterial({ + color: "#ffffff", + }) + ) + this.sphere.receiveShadow = true + this.sphere.castShadow = true + this.scene.add(this.sphere) + } + + update() {} + + destroy() { + this.scene.remove(this.sphere) + } +} diff --git a/src/Experience/World/World.js b/src/Experience/World/World.js new file mode 100644 index 0000000..b8925f6 --- /dev/null +++ b/src/Experience/World/World.js @@ -0,0 +1,154 @@ +import * as THREE from "three" +import Experience from "../Experience.js" +import Environment from "./Environment.js" +import Cube from "./Cube.js" + +export default class World { + constructor() { + this.experience = new Experience() + this.scene = this.experience.scene + this.interface = this.experience.interface + + this.debug = this.experience.debug + + this.mouseTimeout = undefined + this.cubes = [] + + this.colorsPick = {} + + this.marioSprite = undefined + + this.downVolTimout = undefined + this.upVolTimout = undefined + + this.interface.on("startExperience", () => { + this.environment = new Environment() + this.scene.background = new THREE.Color(0x000000) + this.updateColorPick() + this.initMario() + + document + .querySelector("body") + .addEventListener("mousemove", (e) => this.onMouseMove(e)) + document + .querySelector("body") + .addEventListener("touchmove", (e) => this.onMouseMove(e)) + }) + } + + initMario() { + const spriteTexture = new THREE.TextureLoader().load( + `${import.meta.env.BASE_URL}/textures/mario.png` + ) + const spriteMaterial = new THREE.SpriteMaterial({ map: spriteTexture }) + this.marioSprite = new THREE.Sprite(spriteMaterial) + this.marioSprite.position.set(0, 0, 0) + this.scene.add(this.marioSprite) + } + + onMouseMove(event) { + if (!this.experience.audio) return + + const mousePosition = this.getMousePosition(event) + + this.upVolume() + this.spawnEphemeralCubes(mousePosition, 200) + this.scene.background = new THREE.Color(this.getBackgroundRandomColor()) + this.marioSprite.position.y = + Math.sin(this.experience.time.elapsed * 0.02) * 0.5 + 0.5 + + if (this.mouseTimeout) clearTimeout(this.mouseTimeout) + this.mouseTimeout = setTimeout(() => this.onMouseStop(), 50) + } + + onMouseStop() { + this.downVolume() + this.scene.background = new THREE.Color(0x000000) + this.marioSprite.position.y = 0 + } + + updateColorPick() { + this.colorsPick = { + r: Math.round(Math.random()), + g: Math.round(Math.random()), + b: Math.round(Math.random()), + } + setTimeout(() => this.updateColorPick(), 1000) + } + + getBackgroundRandomColor() { + return new THREE.Color( + this.colorsPick.r * this.experience.audio.averageMidFrequencies, + this.colorsPick.g * this.experience.audio.averageMidFrequencies, + this.colorsPick.b * this.experience.audio.averageMidFrequencies + ) + } + + upVolume() { + if (this.experience.audio.gainNode.gain.value >= 0.8) return + clearTimeout(this.downVolTimout) + + this.setVolume( + Math.round((this.experience.audio.gainNode.gain.value + 0.1) * 10) / 10 + ) + this.upVolTimout = setTimeout(() => { + this.upVolume() + }, 200) + } + + downVolume() { + if (this.experience.audio.gainNode.gain.value <= 0.2) return + clearTimeout(this.upVolTimout) + + this.setVolume( + Math.round((this.experience.audio.gainNode.gain.value - 0.1) * 10) / 10 + ) + this.downVolTimout = setTimeout(() => { + this.downVolume() + }, 200) + } + + setVolume(volume) { + this.experience.audio.gainNode.gain.value = volume + } + + getMousePosition(event) { + const client = event.touches ? event.touches[0] : event + + const raycaster = new THREE.Raycaster() + const mouse = { + x: (client.clientX / window.innerWidth) * 2 - 1, + y: -(client.clientY / window.innerHeight) * 2 + 1, + z: 0, + } + raycaster.setFromCamera(mouse, window.experience.camera.instance) + return { + x: raycaster.ray.direction.x * 20, + y: raycaster.ray.direction.y * 20, + z: 0, + } + } + + spawnEphemeralCubes(position, quantity) { + if (this.cubes.length > quantity) { + this.cubes[0].destroy() + this.cubes = this.cubes.slice(1) + } + + const cube = new Cube(position) + this.cubes.push(cube) + + setTimeout(() => { + cube.destroy() + this.cubes = this.cubes.filter((c) => c !== cube) + }, 1000) + } + + update() { + if (!this.environment) return + + this.cubes.forEach((cube) => { + cube.update() + }) + } +} diff --git a/src/Experience/sources.js b/src/Experience/sources.js new file mode 100644 index 0000000..82bbda8 --- /dev/null +++ b/src/Experience/sources.js @@ -0,0 +1,20 @@ +export default [ + { + name: 'environmentMapTexture', + type: 'cubeTexture', + path: + [ + 'textures/environmentMap/px.jpg', + 'textures/environmentMap/nx.jpg', + 'textures/environmentMap/py.jpg', + 'textures/environmentMap/ny.jpg', + 'textures/environmentMap/pz.jpg', + 'textures/environmentMap/nz.jpg' + ] + }, + { + name: 'music', + type: 'audio', + path: 'audio/onemoretime.mp3' + } +] \ No newline at end of file diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..ba0a8dd --- /dev/null +++ b/src/index.html @@ -0,0 +1,15 @@ + + + + + + Audio experience + + + +

Loading...

+

Start experience

+ + + + \ No newline at end of file diff --git a/src/script.js b/src/script.js new file mode 100644 index 0000000..c06716c --- /dev/null +++ b/src/script.js @@ -0,0 +1,3 @@ +import Experience from "./Experience/Experience.js" + +export const experience = new Experience(document.querySelector("canvas.webgl")) diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..97be706 --- /dev/null +++ b/src/style.css @@ -0,0 +1,49 @@ +* +{ + margin: 0; + padding: 0; +} + +html, +body +{ + overflow: hidden; +} + +.webgl +{ + position: fixed; + top: 0; + left: 0; + outline: none; +} + +.loader { + z-index: 3; + cursor: pointer; + position: absolute; + top: 50%; + left: 50%; + display: none; + transform: translate(-50%, -50%); + text-transform: uppercase; +} + +.loader.is-active { + display: block; +} + +.start-btn { + z-index: 2; + cursor: pointer; + position: absolute; + top: 50%; + left: 50%; + display: none; + transform: translate(-50%, -50%); + text-transform: uppercase; +} + +.start-btn.is-active { + display: block; +} diff --git a/static/.DS_Store b/static/.DS_Store new file mode 100644 index 0000000..8ed537c Binary files /dev/null and b/static/.DS_Store differ diff --git a/static/audio/.DS_Store b/static/audio/.DS_Store new file mode 100644 index 0000000..fde65af Binary files /dev/null and b/static/audio/.DS_Store differ diff --git a/static/audio/onemoretime.mp3 b/static/audio/onemoretime.mp3 new file mode 100644 index 0000000..222a5f1 Binary files /dev/null and b/static/audio/onemoretime.mp3 differ diff --git a/static/models/.gitkeep b/static/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/static/textures/environmentMap/nx.jpg b/static/textures/environmentMap/nx.jpg new file mode 100644 index 0000000..24f768e Binary files /dev/null and b/static/textures/environmentMap/nx.jpg differ diff --git a/static/textures/environmentMap/ny.jpg b/static/textures/environmentMap/ny.jpg new file mode 100644 index 0000000..e2e550a Binary files /dev/null and b/static/textures/environmentMap/ny.jpg differ diff --git a/static/textures/environmentMap/nz.jpg b/static/textures/environmentMap/nz.jpg new file mode 100644 index 0000000..10f3031 Binary files /dev/null and b/static/textures/environmentMap/nz.jpg differ diff --git a/static/textures/environmentMap/px.jpg b/static/textures/environmentMap/px.jpg new file mode 100644 index 0000000..42f7818 Binary files /dev/null and b/static/textures/environmentMap/px.jpg differ diff --git a/static/textures/environmentMap/py.jpg b/static/textures/environmentMap/py.jpg new file mode 100644 index 0000000..8e5589e Binary files /dev/null and b/static/textures/environmentMap/py.jpg differ diff --git a/static/textures/environmentMap/pz.jpg b/static/textures/environmentMap/pz.jpg new file mode 100644 index 0000000..c915ddc Binary files /dev/null and b/static/textures/environmentMap/pz.jpg differ diff --git a/static/textures/mario.png b/static/textures/mario.png new file mode 100644 index 0000000..b4c9ffc Binary files /dev/null and b/static/textures/mario.png differ diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..4088268 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,25 @@ +import { defineConfig } from "vite" + +const isCodeSandbox = "SANDBOX_URL" in process.env || "CODESANDBOX_HOST" in process.env + +export default defineConfig(({ command }) => { + const baseUrl = + command === "serve" ? "https://projet.floriansylvain.fr/Creative_Coding" : "" + + return { + define: { + BASE_URL: JSON.stringify(baseUrl), + }, + root: "src/", + publicDir: "../static/", + base: "/Creative_Coding/", + server: { + host: true, + open: !isCodeSandbox, // Open if it's not a CodeSandbox + }, + build: { + outDir: "../dist", + emptyOutDir: true, + }, + } +})