mirror of
https://github.com/corvax-team/ss14-wl.git
synced 2026-02-15 03:31:38 +01:00
Atmospherics/Temperature HeatContainers (#39997)
* Initial HeatContainer logic * comment fixes * Comment changes + ChangeHeatCapacity * highly intelligent specimen * n-body full heat exchange methods * extract to partials * highly intelligent specimen * fixes + ChangeHeatCapacityKeepTemperature * Divide and merge methods * even divide * different merge signature * forgot one little thing * address review * missing docs * addr review * oops * review --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
This commit is contained in:
66
Content.Shared/Temperature/HeatContainer/HeatContainer.cs
Normal file
66
Content.Shared/Temperature/HeatContainer/HeatContainer.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Temperature.HeatContainer;
|
||||
|
||||
/// <summary>
|
||||
/// A general-purpose container for heat energy.
|
||||
/// Any object that contains, stores, or transfers heat should use a <see cref="HeatContainer"/>
|
||||
/// instead of implementing its own system.
|
||||
/// This allows for consistent heat transfer mechanics across different objects and systems.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable, DataDefinition]
|
||||
[Access(typeof(HeatContainerHelpers), typeof(SharedAtmosphereSystem))]
|
||||
public partial struct HeatContainer : IRobustCloneable<HeatContainer>
|
||||
{
|
||||
/// <summary>
|
||||
/// The heat capacity of this container in Joules per Kelvin.
|
||||
/// This determines how much energy is required to change the temperature of the container.
|
||||
/// Higher values mean the container can absorb or release more heat energy
|
||||
/// without a significant change in temperature.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float HeatCapacity = 4000f; // about 1kg of water
|
||||
|
||||
/// <summary>
|
||||
/// The current temperature of the container in Kelvin.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Temperature = Atmospherics.T20C; // room temperature
|
||||
|
||||
/// <summary>
|
||||
/// The current temperature of the container in Celsius.
|
||||
/// Ideal if you just need to read the temperature for UI.
|
||||
/// Do not perform computations in Celsius/set this value, use Kelvin instead.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float TemperatureC => TemperatureHelpers.KelvinToCelsius(Temperature);
|
||||
|
||||
/// <summary>
|
||||
/// The current thermal energy of the container in Joules.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float InternalEnergy => Temperature * HeatCapacity;
|
||||
|
||||
public HeatContainer(float heatCapacity, float temperature)
|
||||
{
|
||||
HeatCapacity = heatCapacity;
|
||||
Temperature = temperature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy constructor for implementing ICloneable.
|
||||
/// </summary>
|
||||
/// <param name="c">The HeatContainer to copy.</param>
|
||||
private HeatContainer(HeatContainer c)
|
||||
{
|
||||
HeatCapacity = c.HeatCapacity;
|
||||
Temperature = c.Temperature;
|
||||
}
|
||||
|
||||
public HeatContainer Clone()
|
||||
{
|
||||
return new HeatContainer(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Shared.Temperature.HeatContainer;
|
||||
|
||||
public static partial class HeatContainerHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Conducts heat between a <see cref="HeatContainer"/> and some body with a different temperature,
|
||||
/// given some constant thermal conductance g and a small time delta.
|
||||
/// </summary>
|
||||
/// <param name="c">The <see cref="HeatContainer"/> to conduct heat to.</param>
|
||||
/// <param name="temp">The temperature of the second object that we are conducting heat with, in kelvin.</param>
|
||||
/// <param name="deltaTime">
|
||||
/// The amount of time that the heat is allowed to conduct, in seconds.
|
||||
/// This value should be small such that deltaTime << C / g where C is the heat capacity of the container.
|
||||
/// If you need to simulate a larger time step split it into several smaller ones.
|
||||
/// </param>
|
||||
/// <param name="g">The thermal conductance in watt per kelvin. This describes how well heat flows between the bodies.</param>
|
||||
/// <returns>The amount of heat in joules that was added to the heat container.</returns>
|
||||
/// <example>A positive value indicates heat transfer from a hot body to a cold heat container c.</example>
|
||||
/// <remarks>
|
||||
/// This performs a single step using the Euler method for solving the Fourier heat equation
|
||||
/// \frac{dQ}{dt} = g \Delta T.
|
||||
/// If we need more precision in the future consider using a higher order integration scheme.
|
||||
/// If we need support for larger time steps in the future consider adding a method to split the time delta into several
|
||||
/// integration steps with adaptive step size.
|
||||
/// </remarks>
|
||||
[PublicAPI]
|
||||
public static float ConductHeat(this HeatContainer c, float temp, float deltaTime, float g)
|
||||
{
|
||||
var dQ = c.ConductHeatQuery(temp, deltaTime, g);
|
||||
c.AddHeat(dQ);
|
||||
return dQ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Conducts heat between two <see cref="HeatContainer"/>s,
|
||||
/// given some constant thermal conductance g and a small time delta.
|
||||
/// </summary>
|
||||
/// <param name="cA">The first <see cref="HeatContainer"/> to conduct heat to.</param>
|
||||
/// <param name="cB">The second <see cref="HeatContainer"/> to conduct heat to.</param>
|
||||
/// <param name="deltaTime">
|
||||
/// The amount of time that the heat is allowed to conduct, in seconds.
|
||||
/// This value should be small such that deltaTime << C / g where C is the heat capacity of the containers.
|
||||
/// If you need to simulate a larger time step split it into several smaller ones.
|
||||
/// </param>
|
||||
/// <param name="g">The thermal conductance in watt per kelvin. This describes how well heat flows between the bodies.</param>
|
||||
/// <returns>The amount of heat in joules that is exchanged between the bodies.</returns>
|
||||
/// <example>A positive value indicates heat transfer from a hot cB to a cold cA.</example>
|
||||
/// <remarks>
|
||||
/// This performs a single step using the Euler method for solving the Fourier heat equation
|
||||
/// \frac{dQ}{dt} = g \Delta T.
|
||||
/// If we need more precision in the future consider using a higher order integration scheme.
|
||||
/// If we need support for larger time steps in the future consider adding a method to split the time delta into several
|
||||
/// integration steps with adaptive step size.
|
||||
/// </remarks>
|
||||
[PublicAPI]
|
||||
public static float ConductHeat(this HeatContainer cA, HeatContainer cB, float deltaTime, float g)
|
||||
{
|
||||
var dQ = ConductHeatQuery(cA, cB.Temperature, deltaTime, g);
|
||||
cA.AddHeat(dQ);
|
||||
cB.AddHeat(-dQ);
|
||||
return dQ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the amount of heat that would be conducted between a <see cref="HeatContainer"/> and some body with a different temperature,
|
||||
/// given some constant thermal conductance g and a small time delta.
|
||||
/// </summary>
|
||||
/// <param name="c">The <see cref="HeatContainer"/> to conduct heat to.</param>
|
||||
/// <param name="temp">The temperature of the second object that we are conducting heat with, in kelvin.</param>
|
||||
/// <param name="deltaTime">
|
||||
/// The amount of time that the heat is allowed to conduct, in seconds.
|
||||
/// This value should be small such that deltaTime << C / g where C is the heat capacity of the container.
|
||||
/// If you need to simulate a larger time step split it into several smaller ones.
|
||||
/// </param>
|
||||
/// <param name="g">The thermal conductance in watt per kelvin. This describes how well heat flows between the bodies.</param>
|
||||
/// <returns>The amount of heat in joules that would be exchanged between the bodies.</returns>
|
||||
/// <example>A positive value indicates heat transfer from a hot body to a cold heat container c.</example>
|
||||
/// <remarks>
|
||||
/// This performs a single step using the Euler method for solving the Fourier heat equation
|
||||
/// \frac{dQ}{dt} = g \Delta T.
|
||||
/// If we need more precision in the future consider using a higher order integration scheme.
|
||||
/// If we need support for larger time steps in the future consider adding a method to split the time delta into several
|
||||
/// integration steps with adaptive step size.
|
||||
/// </remarks>
|
||||
[PublicAPI]
|
||||
public static float ConductHeatQuery(this HeatContainer c, float temp, float deltaTime, float g)
|
||||
{
|
||||
var dQ = g * (temp - c.Temperature) * deltaTime;
|
||||
var dQMax = Math.Abs(ConductHeatToTempQuery(c, temp));
|
||||
|
||||
// Clamp the transferred heat amount in case we are overshooting the equilibrium temperature because our time step was too large.
|
||||
return Math.Clamp(dQ, -dQMax, dQMax);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the amount of heat that would be conducted between two <see cref="HeatContainer"/>s,
|
||||
/// given some conductivity constant k and a time delta. Does not modify the containers.
|
||||
/// </summary>
|
||||
/// <param name="c1">The first <see cref="HeatContainer"/> to conduct heat to.</param>
|
||||
/// <param name="c2">The second <see cref="HeatContainer"/> to conduct heat to.</param>
|
||||
/// <param name="deltaTime">
|
||||
/// The amount of time that the heat is allowed to conduct, in seconds.
|
||||
/// This value should be small such that deltaTime << C / g where C is the heat capacity of the container.
|
||||
/// If you need to simulate a larger time step split it into several smaller ones.
|
||||
/// </param>
|
||||
/// <param name="g">The thermal conductance in watt per kelvin. This describes how well heat flows between the bodies.</param>
|
||||
/// <returns>The amount of heat in joules that would be exchanged between the bodies.</returns>
|
||||
/// <example>A positive value indicates heat transfer from a hot c2 to a cold c1.</example>
|
||||
/// <remarks>
|
||||
/// This performs a single step using the Euler method for solving the Fourier heat equation
|
||||
/// \frac{dQ}{dt} = g \Delta T.
|
||||
/// If we need more precision in the future consider using a higher order integration scheme.
|
||||
/// If we need support for larger time steps in the future consider adding a method to split the time delta into several
|
||||
/// integration steps with adaptive step size.
|
||||
/// </remarks>
|
||||
[PublicAPI]
|
||||
public static float ConductHeatQuery(this HeatContainer c1, HeatContainer c2, float deltaTime, float g)
|
||||
{
|
||||
return ConductHeatQuery(c1, c2.Temperature, deltaTime, g);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the temperature of a <see cref="HeatContainer"/> to a target temperature by
|
||||
/// adding or removing the necessary amount of heat.
|
||||
/// </summary>
|
||||
/// <param name="c">The <see cref="HeatContainer"/> to change the temperature of.</param>
|
||||
/// <param name="targetTemp">The desired temperature to reach.</param>
|
||||
/// <returns>The amount of heat in joules that was transferred to or from the <see cref="HeatContainer"/>
|
||||
/// to reach the target temperature.</returns>
|
||||
/// <example>A positive value indicates heat must be added to the container to reach the target temperature.</example>
|
||||
[PublicAPI]
|
||||
public static float ConductHeatToTemp(this HeatContainer c, float targetTemp)
|
||||
{
|
||||
var dQ = ConductHeatToTempQuery(c, targetTemp);
|
||||
c.Temperature = targetTemp;
|
||||
return dQ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the amount of heat that must be transferred to or from a <see cref="HeatContainer"/>
|
||||
/// to reach a target temperature. Does not modify the heat container.
|
||||
/// </summary>
|
||||
/// <param name="c">The <see cref="HeatContainer"/> to query.</param>
|
||||
/// <param name="targetTemp">The desired temperature to reach.</param>
|
||||
/// <returns>The amount of heat in joules that must be transferred to or from the <see cref="HeatContainer"/>
|
||||
/// to reach the target temperature.</returns>
|
||||
/// <example>A positive value indicates heat must be added to the container to reach the target temperature.</example>
|
||||
[PublicAPI]
|
||||
public static float ConductHeatToTempQuery(this HeatContainer c, float targetTemp)
|
||||
{
|
||||
return (targetTemp - c.Temperature) * c.HeatCapacity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Shared.Temperature.HeatContainer;
|
||||
|
||||
public static partial class HeatContainerHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Splits a <see cref="HeatContainer"/> into two.
|
||||
/// </summary>
|
||||
/// <param name="c">The <see cref="HeatContainer"/> to split. This will be modified to contain the remaining heat capacity.</param>
|
||||
/// <param name="fraction">The fraction of the heat capacity to move to the new container. Clamped between 0 and 1.</param>
|
||||
/// <returns>A new <see cref="HeatContainer"/> containing the specified fraction of the original container's heat capacity and the same temperature.</returns>
|
||||
[PublicAPI]
|
||||
public static HeatContainer Split(this ref HeatContainer c, float fraction = 0.5f)
|
||||
{
|
||||
fraction = Math.Clamp(fraction, 0f, 1f);
|
||||
var newHeatCapacity = c.HeatCapacity * fraction;
|
||||
|
||||
var newContainer = new HeatContainer
|
||||
{
|
||||
HeatCapacity = newHeatCapacity,
|
||||
Temperature = c.Temperature,
|
||||
};
|
||||
|
||||
c.HeatCapacity -= newHeatCapacity;
|
||||
|
||||
return newContainer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Divides a source <see cref="HeatContainer"/> into a specified number of equal parts.
|
||||
/// </summary>
|
||||
/// <param name="c">The input <see cref="HeatContainer"/> to split.</param>
|
||||
/// <param name="num">The number of <see cref="HeatContainer"/>s
|
||||
/// to split the source <see cref="HeatContainer"/> into.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when attempting to divide the source container by zero.</exception>
|
||||
/// <returns>An array of <see cref="HeatContainer"/>s equally split from the source <see cref="HeatContainer"/>.</returns>
|
||||
[PublicAPI]
|
||||
public static HeatContainer[] Divide(this HeatContainer c, uint num)
|
||||
{
|
||||
if (num == 0)
|
||||
throw new ArgumentException("Cannot divide by zero.", nameof(num));
|
||||
|
||||
var fraction = 1f / num;
|
||||
var cFrac = c.Split(fraction);
|
||||
var containers = new HeatContainer[num];
|
||||
|
||||
for (var i = 0; i < num; i++)
|
||||
{
|
||||
containers[i] = cFrac;
|
||||
}
|
||||
|
||||
return containers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Shared.Temperature.HeatContainer;
|
||||
|
||||
public static partial class HeatContainerHelpers
|
||||
{
|
||||
#region 2-Body Exchange
|
||||
|
||||
/// <summary>
|
||||
/// Determines the amount of heat energy that must be transferred between two heat containers
|
||||
/// to bring them into thermal equilibrium.
|
||||
/// Does not modify the containers.
|
||||
/// </summary>
|
||||
/// <param name="cA">The first <see cref="HeatContainer"/> to exchange heat.</param>
|
||||
/// <param name="cB">The second <see cref="HeatContainer"/> to exchange heat with.</param>
|
||||
/// <returns>The amount of heat in joules that is needed
|
||||
/// to bring the containers to thermal equilibrium.</returns>
|
||||
/// <example>A positive value indicates heat transfer from a hot cA to a cold cB.</example>
|
||||
[PublicAPI]
|
||||
public static float EquilibriumHeatQuery(this HeatContainer cA, HeatContainer cB)
|
||||
{
|
||||
/*
|
||||
The solution is derived from the following facts:
|
||||
1. Let Q be the amount of heat energy transferred from cA to cB.
|
||||
2. T_A > T_B, so heat will flow from cA to cB.
|
||||
3. The energy lost by T_A is equal to Q = C_A * (T_A_initial - T_A_final)
|
||||
4. The energy gained by T_B is equal to Q = C_B * (T_B_final - T_B_initial)
|
||||
5. Energy is conserved. So T_A_final and T_B_final can be expressed as:
|
||||
T_A_final = T_A_initial - Q / C_A
|
||||
T_B_final = T_B_initial + Q / C_B
|
||||
6. At thermal equilibrium, T_A_final = T_B_final.
|
||||
7. Solve for Q.
|
||||
*/
|
||||
return (cA.Temperature - cB.Temperature) *
|
||||
(cA.HeatCapacity * cB.HeatCapacity / (cA.HeatCapacity + cB.HeatCapacity));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the resulting temperature if two heat containers are brought into thermal equilibrium.
|
||||
/// Does not modify the containers.
|
||||
/// </summary>
|
||||
/// <param name="cA">The first <see cref="HeatContainer"/> to exchange heat.</param>
|
||||
/// <param name="cB">The second <see cref="HeatContainer"/> to exchange heat with.</param>
|
||||
/// <returns>The resulting equilibrium temperature both containers will be at.</returns>
|
||||
[PublicAPI]
|
||||
public static float EquilibriumTemperatureQuery(this HeatContainer cA, HeatContainer cB)
|
||||
{
|
||||
// Insert the above solution for Q into T_A_final = T_A_initial - Q / C_A and rearrange the result.
|
||||
return (cA.HeatCapacity * cA.Temperature - cB.HeatCapacity * cB.Temperature) / (cA.HeatCapacity + cB.HeatCapacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Brings two <see cref="HeatContainer"/>s into thermal equilibrium by exchanging heat.
|
||||
/// </summary>
|
||||
/// <param name="cA">The first <see cref="HeatContainer"/> to exchange heat.</param>
|
||||
/// <param name="cB">The second <see cref="HeatContainer"/> to exchange heat with.</param>
|
||||
[PublicAPI]
|
||||
public static void Equilibrate(this HeatContainer cA, HeatContainer cB)
|
||||
{
|
||||
var tFinal = EquilibriumTemperatureQuery(cA, cB);
|
||||
cA.Temperature = tFinal;
|
||||
cB.Temperature = tFinal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Brings two <see cref="HeatContainer"/>s into thermal equilibrium by exchanging heat.
|
||||
/// </summary>
|
||||
/// <param name="cA">The first <see cref="HeatContainer"/> to exchange heat.</param>
|
||||
/// <param name="cB">The second <see cref="HeatContainer"/> to exchange heat with.</param>
|
||||
/// <param name="dQ">The amount of heat in joules that was transferred from container A to B.</param>
|
||||
[PublicAPI]
|
||||
public static void Equilibrate(this HeatContainer cA, HeatContainer cB, out float dQ)
|
||||
{
|
||||
var tInitialA = cA.Temperature;
|
||||
var tFinal = EquilibriumTemperatureQuery(cA, cB);
|
||||
cA.Temperature = tFinal;
|
||||
cB.Temperature = tFinal;
|
||||
dQ = (tInitialA - tFinal) / cA.HeatCapacity;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region N-Body Exchange
|
||||
|
||||
/// <summary>
|
||||
/// Brings an array of <see cref="HeatContainer"/>s into thermal equilibrium by exchanging heat.
|
||||
/// </summary>
|
||||
/// <param name="cN">The array of <see cref="HeatContainer"/>s to bring into thermal equilibrium.</param>
|
||||
[PublicAPI]
|
||||
public static void Equilibrate(this HeatContainer[] cN)
|
||||
{
|
||||
var tF = cN.EquilibriumTemperatureQuery();
|
||||
for (var i = 0; i < cN.Length; i++)
|
||||
{
|
||||
cN[i].Temperature = tF;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Brings a <see cref="HeatContainer"/> into thermal equilibrium
|
||||
/// with an array of other <see cref="HeatContainer"/>s by exchanging heat.
|
||||
/// </summary>
|
||||
/// <param name="cA">The first <see cref="HeatContainer"/> to bring into thermal equilibrium.</param>
|
||||
/// <param name="cN">The array of <see cref="HeatContainer"/>s to bring into thermal equilibrium.</param>
|
||||
[PublicAPI]
|
||||
public static void Equilibrate(this HeatContainer cA, HeatContainer[] cN)
|
||||
{
|
||||
var tF = cA.EquilibriumTemperatureQuery(cN);
|
||||
|
||||
cA.Temperature = tF;
|
||||
for (var i = 0; i < cN.Length; i++)
|
||||
{
|
||||
cN[i].Temperature = tF;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the final temperature of an array of <see cref="HeatContainer"/>s
|
||||
/// when they are brought into thermal equilibrium. Does not modify the containers.
|
||||
/// </summary>
|
||||
/// <param name="cN">The array of <see cref="HeatContainer"/>s to bring into thermal equilibrium.</param>
|
||||
/// <returns>The temperature of all <see cref="HeatContainer"/>s involved after reaching thermal equilibrium.</returns>
|
||||
[PublicAPI]
|
||||
public static float EquilibriumTemperatureQuery(this HeatContainer[] cN)
|
||||
{
|
||||
/*
|
||||
The solution is derived via the following:
|
||||
|
||||
1. In thermal equilibrium all bodies have the same temperature T_f.
|
||||
|
||||
2. Heat exchange for each body is defined by the equation \Delta Q_n = C_n \Delta T_n = C_n (T_f - T_n)
|
||||
where C_n is the heat capacity and \Delta T_n the change in temperature of the n-th body.
|
||||
|
||||
3. Heat energy must be conserved, so the sum of all heat changes must equal zero.
|
||||
Therefore, \sum_{n=1}^{N} Q_n = 0.
|
||||
|
||||
4. Substitute and expand.
|
||||
\sum_{n=1}^{N} C_n (T_f - T_n) = 0.
|
||||
|
||||
5. Unroll and expand.
|
||||
C_1(T_f - T_1) + C_2(T_f - T_2) + ... + C_n(T_f - T_n) = 0
|
||||
C_1 T_f - C_1 T_1 + C_2 T_f - C_2 T_2 + ... + C_n T_f - C_n T_n = 0
|
||||
|
||||
6. Group like terms.
|
||||
T_f(C_1 + C_2 + ... + C_n) - (C_1 T_1 + C_2 T_2 + ... + C_n T_n) = 0
|
||||
|
||||
7. Solve.
|
||||
T_f(C_1 + C_2 + ... + C_n) = (C_1 T_1 + C_2 T_2 + ... + C_n T_n)
|
||||
T_f = \frac{C_1 T_1 + C_2 T_2 + ... + C_n T_n}{C_1 + C_2 + ... + C_n}
|
||||
|
||||
8. Summation.
|
||||
T_f = \frac{\sum(C_n T_n)}{\sum(C_n)}
|
||||
*/
|
||||
|
||||
var numerator = 0f;
|
||||
var denominator = 0f;
|
||||
|
||||
foreach (var c in cN)
|
||||
{
|
||||
numerator += c.HeatCapacity * c.Temperature;
|
||||
denominator += c.HeatCapacity;
|
||||
}
|
||||
|
||||
return numerator / denominator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the final temperature of an array of <see cref="HeatContainer"/>s
|
||||
/// when they are brought into thermal equilibrium. Does not modify the containers.
|
||||
/// </summary>
|
||||
/// <param name="cN">The array of <see cref="HeatContainer"/>s to bring into thermal equilibrium.</param>
|
||||
/// <param name="dQ">The amount of heat in joules that was added to each container
|
||||
/// to reach thermal equilibrium.</param>
|
||||
/// <returns>The temperature of all <see cref="HeatContainer"/>s involved after reaching thermal equilibrium.</returns>
|
||||
[PublicAPI]
|
||||
public static float EquilibriumTemperatureQuery(this HeatContainer[] cN, out float[] dQ)
|
||||
{
|
||||
/*
|
||||
For finding the total heat exchanged during the equalization between a group of bodies
|
||||
take the difference of the internal energy before and after the exchange.
|
||||
|
||||
dQ = C * (T_f - T_i) for each container
|
||||
*/
|
||||
|
||||
var tF = cN.EquilibriumTemperatureQuery();
|
||||
dQ = new float[cN.Length];
|
||||
|
||||
for (var i = 0; i < cN.Length; i++)
|
||||
{
|
||||
dQ[i] = cN[i].HeatCapacity * (tF - cN[i].Temperature);
|
||||
}
|
||||
|
||||
return tF;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the final temperature of a <see cref="HeatContainer"/> when it is brought into thermal equilibrium
|
||||
/// with an array of other <see cref="HeatContainer"/>s. Does not modify the containers.
|
||||
/// </summary>
|
||||
/// <param name="cA">The first <see cref="HeatContainer"/> to bring into thermal equilibrium.</param>
|
||||
/// <param name="cN">The array of <see cref="HeatContainer"/>s to bring into thermal equilibrium.</param>
|
||||
/// <returns>The temperature of all <see cref="HeatContainer"/>s involved after reaching thermal equilibrium.</returns>
|
||||
[PublicAPI]
|
||||
public static float EquilibriumTemperatureQuery(this HeatContainer cA, HeatContainer[] cN)
|
||||
{
|
||||
var cAll = new HeatContainer[cN.Length + 1];
|
||||
cAll[0] = cA;
|
||||
cN.CopyTo(cAll, 1);
|
||||
|
||||
return cAll.EquilibriumTemperatureQuery();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Shared.Temperature.HeatContainer;
|
||||
|
||||
public static partial class HeatContainerHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Merges two heat containers into one, conserving total internal energy.
|
||||
/// </summary>
|
||||
/// <param name="cA">The first <see cref="HeatContainer"/> to merge. This will be modified to contain the merged result.</param>
|
||||
/// <param name="cB">The second <see cref="HeatContainer"/> to merge.</param>
|
||||
[PublicAPI]
|
||||
public static void Merge(this ref HeatContainer cA, HeatContainer cB)
|
||||
{
|
||||
var merged = new HeatContainer
|
||||
{
|
||||
HeatCapacity = cA.HeatCapacity + cB.HeatCapacity,
|
||||
Temperature = (cA.InternalEnergy + cB.InternalEnergy) / (cA.HeatCapacity + cB.HeatCapacity)
|
||||
};
|
||||
|
||||
cA = merged;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Merges an array of <see cref="HeatContainer"/>s into a single heat container, conserving total internal energy.
|
||||
/// </summary>
|
||||
/// <param name="cA">The first <see cref="HeatContainer"/> to merge.
|
||||
/// This will be modified to contain the merged result.</param>
|
||||
/// <param name="cN">The array of <see cref="HeatContainer"/>s to merge.</param>
|
||||
[PublicAPI]
|
||||
public static void Merge(this ref HeatContainer cA, HeatContainer[] cN)
|
||||
{
|
||||
var cAcN = new HeatContainer[cN.Length + 1];
|
||||
cAcN[0] = cA;
|
||||
cN.CopyTo(cAcN, 1);
|
||||
|
||||
cA = cAcN.Merge();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges an array of <see cref="HeatContainer"/>s into a single heat container, conserving total internal energy.
|
||||
/// </summary>
|
||||
/// <param name="cN">The array of <see cref="HeatContainer"/>s to merge.</param>
|
||||
/// <returns>A new <see cref="HeatContainer"/> representing the merged result.</returns>
|
||||
[PublicAPI]
|
||||
public static HeatContainer Merge(this HeatContainer[] cN)
|
||||
{
|
||||
var totalHeatCapacity = 0f;
|
||||
var totalEnergy = 0f;
|
||||
|
||||
foreach (var c in cN)
|
||||
{
|
||||
totalHeatCapacity += c.HeatCapacity;
|
||||
totalEnergy += c.InternalEnergy;
|
||||
}
|
||||
|
||||
var result = new HeatContainer
|
||||
{
|
||||
HeatCapacity = totalHeatCapacity,
|
||||
Temperature = totalEnergy / totalHeatCapacity,
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Shared.Temperature.HeatContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Class containing helper methods for working with <see cref="HeatContainer"/>s.
|
||||
/// Use these classes instead of implementing your own heat transfer logic.
|
||||
/// </summary>
|
||||
public static partial class HeatContainerHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds or removes heat energy from the container.
|
||||
/// Positive values add heat, negative values remove heat.
|
||||
/// The temperature can never become lower than 0K even if more heat is removed.
|
||||
/// </summary>
|
||||
/// <param name="c">The <see cref="HeatContainer"/> to add or remove energy.</param>
|
||||
/// <param name="dQ">The energy in joules to add or remove.</param>
|
||||
[PublicAPI]
|
||||
public static void AddHeat(this HeatContainer c, float dQ)
|
||||
{
|
||||
c.Temperature = c.AddHeatQuery(dQ);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the resulting temperature of the container after adding or removing heat energy.
|
||||
/// Positive values add heat, negative values remove heat. This method doesn't change the container's state.
|
||||
/// The temperature can never become lower than 0K even if more heat is removed.
|
||||
/// </summary>
|
||||
/// <param name="c">The <see cref="HeatContainer"/> to query.</param>
|
||||
/// <param name="dQ">The energy in joules to add or remove.</param>
|
||||
/// <returns>The resulting temperature in kelvin after the heat change.</returns>
|
||||
[PublicAPI]
|
||||
public static float AddHeatQuery(this HeatContainer c, float dQ)
|
||||
{
|
||||
// Don't allow the temperature to go below the absolute minimum.
|
||||
return Math.Max(0f, c.Temperature + dQ / c.HeatCapacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the heat capacity of a <see cref="HeatContainer"/> without altering its thermal energy.
|
||||
/// Adjusts the temperature accordingly to maintain the same internal energy.
|
||||
/// </summary>
|
||||
/// <param name="c">The <see cref="HeatContainer"/> to modify.</param>
|
||||
/// <param name="newHeatCapacity">The new heat capacity to set.</param>
|
||||
[PublicAPI]
|
||||
public static void SetHeatCapacity(this HeatContainer c, float newHeatCapacity)
|
||||
{
|
||||
var currentEnergy = c.InternalEnergy;
|
||||
c.HeatCapacity = newHeatCapacity;
|
||||
c.Temperature = currentEnergy / c.HeatCapacity;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user