Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: display heat loss #47

Merged
merged 1 commit into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading