Skip to content

Commit

Permalink
feat: display the heat loss rate with arrows (#47)
Browse files Browse the repository at this point in the history
* feat(refactor): split the house into smaller components
* feat: compute the heat loss for each wall directly
* feat: improve the simulation informations UI
* feat: simplify the house and set it as a square
  • Loading branch information
ReidyT authored Nov 22, 2024
1 parent 8550e7e commit b1ebce1
Show file tree
Hide file tree
Showing 36 changed files with 1,013 additions and 254 deletions.
Binary file removed models/OriginalFirTree.glb
Binary file not shown.
Binary file removed models/OriginalHouse.glb
Binary file not shown.
Binary file removed models/OriginalTree.glb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
"i18next": "^23.9.0",
"lucide-react": "^0.456.0",
"papaparse": "^5.4.1",
"react": "18.3.1",
"react-dom": "18.3.1",
Expand Down
1 change: 1 addition & 0 deletions public/RobotoRegular.json

Large diffs are not rendered by default.

Binary file added public/models/HeatLossArrow.glb
Binary file not shown.
Binary file removed public/models/House.glb
Binary file not shown.
Binary file added public/models/ResidentialHouse.glb
Binary file not shown.
1 change: 0 additions & 1 deletion src/config/models.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/config/simulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export const SIMULATION_CSV_FILE = {
};

export const SIMULATION_INDOOR_TEMPERATURE_CELCIUS = 22;
export const SIMULATION_AREA_M_SQUARE = 100;
export const SIMULATION_PRICE_KWH = 0.22;
export const SIMULATION_DEFAULT_MATERIAL = {
price: 0,
Expand Down
39 changes: 27 additions & 12 deletions src/context/SimulationContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,21 @@ import {
} from 'react';

import {
SIMULATION_AREA_M_SQUARE,
SIMULATION_DEFAULT_MATERIAL,
SIMULATION_INDOOR_TEMPERATURE_CELCIUS,
SIMULATION_PRICE_KWH,
} from '@/config/simulation';
import { useHeatLoss } from '@/hooks/useHeatLoss';
import {
RegisterComponentParams,
useHouseComponents,
} from '@/hooks/useHouseComponents';
import { useSimulationProgression } from '@/hooks/useSimulationProgression';
import { FormattedHeatLoss } from '@/types/heatLoss';
import {
HeatLossPerComponent,
HouseComponentType,
} from '@/types/houseComponent';
import { Material } from '@/types/material';
import { SimulationProgression, SimulationStatus } from '@/types/simulation';
import { SlidingWindow } from '@/types/temperatures';
Expand All @@ -35,16 +42,17 @@ import {

type SimulationContextType = {
status: SimulationStatus;
heatLoss: FormattedHeatLoss;
heatLosses: HeatLossPerComponent;
totalHeatLoss: FormattedHeatLoss;
electricityCost: number;
indoorTemperature: number;
outdoorTemperature: number;
progression: SimulationProgression;
period: SlidingWindow['period'];
duration: FormattedTime;
material: Material;
materials: Map<HouseComponentType, Material>;
startSimulation: () => void;
registerComponent: (params: RegisterComponentParams) => void;
};

const SimulationContext = createContext<SimulationContextType | null>(null);
Expand Down Expand Up @@ -80,13 +88,18 @@ export const SimulationProvider = ({

// TODO: These parameters will be changed by the user
const indoorTemperature = SIMULATION_INDOOR_TEMPERATURE_CELCIUS;
const area = SIMULATION_AREA_M_SQUARE;
const material: Material = SIMULATION_DEFAULT_MATERIAL;
const materials: Map<HouseComponentType, Material> = useMemo(
() => new Map([[HouseComponentType.Wall, SIMULATION_DEFAULT_MATERIAL]]),
[],
);
const pricekWh = SIMULATION_PRICE_KWH;

const { heatLoss, totalHeatLoss } = useHeatLoss({
material,
area,
const { houseComponents, registerComponent } = useHouseComponents({
materials,
});

const { heatLosses, totalHeatLoss } = useHeatLoss({
houseComponents,
indoorTemperature,
measurementFrequency: csv.measurementFrequency,
temperatures: currentWindow.temperatures,
Expand Down Expand Up @@ -145,25 +158,27 @@ export const SimulationProvider = ({
period: currentWindow.period,
progression,
duration: simulationDuration,
material,
materials,
status: simulationStatus,
heatLoss: formatHeatLossRate(heatLoss),
heatLosses,
totalHeatLoss: formatHeatLossRate(totalHeatLoss),
electricityCost: electricityCost({
pricekWh,
totalEnergyConsumptionkWh:
totalHeatLoss / powerConversionFactors.KiloWatt,
}),
startSimulation,
registerComponent,
}),
[
currentWindow.mean,
currentWindow.period,
heatLoss,
heatLosses,
indoorTemperature,
material,
materials,
pricekWh,
progression,
registerComponent,
simulationDuration,
simulationStatus,
startSimulation,
Expand Down
71 changes: 46 additions & 25 deletions src/hooks/useHeatLoss.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,91 @@
import { useEffect, useMemo, useState } from 'react';

import { Material } from '@/types/material';
import { HeatLossPerComponent, HouseComponents } from '@/types/houseComponent';
import { TimeUnitType } from '@/types/time';
import {
calculateHeatLossConstantFactor,
sumHeatLossRate,
} from '@/utils/heatLoss';

type Props = {
material: Material;
area: number;
houseComponents: HouseComponents;
indoorTemperature: number;
measurementFrequency: TimeUnitType;
temperatures: number[];
};

type UseHeatLossReturnType = {
heatLoss: number;
heatLosses: HeatLossPerComponent;
totalHeatLoss: number;
};

export const useHeatLoss = ({
material,
area,
houseComponents,
indoorTemperature,
measurementFrequency,
temperatures,
}: Props): UseHeatLossReturnType => {
const [heatLoss, setHeatLoss] = useState(0);
const [heatLossPerComponent, setHeatLossPerComponent] =
useState<HeatLossPerComponent>({});
const [totalHeatLoss, setTotalHeatLoss] = useState(0);

const heatLossConstantFactor = useMemo(
// Compute the constant factors per house's components
const heatLossConstantFactors = useMemo(
() =>
calculateHeatLossConstantFactor({
area,
thermalConductivity: material.thermalConductivity,
materialThickness: material.thickness,
}),
[area, material.thermalConductivity, material.thickness],
Array.from(houseComponents.entries()).reduce<HeatLossPerComponent>(
(acc, [id, c]) => ({
...acc,
[id]: calculateHeatLossConstantFactor({
area: c.area,
thermalConductivity: c.material.thermalConductivity,
materialThickness: c.material.thickness,
}),
}),
{},
),
[houseComponents],
);

useEffect(() => {
if (!temperatures) {
setHeatLoss(0);
setHeatLossPerComponent({});
setTotalHeatLoss(0);
return;
}

const newHeatLoss = sumHeatLossRate({
temperatures,
constantFactor: heatLossConstantFactor,
indoorTemperature,
timeUnit: measurementFrequency,
});
const newHeatLossPerComponent = Object.entries(
heatLossConstantFactors,
).reduce<HeatLossPerComponent>(
(acc, [componentId, heatLossConstantFactor]) => ({
...acc,
[componentId]: sumHeatLossRate({
temperatures,
constantFactor: heatLossConstantFactor,
indoorTemperature,
timeUnit: measurementFrequency,
}),
}),
{},
);

setHeatLoss(newHeatLoss);
setTotalHeatLoss((prevT) => prevT + newHeatLoss);
setHeatLossPerComponent(newHeatLossPerComponent);
setTotalHeatLoss(
(prevT) =>
prevT +
Object.values(newHeatLossPerComponent).reduce(
(acc, heatLoss) => acc + heatLoss,
0,
),
);
}, [
measurementFrequency,
heatLossConstantFactor,
temperatures,
indoorTemperature,
heatLossConstantFactors,
]);

return {
heatLoss,
heatLosses: heatLossPerComponent,
totalHeatLoss,
};
};
57 changes: 57 additions & 0 deletions src/hooks/useHouseComponents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useCallback, useRef, useState } from 'react';

import { Vector2 } from 'three';

import { HouseComponentType, HouseComponents } from '@/types/houseComponent';
import { Material } from '@/types/material';

export type RegisterComponentParams = {
id: string;
size: Vector2;
componentType: HouseComponentType;
};

type UseHouseComponentsReturnType = {
houseComponents: HouseComponents;
registerComponent: (params: RegisterComponentParams) => void;
};

type Props = {
materials: Map<HouseComponentType, Material>;
};

export const useHouseComponents = ({
materials,
}: Props): UseHouseComponentsReturnType => {
const houseComponentsRegister = useRef<HouseComponents>(new Map());
const [houseComponents, setHouseComponents] = useState<HouseComponents>(
() => new Map(),
);

const registerComponent = useCallback(
({ id, size, componentType }: RegisterComponentParams): void => {
const material = materials.get(componentType);

if (!material) {
throw new Error(
`No material was found for the component ${componentType}`,
);
}

// The ref is used here to avoid concurrency of updating the state.
// Without the ref, if multiple components register at the same time,
// only the last call to registerComponent will be set in the state.
houseComponentsRegister.current.set(id, {
area: size.x * size.y,
material,
});
setHouseComponents(houseComponentsRegister.current);
},
[houseComponentsRegister, materials],
);

return {
houseComponents,
registerComponent,
};
};
4 changes: 1 addition & 3 deletions src/modules/models/FirTree/useFirTree.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import GLB_FILE_PATH from '@models/FirTree.glb?url';
import { useGLTF } from '@react-three/drei';
import { Mesh, MeshStandardMaterial } from 'three';
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';

const GLB_FILE_PATH = `${MODELS_3D_ROOT_PATH}/FirTree.glb`;

const COLORS_BY_SEASON = {
[Seasons.Summer]: fromRGB({ r: 0.174, g: 0.187, b: 0.097 }),
[Seasons.Autumn]: fromRGB({ r: 0.174, g: 0.187, b: 0.097 }),
Expand Down
89 changes: 89 additions & 0 deletions src/modules/models/HeatLossArrow/HeatLossArrow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
Command: npx [email protected] OriginalArrow.glb --transform --types
Files: OriginalArrow.glb [21.84KB] > Arrow.glb [3.86KB] (82%)
Author: Alihan (https://sketchfab.com/Dare0)
License: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
Source: https://sketchfab.com/3d-models/direction-arrow-6ef46718c7b242e39fcad7f27ee858a5
Title: Direction Arrow
*/
import { useRef } from 'react';

import GLB_FILE_PATH from '@models/HeatLossArrow.glb?url';
import { useGLTF } from '@react-three/drei';
import { useFrame } from '@react-three/fiber';
import { Color, Group, Mesh, MeshStandardMaterial, Vector3 } from 'three';
import { GLTF } from 'three-stdlib';

import { Position } from '@/types/wall';
import { formatHeatLossRate, powerConversionFactors } from '@/utils/heatLoss';

import { HeatLossTextArrow } from './HeatLossTextArrow';

type GLTFResult = GLTF & {
nodes: {
Arrow: Mesh;
};
materials: {
Arrow: MeshStandardMaterial;
};
};

type Props = JSX.IntrinsicElements['group'] & {
heatLoss: number;
position?: Position;
};

// These factors will affect the range of sizes of the arrows.
const MIN_HEATLOSS = 0;
const MAX_HEATLOSS = 5 * powerConversionFactors.KiloWatt;
const MIN_SCALE = 0.8;
const MAX_SCALE = 1.6;

const ARRAY_COLOR = new Color('red');
const TEXT_COLOR = 'white';

export const HeatLossArrow = ({
heatLoss,
position,
...props
}: Props): JSX.Element => {
const { nodes, materials } = useGLTF(GLB_FILE_PATH) as GLTFResult;
const material = materials.Arrow;

const scale =
MIN_SCALE +
(MAX_SCALE - MIN_SCALE) *
Math.min(
1,
Math.max(
0.1,
(heatLoss - MIN_HEATLOSS) / (MAX_HEATLOSS - MIN_HEATLOSS),
),
);

const scaleRef = useRef<Group>(null);

useFrame((_, delta) => {
if (scaleRef.current) {
scaleRef.current.scale.lerp(
new Vector3(1 * scale, 1 * scale, 1 * scale),
delta * 3,
); // Smooth transition
}
});

material.color = ARRAY_COLOR;

return (
<group ref={scaleRef} {...props} position={position} dispose={null}>
<mesh geometry={nodes.Arrow.geometry} material={material} />
<HeatLossTextArrow
heatLoss={formatHeatLossRate(heatLoss)}
color={TEXT_COLOR}
/>
</group>
);
};

useGLTF.preload(GLB_FILE_PATH);
Loading

0 comments on commit b1ebce1

Please sign in to comment.