├── .gitignore ├── LICENSE ├── package-lock.json ├── package.json ├── public ├── DancingScript-VariableFont_wght.ttf ├── Inter_Bold.json ├── background_hdr.exr ├── epic.jpg ├── favicons │ ├── about.txt │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ └── site.webmanifest ├── index.html ├── merry_xmas.svg ├── music.mp3 ├── preview.jpg └── snowglobe-transformed.glb ├── readme.md ├── sandbox.config.json └── src ├── App.js ├── FireWorks.js ├── FireWorksMaterial.js ├── LumaWorld.js ├── Overlay.js ├── PostProcessingEffects.js ├── Scene.js ├── SnowFlakes.js ├── SnowGlobeModel.js ├── index.js └── styles.css /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | .vercel 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Anderson Mancini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "random-snow-globe-test-anderson", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "@lumaai/luma-web": "0.1.15", 9 | "@react-three/drei": "latest", 10 | "@react-three/fiber": "latest", 11 | "@react-three/postprocessing": "latest", 12 | "@types/three": "0.157.2", 13 | "gsap": "3.12.2", 14 | "react": "18.2.0", 15 | "react-dom": "18.2.0", 16 | "react-scripts": "5.0.1", 17 | "three": "latest", 18 | "vercel": "^32.7.2" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test --env=jsdom", 24 | "eject": "react-scripts eject", 25 | "deploy": "vercel --prod" 26 | }, 27 | "browserslist": [ 28 | ">1%", 29 | "not dead", 30 | "not ie <= 11", 31 | "not op_mini all" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /public/DancingScript-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektogamat/christmas-threejs/14aeed59e24e54cd2f40f804aa106e1739bbd694/public/DancingScript-VariableFont_wght.ttf -------------------------------------------------------------------------------- /public/background_hdr.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektogamat/christmas-threejs/14aeed59e24e54cd2f40f804aa106e1739bbd694/public/background_hdr.exr -------------------------------------------------------------------------------- /public/epic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektogamat/christmas-threejs/14aeed59e24e54cd2f40f804aa106e1739bbd694/public/epic.jpg -------------------------------------------------------------------------------- /public/favicons/about.txt: -------------------------------------------------------------------------------- 1 | This favicon was generated using the following graphics from Twitter Twemoji: 2 | 3 | - Graphics Title: 1f384.svg 4 | - Graphics Author: Copyright 2020 Twitter, Inc and other contributors (https://github.com/twitter/twemoji) 5 | - Graphics Source: https://github.com/twitter/twemoji/blob/master/assets/svg/1f384.svg 6 | - Graphics License: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/) 7 | -------------------------------------------------------------------------------- /public/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektogamat/christmas-threejs/14aeed59e24e54cd2f40f804aa106e1739bbd694/public/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektogamat/christmas-threejs/14aeed59e24e54cd2f40f804aa106e1739bbd694/public/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektogamat/christmas-threejs/14aeed59e24e54cd2f40f804aa106e1739bbd694/public/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektogamat/christmas-threejs/14aeed59e24e54cd2f40f804aa106e1739bbd694/public/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektogamat/christmas-threejs/14aeed59e24e54cd2f40f804aa106e1739bbd694/public/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektogamat/christmas-threejs/14aeed59e24e54cd2f40f804aa106e1739bbd694/public/favicons/favicon.ico -------------------------------------------------------------------------------- /public/favicons/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Merry Threejs Christmas - By Anderson Mancini 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /public/merry_xmas.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 29 | 33 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /public/music.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektogamat/christmas-threejs/14aeed59e24e54cd2f40f804aa106e1739bbd694/public/music.mp3 -------------------------------------------------------------------------------- /public/preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektogamat/christmas-threejs/14aeed59e24e54cd2f40f804aa106e1739bbd694/public/preview.jpg -------------------------------------------------------------------------------- /public/snowglobe-transformed.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ektogamat/christmas-threejs/14aeed59e24e54cd2f40f804aa106e1739bbd694/public/snowglobe-transformed.glb -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Merry Three.js Christmas 2 | 3 | Christmas scene using threejs, react three fiber and gaussian splatting 4 | 5 | [![screenshot](https://threejs-christmas.vercel.app/preview.jpg)](https://threejs-christmas.vercel.app/) 6 | 7 | ## Setup 8 | 9 | Download [Node.js](https://nodejs.org/en/download/). 10 | Run this followed commands: 11 | 12 | ```bash 13 | # Install dependencies (only the first time) 14 | npm install 15 | 16 | # Run the local server at localhost:8080 17 | npm start 18 | 19 | # Build for production in the dist/ directory 20 | npm run build 21 | ``` 22 | -------------------------------------------------------------------------------- /sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": false, 3 | "hardReloadOnChange": false, 4 | "view": "browser" 5 | } 6 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { Canvas } from '@react-three/fiber' 2 | import { Loader, PositionalAudio } from '@react-three/drei' 3 | import { useState, Suspense, useRef, useEffect } from 'react' 4 | import PostProcessingEffects from './PostProcessingEffects' 5 | import SnowGlobeModel from './SnowGlobeModel' 6 | import Overlay from './Overlay' 7 | import SceneSetup from './Scene' 8 | 9 | export default function App() { 10 | const audioRef = useRef() 11 | const [inside, setInside] = useState(false) 12 | const isMobile = window.innerWidth < 768 13 | const canvasConfig = { antialias: false, depth: false, stencil: false, alpha: false } 14 | const [ready, setReady] = useState(false) 15 | 16 | return ( 17 | <> 18 | setReady(true)} 20 | gl={canvasConfig} 21 | camera={{ position: [0, 0, 5], fov: 35, far: 20000 }} 22 | dpr={1} 23 | > 24 | 25 | 26 | 32 | 33 | 41 | 42 | 43 | 44 | 45 | 49 | 50 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /src/FireWorks.js: -------------------------------------------------------------------------------- 1 | import FireWorksMaterial from './FireWorksMaterial' 2 | import { forwardRef } from 'react' 3 | 4 | export default forwardRef(function FireWorks(props, ref) { 5 | return ( 6 | 7 | 8 | 9 | 10 | ) 11 | }) 12 | -------------------------------------------------------------------------------- /src/FireWorksMaterial.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useMemo, useEffect } from 'react' 2 | import { shaderMaterial } from '@react-three/drei' 3 | import { extend, useFrame, useThree } from '@react-three/fiber' 4 | import { FrontSide, AdditiveBlending, Vector2 } from 'three' 5 | 6 | /** 7 | * Fireworks component by Anderson Mancini. 8 | * Based on https://www.shadertoy.com/view/4dBGRw 9 | */ 10 | 11 | export default function FireWorksMaterial() { 12 | const { size, viewport } = useThree() 13 | const ref = useRef() 14 | 15 | const FireWorksMaterial = useMemo(() => { 16 | return shaderMaterial( 17 | { 18 | time: 0, 19 | iResolution: new Vector2(size.width, size.height) 20 | }, 21 | ` 22 | varying vec2 vUv; 23 | void main() { 24 | 25 | vUv = uv; 26 | gl_Position = vec4(position, 1.0); 27 | 28 | }`, 29 | ` 30 | varying vec2 vUv; 31 | uniform float time; 32 | uniform vec2 iResolution; 33 | 34 | vec3 texture(vec2 pos, vec2 rocketpos){ 35 | float d = (pos.x-rocketpos.x)+(pos.y-rocketpos.y); 36 | vec3 col=vec3(1.0); 37 | 38 | d = mod(d*15.,2.0); 39 | if(d>1.0){ 40 | col=vec3(0.0,0.0,1.0); 41 | } 42 | return col; 43 | } 44 | 45 | vec3 rocket(vec2 pos, vec2 rocketpos){ 46 | vec3 col = vec3(0.0); 47 | float f = 0.; 48 | float absx= abs(rocketpos.x - pos.x); 49 | float absy = abs(rocketpos.y-pos.y); 50 | if(absx<0.05&&absy<0.15){ 51 | col=texture(pos, rocketpos); 52 | } 53 | float point=(rocketpos.y-pos.y-0.25)*-0.7; 54 | if((rocketpos.y-pos.y)>0.1){ 55 | f=smoothstep(point-0.001,point+0.001,absx); 56 | 57 | col=mix(vec3(1.0,0.0,0.0),col, f); 58 | } 59 | return col; 60 | } 61 | 62 | float rand(float val, float seed){return cos(val*sin(val*seed)*seed);} 63 | float distance2( in vec2 a, in vec2 b ) { return dot(a-b,a-b); } 64 | mat2 rr = mat2( cos(1.0), -sin(1.0), sin(1.0), cos(1.0) ); 65 | 66 | vec3 drawParticles(vec2 pos, vec3 particolor, float time, vec2 cpos, float gravity, float seed, float timelength){ 67 | vec3 col= vec3(0.0); 68 | vec2 pp = vec2(1.0,0.0); 69 | for(float i=1.0;i<=64.0;i++){ 70 | float d=rand(i, seed); 71 | float fade=(i/64.0)*time; 72 | vec2 particpos = cpos + time*pp*d; 73 | pp = rr*pp; 74 | col = mix(particolor/fade, col, smoothstep(0.0, 0.0001, distance2(particpos, pos))); 75 | } 76 | col*=smoothstep(0.0,1.0,(timelength-time)/timelength); 77 | 78 | return col; 79 | } 80 | 81 | vec3 drawFireworks(float time, vec2 uv, vec3 particolor, float seed){ 82 | 83 | float timeoffset = 1.0; 84 | vec3 col=vec3(0.0); 85 | if(time<=0.){ 86 | return col; 87 | } 88 | if(mod(time, 2.0)>timeoffset){ 89 | col= drawParticles(uv, particolor, mod(time, 2.0)-timeoffset, vec2(rand(ceil(time/2.0),seed),-0.5), 0.5, ceil(time/2.0), seed); 90 | }else{ 91 | col= rocket(uv*15., vec2(15.*rand(ceil(time/2.0),seed),15.*(-0.5+(timeoffset-mod(time, 2.0))))); 92 | } 93 | 94 | return col; 95 | } 96 | 97 | void main() { 98 | vec3 col=vec3(0.1,0.1,0.2); 99 | vec2 uv =1.0 - 2.0* gl_FragCoord.xy / iResolution.xy; 100 | uv.x *= iResolution.x/iResolution.y; 101 | col += drawFireworks(time , uv,vec3(1.0,0.1,0.1), 1.); 102 | col += drawFireworks(time-.6, uv,vec3(0.0,1.0,0.5), 1.2); 103 | col += drawFireworks(time-0.9, uv,vec3(1.0,1.0,0.1), 1.9); 104 | col += drawFireworks(time-0.1, uv,vec3(1.0,1.0,0.1), 1.1); 105 | 106 | col -= 0.15; 107 | gl_FragColor = vec4(col, 1.0); 108 | }` 109 | ) 110 | }, []) 111 | 112 | extend({ FireWorksMaterial }) 113 | 114 | useFrame((state, delta) => { 115 | ref.current.time += delta 116 | }) 117 | 118 | useEffect(() => { 119 | ref.current.iResolution.set(size.width, size.height) 120 | }, [viewport]) 121 | 122 | return ( 123 | 132 | ) 133 | } 134 | -------------------------------------------------------------------------------- /src/LumaWorld.js: -------------------------------------------------------------------------------- 1 | import { extend } from '@react-three/fiber' 2 | import { LumaSplatsThree } from '@lumaai/luma-web' 3 | 4 | extend({ LumaSplats: LumaSplatsThree }) 5 | 6 | export default function LumaWorld({ visible }) { 7 | return ( 8 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /src/Overlay.js: -------------------------------------------------------------------------------- 1 | export default function Overlay({ inside, setInside }) { 2 | return ( 3 | <> 4 |
5 | 6 |
7 |
8 | 15 |
16 | Created with love by Anderson Mancini. Using LumaAI for the outside Gaussian Splatting and BlockadeLabs for internal snow globe. 17 |
18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/PostProcessingEffects.js: -------------------------------------------------------------------------------- 1 | import { EffectComposer, Bloom, HueSaturation, Vignette, SMAA, TiltShift2 } from '@react-three/postprocessing' 2 | 3 | export default function PostProcessingEffects() { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/Scene.js: -------------------------------------------------------------------------------- 1 | import { Environment, Lightformer, OrbitControls } from '@react-three/drei' 2 | 3 | export default function SceneSetup({ isMobile }) { 4 | return ( 5 | <> 6 | 10 | 11 | 20 | 24 | 25 | self.lookAt(0, 0, 0)} 31 | /> 32 | self.lookAt(0, 0, 0)} 35 | position={[-5, 1, -1]} 36 | rotation-y={Math.PI / 2} 37 | scale={[50, 10, 1]} 38 | /> 39 | self.lookAt(0, 0, 0)} 42 | position={[10, 1, 0]} 43 | rotation-y={-Math.PI / 2} 44 | scale={[50, 10, 1]} 45 | /> 46 | 47 | 48 | 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /src/SnowFlakes.js: -------------------------------------------------------------------------------- 1 | import { useFrame } from '@react-three/fiber' 2 | import * as THREE from 'three' 3 | import { useMemo, useRef, Suspense } from 'react' 4 | import { easing } from 'maath' 5 | 6 | export default function SnowFlakes({ count }) { 7 | const mesh = useRef() 8 | const dummy = new THREE.Object3D() 9 | 10 | const particles = useMemo(() => { 11 | const temp = [] 12 | for (let i = 0; i < count; i++) { 13 | const phi = Math.acos(-1 + Math.random() * 2) 14 | const theta = Math.random() * 2 * Math.PI 15 | 16 | const radius = 5 + Math.random() * 6.5 17 | const x = radius * Math.sin(phi) * Math.cos(theta) 18 | const y = radius * Math.sin(phi) * Math.sin(theta) 19 | const z = radius * Math.cos(phi) 20 | 21 | const speed = 0.001 + Math.random() / 10 22 | 23 | const scale = Math.cos(0.5 + Math.random() * 2.5) 24 | 25 | temp.push({ phi, theta, radius, speed, x, y, z, mx: 0, my: 0, scale }) 26 | } 27 | return temp 28 | }, [count]) 29 | 30 | useFrame((state, delta) => { 31 | easing.damp3(mesh.current.rotation, [0, -state.mouse.y * state.viewport.width * 2, 0], 0.75, delta) 32 | 33 | particles.forEach((particle, i) => { 34 | let { phi, theta, radius, speed, scale } = particle 35 | theta += speed / 20 36 | 37 | const x = radius * Math.sin(phi) * Math.cos(theta) 38 | const y = 15 + radius * Math.sin(phi) * Math.sin(theta) 39 | const z = radius * Math.cos(phi) 40 | 41 | particle.phi = phi 42 | particle.theta = theta 43 | 44 | dummy.position.set(x, y, z) 45 | dummy.scale.setScalar(scale) 46 | dummy.rotation.set(phi, theta, 0) 47 | dummy.updateMatrix() 48 | 49 | mesh.current.setMatrixAt(i, dummy.matrix) 50 | }) 51 | 52 | mesh.current.instanceMatrix.needsUpdate = true 53 | }) 54 | 55 | return ( 56 | 57 | 58 | 59 | 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /src/SnowGlobeModel.js: -------------------------------------------------------------------------------- 1 | /* 2 | Auto-generated by: https://github.com/pmndrs/gltfjsx 3 | Author: MMKH (https://sketchfab.com/mmkh) 4 | License: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/) 5 | Source: https://sketchfab.com/3d-models/snowglobe-day-11-3dinktober2019-snow-08b4443a71c94647a1298346a1ea1203 6 | Title: Snowglobe - Day 11 #3DInktober2019-Snow 7 | */ 8 | 9 | import { useFrame, useThree } from '@react-three/fiber' 10 | import { MeshTransmissionMaterial, useGLTF, useTexture, Text, Billboard, Text3D } from '@react-three/drei' 11 | import * as THREE from 'three' 12 | import { useMemo, useRef, useLayoutEffect, useState, useEffect, memo } from 'react' 13 | import { easing } from 'maath' 14 | import gsap from 'gsap' 15 | import SnowFlakes from './SnowFlakes' 16 | import FireWorks from './FireWorks' 17 | import LumaWorld from './LumaWorld' 18 | 19 | export default function SnowGlobeModel(props) { 20 | const { nodes, materials } = useGLTF('/snowglobe-transformed.glb') 21 | const footer = document.querySelector('.footer') 22 | const snowGlobeRef = useRef() 23 | const snowGlobeRef2 = useRef() 24 | const internalWorldRef = useRef() 25 | const fireWorksRef = useRef() 26 | const [insideMesh, setInsideMesh] = useState(false) 27 | 28 | const groupRef = useRef() 29 | const texture = useTexture('/epic.jpg') 30 | const { camera } = useThree() 31 | const cameraPosition = camera.position 32 | const ray = new THREE.Ray(new THREE.Vector2(0, 0), cameraPosition) 33 | const raycaster = new THREE.Raycaster() 34 | 35 | function useGsapContext(scope) { 36 | const ctx = useMemo(() => gsap.context(() => {}, scope), [scope]) 37 | return ctx 38 | } 39 | 40 | const ctx = useGsapContext(snowGlobeRef) 41 | 42 | useLayoutEffect(() => { 43 | gsap.to(camera.position, { 44 | z: props.inside ? 0.1 : 3, 45 | x: props.inside ? 0.1 : 3, 46 | ease: 'power3.inOut', 47 | duration: 1.8 48 | }) 49 | 50 | return () => ctx.revert() 51 | }, [props.inside]) 52 | 53 | useFrame((state, delta) => { 54 | checkIntersection(snowGlobeRef.current, delta) 55 | }) 56 | 57 | const checkIntersection = (object, delta) => { 58 | raycaster.set(cameraPosition, ray.direction) 59 | 60 | const intersections = raycaster.intersectObject(object) 61 | 62 | if (intersections.length > 0) { 63 | setInsideMesh(false) 64 | } else { 65 | setInsideMesh(true) 66 | } 67 | easing.dampC(internalWorldRef.current.material.color, intersections.length > 0 ? 'grey' : 'white', 0.25, delta) 68 | easing.damp(footer.style, 'opacity', intersections.length > 0 ? '0.1' : '1', 0.25, delta) 69 | } 70 | 71 | useEffect(() => { 72 | if (!insideMesh) { 73 | snowGlobeRef2.current.visible = false 74 | snowGlobeRef.current.visible = false 75 | fireWorksRef.current.visible = true 76 | camera.fov = 95 77 | camera.updateProjectionMatrix() 78 | } else { 79 | camera.fov = 65 80 | camera.updateProjectionMatrix() 81 | snowGlobeRef2.current.visible = true 82 | snowGlobeRef.current.visible = true 83 | fireWorksRef.current.visible = false 84 | } 85 | }, [insideMesh]) 86 | 87 | return ( 88 | 93 | 99 | 113 | 114 | 115 | 119 | 120 | 125 | 126 | 127 | 133 | 139 | 140 | 141 | {!props.isMobile && } 142 | 143 | 144 | ) 145 | } 146 | useGLTF.preload('/snowglobe-transformed.glb') 147 | 148 | function Texts() { 149 | return ( 150 | <> 151 | 157 | ANDERSON MANCINI 158 | 163 | 164 | 165 | 173 | Wishing you a restful holiday season. {'\n\n'}May your Christmas be blessed with lots of love, peace, and happiness. 174 | 175 | 182 | I will be on vacation until January 8th. Thank you for all your support in 2023. Great things are coming in 2024. Please stay tunned. 183 | 184 | 190 | Anderson Mancini 191 | 192 | 193 | 194 | ) 195 | } 196 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import './styles.css' 3 | import App from './App' 4 | 5 | createRoot(document.getElementById('root')).render() 6 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | user-select: none; 4 | } 5 | 6 | html, 7 | body, 8 | #root { 9 | width: 100%; 10 | height: 100%; 11 | margin: 0; 12 | padding: 0; 13 | overflow: hidden; 14 | } 15 | 16 | body { 17 | background: #f0f0f0; 18 | } 19 | header { 20 | position: absolute; 21 | top: 0; 22 | left: 50%; 23 | width: 20%; 24 | transform: translateX(-50%); 25 | display: flex; 26 | } 27 | footer { 28 | position: absolute; 29 | bottom: 40px; 30 | color: white; 31 | left: 50%; 32 | transform: translateX(-50%); 33 | font-family: Arial, sans-serif; 34 | font-size: 12px; 35 | text-align: center; 36 | width: 100%; 37 | } 38 | 39 | button { 40 | margin: 20px 0; 41 | padding: 20px 40px; 42 | border-radius: 50px; 43 | border: none; 44 | font-size: calc(1em + 0.690625vw); 45 | font-weight: bold; 46 | cursor: pointer; 47 | transition: all 1.2s ease; 48 | } 49 | 50 | button:hover { 51 | padding: 20px 50px; 52 | color: white; 53 | background-color: black; 54 | } 55 | 56 | @media only screen and (max-width: 600px) { 57 | header { 58 | width: 50%; 59 | } 60 | } 61 | --------------------------------------------------------------------------------