commit b18d4c090b984ad440016533f0286057c3f5a1f8 Author: Anuj K Date: Thu Aug 28 13:57:09 2025 +0530 first diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/index.html b/index.html new file mode 100644 index 0000000..7e248b6 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Young Pandas + + +
+ + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4c03d6f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1038 @@ +{ + "name": "young-pandas", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "young-pandas", + "version": "0.0.0", + "dependencies": { + "three": "^0.179.1" + }, + "devDependencies": { + "vite": "^7.1.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.49.0.tgz", + "integrity": "sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.49.0.tgz", + "integrity": "sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.49.0.tgz", + "integrity": "sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.49.0.tgz", + "integrity": "sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.49.0.tgz", + "integrity": "sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.49.0.tgz", + "integrity": "sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.49.0.tgz", + "integrity": "sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.49.0.tgz", + "integrity": "sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.49.0.tgz", + "integrity": "sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.49.0.tgz", + "integrity": "sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.49.0.tgz", + "integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.49.0.tgz", + "integrity": "sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.49.0.tgz", + "integrity": "sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.49.0.tgz", + "integrity": "sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.49.0.tgz", + "integrity": "sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.49.0.tgz", + "integrity": "sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.49.0.tgz", + "integrity": "sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.49.0.tgz", + "integrity": "sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.49.0.tgz", + "integrity": "sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.49.0.tgz", + "integrity": "sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.49.0.tgz", + "integrity": "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.49.0", + "@rollup/rollup-android-arm64": "4.49.0", + "@rollup/rollup-darwin-arm64": "4.49.0", + "@rollup/rollup-darwin-x64": "4.49.0", + "@rollup/rollup-freebsd-arm64": "4.49.0", + "@rollup/rollup-freebsd-x64": "4.49.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.49.0", + "@rollup/rollup-linux-arm-musleabihf": "4.49.0", + "@rollup/rollup-linux-arm64-gnu": "4.49.0", + "@rollup/rollup-linux-arm64-musl": "4.49.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.49.0", + "@rollup/rollup-linux-ppc64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-musl": "4.49.0", + "@rollup/rollup-linux-s390x-gnu": "4.49.0", + "@rollup/rollup-linux-x64-gnu": "4.49.0", + "@rollup/rollup-linux-x64-musl": "4.49.0", + "@rollup/rollup-win32-arm64-msvc": "4.49.0", + "@rollup/rollup-win32-ia32-msvc": "4.49.0", + "@rollup/rollup-win32-x64-msvc": "4.49.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/three": { + "version": "0.179.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.179.1.tgz", + "integrity": "sha512-5y/elSIQbrvKOISxpwXCR4sQqHtGiOI+MKLc3SsBdDXA2hz3Mdp3X59aUp8DyybMa34aeBwbFTpdoLJaUDEWSw==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", + "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f742ff1 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "young-pandas", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^7.1.2" + }, + "dependencies": { + "three": "^0.179.1" + } +} diff --git a/public/agility.glb b/public/agility.glb new file mode 100644 index 0000000..6bafa9f Binary files /dev/null and b/public/agility.glb differ diff --git a/public/agility.html b/public/agility.html new file mode 100644 index 0000000..e95732e --- /dev/null +++ b/public/agility.html @@ -0,0 +1,245 @@ + + + + + + 3D Model Viewer + + + + + + + + diff --git a/public/innovation.glb b/public/innovation.glb new file mode 100644 index 0000000..673430c Binary files /dev/null and b/public/innovation.glb differ diff --git a/public/shader-flash.webm b/public/shader-flash.webm new file mode 100644 index 0000000..c96afed Binary files /dev/null and b/public/shader-flash.webm differ diff --git a/public/storytelling.glb b/public/storytelling.glb new file mode 100644 index 0000000..a1e7a8d Binary files /dev/null and b/public/storytelling.glb differ diff --git a/public/storytelling.html b/public/storytelling.html new file mode 100644 index 0000000..ff6badb --- /dev/null +++ b/public/storytelling.html @@ -0,0 +1,249 @@ + + + + + + 3D Model Viewer + + + + + + + + diff --git a/src/innovation.js b/src/innovation.js new file mode 100644 index 0000000..b07b148 --- /dev/null +++ b/src/innovation.js @@ -0,0 +1,341 @@ +import './style.css' + +import * as THREE from 'three'; +import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; +import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; +import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; +import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js'; +import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; +import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; +import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; + +// Scene setup +const scene = new THREE.Scene(); +const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); +camera.setFocalLength(50); + +const raycaster = new THREE.Raycaster(); +const mouse = new THREE.Vector2(); +let isTwisting = false; +let twistProgress = 0; +const twistSpeed = 0.05; // Adjust speed +const twistStrength = 0.3; // Adjust strength +let scrollCount = 0; +const scrollThreshold = 20; // Number of scroll events to trigger the animation + + +// Renderer setup +const renderer = new THREE.WebGLRenderer({ antialias: true }); + +renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); +renderer.setSize(window.innerWidth, window.innerHeight); +renderer.setClearColor(0x000000); +renderer.shadowMap.enabled = true; +renderer.shadowMap.type = THREE.PCFSoftShadowMap; +renderer.toneMapping = THREE.ACESFilmicToneMapping; +renderer.toneMappingExposure = 1.2; +renderer.outputColorSpace = THREE.SRGBColorSpace; +renderer.physicallyCorrectLights = true; + +document.body.appendChild(renderer.domElement); + +// Post-processing: Bloom +const composer = new EffectComposer(renderer); +const renderPass = new RenderPass(scene, camera); +composer.addPass(renderPass); + +const bloomPass = new UnrealBloomPass( + new THREE.Vector2(window.innerWidth, window.innerHeight), + 1.0, // strength + 0.45, // radius + 0.85 // threshold +); +composer.addPass(bloomPass); + +// Video texture for emissive "screen"-like effect on orange material +const video = document.createElement('video'); +video.src = '/shader-flash.webm'; +video.muted = true; +video.loop = true; +video.playsInline = true; +video.autoplay = true; +video.preload = 'auto'; + +const videoTexture = new THREE.VideoTexture(video); +videoTexture.colorSpace = THREE.SRGBColorSpace; +videoTexture.generateMipmaps = false; +videoTexture.minFilter = THREE.LinearFilter; +videoTexture.magFilter = THREE.LinearFilter; + +// Ensure autoplay starts (muted autoplay is commonly allowed) +video.play().catch(() => {}); + +// Local procedural environment for better PBR response (no network) +const pmrem = new THREE.PMREMGenerator(renderer); +const roomEnv = new RoomEnvironment(); +scene.environment = pmrem.fromScene(roomEnv).texture; +pmrem.dispose(); +roomEnv.dispose(); +scene.environment = null; // This will make the renderer's clear color visible again + +// Lighting is authored below. + +// Lighting +const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); +scene.add(ambientLight); + +const hemiLight = new THREE.HemisphereLight(0xffffff, 0x666666, 1.5); +hemiLight.position.set(0, 20, 0); +scene.add(hemiLight); + +// // Key light (main directional) - angled to avoid direct reflection +// const keyLight = new THREE.DirectionalLight(0xffffff, 2.0); +// keyLight.position.set(12, 8, 8); +// keyLight.castShadow = true; +// keyLight.shadow.mapSize.width = 2048; +// keyLight.shadow.mapSize.height = 2048; +// scene.add(keyLight); + +// Fill light (opposite side) - angled +const fillLight = new THREE.DirectionalLight(0xffffff, 1.2); +fillLight.position.set(-12, 6, -8); +scene.add(fillLight); + +// Top light - angled to avoid direct downward reflection +const topLight = new THREE.DirectionalLight(0xffffff, 1.5); +topLight.position.set(5, 15, 5); +scene.add(topLight); + +// Bottom light - angled upward +const bottomLight = new THREE.DirectionalLight(0xffffff, 0.8); +bottomLight.position.set(-3, -8, 3); +scene.add(bottomLight); + +// Side lights for even illumination - angled +const leftLight = new THREE.DirectionalLight(0xffffff, 1.0); +leftLight.position.set(-12, 2, 5); +scene.add(leftLight); + +const rightLight = new THREE.DirectionalLight(0xffffff, 1.0); +rightLight.position.set(12, 2, -5); +scene.add(rightLight); + +// Front and back lights - angled to avoid direct camera reflection +const frontLight = new THREE.DirectionalLight(0xffffff, 0.8); +frontLight.position.set(8, 4, 12); +scene.add(frontLight); + +const backLight = new THREE.DirectionalLight(0xffffff, 0.8); +backLight.position.set(-8, 4, -12); +scene.add(backLight); + +// Reduced camera light +const cameraLight = new THREE.PointLight(0xffffff, 0.8, 0, 2); +camera.add(cameraLight); +scene.add(camera); + +// Controls +const controls = new OrbitControls(camera, renderer.domElement); +controls.enableDamping = true; +controls.dampingFactor = 0.25; + +const loader = new GLTFLoader(); +const dracoLoader = new DRACOLoader(); +dracoLoader.setDecoderPath('node_modules/three/examples/jsm/libs/draco/'); +loader.setDRACOLoader(dracoLoader); +let mixer = null; + +loader.load('/innovation.glb', (gltf) => { + const model = gltf.scene; + scene.add(model); + + // --- Define and Apply Materials --- + const glassMaterial = new THREE.MeshPhysicalMaterial({ + color: 0xffffff, + metalness: 0.2, + roughness: 0.05, + transmission: 1, + ior: 2, + thickness: 2, + clearcoat: 1.0, + clearcoatRoughness: 0.1, + attenuationColor: new THREE.Color(0xffffff), + attenuationDistance: 0.8, + envMapIntensity: 0, + specularIntensity: 1.0, + specularColor: new THREE.Color(0x000000), + transparent: true, + depthWrite: false, + alphaTest: 0 + }); + + const lightOrangeMaterial = new THREE.MeshStandardMaterial({ + color: 0xff8600, metalness: 0.05, roughness: 0.4, + envMapIntensity: 0, emissive: new THREE.Color(0xffad47), + emissiveMap: videoTexture, emissiveIntensity: 2.25 + }); + + const orangeMeshes = ['dblsc', 'ec', 'gemini', 'infinity', 'star', 'dpd']; + const targetGlassNames = ['Cube.alt90.df']; + const sanitize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, ''); + const nameMatches = (name, targets) => { + const clean = sanitize(name); + return targets.some((t) => { + const ct = sanitize(t); + return clean === ct || clean.includes(ct) || ct.includes(clean); + }); + }; + + model.traverse((object) => { + if (object.isMesh) { + object.castShadow = true; + object.receiveShadow = true; + if (nameMatches(object.name, targetGlassNames)) { + // Create outer glass shell + object.material = glassMaterial.clone(); + object.material.side = THREE.DoubleSide; + object.material.depthWrite = false; + object.renderOrder = 2; // Render outer glass last + + // Create inner glass shell for better depth perception + const innerShell = object.clone(); + innerShell.material = glassMaterial.clone(); + innerShell.material.side = THREE.DoubleSide; + innerShell.material.depthWrite = false; + innerShell.material.thickness = 4; // Thinner inner layer + innerShell.material.transmission = 0.8; // More transparent inner layer + innerShell.renderOrder = 1; // Render inner glass before outer + + // Scale inner shell slightly smaller + innerShell.scale.multiplyScalar(0.95); + object.parent.add(innerShell); + + } else if (nameMatches(object.name, orangeMeshes)) { + object.material = lightOrangeMaterial.clone(); + object.renderOrder = 0; // Render orange objects first + } + } + }); + + // Compute bounds for camera framing + const box = new THREE.Box3().setFromObject(model); + const size = box.getSize(new THREE.Vector3()); + const center = box.getCenter(new THREE.Vector3()); + + // Set up animations + if (gltf.animations && gltf.animations.length > 0) { + mixer = new THREE.AnimationMixer(model); + gltf.animations.forEach((clip) => { + mixer.clipAction(clip).play(); + }); + mixer.timeScale = 3.0; + } + + // Position camera + const maxDim = Math.max(size.x, size.y, size.z); + camera.position.set(center.x, center.y, center.z + maxDim * 2); + controls.target.copy(center); + controls.update(); +}, undefined, (error) => { + console.error('Error loading model:', error); +}); +const clock = new THREE.Clock(); + +function onMouseScroll(event) { + // Only count scrolls if the animation is not already running + if (!isTwisting) { + // You can check event.deltaY to determine scroll direction + if (event.deltaY !== 0) { + scrollCount++; + console.log(`Scroll count: ${scrollCount}`); // For debugging + } + + if (scrollCount >= scrollThreshold) { + isTwisting = true; + twistProgress = 0; + scrollCount = 0; // Reset the counter + } + } +} + +function twistMesh(mesh, progress) { + if (!mesh || !mesh.geometry || !mesh.geometry.attributes.position) { + return; + } + + const positions = mesh.geometry.attributes.position; + + // Store original positions on the first run + if (!mesh.geometry.userData.originalPositions) { + mesh.geometry.userData.originalPositions = new Float32Array(positions.array); + + // Also store bounding box data + const box = new THREE.Box3().setFromObject(mesh); + mesh.geometry.userData.bounds = { + size: box.getSize(new THREE.Vector3()), + center: box.getCenter(new THREE.Vector3()) + }; + } + + const original = mesh.geometry.userData.originalPositions; + const { size, center } = mesh.geometry.userData.bounds; + const totalHeight = size.y; // Use Y-size for the twist axis + + for (let i = 0; i < positions.count; i++) { + const x = original[i * 3]; + const y = original[i * 3 + 1]; + const z = original[i * 3 + 2]; + + // Normalize the y-position from 0 to 1 based on the mesh's height + const normalizedY = (y - center.y + totalHeight / 2) / totalHeight; + + // Calculate the twist angle based on normalized y and progress + const twistAngle = normalizedY * progress * twistStrength * 2 * Math.PI; + + // Apply rotation to the X and Z coordinates + positions.setX(i, x * Math.cos(twistAngle) - z * Math.sin(twistAngle)); + positions.setY(i, y); // Y remains unchanged as it's the axis of rotation + positions.setZ(i, x * Math.sin(twistAngle) + z * Math.cos(twistAngle)); + } + + positions.needsUpdate = true; + mesh.geometry.computeVertexNormals(); +} + +// Attach the click event listener +window.addEventListener('wheel', onMouseScroll, {passive: true}); + +function animate() { + requestAnimationFrame(animate); + + const delta = clock.getDelta(); + if (mixer) mixer.update(delta); + + controls.update(); + + // The main loop for the twisting animation + if (isTwisting) { + twistProgress += twistSpeed; + if (twistProgress > 1.0) { + twistProgress = 1.0; + isTwisting = false; + } + + // Traverse the entire scene to find all meshes to twist + scene.traverse((object) => { + if (object.isMesh) { + twistMesh(object, twistProgress); + } + }); + } + + composer.render(); +} +animate(); + +window.addEventListener('resize', () => { + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + renderer.setSize(window.innerWidth, window.innerHeight); + composer.setSize(window.innerWidth, window.innerHeight); +}); diff --git a/src/main copy.js b/src/main copy.js new file mode 100644 index 0000000..3eb854e --- /dev/null +++ b/src/main copy.js @@ -0,0 +1,643 @@ +import './style.css' + +import * as THREE from 'three'; +import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; +import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; +import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; +import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js'; +import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; +import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; +import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; + +// Scene setup +const scene = new THREE.Scene(); +const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); +camera.setFocalLength(50); + +const raycaster = new THREE.Raycaster(); +const mouse = new THREE.Vector2(); + +// Transition state management +let currentScene = 0; // 0: innovation, 1: agility, 2: storytelling +let isTransitioning = false; +let isTwisting = false; +let twistProgress = 0; +const twistSpeed = 0.02; // Easily adjustable twist speed +const twistStrength = 0.3; +const fadeSpeed = 1; // Easily adjustable fade speed +const transitionDuration = 1; // Easily adjustable transition duration (seconds) +let scrollCount = 0; +const scrollThreshold = 10; // Changed to 10 as requested +let transitionStartTime = 0; + +// Scene objects +let currentModel = null; +let nextModel = null; +let mixer = null; +let nextMixer = null; +let autoRotationAngle = 0; + +// Renderer setup +const renderer = new THREE.WebGLRenderer({ antialias: true }); + +renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); +renderer.setSize(window.innerWidth, window.innerHeight); +renderer.setClearColor(0x000000); +renderer.shadowMap.enabled = true; +renderer.shadowMap.type = THREE.PCFSoftShadowMap; +renderer.toneMapping = THREE.ACESFilmicToneMapping; +renderer.toneMappingExposure = 1.2; +renderer.outputColorSpace = THREE.SRGBColorSpace; +renderer.physicallyCorrectLights = true; + +document.body.appendChild(renderer.domElement); + +// Post-processing: Bloom +const composer = new EffectComposer(renderer); +const renderPass = new RenderPass(scene, camera); +composer.addPass(renderPass); + +const bloomPass = new UnrealBloomPass( + new THREE.Vector2(window.innerWidth, window.innerHeight), + 1.0, // strength + 0.45, // radius + 0.85 // threshold +); +composer.addPass(bloomPass); + +// Video texture for emissive "screen"-like effect on orange material +const video = document.createElement('video'); +video.src = '/shader-flash.webm'; +video.muted = true; +video.loop = true; +video.playsInline = true; +video.autoplay = true; +video.preload = 'auto'; + +const videoTexture = new THREE.VideoTexture(video); +videoTexture.colorSpace = THREE.SRGBColorSpace; +videoTexture.generateMipmaps = false; +videoTexture.minFilter = THREE.LinearFilter; +videoTexture.magFilter = THREE.LinearFilter; + +// Ensure autoplay starts (muted autoplay is commonly allowed) +video.play().catch(() => {}); + +// Local procedural environment for better PBR response (no network) +const pmrem = new THREE.PMREMGenerator(renderer); +const roomEnv = new RoomEnvironment(); +scene.environment = pmrem.fromScene(roomEnv).texture; +pmrem.dispose(); +roomEnv.dispose(); +scene.environment = null; // This will make the renderer's clear color visible again + +// Consistent Lighting Setup +const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); +scene.add(ambientLight); + +const hemiLight = new THREE.HemisphereLight(0xffffff, 0x666666, 1.5); +hemiLight.position.set(0, 20, 0); +scene.add(hemiLight); + +const fillLight = new THREE.DirectionalLight(0xffffff, 1.2); +fillLight.position.set(-12, 6, -8); +scene.add(fillLight); + +const topLight = new THREE.DirectionalLight(0xffffff, 1.5); +topLight.position.set(5, 15, 5); +scene.add(topLight); + +const bottomLight = new THREE.DirectionalLight(0xffffff, 0.8); +bottomLight.position.set(-3, -8, 3); +scene.add(bottomLight); + +const leftLight = new THREE.DirectionalLight(0xffffff, 1.0); +leftLight.position.set(-12, 2, 5); +scene.add(leftLight); + +const rightLight = new THREE.DirectionalLight(0xffffff, 1.0); +rightLight.position.set(12, 2, -5); +scene.add(rightLight); + +const frontLight = new THREE.DirectionalLight(0xffffff, 0.8); +frontLight.position.set(8, 4, 12); +scene.add(frontLight); + +const backLight = new THREE.DirectionalLight(0xffffff, 0.8); +backLight.position.set(-8, 4, -12); +scene.add(backLight); + +const cameraLight = new THREE.PointLight(0xffffff, 0.8, 0, 2); +camera.add(cameraLight); +scene.add(camera); + +// Controls +const controls = new OrbitControls(camera, renderer.domElement); +controls.enableDamping = true; +controls.dampingFactor = 0.25; + +// Material definitions +// Clear thick glass for innovation +const innovationGlassMaterial = new THREE.MeshPhysicalMaterial({ + color: 0xffffff, + metalness: 0.2, + roughness: 0.05, + transmission: 1, + ior: 2, + thickness: 2, + clearcoat: 1.0, + clearcoatRoughness: 0.1, + attenuationColor: new THREE.Color(0xffffff), + attenuationDistance: 0.8, + envMapIntensity: 0, + specularIntensity: 1.0, + specularColor: new THREE.Color(0x000000), + transparent: true, + depthWrite: false, + alphaTest: 0 +}); + +// Slightly frosted glass for agility and storytelling +const frostedGlassMaterial = new THREE.MeshPhysicalMaterial({ + color: 0xffffff, + metalness: 0.0, + roughness: 0.25, + transmission: 1.0, + ior: 1.5, + thickness: 2.0, + clearcoat: 0.75, + clearcoatRoughness: 0.25, + attenuationColor: new THREE.Color(0xffffff), + attenuationDistance: 1.5, + envMapIntensity: 1.25, + specularIntensity: 1.0, + specularColor: new THREE.Color(0xffffff), + transparent: true, + depthWrite: false, + side: THREE.DoubleSide +}); + +// Orange material with video shader for innovation +const lightOrangeMaterial = new THREE.MeshStandardMaterial({ + color: 0xff8600, + metalness: 0.05, + roughness: 0.4, + envMapIntensity: 0, + emissive: new THREE.Color(0xffad47), + emissiveMap: videoTexture, + emissiveIntensity: 2.25 +}); + +const loader = new GLTFLoader(); +const dracoLoader = new DRACOLoader(); +dracoLoader.setDecoderPath('node_modules/three/examples/jsm/libs/draco/'); +loader.setDRACOLoader(dracoLoader); + +// Apply materials based on model type +function applyMaterials(model, modelType) { + console.log(`=== Material Assignment Debug for ${modelType} ===`); + let meshCount = 0; + + model.traverse((object) => { + if (object.isMesh) { + meshCount++; + console.log(`Found mesh: "${object.name}"`); + + const previousMaterial = object.material; + object.castShadow = true; + object.receiveShadow = true; + + if (modelType === 'innovation') { + // Innovation-specific material logic + const orangeMeshes = ['dblsc', 'ec', 'gemini', 'infinity', 'star', 'dpd']; + const targetGlassNames = ['Cube.alt90.df']; + const sanitize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, ''); + const nameMatches = (name, targets) => { + const clean = sanitize(name); + return targets.some((t) => { + const ct = sanitize(t); + return clean === ct || clean.includes(ct) || ct.includes(clean); + }); + }; + + if (nameMatches(object.name, targetGlassNames)) { + // Create outer glass shell with innovation-specific material + object.material = innovationGlassMaterial.clone(); + object.material.side = THREE.DoubleSide; + object.material.depthWrite = false; + object.renderOrder = 2; + + // Create inner glass shell + const innerShell = object.clone(); + innerShell.material = innovationGlassMaterial.clone(); + innerShell.material.side = THREE.DoubleSide; + innerShell.material.depthWrite = false; + innerShell.material.thickness = 4; + innerShell.material.transmission = 0.8; + innerShell.renderOrder = 1; + innerShell.scale.multiplyScalar(0.95); + object.parent.add(innerShell); + + } else if (nameMatches(object.name, orangeMeshes)) { + object.material = lightOrangeMaterial.clone(); + object.renderOrder = 0; + } + } else { + // Agility and Storytelling use frosted glass material for all meshes + if (object.name.startsWith('base')) { + console.log(` → Applying frosted glass material to "${object.name}"`); + object.material = frostedGlassMaterial.clone(); + } else { + console.log(` → Applying frosted glass material (fallback) to "${object.name}"`); + object.material = frostedGlassMaterial.clone(); + } + } + + object.material.needsUpdate = true; + + // Cleanup previous materials + if (Array.isArray(previousMaterial)) { + previousMaterial.forEach((mat) => mat && mat.dispose && mat.dispose()); + } else if (previousMaterial && previousMaterial.dispose) { + previousMaterial.dispose(); + } + } + }); + + console.log(`Total meshes processed: ${meshCount}`); + console.log(`=== End Material Assignment Debug for ${modelType} ===`); +} + +// Center and frame model with camera +function centerAndFrameModel(model, targetCamera = camera) { + const box = new THREE.Box3().setFromObject(model); + const center = box.getCenter(new THREE.Vector3()); + model.position.sub(center); + model.updateMatrixWorld(true); + + const size = box.getSize(new THREE.Vector3()); + const maxDim = Math.max(size.x, size.y, size.z); + + // Only set camera position if it's not already positioned (avoid reset during transitions) + if (!isTransitioning) { + targetCamera.position.set(0, 0, maxDim * 2); + controls.target.set(0, 0, 0); + controls.update(); + } +} + +// Setup animations based on model type +function setupAnimations(model, gltf, modelType) { + if (gltf.animations && gltf.animations.length > 0) { + const animMixer = new THREE.AnimationMixer(model); + + gltf.animations.forEach((clip) => { + const action = animMixer.clipAction(clip); + + if (modelType === 'innovation') { + // PingPong loop for innovation + action.loop = THREE.LoopPingPong; + action.play(); + } else if (modelType === 'agility') { + // Regular loop for agility + action.loop = THREE.LoopRepeat; + action.play(); + } else if (modelType === 'storytelling') { + // Play once for storytelling + action.loop = THREE.LoopOnce; + action.clampWhenFinished = true; + action.play(); + } + }); + + if (modelType === 'innovation') { + animMixer.timeScale = 3.0; // Keep existing timeScale for innovation + } + + return animMixer; + } + return null; +} + +// Load model function +function loadModel(filename, modelType, onLoadCallback) { + loader.load(`/${filename}`, (gltf) => { + const model = gltf.scene; + + // Apply materials + applyMaterials(model, modelType); + + // Setup animations + const animMixer = setupAnimations(model, gltf, modelType); + + // Center and frame model + centerAndFrameModel(model); + + if (onLoadCallback) { + onLoadCallback(model, animMixer); + } + }, undefined, (error) => { + console.error(`Error loading ${filename}:`, error); + }); +} + +// Load initial innovation model +loadModel('innovation.glb', 'innovation', (model, animMixer) => { + currentModel = model; + mixer = animMixer; + scene.add(currentModel); +}); + +// Twist animation function +function twistMesh(mesh, progress) { + if (!mesh || !mesh.geometry || !mesh.geometry.attributes.position) { + return; + } + + const positions = mesh.geometry.attributes.position; + + // Store original positions on the first run + if (!mesh.geometry.userData.originalPositions) { + mesh.geometry.userData.originalPositions = new Float32Array(positions.array); + + // Also store bounding box data + const box = new THREE.Box3().setFromObject(mesh); + mesh.geometry.userData.bounds = { + size: box.getSize(new THREE.Vector3()), + center: box.getCenter(new THREE.Vector3()) + }; + } + + const original = mesh.geometry.userData.originalPositions; + const { size, center } = mesh.geometry.userData.bounds; + const totalHeight = size.y; // Use Y-size for the twist axis + + for (let i = 0; i < positions.count; i++) { + const x = original[i * 3]; + const y = original[i * 3 + 1]; + const z = original[i * 3 + 2]; + + // Normalize the y-position from 0 to 1 based on the mesh's height + const normalizedY = (y - center.y + totalHeight / 2) / totalHeight; + + // Calculate the twist angle based on normalized y and progress + const twistAngle = normalizedY * progress * twistStrength * 2 * Math.PI; + + // Apply rotation to the X and Z coordinates + positions.setX(i, x * Math.cos(twistAngle) - z * Math.sin(twistAngle)); + positions.setY(i, y); // Y remains unchanged as it's the axis of rotation + positions.setZ(i, x * Math.sin(twistAngle) + z * Math.cos(twistAngle)); + } + + positions.needsUpdate = true; + mesh.geometry.computeVertexNormals(); +} + +// Reset mesh geometry to original state +function resetMeshGeometry(mesh) { + if (!mesh || !mesh.geometry || !mesh.geometry.userData.originalPositions) { + return; + } + + const positions = mesh.geometry.attributes.position; + const original = mesh.geometry.userData.originalPositions; + + for (let i = 0; i < positions.count; i++) { + positions.setXYZ(i, original[i * 3], original[i * 3 + 1], original[i * 3 + 2]); + } + + positions.needsUpdate = true; + mesh.geometry.computeVertexNormals(); +} + +// Start transition to next scene +function startTransition() { + if (isTransitioning || currentScene >= 2) return; + + isTransitioning = true; + isTwisting = true; + twistProgress = 0; + transitionStartTime = performance.now(); + + // Load next model + let nextModelFile = ''; + let nextModelType = ''; + + if (currentScene === 0) { + nextModelFile = 'agility.glb'; + nextModelType = 'agility'; + } else if (currentScene === 1) { + nextModelFile = 'storytelling.glb'; + nextModelType = 'storytelling'; + } + + if (nextModelFile) { + loadModel(nextModelFile, nextModelType, (model, animMixer) => { + nextModel = model; + nextMixer = animMixer; + + // Start next model as invisible and positioned below + nextModel.position.y = -10; + nextModel.traverse((obj) => { + if (obj.material) { + if (Array.isArray(obj.material)) { + obj.material.forEach(mat => { + mat.transparent = true; + mat.opacity = 0; + }); + } else { + obj.material.transparent = true; + obj.material.opacity = 0; + } + } + }); + + scene.add(nextModel); + }); + } +} + +// Update transition animation +function updateTransition(deltaTime) { + if (!isTransitioning) return; + + const elapsed = (performance.now() - transitionStartTime) / 1000; + const transitionProgress = Math.min(elapsed / transitionDuration, 1); + + // Smooth easing function (ease-in-out) + const easeInOut = (t) => t * t * (3 - 2 * t); + const easedProgress = easeInOut(transitionProgress); + + if (currentModel) { + // Move current model up and fade out + currentModel.position.y = easedProgress * 10; + + currentModel.traverse((obj) => { + if (obj.material) { + const targetOpacity = 1 - easedProgress; + if (Array.isArray(obj.material)) { + obj.material.forEach(mat => { + mat.transparent = true; + mat.opacity = targetOpacity; + }); + } else { + obj.material.transparent = true; + obj.material.opacity = targetOpacity; + } + } + }); + } + + if (nextModel) { + // Move next model to center and fade in + nextModel.position.y = -10 + (easedProgress * 10); + + nextModel.traverse((obj) => { + if (obj.material) { + const targetOpacity = easedProgress; + if (Array.isArray(obj.material)) { + obj.material.forEach(mat => { + mat.transparent = true; + mat.opacity = targetOpacity; + }); + } else { + obj.material.transparent = true; + obj.material.opacity = targetOpacity; + } + } + }); + } + + // Complete transition + if (transitionProgress >= 1) { + // Remove current model + if (currentModel) { + scene.remove(currentModel); + + // Clean up geometry user data + currentModel.traverse((obj) => { + if (obj.geometry && obj.geometry.userData.originalPositions) { + delete obj.geometry.userData.originalPositions; + delete obj.geometry.userData.bounds; + } + }); + } + + // Switch to next model + if (nextModel) { + currentModel = nextModel; + mixer = nextMixer; + + // Reset position and opacity + currentModel.position.y = 0; + currentModel.traverse((obj) => { + if (obj.material) { + if (Array.isArray(obj.material)) { + obj.material.forEach(mat => { + mat.opacity = 1; + if (currentScene === 2) { // Keep transparency for storytelling glass + mat.transparent = mat.transmission > 0; + } else { + mat.transparent = mat.transmission > 0; + } + }); + } else { + obj.material.opacity = 1; + if (currentScene === 2) { // Keep transparency for storytelling glass + obj.material.transparent = obj.material.transmission > 0; + } else { + obj.material.transparent = obj.material.transmission > 0; + } + } + } + }); + } + + nextModel = null; + nextMixer = null; + isTransitioning = false; + isTwisting = false; + twistProgress = 0; + currentScene++; + scrollCount = 0; + + console.log(`Transition complete. Current scene: ${currentScene}`); + } +} + +// Scroll event handler +function onMouseScroll(event) { + // Only count downward scrolls and if not currently transitioning + if (!isTransitioning && event.deltaY > 0) { + scrollCount++; + console.log(`Scroll count: ${scrollCount}`); + + if (scrollCount >= scrollThreshold) { + startTransition(); + } + } +} + +// Attach scroll event listener +window.addEventListener('wheel', onMouseScroll, {passive: true}); + +// Animation loop +const clock = new THREE.Clock(); + +function animate() { + requestAnimationFrame(animate); + + const delta = clock.getDelta(); + + // Update mixers + if (mixer) mixer.update(delta); + if (nextMixer) nextMixer.update(delta); + + // Update transition + if (isTransitioning) { + updateTransition(delta); + + // Apply twist during transition + if (isTwisting && currentModel) { + twistProgress += twistSpeed; + if (twistProgress > 1.0) { + twistProgress = 1.0; + + // Reset geometry after twist completes + // currentModel.traverse((object) => { + // if (object.isMesh) { + // resetMeshGeometry(object); + // } + // }); + + isTwisting = false; + } else { + // Apply twist to current model + currentModel.traverse((object) => { + if (object.isMesh) { + twistMesh(object, twistProgress); + } + }); + } + } + } + + // Turntable rotation for current model + if (currentModel && !isTransitioning) { + autoRotationAngle += delta * 0.5; + currentModel.rotation.y = autoRotationAngle; + } + + controls.update(); + composer.render(); +} + +animate(); + +// Handle window resize +window.addEventListener('resize', () => { + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + renderer.setSize(window.innerWidth, window.innerHeight); + composer.setSize(window.innerWidth, window.innerHeight); +}); \ No newline at end of file diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..9319170 --- /dev/null +++ b/src/main.js @@ -0,0 +1,701 @@ +import './style.css' + +import * as THREE from 'three'; +import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; +import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; +import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; +import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js'; +import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; +import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; +import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; + +// Scene setup +const scene = new THREE.Scene(); +const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); +camera.setFocalLength(50); + +const raycaster = new THREE.Raycaster(); +const mouse = new THREE.Vector2(); + +// Transition state management +let currentScene = 0; // 0: innovation, 1: agility, 2: storytelling +let isTransitioning = false; +let isTwisting = false; +let twistProgress = 0; +const twistSpeed = 0.02; // Easily adjustable twist speed +const twistStrength = 0.3; +const fadeSpeed = 1; // Easily adjustable fade speed +const transitionDuration = 1; // Easily adjustable transition duration (seconds) +let scrollDownCount = 0; +let scrollUpCount = 0; +const scrollThreshold = 10; // Changed to 10 as requested +let transitionStartTime = 0; +let transitionDirection = 1; // 1 for forward, -1 for backward + +// Scene objects +let currentModel = null; +let nextModel = null; +let mixer = null; +let nextMixer = null; +let autoRotationAngle = 0; + +// Renderer setup +const renderer = new THREE.WebGLRenderer({ antialias: true }); + +renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); +renderer.setSize(window.innerWidth, window.innerHeight); +renderer.setClearColor(0x000000); +renderer.shadowMap.enabled = true; +renderer.shadowMap.type = THREE.PCFSoftShadowMap; +renderer.toneMapping = THREE.ACESFilmicToneMapping; +renderer.toneMappingExposure = 1.2; +renderer.outputColorSpace = THREE.SRGBColorSpace; +renderer.physicallyCorrectLights = true; + +document.body.appendChild(renderer.domElement); + +// Post-processing: Bloom +const composer = new EffectComposer(renderer); +const renderPass = new RenderPass(scene, camera); +composer.addPass(renderPass); + +const bloomPass = new UnrealBloomPass( + new THREE.Vector2(window.innerWidth, window.innerHeight), + 1.0, // strength + 0.45, // radius + 0.85 // threshold +); +composer.addPass(bloomPass); + +// Video texture for emissive "screen"-like effect on orange material +const video = document.createElement('video'); +video.src = '/shader-flash.webm'; +video.muted = true; +video.loop = true; +video.playsInline = true; +video.autoplay = true; +video.preload = 'auto'; + +const videoTexture = new THREE.VideoTexture(video); +videoTexture.colorSpace = THREE.SRGBColorSpace; +videoTexture.generateMipmaps = false; +videoTexture.minFilter = THREE.LinearFilter; +videoTexture.magFilter = THREE.LinearFilter; + +// Ensure autoplay starts (muted autoplay is commonly allowed) +video.play().catch(() => {}); + +// Local procedural environment for better PBR response (no network) +const pmrem = new THREE.PMREMGenerator(renderer); +const roomEnv = new RoomEnvironment(); +scene.environment = pmrem.fromScene(roomEnv).texture; +pmrem.dispose(); +roomEnv.dispose(); +scene.environment = null; // This will make the renderer's clear color visible again + +// Consistent Lighting Setup +const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); +scene.add(ambientLight); + +const hemiLight = new THREE.HemisphereLight(0xffffff, 0x666666, 1.5); +hemiLight.position.set(0, 20, 0); +scene.add(hemiLight); + +const fillLight = new THREE.DirectionalLight(0xffffff, 1.2); +fillLight.position.set(-12, 6, -8); +scene.add(fillLight); + +const topLight = new THREE.DirectionalLight(0xffffff, 1.5); +topLight.position.set(5, 15, 5); +scene.add(topLight); + +const bottomLight = new THREE.DirectionalLight(0xffffff, 0.8); +bottomLight.position.set(-3, -8, 3); +scene.add(bottomLight); + +const leftLight = new THREE.DirectionalLight(0xffffff, 1.0); +leftLight.position.set(-12, 2, 5); +scene.add(leftLight); + +const rightLight = new THREE.DirectionalLight(0xffffff, 1.0); +rightLight.position.set(12, 2, -5); +scene.add(rightLight); + +const frontLight = new THREE.DirectionalLight(0xffffff, 0.8); +frontLight.position.set(8, 4, 12); +scene.add(frontLight); + +const backLight = new THREE.DirectionalLight(0xffffff, 0.8); +backLight.position.set(-8, 4, -12); +scene.add(backLight); + +const cameraLight = new THREE.PointLight(0xffffff, 0.8, 0, 2); +camera.add(cameraLight); +scene.add(camera); + +// Controls with zoom disabled +const controls = new OrbitControls(camera, renderer.domElement); +controls.enableDamping = true; +controls.dampingFactor = 0.25; +controls.enableZoom = false; // Disable zoom + +// Material definitions +// Clear thick glass for innovation +const innovationGlassMaterial = new THREE.MeshPhysicalMaterial({ + color: 0xffffff, + metalness: 0.2, + roughness: 0.05, + transmission: 1, + ior: 2, + thickness: 2, + clearcoat: 1.0, + clearcoatRoughness: 0.1, + attenuationColor: new THREE.Color(0xffffff), + attenuationDistance: 0.8, + envMapIntensity: 0, + specularIntensity: 1.0, + specularColor: new THREE.Color(0x000000), + transparent: true, + depthWrite: false, + alphaTest: 0 +}); + +// Slightly frosted glass for agility and storytelling +const frostedGlassMaterial = new THREE.MeshPhysicalMaterial({ + color: 0xffffff, + metalness: 0.0, + roughness: 0.25, + transmission: 1.0, + ior: 1.5, + thickness: 2.0, + clearcoat: 0.75, + clearcoatRoughness: 0.25, + attenuationColor: new THREE.Color(0xffffff), + attenuationDistance: 1.5, + envMapIntensity: 1.25, + specularIntensity: 1.0, + specularColor: new THREE.Color(0xffffff), + transparent: true, + depthWrite: false, + side: THREE.DoubleSide +}); + +// Orange material with video shader for innovation +const lightOrangeMaterial = new THREE.MeshStandardMaterial({ + color: 0xff8600, + metalness: 0.05, + roughness: 0.4, + envMapIntensity: 0, + emissive: new THREE.Color(0xffad47), + emissiveMap: videoTexture, + emissiveIntensity: 2.25 +}); + +const loader = new GLTFLoader(); +const dracoLoader = new DRACOLoader(); +dracoLoader.setDecoderPath('node_modules/three/examples/jsm/libs/draco/'); +loader.setDRACOLoader(dracoLoader); + +// Apply materials based on model type +function applyMaterials(model, modelType) { + console.log(`=== Material Assignment Debug for ${modelType} ===`); + let meshCount = 0; + + model.traverse((object) => { + if (object.isMesh) { + meshCount++; + console.log(`Found mesh: "${object.name}"`); + + const previousMaterial = object.material; + object.castShadow = true; + object.receiveShadow = true; + + if (modelType === 'innovation') { + // Innovation-specific material logic + const orangeMeshes = ['dblsc', 'ec', 'gemini', 'infinity', 'star', 'dpd']; + const targetGlassNames = ['Cube.alt90.df']; + const sanitize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, ''); + const nameMatches = (name, targets) => { + const clean = sanitize(name); + return targets.some((t) => { + const ct = sanitize(t); + return clean === ct || clean.includes(ct) || ct.includes(clean); + }); + }; + + if (nameMatches(object.name, targetGlassNames)) { + // Create outer glass shell with innovation-specific material + object.material = innovationGlassMaterial.clone(); + object.material.side = THREE.DoubleSide; + object.material.depthWrite = false; + object.renderOrder = 2; + + // Create inner glass shell + const innerShell = object.clone(); + innerShell.material = innovationGlassMaterial.clone(); + innerShell.material.side = THREE.DoubleSide; + innerShell.material.depthWrite = false; + innerShell.material.thickness = 4; + innerShell.material.transmission = 0.8; + innerShell.renderOrder = 1; + innerShell.scale.multiplyScalar(0.95); + object.parent.add(innerShell); + + } else if (nameMatches(object.name, orangeMeshes)) { + object.material = lightOrangeMaterial.clone(); + object.renderOrder = 0; + } + } else { + // Agility and Storytelling use frosted glass material for all meshes + if (object.name.startsWith('base')) { + console.log(` → Applying frosted glass material to "${object.name}"`); + object.material = frostedGlassMaterial.clone(); + } else { + console.log(` → Applying frosted glass material (fallback) to "${object.name}"`); + object.material = frostedGlassMaterial.clone(); + } + } + + object.material.needsUpdate = true; + + // Cleanup previous materials + if (Array.isArray(previousMaterial)) { + previousMaterial.forEach((mat) => mat && mat.dispose && mat.dispose()); + } else if (previousMaterial && previousMaterial.dispose) { + previousMaterial.dispose(); + } + } + }); + + console.log(`Total meshes processed: ${meshCount}`); + console.log(`=== End Material Assignment Debug for ${modelType} ===`); +} + +// Center and frame model with camera +function centerAndFrameModel(model, targetCamera = camera) { + const box = new THREE.Box3().setFromObject(model); + const center = box.getCenter(new THREE.Vector3()); + model.position.sub(center); + model.updateMatrixWorld(true); + + const size = box.getSize(new THREE.Vector3()); + const maxDim = Math.max(size.x, size.y, size.z); + + // Only set camera position if it's not already positioned (avoid reset during transitions) + // Increased distance multiplier from 2 to 2.5 for further camera position + if (!isTransitioning) { + const cameraDistance = maxDim * 2.5; + targetCamera.position.set(0, 0, cameraDistance); + controls.target.set(0, 0, 0); + + // Set distance limits to lock the camera at this distance + controls.minDistance = cameraDistance; + controls.maxDistance = cameraDistance; + + controls.update(); + } +} + +// Setup animations based on model type +function setupAnimations(model, gltf, modelType) { + if (gltf.animations && gltf.animations.length > 0) { + const animMixer = new THREE.AnimationMixer(model); + + gltf.animations.forEach((clip) => { + const action = animMixer.clipAction(clip); + + if (modelType === 'innovation') { + // PingPong loop for innovation + action.loop = THREE.LoopPingPong; + action.play(); + } else if (modelType === 'agility') { + // Regular loop for agility + action.loop = THREE.LoopRepeat; + action.play(); + } else if (modelType === 'storytelling') { + // Play once for storytelling + action.loop = THREE.LoopOnce; + action.clampWhenFinished = true; + action.play(); + } + }); + + if (modelType === 'innovation') { + animMixer.timeScale = 3.0; // Keep existing timeScale for innovation + } + + return animMixer; + } + return null; +} + +// Load model function +function loadModel(filename, modelType, onLoadCallback) { + loader.load(`/${filename}`, (gltf) => { + const model = gltf.scene; + + // Apply materials + applyMaterials(model, modelType); + + // Setup animations + const animMixer = setupAnimations(model, gltf, modelType); + + // Center and frame model + centerAndFrameModel(model); + + if (onLoadCallback) { + onLoadCallback(model, animMixer); + } + }, undefined, (error) => { + console.error(`Error loading ${filename}:`, error); + }); +} + +// Load initial innovation model +loadModel('innovation.glb', 'innovation', (model, animMixer) => { + currentModel = model; + mixer = animMixer; + scene.add(currentModel); +}); + +// Twist animation function - Updated to twist around world center (0,0,0) +function twistMesh(mesh, progress) { + if (!mesh || !mesh.geometry || !mesh.geometry.attributes.position) { + return; + } + + const positions = mesh.geometry.attributes.position; + + // Store original positions on the first run + if (!mesh.geometry.userData.originalPositions) { + mesh.geometry.userData.originalPositions = new Float32Array(positions.array); + + // Store original world positions for each vertex + mesh.geometry.userData.originalWorldPositions = []; + + // Update world matrix to get accurate world positions + mesh.updateMatrixWorld(true); + + const tempVector = new THREE.Vector3(); + for (let i = 0; i < positions.count; i++) { + tempVector.fromBufferAttribute(positions, i); + tempVector.applyMatrix4(mesh.matrixWorld); + mesh.geometry.userData.originalWorldPositions.push({ + x: tempVector.x, + y: tempVector.y, + z: tempVector.z + }); + } + + // Store the inverse of the current world matrix for transforming back to local space + mesh.geometry.userData.inverseWorldMatrix = mesh.matrixWorld.clone().invert(); + } + + const originalWorldPositions = mesh.geometry.userData.originalWorldPositions; + const inverseWorldMatrix = mesh.geometry.userData.inverseWorldMatrix; + + for (let i = 0; i < positions.count; i++) { + const worldPos = originalWorldPositions[i]; + + // Use world Y position for consistent twisting around world Y-axis + const worldY = worldPos.y; + + // Calculate twist angle based on world Y position + // Normalize Y based on a reasonable range (adjust as needed) + const normalizedY = (worldY + 5) / 10; // Assuming meshes are roughly within -5 to +5 world units in Y + const twistAngle = normalizedY * progress * twistStrength * 2 * Math.PI; + + // Apply twist in world coordinates around world Y-axis + const twistedWorldX = worldPos.x * Math.cos(twistAngle) - worldPos.z * Math.sin(twistAngle); + const twistedWorldY = worldPos.y; // Y remains unchanged + const twistedWorldZ = worldPos.x * Math.sin(twistAngle) + worldPos.z * Math.cos(twistAngle); + + // Convert twisted world position back to local coordinates + const twistedWorldVector = new THREE.Vector3(twistedWorldX, twistedWorldY, twistedWorldZ); + twistedWorldVector.applyMatrix4(inverseWorldMatrix); + + positions.setXYZ(i, twistedWorldVector.x, twistedWorldVector.y, twistedWorldVector.z); + } + + positions.needsUpdate = true; + mesh.geometry.computeVertexNormals(); +} + +// Reset mesh geometry to original state +function resetMeshGeometry(mesh) { + if (!mesh || !mesh.geometry || !mesh.geometry.userData.originalPositions) { + return; + } + + const positions = mesh.geometry.attributes.position; + const original = mesh.geometry.userData.originalPositions; + + for (let i = 0; i < positions.count; i++) { + positions.setXYZ(i, original[i * 3], original[i * 3 + 1], original[i * 3 + 2]); + } + + positions.needsUpdate = true; + mesh.geometry.computeVertexNormals(); +} + +// Start transition to next or previous scene +function startTransition(direction = 1) { + if (isTransitioning) return; + + // Check bounds + if (direction > 0 && currentScene >= 2) return; // Can't go forward from storytelling + if (direction < 0 && currentScene <= 0) return; // Can't go backward from innovation + + isTransitioning = true; + isTwisting = true; + twistProgress = 0; + transitionStartTime = performance.now(); + transitionDirection = direction; + + // Determine next model based on direction + let nextModelFile = ''; + let nextModelType = ''; + + if (direction > 0) { + // Moving forward + if (currentScene === 0) { + nextModelFile = 'agility.glb'; + nextModelType = 'agility'; + } else if (currentScene === 1) { + nextModelFile = 'storytelling.glb'; + nextModelType = 'storytelling'; + } + } else { + // Moving backward + if (currentScene === 1) { + nextModelFile = 'innovation.glb'; + nextModelType = 'innovation'; + } else if (currentScene === 2) { + nextModelFile = 'agility.glb'; + nextModelType = 'agility'; + } + } + + if (nextModelFile) { + loadModel(nextModelFile, nextModelType, (model, animMixer) => { + nextModel = model; + nextMixer = animMixer; + + // Start next model as invisible but in normal position (no vertical offset) + nextModel.position.y = 0; + nextModel.traverse((obj) => { + if (obj.material) { + if (Array.isArray(obj.material)) { + obj.material.forEach(mat => { + mat.transparent = true; + mat.opacity = 0; + }); + } else { + obj.material.transparent = true; + obj.material.opacity = 0; + } + } + }); + + scene.add(nextModel); + }); + } +} + +// Update transition animation +function updateTransition(deltaTime) { + if (!isTransitioning) return; + + const elapsed = (performance.now() - transitionStartTime) / 1000; + const transitionProgress = Math.min(elapsed / transitionDuration, 1); + + // Smooth easing function (ease-in-out) + const easeInOut = (t) => t * t * (3 - 2 * t); + const easedProgress = easeInOut(transitionProgress); + + if (currentModel) { + // Move current model up and fade out + currentModel.position.y = easedProgress * 10; + + currentModel.traverse((obj) => { + if (obj.material) { + const targetOpacity = 1 - easedProgress; + if (Array.isArray(obj.material)) { + obj.material.forEach(mat => { + mat.transparent = true; + mat.opacity = targetOpacity; + }); + } else { + obj.material.transparent = true; + obj.material.opacity = targetOpacity; + } + } + }); + } + + if (nextModel) { + // Keep next model in place and just fade in (no vertical movement) + nextModel.position.y = 0; + + nextModel.traverse((obj) => { + if (obj.material) { + const targetOpacity = easedProgress; + if (Array.isArray(obj.material)) { + obj.material.forEach(mat => { + mat.transparent = true; + mat.opacity = targetOpacity; + }); + } else { + obj.material.transparent = true; + obj.material.opacity = targetOpacity; + } + } + }); + } + + // Complete transition + if (transitionProgress >= 1) { + // Remove current model + if (currentModel) { + scene.remove(currentModel); + + // Clean up geometry user data + currentModel.traverse((obj) => { + if (obj.geometry && obj.geometry.userData.originalPositions) { + delete obj.geometry.userData.originalPositions; + delete obj.geometry.userData.bounds; + } + }); + } + + // Switch to next model + if (nextModel) { + currentModel = nextModel; + mixer = nextMixer; + + // Reset position and opacity + currentModel.position.y = 0; + currentModel.traverse((obj) => { + if (obj.material) { + if (Array.isArray(obj.material)) { + obj.material.forEach(mat => { + mat.opacity = 1; + if (currentScene === 2) { // Keep transparency for storytelling glass + mat.transparent = mat.transmission > 0; + } else { + mat.transparent = mat.transmission > 0; + } + }); + } else { + obj.material.opacity = 1; + if (currentScene === 2) { // Keep transparency for storytelling glass + obj.material.transparent = obj.material.transmission > 0; + } else { + obj.material.transparent = obj.material.transmission > 0; + } + } + } + }); + } + + nextModel = null; + nextMixer = null; + isTransitioning = false; + isTwisting = false; + twistProgress = 0; + currentScene += transitionDirection; // Update scene based on direction + scrollDownCount = 0; + scrollUpCount = 0; + + console.log(`Transition complete. Current scene: ${currentScene}`); + } +} + +// Scroll event handler +function onMouseScroll(event) { + if (isTransitioning) return; + + if (event.deltaY > 0) { + // Scrolling down - move forward + scrollDownCount++; + scrollUpCount = 0; // Reset up count + console.log(`Scroll down count: ${scrollDownCount}`); + + if (scrollDownCount >= scrollThreshold) { + startTransition(1); // Forward direction + } + } else if (event.deltaY < 0) { + // Scrolling up - move backward + scrollUpCount++; + scrollDownCount = 0; // Reset down count + console.log(`Scroll up count: ${scrollUpCount}`); + + if (scrollUpCount >= scrollThreshold) { + startTransition(-1); // Backward direction + } + } +} + +// Attach scroll event listener +window.addEventListener('wheel', onMouseScroll, {passive: true}); + +// Animation loop +const clock = new THREE.Clock(); + +function animate() { + requestAnimationFrame(animate); + + const delta = clock.getDelta(); + + // Update mixers + if (mixer) mixer.update(delta); + if (nextMixer) nextMixer.update(delta); + + // Update transition + if (isTransitioning) { + updateTransition(delta); + + // Apply twist during transition + if (isTwisting && currentModel) { + twistProgress += twistSpeed; + if (twistProgress > 1.0) { + twistProgress = 1.0; + + // Reset geometry after twist completes + // currentModel.traverse((object) => { + // if (object.isMesh) { + // resetMeshGeometry(object); + // } + // }); + + isTwisting = false; + } else { + // Apply twist to current model + currentModel.traverse((object) => { + if (object.isMesh) { + twistMesh(object, twistProgress); + } + }); + } + } + } + + // Turntable rotation for current model + if (currentModel && !isTransitioning) { + autoRotationAngle += delta * 0.5; + currentModel.rotation.y = autoRotationAngle; + } + + controls.update(); + composer.render(); +} + +animate(); + +// Handle window resize +window.addEventListener('resize', () => { + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + renderer.setSize(window.innerWidth, window.innerHeight); + composer.setSize(window.innerWidth, window.innerHeight); +}); \ No newline at end of file diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..d70883b --- /dev/null +++ b/src/style.css @@ -0,0 +1,8 @@ +body { + margin: 0; + padding: 0; + overflow: hidden; +} +canvas { + display: block; +} \ No newline at end of file diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..c2e19a0 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + host: true, + allowedHosts: [ + 'dlozi.aiquiral.me' + ] + } +}); \ No newline at end of file