From 8550e7e14fb58de6e61fb2ca2647cbeb3fcbbff1 Mon Sep 17 00:00:00 2001 From: Thibault Reidy <147397675+ReidyT@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:24:46 +0100 Subject: [PATCH] feat: smoothly change colours (#43) * feat: improve null context errors handling --- src/context/SimulationContext.tsx | 29 ++++++++--------------- src/hooks/useSmoothTransitionColor.tsx | 25 +++++++++++++++++++ src/modules/models/FirTree/useFirTree.tsx | 7 ++++-- src/modules/models/Garden.tsx | 9 ++++--- src/modules/models/Tree/Tree.tsx | 5 +--- src/modules/models/Tree/useTree.tsx | 7 ++++-- src/modules/scenes/FirstScene.tsx | 7 +++--- 7 files changed, 55 insertions(+), 34 deletions(-) create mode 100644 src/hooks/useSmoothTransitionColor.tsx diff --git a/src/context/SimulationContext.tsx b/src/context/SimulationContext.tsx index 0433d5c..f114d09 100644 --- a/src/context/SimulationContext.tsx +++ b/src/context/SimulationContext.tsx @@ -33,8 +33,6 @@ import { initSlidingWindow, } from '../models/TemperatureIterator'; -const SIMULATION_ERROR_CONTEXT = undefinedContextErrorFactory('Simulation'); - type SimulationContextType = { status: SimulationStatus; heatLoss: FormattedHeatLoss; @@ -49,21 +47,7 @@ type SimulationContextType = { startSimulation: () => void; }; -const SimulationContext = createContext({ - status: SimulationStatus.IDLE, - heatLoss: { value: 0, unit: 'W' }, - totalHeatLoss: { value: 0, unit: 'W' }, - electricityCost: 0, - indoorTemperature: 0, - outdoorTemperature: 0, - progression: { progress: 0, timeLeft: 0 }, - period: { from: new Date(), to: new Date(), durationInHours: 0 }, - duration: { value: 0, unit: TimeUnit.Hours }, - material: { price: 0, thermalConductivity: 0, thickness: 0 }, - startSimulation: () => { - throw SIMULATION_ERROR_CONTEXT; - }, -}); +const SimulationContext = createContext(null); type Props = { children: ReactNode; @@ -194,5 +178,12 @@ export const SimulationProvider = ({ ); }; -export const useSimulation = (): SimulationContextType => - useContext(SimulationContext); +export const useSimulation = (): SimulationContextType => { + const context = useContext(SimulationContext); + + if (!context) { + throw undefinedContextErrorFactory('Simulation'); + } + + return context; +}; diff --git a/src/hooks/useSmoothTransitionColor.tsx b/src/hooks/useSmoothTransitionColor.tsx new file mode 100644 index 0000000..a92a33c --- /dev/null +++ b/src/hooks/useSmoothTransitionColor.tsx @@ -0,0 +1,25 @@ +import { useRef } from 'react'; + +import { useFrame } from '@react-three/fiber'; +import { Color, MeshStandardMaterial } from 'three'; + +const TRANSITION_SPEED = 5; + +export const useSmoothTransitionColor = ({ + color, + initialMaterial = new MeshStandardMaterial(), +}: { + color: string | Color; + initialMaterial?: MeshStandardMaterial; +}): MeshStandardMaterial => { + const materialRef = useRef(initialMaterial); + + useFrame((_, delta) => { + if (materialRef.current) { + const targetColor = new Color(color); + materialRef.current.color.lerp(targetColor, delta * TRANSITION_SPEED); // Smooth transition + } + }); + + return materialRef.current ?? initialMaterial; +}; diff --git a/src/modules/models/FirTree/useFirTree.tsx b/src/modules/models/FirTree/useFirTree.tsx index c6ec5e8..ce96620 100644 --- a/src/modules/models/FirTree/useFirTree.tsx +++ b/src/modules/models/FirTree/useFirTree.tsx @@ -4,6 +4,7 @@ import { GLTF } from 'three-stdlib'; import { MODELS_3D_ROOT_PATH } from '@/config/models'; import { useSeason } from '@/context/SeasonContext'; +import { useSmoothTransitionColor } from '@/hooks/useSmoothTransitionColor'; import { Seasons } from '@/types/seasons'; import { fromRGB } from '@/utils/colors'; @@ -39,8 +40,10 @@ type UseFirTree = { export const useFirTree = (): UseFirTree => { const { season } = useSeason(); const { nodes, materials } = useGLTF(GLB_FILE_PATH) as GLTFResult; - const spineMaterial = new MeshStandardMaterial().copy(materials.Spine); - spineMaterial.color = COLORS_BY_SEASON[season]; + const spineMaterial = useSmoothTransitionColor({ + color: COLORS_BY_SEASON[season], + initialMaterial: materials.Spine, + }); return { nodes, diff --git a/src/modules/models/Garden.tsx b/src/modules/models/Garden.tsx index b7d34d9..3763938 100644 --- a/src/modules/models/Garden.tsx +++ b/src/modules/models/Garden.tsx @@ -2,6 +2,7 @@ import { RoundedBox } from '@react-three/drei'; import { Vector3 } from '@react-three/fiber'; import { useSeason } from '@/context/SeasonContext'; +import { useSmoothTransitionColor } from '@/hooks/useSmoothTransitionColor'; import { Seasons } from '@/types/seasons'; const COLORS_BY_SEASON = { @@ -20,13 +21,15 @@ type Props = { export const Garden = ({ position }: Props): JSX.Element => { const { season } = useSeason(); + const material = useSmoothTransitionColor({ + color: COLORS_BY_SEASON[season], + }); return ( - - + material={material} + /> ); }; diff --git a/src/modules/models/Tree/Tree.tsx b/src/modules/models/Tree/Tree.tsx index 4873dc2..569d4e7 100644 --- a/src/modules/models/Tree/Tree.tsx +++ b/src/modules/models/Tree/Tree.tsx @@ -6,14 +6,12 @@ Model: "Trees" from: https://www.sloyd.ai/ */ import { GroupProps } from '@react-three/fiber'; -import { Seasons } from '@/types/seasons'; - import { useTree } from './useTree'; type Props = GroupProps; export const Tree = (props: Props): JSX.Element => { - const { nodes, materials, season } = useTree(); + const { nodes, materials } = useTree(); /** * This code has been generated with the command `npx gltfjsx`. @@ -24,7 +22,6 @@ export const Tree = (props: Props): JSX.Element => { { const { season } = useSeason(); const { nodes, materials } = useGLTF(GLB_FILE_PATH) as GLTFResult; - const leafMaterial = new MeshStandardMaterial().copy(materials.Leaf); - leafMaterial.color = COLORS_BY_SEASON[season]; + const leafMaterial = useSmoothTransitionColor({ + color: COLORS_BY_SEASON[season], + initialMaterial: materials.Leaf, + }); return { season, diff --git a/src/modules/scenes/FirstScene.tsx b/src/modules/scenes/FirstScene.tsx index b284a4c..0def763 100644 --- a/src/modules/scenes/FirstScene.tsx +++ b/src/modules/scenes/FirstScene.tsx @@ -84,15 +84,14 @@ const FirstSceneComponent = (): JSX.Element => { - {/* Ambient Light for overall illumination */} - + {/* Main Sunlight Simulation */}