diff --git a/Content.Server/Atmos/Components/HeatExchangerComponent.cs b/Content.Server/Atmos/Components/HeatExchangerComponent.cs index 4ed9e4127634..13c51b307a3b 100644 --- a/Content.Server/Atmos/Components/HeatExchangerComponent.cs +++ b/Content.Server/Atmos/Components/HeatExchangerComponent.cs @@ -3,34 +3,26 @@ namespace Content.Server.Atmos.Components; [RegisterComponent] public sealed partial class HeatExchangerComponent : Component { - [ViewVariables(VVAccess.ReadWrite)] - [DataField("inlet")] - public string InletName { get; set; } = "inlet"; [ViewVariables(VVAccess.ReadWrite)] - [DataField("outlet")] - public string OutletName { get; set; } = "outlet"; + [DataField("pipe")] + public string PipeName { get; set; } = "pipe"; - /// - /// Pipe conductivity (mols/kPa/sec). + + /// + /// Thermal convection multiplier. 0.0-1.0 /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("conductivity")] - public float G { get; set; } = 1f; - - /// - /// Thermal convection coefficient (J/degK/sec). + [ViewVariables(VVAccess.ReadWrite)] + [DataField("convection efficiency")] + public float convection_coeff { get; set; } = .5f; + + + /// + /// the simulated surface area for thermal radiation. >0.0 /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("convectionCoefficient")] - public float K { get; set; } = 8000f; + [ViewVariables(VVAccess.ReadWrite)] + [DataField("radiation area")] + public float surface_area { get; set; } = 3f; - /// - /// Thermal radiation coefficient. Number of "effective" tiles this - /// radiator radiates compared to superconductivity tile losses. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("radiationCoefficient")] - public float alpha { get; set; } = 140f; } diff --git a/Content.Server/Atmos/EntitySystems/HeatExchangerSystem.cs b/Content.Server/Atmos/EntitySystems/HeatExchangerSystem.cs index b3644e88b73a..c3c5f27850d1 100644 --- a/Content.Server/Atmos/EntitySystems/HeatExchangerSystem.cs +++ b/Content.Server/Atmos/EntitySystems/HeatExchangerSystem.cs @@ -47,93 +47,81 @@ private void OnAtmosUpdate(EntityUid uid, HeatExchangerComponent comp, ref Atmos { return; } - - if (!_nodeContainer.TryGetNodes(uid, comp.InletName, comp.OutletName, out PipeNode? inlet, out PipeNode? outlet)) - return; - + + if(!_nodeContainer.TryGetNode(uid,comp.PipeName, out PipeNode? pipe ) ) return; + var dt = args.dt; - // Let n = moles(inlet) - moles(outlet), really a Δn - var P = inlet.Air.Pressure - outlet.Air.Pressure; // really a ΔP - // Such that positive P causes flow from the inlet to the outlet. - - // We want moles transferred to be proportional to the pressure difference, i.e. - // dn/dt = G*P - - // To solve this we need to write dn in terms of P. Since PV=nRT, dP/dn=RT/V. - // This assumes that the temperature change from transferring dn moles is negligible. - // Since we have P=Pi-Po, then dP/dn = dPi/dn-dPo/dn = R(Ti/Vi - To/Vo): - float dPdn = Atmospherics.R * (outlet.Air.Temperature / outlet.Air.Volume + inlet.Air.Temperature / inlet.Air.Volume); - - // Multiplying both sides of the differential equation by dP/dn: - // dn/dt * dP/dn = dP/dt = G*P * (dP/dn) - // Which is a first-order linear differential equation with constant (heh...) coefficients: - // dP/dt + kP = 0, where k = -G*(dP/dn). - // This differential equation has a closed-form solution, namely: - float Pfinal = P * MathF.Exp(-comp.G * dPdn * dt); - - // Finally, back out n, the moles transferred in this tick: - float n = (P - Pfinal) / dPdn; - - GasMixture xfer; - if (n > 0) - xfer = inlet.Air.Remove(n); - else - xfer = outlet.Air.Remove(-n); - - float CXfer = _atmosphereSystem.GetHeatCapacity(xfer, true); - if (CXfer < Atmospherics.MinimumHeatCapacity) - return; - - var radTemp = Atmospherics.TCMB; - - var environment = _atmosphereSystem.GetContainingMixture(uid, true, true); - bool hasEnv = false; - float CEnv = 0f; - if (environment != null) - { - CEnv = _atmosphereSystem.GetHeatCapacity(environment, true); - hasEnv = CEnv >= Atmospherics.MinimumHeatCapacity && environment.TotalMoles > 0f; - if (hasEnv) - radTemp = environment.Temperature; - } - - // How ΔT' scales in respect to heat transferred - float TdivQ = 1f / CXfer; - // Since it's ΔT, also account for the environment's temperature change - if (hasEnv) - TdivQ += 1f / CEnv; - - // Radiation - float dTR = xfer.Temperature - radTemp; - float dTRA = MathF.Abs(dTR); - float a0 = tileLoss / MathF.Pow(Atmospherics.T20C, 4); - // ΔT' = -kΔT^4, k = -ΔT'/ΔT^4 - float kR = comp.alpha * a0 * TdivQ; - // Based on the fact that ((3t)^(-1/3))' = -(3t)^(-4/3) = -((3t)^(-1/3))^4, and ΔT' = -kΔT^4. - float dT2R = dTR * MathF.Pow((1f + 3f * kR * dt * dTRA * dTRA * dTRA), -1f/3f); - float dER = (dTR - dT2R) / TdivQ; - _atmosphereSystem.AddHeat(xfer, -dER); - if (hasEnv && environment != null) - { - _atmosphereSystem.AddHeat(environment, dER); - - // Convection - - // Positive dT is from pipe to surroundings - float dT = xfer.Temperature - environment.Temperature; - // ΔT' = -kΔT, k = -ΔT' / ΔT - float k = comp.K * TdivQ; - float dT2 = dT * MathF.Exp(-k * dt); - float dE = (dT - dT2) / TdivQ; - _atmosphereSystem.AddHeat(xfer, -dE); - _atmosphereSystem.AddHeat(environment, dE); - } - - if (n > 0) - _atmosphereSystem.Merge(outlet.Air, xfer); - else - _atmosphereSystem.Merge(inlet.Air, xfer); - + + //first priority is to figure out convection. + var environment = _atmosphereSystem.GetContainingMixture(uid, true, true); + if(environment!=null){ + float envT=environment.Temperature; + float pipeT=pipe.Air.Temperature; + + // (1- 1/( (5*moles+volume)/volume ))^.75 + // math formula, where x is moles and v is volume: \left(1-\frac{1}{\frac{5x+v}{v}}\right)^{.75} + //this is to simulate more moles making more heat transfer avalible. + //this has no basis in reality. + float env_convection_coef= 1f-(1f/((500f*environment.TotalMoles+environment.Volume)/environment.Volume)); + env_convection_coef=MathF.Pow(env_convection_coef,.75f); + + if(envT>pipeT){ // env -> pipe + float EnergyToConvect= comp.convection_coeff*env_convection_coef*(envT-pipeT); + float heatcap_env= _atmosphereSystem.GetHeatCapacity(environment, true); + float heatcap_pipe= _atmosphereSystem.GetHeatCapacity(pipe.Air, true); + + if(heatcap_env>Atmospherics.MinimumHeatCapacity && heatcap_pipe>Atmospherics.MinimumHeatCapacity){ + pipe.Air.Temperature+= EnergyToConvect*dt/heatcap_pipe; //divide by heat capacity to get temp change + environment.Temperature-=EnergyToConvect*dt/heatcap_env; + } + }else{ // pipe->env + float EnergyToConvect= comp.convection_coeff*env_convection_coef*(pipeT-envT); + float heatcap_env= _atmosphereSystem.GetHeatCapacity(environment, true); + float heatcap_pipe= _atmosphereSystem.GetHeatCapacity(pipe.Air, true); + + if(heatcap_env>Atmospherics.MinimumHeatCapacity && heatcap_pipe>Atmospherics.MinimumHeatCapacity){ + pipe.Air.Temperature-= EnergyToConvect*dt/heatcap_pipe; + environment.Temperature+=EnergyToConvect*dt/heatcap_env; + } + } + } + + + //next up, simulate heat loss via radiation. we are assuming a perfect black body for this and also spherical cows. + //this uses the Stefan–Boltzmann law + if(environment!=null){ + float pipetemp=pipe.Air.Temperature; + float envtemp=environment.Temperature; + if (pipe.Air.Temperature pipe + float energy_radiated = Atmospherics.StefanBoltzmann*comp.surface_area*( MathF.Pow(envtemp ,4f)-MathF.Pow(pipetemp ,4f) ); + float heatcap_env= _atmosphereSystem.GetHeatCapacity(environment, true); + float heatcap_pipe= _atmosphereSystem.GetHeatCapacity(pipe.Air, true); + + if(heatcap_env>Atmospherics.MinimumHeatCapacity) environment.Temperature-=energy_radiated*dt/heatcap_env; + if(heatcap_pipe>Atmospherics.MinimumHeatCapacity) pipe.Air.Temperature+=energy_radiated*dt/heatcap_pipe; + }else{ // pipe -> environment + float energy_radiated = Atmospherics.StefanBoltzmann*comp.surface_area*( MathF.Pow(pipetemp ,4f)-MathF.Pow(envtemp ,4f) ); + float heatcap_env= _atmosphereSystem.GetHeatCapacity(environment, true); + float heatcap_pipe= _atmosphereSystem.GetHeatCapacity(pipe.Air, true); + + if(heatcap_env>Atmospherics.MinimumHeatCapacity) environment.Temperature+=energy_radiated*dt/heatcap_env; + if(heatcap_pipe>Atmospherics.MinimumHeatCapacity) pipe.Air.Temperature-=energy_radiated*dt/heatcap_pipe; + } + }else{ //assume space cooling. + float pipetemp=pipe.Air.Temperature; + if (pipe.Air.Temperature pipe + float energy_radiated = Atmospherics.StefanBoltzmann*comp.surface_area*( MathF.Pow(Atmospherics.TCMB ,4f)-MathF.Pow(pipetemp ,4f) ); + float heatcap_pipe= _atmosphereSystem.GetHeatCapacity(pipe.Air, true); + + if(heatcap_pipe>Atmospherics.MinimumHeatCapacity) pipe.Air.Temperature+=energy_radiated*dt/heatcap_pipe; + }else{ // pipe -> space + float energy_radiated = Atmospherics.StefanBoltzmann*comp.surface_area*( MathF.Pow(pipetemp ,4f)- MathF.Pow(Atmospherics.TCMB ,4f) ); + float heatcap_pipe= _atmosphereSystem.GetHeatCapacity(pipe.Air, true); + + if(heatcap_pipe>Atmospherics.MinimumHeatCapacity) pipe.Air.Temperature-=energy_radiated*dt/heatcap_pipe; + } + } + } } diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index 5d4e84a6c25e..608451f272d0 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -14,6 +14,11 @@ public static class Atmospherics /// public const float R = 8.314462618f; + /// + /// Relates energy radiated to temperature. W*m^-2*K^-4 + /// + public const float StefanBoltzmann = 5.670374419e-8f; + /// /// 1 ATM in kPA. /// diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml index 8327937ba865..327397e1f040 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml @@ -458,14 +458,10 @@ - type: HeatExchanger - type: NodeContainer nodes: - inlet: - !type:PipeNode - nodeGroupID: Pipe - pipeDirection: North - outlet: + pipe: !type:PipeNode nodeGroupID: Pipe - pipeDirection: South + pipeDirection: Longitudinal - type: Construction graph: GasBinary node: radiator