public void SetupParent(Dictionary <Part, PartSim> partSimLookup, LogMsg log) { if (part.parent != null) { parent = null; if (partSimLookup.TryGetValue(part.parent, out parent)) { if (log != null) { log.AppendLine("Parent part is ", parent.name, ":", parent.partId); } if (part.attachMode == AttachModes.SRF_ATTACH && part.attachRules.srfAttach && part.fuelCrossFeed && part.parent.fuelCrossFeed) { if (log != null) { log.Append("Added (", name, ":", partId) .AppendLine(", ", parent.name, ":", parent.partId, ") to surface mounted fuel targets."); } parent.surfaceMountFuelTargets.Add(this); surfaceMountFuelTargets.Add(parent); } } else { if (log != null) { log.AppendLine("No PartSim for parent part (", part.parent.partInfo.name, ")"); } } } }
public void DumpSourcePartSets(LogMsg log, String msg) { if (log == null) { return; } log.AppendLine("DumpSourcePartSets ", msg); foreach (int type in sourcePartSets.Keys) { log.AppendLine("SourcePartSet for ", ResourceContainer.GetResourceName(type)); HashSet <PartSim> sourcePartSet = sourcePartSets[type]; if (sourcePartSet.Count > 0) { foreach (PartSim partSim in sourcePartSet) { log.AppendLine("Part ", partSim.name, ":", partSim.partId); } } else { log.AppendLine("No parts"); } } }
private void BuildDontStageLists(LogMsg log) { if (log != null) { log.AppendLine("Creating list with capacity of ", (currentStage + 1)); } dontStagePartsLists.Clear(); for (int i = 0; i <= currentStage; i++) { if (i < dontStagePartsLists.Count) { dontStagePartsLists[i].Clear(); } else { dontStagePartsLists.Add(new List <PartSim>()); } } for (int i = 0; i < allParts.Count; ++i) { PartSim partSim = allParts[i]; if (partSim.isEngine || !partSim.resources.Empty) { if (log != null) { log.AppendLine(partSim.name, ":", partSim.partId, " is engine or tank, decoupled = ", partSim.decoupledInStage); } if (partSim.decoupledInStage < -1 || partSim.decoupledInStage > currentStage - 1) { if (log != null) { log.AppendLine("decoupledInStage out of range"); } } else { dontStagePartsLists[partSim.decoupledInStage + 1].Add(partSim); } } } for (int i = 1; i <= lastStage; i++) { if (dontStagePartsLists[i].Count == 0) { dontStagePartsLists[i] = dontStagePartsLists[i - 1]; } } }
private static void CheckForMods() { hasCheckedForMods = true; foreach (var assembly in AssemblyLoader.loadedAssemblies) { log.AppendLine("Assembly: ", assembly.assembly); var name = assembly.assembly.ToString().Split(',')[0]; if (name == "RealFuels") { log.AppendLine("Found RealFuels mod"); var RF_ModuleEngineConfigs_Type = assembly.assembly.GetType("RealFuels.ModuleEngineConfigs"); if (RF_ModuleEngineConfigs_Type != null) { RF_ModuleEngineConfigs_localCorrectThrust = RF_ModuleEngineConfigs_Type.GetField("localCorrectThrust"); } var RF_ModuleHybridEngine_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngine"); if (RF_ModuleHybridEngine_Type != null) { RF_ModuleHybridEngine_localCorrectThrust = RF_ModuleHybridEngine_Type.GetField("localCorrectThrust"); } var RF_ModuleHybridEngines_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngines"); if (RF_ModuleHybridEngines_Type != null) { RF_ModuleHybridEngines_localCorrectThrust = RF_ModuleHybridEngines_Type.GetField("localCorrectThrust"); } hasInstalledRealFuels = true; break; } else if (name == "KerbalIspDifficultyScaler") { log.AppendLine("Found KIDS mod"); var KIDS_Utils_Type = assembly.assembly.GetType("KerbalIspDifficultyScaler.KerbalIspDifficultyScalerUtils"); if (KIDS_Utils_Type != null) { KIDS_Utils_GetIspMultiplier = KIDS_Utils_Type.GetMethod("GetIspMultiplier"); } KIDSparameters = new object[6]; hasInstalledKIDS = true; } } log.Flush(); }
public void CreateEngineSims(List <EngineSim> allEngines, double atmosphere, double mach, bool vectoredThrust, bool fullThrust, LogMsg log) { if (log != null) { log.AppendLine("CreateEngineSims for ", this.name); } List <ModuleEngines> cacheModuleEngines = part.FindModulesImplementing <ModuleEngines>(); try { if (cacheModuleEngines.Count > 0) { //find first active engine, assuming that two are never active at the same time foreach (ModuleEngines engine in cacheModuleEngines) { if (engine.isEnabled) { if (log != null) { log.AppendLine("Module: ", engine.moduleName); } EngineSim engineSim = EngineSim.New( this, engine, atmosphere, (float)mach, vectoredThrust, fullThrust, log); allEngines.Add(engineSim); } } } } catch { Debug.Log("[KER] Error Catch in CreateEngineSims"); } }
private void CalculateThrustAndISP() { // Reset all the values vecThrust = Vector3.zero; vecActualThrust = Vector3.zero; totalVectoredExhaustVelocity = Vector3.zero; totalActualVectoredExhaustVelocity = Vector3.zero; totalExhaustVelocity = 0; totalActualExhaustVelocity = 0; simpleTotalThrust = 0d; simpleActualTotalThrust = 0d; totalStageThrust = 0d; totalStageActualThrust = 0d; totalStageFlowRate = 0d; totalStageIspFlowRate = 0d; totalStageThrustForce.Reset(); // Loop through all the active engines totalling the thrust, actual thrust and mass flow rates // The thrust is totalled as vectors for (int i = 0; i < activeEngines.Count; ++i) { EngineSim engine = activeEngines[i]; simpleTotalThrust += engine.thrust; simpleActualTotalThrust += engine.actualThrust; vecThrust += ((float)engine.thrust * engine.thrustVec); vecActualThrust += ((float)engine.actualThrust * engine.thrustVec); totalVectoredExhaustVelocity += engine.thrustVec * (float)((engine.isp * BasicDeltaV.GRAVITY) / engine.thrust); totalExhaustVelocity += totalVectoredExhaustVelocity.magnitude; totalActualVectoredExhaustVelocity += engine.thrustVec * (float)((engine.isp * BasicDeltaV.GRAVITY) / engine.actualThrust); totalActualExhaustVelocity += totalActualVectoredExhaustVelocity.magnitude; totalStageFlowRate += engine.ResourceConsumptions.Mass; totalStageIspFlowRate += engine.ResourceConsumptions.Mass * engine.isp; for (int j = 0; j < engine.appliedForces.Count; ++j) { totalStageThrustForce.AddForce(engine.appliedForces[j]); } } if (log != null) { log.AppendLine("vecThrust = ", vecThrust.ToString(), " magnitude = ", vecThrust.magnitude); } totalStageThrust = vecThrust.magnitude; totalStageActualThrust = vecActualThrust.magnitude; // Calculate the effective isp at this point if (totalStageFlowRate > 0d && totalStageIspFlowRate > 0d) { currentisp = totalStageIspFlowRate / totalStageFlowRate; } else { currentisp = 0; } }
// This is a new function for STAGE_STACK_FLOW(_BALANCE) public void GetSourceSet(int type, bool includeSurfaceMountedParts, List <PartSim> allParts, HashSet <PartSim> visited, HashSet <PartSim> allSources, LogMsg log, String indent) { // Initial version of support for new flow mode // Call a modified version of the old GetSourceSet code that adds all potential sources rather than stopping the recursive scan // when certain conditions are met int priMax = int.MinValue; GetSourceSet_Internal(type, includeSurfaceMountedParts, allParts, visited, allSources, ref priMax, log, indent); if (log != null) { log.AppendLine(allSources.Count, " parts with priority of ", priMax); } }
public bool SetResourceDrains(LogMsg log, List <PartSim> allParts, List <PartSim> allFuelLines, HashSet <PartSim> drainingParts) { //DumpSourcePartSets(log, "before clear"); foreach (HashSet <PartSim> sourcePartSet in sourcePartSets.Values) { sourcePartSet.Clear(); } //DumpSourcePartSets(log, "after clear"); for (int index = 0; index < this.resourceConsumptionsForMass.Types.Count; index++) { int type = this.resourceConsumptionsForMass.Types[index]; HashSet <PartSim> sourcePartSet; if (!sourcePartSets.TryGetValue(type, out sourcePartSet)) { sourcePartSet = new HashSet <PartSim>(); sourcePartSets.Add(type, sourcePartSet); } switch ((ResourceFlowMode)this.resourceFlowModes[type]) { case ResourceFlowMode.NO_FLOW: if (partSim.resources[type] > SimManager.RESOURCE_MIN && partSim.resourceFlowStates[type] != 0) { sourcePartSet.Add(partSim); } break; case ResourceFlowMode.ALL_VESSEL: case ResourceFlowMode.ALL_VESSEL_BALANCE: for (int i = 0; i < allParts.Count; i++) { PartSim aPartSim = allParts[i]; if (aPartSim.resources[type] > SimManager.RESOURCE_MIN && aPartSim.resourceFlowStates[type] != 0) { sourcePartSet.Add(aPartSim); } } break; case ResourceFlowMode.STAGE_PRIORITY_FLOW: case ResourceFlowMode.STAGE_PRIORITY_FLOW_BALANCE: if (log != null) { log.Append("Find ", ResourceContainer.GetResourceName(type), " sources for ", partSim.name) .AppendLine(":", partSim.partId); } foreach (HashSet <PartSim> stagePartSet in stagePartSets.Values) { stagePartSet.Clear(); } var maxStage = -1; for (int i = 0; i < allParts.Count; i++) { var aPartSim = allParts[i]; //if (log != null) log.Append(aPartSim.name, ":" + aPartSim.partId, " contains ", aPartSim.resources[type]) // .AppendLine((aPartSim.resourceFlowStates[type] == 0) ? " (disabled)" : ""); if (aPartSim.resources[type] <= SimManager.RESOURCE_MIN || aPartSim.resourceFlowStates[type] == 0) { continue; } int stage = aPartSim.inverseStage; if (stage > maxStage) { maxStage = stage; } HashSet <PartSim> tempPartSet; if (!stagePartSets.TryGetValue(stage, out tempPartSet)) { tempPartSet = new HashSet <PartSim>(); stagePartSets.Add(stage, tempPartSet); } tempPartSet.Add(aPartSim); } for (int j = maxStage; j >= -1; j--) { //if (log != null) log.AppendLine("Testing stage ", j); HashSet <PartSim> stagePartSet; if (stagePartSets.TryGetValue(j, out stagePartSet) && stagePartSet.Count > 0) { //if (log != null) log.AppendLine("Not empty"); // We have to copy the contents of the set here rather than copying the set reference or // bad things (tm) happen foreach (PartSim aPartSim in stagePartSet) { sourcePartSet.Add(aPartSim); } break; } } break; case ResourceFlowMode.STACK_PRIORITY_SEARCH: case ResourceFlowMode.STAGE_STACK_FLOW: case ResourceFlowMode.STAGE_STACK_FLOW_BALANCE: visited.Clear(); if (log != null) { log.Append("Find ", ResourceContainer.GetResourceName(type), " sources for ", partSim.name) .AppendLine(":", partSim.partId); } partSim.GetSourceSet(type, allParts, visited, sourcePartSet, log, ""); break; default: if (log != null) { log.Append("SetResourceDrains(", partSim.name, ":", partSim.partId) .AppendLine(") Unexpected flow type for ", ResourceContainer.GetResourceName(type), ")"); } break; } if (log != null && sourcePartSet.Count > 0) { log.AppendLine("Source parts for ", ResourceContainer.GetResourceName(type), ":"); foreach (PartSim partSim in sourcePartSet) { log.AppendLine(partSim.name, ":", partSim.partId); } } //DumpSourcePartSets(log, "after " + ResourceContainer.GetResourceName(type)); } // If we don't have sources for all the needed resources then return false without setting up any drains for (int i = 0; i < this.resourceConsumptionsForMass.Types.Count; i++) { int type = this.resourceConsumptionsForMass.Types[i]; HashSet <PartSim> sourcePartSet; if (!sourcePartSets.TryGetValue(type, out sourcePartSet) || sourcePartSet.Count == 0) { if (log != null) { log.AppendLine("No source of ", ResourceContainer.GetResourceName(type)); } isActive = false; return(false); } } // Now we set the drains on the members of the sets and update the draining parts set for (int i = 0; i < this.resourceConsumptionsForMass.Types.Count; i++) { int type = this.resourceConsumptionsForMass.Types[i]; HashSet <PartSim> sourcePartSet = sourcePartSets[type]; ResourceFlowMode mode = (ResourceFlowMode)resourceFlowModes[type]; double consumption = resourceConsumptionsForMass[type]; double amount = 0d; double total = 0d; if (mode == ResourceFlowMode.ALL_VESSEL_BALANCE || mode == ResourceFlowMode.STAGE_PRIORITY_FLOW_BALANCE || mode == ResourceFlowMode.STAGE_STACK_FLOW_BALANCE || mode == ResourceFlowMode.STACK_PRIORITY_SEARCH) { foreach (PartSim partSim in sourcePartSet) { total += partSim.resources[type]; } } else { amount = consumption / sourcePartSet.Count; } // Loop through the members of the set foreach (PartSim partSim in sourcePartSet) { if (total != 0d) { amount = consumption * partSim.resources[type] / total; } if (log != null) { log.Append("Adding drain of ", amount, " ", ResourceContainer.GetResourceName(type)) .AppendLine(" to ", partSim.name, ":", partSim.partId); } partSim.resourceDrains.Add(type, amount); drainingParts.Add(partSim); } } return(true); }
// This function runs the simulation and returns a newly created array of Stage objects public Stage[] RunSimulation(LogMsg _log) { log = _log; if (log != null) log.AppendLine("RunSimulation started"); _timer.Reset(); _timer.Start(); // Start with the last stage to simulate // (this is in a member variable so it can be accessed by AllowedToStage and ActivateStage) currentStage = lastStage; // Work out which engines would be active if just doing the staging and if this is different to the // currently active engines then generate an extra stage // Loop through all the engines bool anyActive = false; for (int i = 0; i < allEngines.Count; ++i) { EngineSim engine = allEngines[i]; if (log != null) log.AppendLine("Testing engine mod of ", engine.partSim.name, ":", engine.partSim.partId); bool bActive = engine.isActive; bool bStage = (engine.partSim.inverseStage >= currentStage); if (log != null) log.AppendLine("bActive = ", bActive, " bStage = ", bStage); if (HighLogic.LoadedSceneIsFlight) { if (bActive) { anyActive = true; } if (bActive != bStage) { // If the active state is different to the state due to staging if (log != null) log.AppendLine("Need to do current active engines first"); doingCurrent = true; } } else { if (bStage) { if (log != null) log.AppendLine("Marking as active"); engine.isActive = true; } } } // If we need to do current because of difference in engine activation and there actually are active engines // then we do the extra stage otherwise activate the next stage and don't treat it as current if (doingCurrent && anyActive) { currentStage++; } else { ActivateStage(); doingCurrent = false; } // Create a list of lists of PartSims that prevent decoupling BuildDontStageLists(log); if (log != null) log.Flush(); // Create the array of stages that will be returned Stage[] stages = new Stage[currentStage + 1]; int startStage = currentStage; // Loop through the stages while (currentStage >= 0) { if (log != null) { log.AppendLine("Simulating stage ", currentStage); log.Flush(); _timer.Reset(); _timer.Start(); } // Update active engines and resource drains UpdateResourceDrains(); // Update the masses of the parts to correctly handle "no physics" parts stageStartMass = UpdatePartMasses(); if (log != null) allParts[0].DumpPartToLog(log, "", allParts); // Create the Stage object for this stage Stage stage = new Stage(); stageTime = 0d; vecStageDeltaV = Vector3.zero; stageStartCom = ShipCom; stepStartMass = stageStartMass; stepEndMass = 0; CalculateThrustAndISP(); // Store various things in the Stage object stage.thrust = totalStageThrust; stage.thrustToWeight = totalStageThrust / (stageStartMass * gravity); stage.maxThrustToWeight = stage.thrustToWeight; stage.actualThrust = totalStageActualThrust; stage.actualThrustToWeight = totalStageActualThrust / (stageStartMass * gravity); if (log != null) { log.AppendLine("stage.thrust = ", stage.thrust); log.AppendLine("StageMass = ", stageStartMass); log.AppendLine("Initial maxTWR = ", stage.maxThrustToWeight); } // calculate torque and associates stage.maxThrustTorque = totalStageThrustForce.TorqueAt(stageStartCom).magnitude; // torque divided by thrust. imagine that all engines are at the end of a lever that tries to turn the ship. // this numerical value, in meters, would represent the length of that lever. double torqueLeverArmLength = (stage.thrust <= 0) ? 0 : stage.maxThrustTorque / stage.thrust; // how far away are the engines from the CoM, actually? double thrustDistance = (stageStartCom - totalStageThrustForce.GetAverageForceApplicationPoint()).magnitude; // the combination of the above two values gives an approximation of the offset angle. double sinThrustOffsetAngle = 0; if (thrustDistance > 1e-7) { sinThrustOffsetAngle = torqueLeverArmLength / thrustDistance; if (sinThrustOffsetAngle > 1) { sinThrustOffsetAngle = 1; } } stage.thrustOffsetAngle = Math.Asin(sinThrustOffsetAngle) * 180 / Math.PI; // Calculate the total cost of the vessel at this point stage.totalCost = 0d; for (int i = 0; i < allParts.Count; ++i) { if (currentStage > allParts[i].decoupledInStage) stage.totalCost += allParts[i].GetCost(currentStage); } // The total mass is simply the mass at the start of the stage stage.totalMass = stageStartMass; // If we have done a previous stage if (currentStage < startStage) { // Calculate what the previous stage's mass and cost were by subtraction Stage prev = stages[currentStage + 1]; prev.cost = prev.totalCost - stage.totalCost; prev.mass = prev.totalMass - stage.totalMass; } // The above code will never run for the last stage so set those directly if (currentStage == 0) { stage.cost = stage.totalCost; stage.mass = stage.totalMass; } dontStageParts = dontStagePartsLists[currentStage]; if (log != null) { log.AppendLine("Stage setup took ", _timer.ElapsedMilliseconds, "ms"); if (dontStageParts.Count > 0) { log.AppendLine("Parts preventing staging:"); for (int i = 0; i < dontStageParts.Count; i++) { PartSim partSim = dontStageParts[i]; partSim.DumpPartToLog(log, ""); } } else { log.AppendLine("No parts preventing staging"); } log.Flush(); } // Now we will loop until we are allowed to stage int loopCounter = 0; while (!AllowedToStage()) { loopCounter++; //if (log != null) log.AppendLine("loop = ", loopCounter); // Calculate how long each draining tank will take to drain and run for the minimum time double resourceDrainTime = double.MaxValue; PartSim partMinDrain = null; foreach (PartSim partSim in drainingParts) { double time = partSim.TimeToDrainResource(log); if (time < resourceDrainTime) { resourceDrainTime = time; partMinDrain = partSim; } } if (log != null) log.Append("Drain time = ", resourceDrainTime, " (", partMinDrain.name) .AppendLine(":", partMinDrain.partId, ")"); foreach (PartSim partSim in drainingParts) { partSim.DrainResources(resourceDrainTime, log); } // Get the mass after draining stepEndMass = ShipMass; stageTime += resourceDrainTime; double stepEndTWR = totalStageThrust / (stepEndMass * gravity); /*if (log != null) { log.AppendLine("After drain mass = ", stepEndMass); log.AppendLine("currentThrust = ", totalStageThrust); log.AppendLine("currentTWR = ", stepEndTWR); }*/ if (stepEndTWR > stage.maxThrustToWeight) { stage.maxThrustToWeight = stepEndTWR; } //if (log != null) log.AppendLine("newMaxTWR = ", stage.maxThrustToWeight); // If we have drained anything and the masses make sense then add this step's deltaV to the stage total if (resourceDrainTime > 0d && stepStartMass > stepEndMass && stepStartMass > 0d && stepEndMass > 0d) { vecStageDeltaV += vecThrust * (float)((currentisp * Units.GRAVITY * Math.Log(stepStartMass / stepEndMass)) / simpleTotalThrust); } // Update the active engines and resource drains for the next step UpdateResourceDrains(); // Recalculate the current thrust and isp for the next step CalculateThrustAndISP(); // Check if we actually changed anything if (stepStartMass == stepEndMass) { //MonoBehaviour.print("No change in mass"); break; } // Check to stop rampant looping if (loopCounter == 1000) { if (log != null) { log.AppendLine("exceeded loop count"); log.AppendLine("stageStartMass = " + stageStartMass); log.AppendLine("stepStartMass = " + stepStartMass); log.AppendLine("StepEndMass = " + stepEndMass); } break; } // The next step starts at the mass this one ended at stepStartMass = stepEndMass; } // Store more values in the Stage object and stick it in the array // Store the magnitude of the deltaV vector stage.deltaV = vecStageDeltaV.magnitude; stage.resourceMass = stageStartMass - stepEndMass; // Recalculate effective stage isp from the stage deltaV (flip the standard deltaV calculation around) // Note: If the mass doesn't change then this is a divide by zero if (stageStartMass != stepStartMass) { stage.isp = stage.deltaV / (Units.GRAVITY * Math.Log(stageStartMass / stepStartMass)); } else { stage.isp = 0; } // Zero stage time if more than a day (this should be moved into the window code) stage.time = (stageTime < SECONDS_PER_DAY) ? stageTime : 0d; stage.number = doingCurrent ? -1 : currentStage; // Set the stage number to -1 if doing current engines stage.totalPartCount = allParts.Count; stage.maxMach = maxMach; stages[currentStage] = stage; // Now activate the next stage currentStage--; doingCurrent = false; if (log != null) { // Log how long the stage took _timer.Stop(); log.AppendLine("Simulating stage took ", _timer.ElapsedMilliseconds, "ms"); stage.Dump(log); _timer.Reset(); _timer.Start(); } // Activate the next stage ActivateStage(); if (log != null) { // Log how long it took to activate _timer.Stop(); log.AppendLine("ActivateStage took ", _timer.ElapsedMilliseconds, "ms"); } } // Now we add up the various total fields in the stages for (int i = 0; i < stages.Length; i++) { // For each stage we total up the cost, mass, deltaV and time for this stage and all the stages above for (int j = i; j >= 0; j--) { stages[i].totalDeltaV += stages[j].deltaV; stages[i].totalTime += stages[j].time; stages[i].partCount = i > 0 ? stages[i].totalPartCount - stages[i - 1].totalPartCount : stages[i].totalPartCount; } // We also total up the deltaV for stage and all stages below for (int j = i; j < stages.Length; j++) { stages[i].inverseTotalDeltaV += stages[j].deltaV; } // Zero the total time if the value will be huge (24 hours?) to avoid the display going weird // (this should be moved into the window code) if (stages[i].totalTime > SECONDS_PER_DAY) { stages[i].totalTime = 0d; } } FreePooledObject(); _timer.Stop(); if (log != null) { log.AppendLine("RunSimulation: ", _timer.ElapsedMilliseconds, "ms"); log.Flush(); } log = null; return stages; }
public static EngineSim New(PartSim theEngine, ModuleEngines engineMod, double atmosphere, float machNumber, bool vectoredThrust, bool fullThrust, LogMsg log) { float maxFuelFlow = engineMod.maxFuelFlow; float minFuelFlow = engineMod.minFuelFlow; float thrustPercentage = engineMod.thrustPercentage; List<Transform> thrustTransforms = engineMod.thrustTransforms; List<float> thrustTransformMultipliers = engineMod.thrustTransformMultipliers; Vector3 vecThrust = CalculateThrustVector(vectoredThrust ? thrustTransforms : null, vectoredThrust ? thrustTransformMultipliers : null, log); FloatCurve atmosphereCurve = engineMod.atmosphereCurve; bool atmChangeFlow = engineMod.atmChangeFlow; FloatCurve atmCurve = engineMod.useAtmCurve ? engineMod.atmCurve : null; FloatCurve velCurve = engineMod.useVelCurve ? engineMod.velCurve : null; float currentThrottle = engineMod.currentThrottle; float IspG = engineMod.g; bool throttleLocked = engineMod.throttleLocked || fullThrust; List<Propellant> propellants = engineMod.propellants; bool active = engineMod.isOperational; float resultingThrust = engineMod.resultingThrust; bool isFlamedOut = engineMod.flameout; EngineSim engineSim = pool.Borrow(); engineSim.isp = 0.0; engineSim.maxMach = 0.0f; engineSim.actualThrust = 0.0; engineSim.partSim = theEngine; engineSim.isActive = active; engineSim.thrustVec = vecThrust; engineSim.isFlamedOut = isFlamedOut; engineSim.resourceConsumptions.Reset(); engineSim.resourceFlowModes.Reset(); engineSim.appliedForces.Clear(); double flowRate = 0.0; if (engineSim.partSim.hasVessel) { if (log != null) log.AppendLine("hasVessel is true"); float flowModifier = GetFlowModifier(atmChangeFlow, atmCurve, engineSim.partSim.part.atmDensity, velCurve, machNumber, ref engineSim.maxMach); engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere); engineSim.thrust = GetThrust(Mathf.Lerp(minFuelFlow, maxFuelFlow, GetThrustPercent(thrustPercentage)) * flowModifier, engineSim.isp); engineSim.actualThrust = engineSim.isActive ? resultingThrust : 0.0; if (log != null) { log.buf.AppendFormat("flowMod = {0:g6}\n", flowModifier); log.buf.AppendFormat("isp = {0:g6}\n", engineSim.isp); log.buf.AppendFormat("thrust = {0:g6}\n", engineSim.thrust); log.buf.AppendFormat("actual = {0:g6}\n", engineSim.actualThrust); } if (throttleLocked) { if (log != null) log.AppendLine("throttleLocked is true, using thrust for flowRate"); flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); } else { if (currentThrottle > 0.0f && engineSim.partSim.isLanded == false) { // TODO: This bit doesn't work for RF engines if (log != null) log.AppendLine("throttled up and not landed, using actualThrust for flowRate"); flowRate = GetFlowRate(engineSim.actualThrust, engineSim.isp); } else { if (log != null) log.AppendLine("throttled down or landed, using thrust for flowRate"); flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); } } } else { if (log != null) log.buf.AppendLine("hasVessel is false"); float flowModifier = GetFlowModifier(atmChangeFlow, atmCurve, CelestialBodies.SelectedBody.GetDensity(BuildAdvanced.Altitude), velCurve, machNumber, ref engineSim.maxMach); engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere); engineSim.thrust = GetThrust(Mathf.Lerp(minFuelFlow, maxFuelFlow, GetThrustPercent(thrustPercentage)) * flowModifier, engineSim.isp); engineSim.actualThrust = 0d; if (log != null) { log.buf.AppendFormat("flowMod = {0:g6}\n", flowModifier); log.buf.AppendFormat("isp = {0:g6}\n", engineSim.isp); log.buf.AppendFormat("thrust = {0:g6}\n", engineSim.thrust); log.buf.AppendFormat("actual = {0:g6}\n", engineSim.actualThrust); log.AppendLine("no vessel, using thrust for flowRate"); } flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); } if (log != null) log.buf.AppendFormat("flowRate = {0:g6}\n", flowRate); float flowMass = 0f; for (int i = 0; i < propellants.Count; ++i) { Propellant propellant = propellants[i]; if (!propellant.ignoreForIsp) flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id); } if (log != null) log.buf.AppendFormat("flowMass = {0:g6}\n", flowMass); for (int i = 0; i < propellants.Count; ++i) { Propellant propellant = propellants[i]; if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir") { continue; } double consumptionRate = propellant.ratio * flowRate / flowMass; if (log != null) log.buf.AppendFormat( "Add consumption({0}, {1}:{2:d}) = {3:g6}\n", ResourceContainer.GetResourceName(propellant.id), theEngine.name, theEngine.partId, consumptionRate); engineSim.resourceConsumptions.Add(propellant.id, consumptionRate); engineSim.resourceFlowModes.Add(propellant.id, (double)propellant.GetFlowMode()); } for (int i = 0; i < thrustTransforms.Count; i++) { Transform thrustTransform = thrustTransforms[i]; Vector3d direction = thrustTransform.forward.normalized; Vector3d position = thrustTransform.position; AppliedForce appliedForce = AppliedForce.New(direction * engineSim.thrust * thrustTransformMultipliers[i], position); engineSim.appliedForces.Add(appliedForce); } return engineSim; }
public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log) { if (log != null) log.AppendLine("SetupAttachNodes for ", name, ":", partId); attachNodes.Clear(); for (int i = 0; i < part.attachNodes.Count; ++i) { AttachNode attachNode = part.attachNodes[i]; if (log != null) log.AppendLine("AttachNode ", attachNode.id, " = ", (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null")); if (attachNode.attachedPart != null && attachNode.id != "Strut") { PartSim attachedSim; if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim)) { if (log != null) log.AppendLine("Adding attached node ", attachedSim.name, ":", attachedSim.partId); attachNodes.Add(AttachNodeSim.New(attachedSim, attachNode.id, attachNode.nodeType)); } else { if (log != null) log.AppendLine("No PartSim for attached part (", attachNode.attachedPart.partInfo.name, ")"); } } } for (int i = 0; i < part.fuelLookupTargets.Count; ++i) { Part p = part.fuelLookupTargets[i]; if (p != null) { PartSim targetSim; if (partSimLookup.TryGetValue(p, out targetSim)) { if (log != null) log.AppendLine("Fuel target: ", targetSim.name, ":", targetSim.partId); fuelTargets.Add(targetSim); } else { if (log != null) log.AppendLine("No PartSim for fuel target (", p.name, ")"); } } } }
public void GetSourceSet_Internal(int type, bool includeSurfaceMountedParts, List<PartSim> allParts, HashSet<PartSim> visited, HashSet<PartSim> allSources, ref int priMax, LogMsg log, String indent) { if (log != null) { log.Append(indent, "GetSourceSet_Internal(", ResourceContainer.GetResourceName(type), ") for ") .AppendLine(name, ":", partId); indent += " "; } // Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns as is. if (visited.Contains(this)) { if (log != null) log.Append(indent, "Nothing added, already visited (", name, ":") .AppendLine(partId + ")"); return; } if (log != null) log.AppendLine(indent, "Adding this to visited"); visited.Add(this); // Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed. // Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result. //MonoBehaviour.print("for each fuel line"); int lastCount = allSources.Count; for (int i = 0; i < this.fuelTargets.Count; i++) { PartSim partSim = this.fuelTargets[i]; if (partSim != null) { if (visited.Contains(partSim)) { if (log != null) log.Append(indent, "Fuel target already visited, skipping (", partSim.name, ":") .AppendLine(partSim.partId, ")"); } else { if (log != null) log.Append(indent, "Adding fuel target as source (", partSim.name, ":") .AppendLine(partSim.partId, ")"); partSim.GetSourceSet_Internal(type, includeSurfaceMountedParts, allParts, visited, allSources, ref priMax, log, indent); } } } if (fuelCrossFeed) { if (includeSurfaceMountedParts) { // check surface mounted fuel targets for (int i = 0; i < surfaceMountFuelTargets.Count; i++) { PartSim partSim = this.surfaceMountFuelTargets[i]; if (partSim != null) { if (visited.Contains(partSim)) { if (log != null) log.Append(indent, "Surface part already visited, skipping (", partSim.name, ":") .AppendLine(partSim.partId, ")"); } else { if (log != null) log.Append(indent, "Adding surface part as source (", partSim.name, ":") .AppendLine(partSim.partId, ")"); partSim.GetSourceSet_Internal(type, includeSurfaceMountedParts, allParts, visited, allSources, ref priMax, log, indent); } } } } lastCount = allSources.Count; //MonoBehaviour.print("for each attach node"); for (int i = 0; i < this.attachNodes.Count; i++) { AttachNodeSim attachSim = this.attachNodes[i]; if (attachSim.attachedPartSim != null) { if (attachSim.nodeType == AttachNode.NodeType.Stack) { if ((string.IsNullOrEmpty(noCrossFeedNodeKey) == false && attachSim.id.Contains(noCrossFeedNodeKey)) == false) { if (visited.Contains(attachSim.attachedPartSim)) { if (log != null) log.Append(indent, "Attached part already visited, skipping (", attachSim.attachedPartSim.name, ":") .AppendLine(attachSim.attachedPartSim.partId, ")"); } else { if (log != null) log.Append(indent, "Adding attached part as source (", attachSim.attachedPartSim.name, ":") .AppendLine(attachSim.attachedPartSim.partId, ")"); attachSim.attachedPartSim.GetSourceSet_Internal(type, includeSurfaceMountedParts, allParts, visited, allSources, ref priMax, log, indent); } } } } } } // If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel // type was not disabled) and it contains fuel, it adds itself. if (resources.HasType(type) && resourceFlowStates[type] > 0.0) { if (resources[type] > resRequestRemainingThreshold) { // Get the priority of this tank int pri = GetResourcePriority(); if (pri > priMax) { // This tank is higher priority than the previously added ones so we clear the sources // and set the priMax to this priority allSources.Clear(); priMax = pri; } // If this is the correct priority then add this to the sources if (pri == priMax) { if (log != null) log.Append(indent, "Adding enabled tank as source (", name, ":") .AppendLine(partId, ")"); allSources.Add(this); } } } else { if (log != null) log.Append(indent, "Not fuel tank or disabled. HasType = ", resources.HasType(type)) .AppendLine(" FlowState = " + resourceFlowStates[type]); } }
public void DumpPartToLog(LogMsg log, String prefix, List<PartSim> allParts = null) { if (log == null) return; log.Append(prefix); log.Append(name); log.Append(":[id = ", partId, ", decouple = ", decoupledInStage); log.Append(", invstage = ", inverseStage); //log.Append(", vesselName = '", vesselName, "'"); //log.Append(", vesselType = ", SimManager.GetVesselTypeString(vesselType)); //log.Append(", initialVesselName = '", initialVesselName, "'"); log.Append(", isNoPhys = ", isNoPhysics); log.buf.AppendFormat(", baseMass = {0}", baseMass); log.buf.AppendFormat(", baseMassForCoM = {0}", baseMassForCoM); log.Append(", fuelCF = {0}", fuelCrossFeed); log.Append(", noCFNKey = '{0}'", noCrossFeedNodeKey); log.Append(", isSep = {0}", isSepratron); for (int i = 0; i < resources.Types.Count; i++) { int type = resources.Types[i]; log.buf.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), resources[type]); } if (attachNodes.Count > 0) { log.Append(", attached = <"); attachNodes[0].DumpToLog(log); for (int i = 1; i < attachNodes.Count; i++) { log.Append(", "); attachNodes[i].DumpToLog(log); } log.Append(">"); } if (surfaceMountFuelTargets.Count > 0) { log.Append(", surface = <"); log.Append(surfaceMountFuelTargets[0].name, ":", surfaceMountFuelTargets[0].partId); for (int i = 1; i < surfaceMountFuelTargets.Count; i++) { log.Append(", ", surfaceMountFuelTargets[i].name, ":", surfaceMountFuelTargets[i].partId); } log.Append(">"); } // Add more info here log.AppendLine("]"); if (allParts != null) { String newPrefix = prefix + " "; for (int i = 0; i < allParts.Count; i++) { PartSim partSim = allParts[i]; if (partSim.parent == this) partSim.DumpPartToLog(log, newPrefix, allParts); } } }
public static PartSim New(Part p, int id, double atmosphere, LogMsg log) { PartSim partSim = pool.Borrow(); partSim.part = p; partSim.centerOfMass = p.transform.TransformPoint(p.CoMOffset); partSim.partId = id; partSim.name = p.partInfo.name; if (log != null) log.AppendLine("Create PartSim for ", partSim.name); partSim.parent = null; partSim.parentAttach = p.attachMode; partSim.fuelCrossFeed = p.fuelCrossFeed; partSim.noCrossFeedNodeKey = p.NoCrossFeedNodeKey; partSim.decoupledInStage = partSim.DecoupledInStage(p); partSim.isFuelLine = p.HasModule<CModuleFuelLine>(); partSim.isSepratron = partSim.IsSepratron(); partSim.inverseStage = p.inverseStage; if (log != null) log.AppendLine("inverseStage = ", partSim.inverseStage); partSim.resPriorityOffset = p.resourcePriorityOffset; partSim.resPriorityUseParentInverseStage = p.resourcePriorityUseParentInverseStage; partSim.resRequestRemainingThreshold = p.resourceRequestRemainingThreshold; partSim.baseCost = p.GetCostDry(); if (log != null) log.AppendLine("Parent part = ", (p.parent == null ? "null" : p.parent.partInfo.name)) .AppendLine("physicalSignificance = ", p.physicalSignificance) .AppendLine("PhysicsSignificance = ", p.PhysicsSignificance); // Work out if the part should have no physical significance // The root part is never "no physics" partSim.isNoPhysics = p.physicalSignificance == Part.PhysicalSignificance.NONE || p.PhysicsSignificance == 1; if (p.HasModule<LaunchClamp>()) { partSim.realMass = 0d; if (log != null) log.AppendLine("Ignoring mass of launch clamp"); } else { partSim.realMass = p.mass; if (log != null) log.AppendLine("Using part.mass of ", p.mass); } partSim.postStageMassAdjust = 0f; if (log != null) log.AppendLine("Calculating postStageMassAdjust, prefabMass = ", p.prefabMass); int count = p.Modules.Count; for (int i = 0; i < count; i++) { if (log != null) log.AppendLine("Module: ", p.Modules[i].moduleName); IPartMassModifier partMassModifier = p.Modules[i] as IPartMassModifier; if (partMassModifier != null) { if (log != null) log.AppendLine("ChangeWhen = ", partMassModifier.GetModuleMassChangeWhen()); if (partMassModifier.GetModuleMassChangeWhen() == ModifierChangeWhen.STAGED) { float preStage = partMassModifier.GetModuleMass(p.prefabMass, ModifierStagingSituation.UNSTAGED); float postStage = partMassModifier.GetModuleMass(p.prefabMass, ModifierStagingSituation.STAGED); if (log != null) log.AppendLine("preStage = ", preStage, " postStage = ", postStage); partSim.postStageMassAdjust += (postStage - preStage); } } } if (log != null) log.AppendLine("postStageMassAdjust = ", partSim.postStageMassAdjust); for (int i = 0; i < p.Resources.Count; i++) { PartResource resource = p.Resources[i]; // Make sure it isn't NaN as this messes up the part mass and hence most of the values // This can happen if a resource capacity is 0 and tweakable if (!Double.IsNaN(resource.amount)) { if (log != null) log.AppendLine(resource.resourceName, " = ", resource.amount); partSim.resources.Add(resource.info.id, resource.amount); partSim.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0); } else { if (log != null) log.AppendLine(resource.resourceName, " is NaN. Skipping."); } } partSim.hasVessel = (p.vessel != null); partSim.isLanded = partSim.hasVessel && p.vessel.Landed; if (partSim.hasVessel) { partSim.vesselName = p.vessel.vesselName; partSim.vesselType = p.vesselType; } partSim.initialVesselName = p.initialVesselName; partSim.hasMultiModeEngine = p.HasModule<MultiModeEngine>(); partSim.hasModuleEngines = p.HasModule<ModuleEngines>(); partSim.isEngine = partSim.hasMultiModeEngine || partSim.hasModuleEngines; if (log != null) log.AppendLine("Created ", partSim.name, ". Decoupled in stage ", partSim.decoupledInStage); return partSim; }
public void DumpPartToLog(LogMsg log, String prefix, List <PartSim> allParts = null) { if (log == null) { return; } log.Append(prefix); log.Append(name); log.Append(":[id = ", partId, ", decouple = ", decoupledInStage); log.Append(", invstage = ", inverseStage); //log.Append(", vesselName = '", vesselName, "'"); //log.Append(", vesselType = ", SimManager.GetVesselTypeString(vesselType)); //log.Append(", initialVesselName = '", initialVesselName, "'"); log.Append(", isNoPhys = ", isNoPhysics); log.buf.AppendFormat(", baseMass = {0}", baseMass); log.buf.AppendFormat(", baseMassForCoM = {0}", baseMassForCoM); log.Append(", fuelCF = {0}", fuelCrossFeed); log.Append(", noCFNKey = '{0}'", noCrossFeedNodeKey); log.Append(", isSep = {0}", isSepratron); for (int i = 0; i < resources.Types.Count; i++) { int type = resources.Types[i]; log.buf.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), resources[type]); } if (attachNodes.Count > 0) { log.Append(", attached = <"); attachNodes[0].DumpToLog(log); for (int i = 1; i < attachNodes.Count; i++) { log.Append(", "); attachNodes[i].DumpToLog(log); } log.Append(">"); } if (surfaceMountFuelTargets.Count > 0) { log.Append(", surface = <"); log.Append(surfaceMountFuelTargets[0].name, ":", surfaceMountFuelTargets[0].partId); for (int i = 1; i < surfaceMountFuelTargets.Count; i++) { log.Append(", ", surfaceMountFuelTargets[i].name, ":", surfaceMountFuelTargets[i].partId); } log.Append(">"); } // Add more info here log.AppendLine("]"); if (allParts != null) { String newPrefix = prefix + " "; for (int i = 0; i < allParts.Count; i++) { PartSim partSim = allParts[i]; if (partSim.parent == this) { partSim.DumpPartToLog(log, newPrefix, allParts); } } } }
public void CreateEngineSims(List <EngineSim> allEngines, double atmosphere, double mach, bool vectoredThrust, bool fullThrust, LogMsg log) { if (log != null) { log.AppendLine("CreateEngineSims for ", this.name); } var partMods = this.part.Modules; var numMods = partMods.Count; if (hasMultiModeEngine) { // A multi-mode engine has multiple ModuleEngines but only one is active at any point // The mode of the engine is the engineID of the ModuleEngines that is (are?) active string mode = part.GetModule <MultiModeEngine>().mode; for (int i = 0; i < numMods; i++) { //log.AppendLine("Module: ", partMods[i].moduleName); var engine = partMods[i] as ModuleEngines; if (engine != null && engine.engineID == mode) { if (log != null) { log.AppendLine("Module: ", engine.moduleName); } EngineSim engineSim = EngineSim.New( this, engine, atmosphere, (float)mach, vectoredThrust, fullThrust, log); allEngines.Add(engineSim); } } } else if (hasModuleEngines) { for (int i = 0; i < numMods; i++) { //log.AppendLine("Module: ", partMods[i].moduleName); var engine = partMods[i] as ModuleEngines; if (engine != null) { if (log != null) { log.AppendLine("Module: ", engine.moduleName); } EngineSim engineSim = EngineSim.New( this, engine, atmosphere, (float)mach, vectoredThrust, fullThrust, log); allEngines.Add(engineSim); } } } }
public static PartSim New(Part p, int id, double atmosphere, LogMsg log) { PartSim partSim = pool.Borrow(); partSim.part = p; partSim.centerOfMass = p.transform.TransformPoint(p.CoMOffset); partSim.partId = id; partSim.name = p.partInfo.name; if (log != null) { log.AppendLine("Create PartSim for ", partSim.name); } partSim.parent = null; partSim.parentAttach = p.attachMode; partSim.fuelCrossFeed = p.fuelCrossFeed; partSim.noCrossFeedNodeKey = p.NoCrossFeedNodeKey; partSim.decoupledInStage = partSim.DecoupledInStage(p); partSim.isFuelLine = p.HasModule <CModuleFuelLine>(); partSim.isSepratron = partSim.IsSepratron(); partSim.inverseStage = p.inverseStage; if (log != null) { log.AppendLine("inverseStage = ", partSim.inverseStage); } partSim.resPriorityOffset = p.resourcePriorityOffset; partSim.resPriorityUseParentInverseStage = p.resourcePriorityUseParentInverseStage; partSim.resRequestRemainingThreshold = p.resourceRequestRemainingThreshold; partSim.baseCost = p.GetCostDry(); if (log != null) { log.AppendLine("Parent part = ", (p.parent == null ? "null" : p.parent.partInfo.name)) .AppendLine("physicalSignificance = ", p.physicalSignificance) .AppendLine("PhysicsSignificance = ", p.PhysicsSignificance); } // Work out if the part should have no physical significance // The root part is never "no physics" partSim.isNoPhysics = p.physicalSignificance == Part.PhysicalSignificance.NONE || p.PhysicsSignificance == 1; if (p.HasModule <LaunchClamp>()) { partSim.realMass = 0d; if (log != null) { log.AppendLine("Ignoring mass of launch clamp"); } } else { partSim.realMass = p.mass; if (log != null) { log.AppendLine("Using part.mass of ", p.mass); } } partSim.postStageMassAdjust = 0f; if (log != null) { log.AppendLine("Calculating postStageMassAdjust, prefabMass = ", p.prefabMass); } int count = p.Modules.Count; for (int i = 0; i < count; i++) { if (log != null) { log.AppendLine("Module: ", p.Modules[i].moduleName); } IPartMassModifier partMassModifier = p.Modules[i] as IPartMassModifier; if (partMassModifier != null) { if (log != null) { log.AppendLine("ChangeWhen = ", partMassModifier.GetModuleMassChangeWhen()); } if (partMassModifier.GetModuleMassChangeWhen() == ModifierChangeWhen.STAGED) { float preStage = partMassModifier.GetModuleMass(p.prefabMass, ModifierStagingSituation.UNSTAGED); float postStage = partMassModifier.GetModuleMass(p.prefabMass, ModifierStagingSituation.STAGED); if (log != null) { log.AppendLine("preStage = ", preStage, " postStage = ", postStage); } partSim.postStageMassAdjust += (postStage - preStage); } } } if (log != null) { log.AppendLine("postStageMassAdjust = ", partSim.postStageMassAdjust); } for (int i = 0; i < p.Resources.Count; i++) { PartResource resource = p.Resources[i]; // Make sure it isn't NaN as this messes up the part mass and hence most of the values // This can happen if a resource capacity is 0 and tweakable if (!Double.IsNaN(resource.amount)) { if (log != null) { log.AppendLine(resource.resourceName, " = ", resource.amount); } partSim.resources.Add(resource.info.id, resource.amount); partSim.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0); } else { if (log != null) { log.AppendLine(resource.resourceName, " is NaN. Skipping."); } } } partSim.hasVessel = (p.vessel != null); partSim.isLanded = partSim.hasVessel && p.vessel.Landed; if (partSim.hasVessel) { partSim.vesselName = p.vessel.vesselName; partSim.vesselType = p.vesselType; } partSim.initialVesselName = p.initialVesselName; partSim.hasMultiModeEngine = p.HasModule <MultiModeEngine>(); partSim.hasModuleEngines = p.HasModule <ModuleEngines>(); partSim.isEngine = partSim.hasMultiModeEngine || partSim.hasModuleEngines; if (log != null) { log.AppendLine("Created ", partSim.name, ". Decoupled in stage ", partSim.decoupledInStage); } return(partSim); }
public bool SetResourceDrains(LogMsg log, List<PartSim> allParts, List<PartSim> allFuelLines, HashSet<PartSim> drainingParts) { //DumpSourcePartSets(log, "before clear"); foreach (HashSet<PartSim> sourcePartSet in sourcePartSets.Values) { sourcePartSet.Clear(); } //DumpSourcePartSets(log, "after clear"); for (int index = 0; index < this.resourceConsumptions.Types.Count; index++) { int type = this.resourceConsumptions.Types[index]; HashSet<PartSim> sourcePartSet; if (!sourcePartSets.TryGetValue(type, out sourcePartSet)) { sourcePartSet = new HashSet<PartSim>(); sourcePartSets.Add(type, sourcePartSet); } switch ((ResourceFlowMode)this.resourceFlowModes[type]) { case ResourceFlowMode.NO_FLOW: if (partSim.resources[type] > SimManager.RESOURCE_MIN && partSim.resourceFlowStates[type] != 0) { sourcePartSet.Add(partSim); } break; case ResourceFlowMode.ALL_VESSEL: case ResourceFlowMode.ALL_VESSEL_BALANCE: for (int i = 0; i < allParts.Count; i++) { PartSim aPartSim = allParts[i]; if (aPartSim.resources[type] > SimManager.RESOURCE_MIN && aPartSim.resourceFlowStates[type] != 0) { sourcePartSet.Add(aPartSim); } } break; case ResourceFlowMode.STAGE_PRIORITY_FLOW: case ResourceFlowMode.STAGE_PRIORITY_FLOW_BALANCE: if (log != null) log.Append("Find ", ResourceContainer.GetResourceName(type), " sources for ", partSim.name) .AppendLine(":" , partSim.partId); foreach (HashSet<PartSim> stagePartSet in stagePartSets.Values) { stagePartSet.Clear(); } var maxStage = -1; for (int i = 0; i < allParts.Count; i++) { var aPartSim = allParts[i]; //if (log != null) log.Append(aPartSim.name, ":" + aPartSim.partId, " contains ", aPartSim.resources[type]) // .AppendLine((aPartSim.resourceFlowStates[type] == 0) ? " (disabled)" : ""); if (aPartSim.resources[type] <= SimManager.RESOURCE_MIN || aPartSim.resourceFlowStates[type] == 0) { continue; } int stage = aPartSim.inverseStage; if (stage > maxStage) { maxStage = stage; } HashSet<PartSim> tempPartSet; if (!stagePartSets.TryGetValue(stage, out tempPartSet)) { tempPartSet = new HashSet<PartSim>(); stagePartSets.Add(stage, tempPartSet); } tempPartSet.Add(aPartSim); } for (int j = maxStage; j >= -1; j--) { //if (log != null) log.AppendLine("Testing stage ", j); HashSet<PartSim> stagePartSet; if (stagePartSets.TryGetValue(j, out stagePartSet) && stagePartSet.Count > 0) { //if (log != null) log.AppendLine("Not empty"); // We have to copy the contents of the set here rather than copying the set reference or // bad things (tm) happen foreach (PartSim aPartSim in stagePartSet) { sourcePartSet.Add(aPartSim); } break; } } break; case ResourceFlowMode.STACK_PRIORITY_SEARCH: case ResourceFlowMode.STAGE_STACK_FLOW: case ResourceFlowMode.STAGE_STACK_FLOW_BALANCE: visited.Clear(); if (log != null) log.Append("Find ", ResourceContainer.GetResourceName(type), " sources for ", partSim.name) .AppendLine(":", partSim.partId); partSim.GetSourceSet(type, true, allParts, visited, sourcePartSet, log, ""); break; default: if (log != null) log.Append("SetResourceDrains(", partSim.name, ":", partSim.partId) .AppendLine(") Unexpected flow type for ", ResourceContainer.GetResourceName(type), ")"); break; } if (log != null && sourcePartSet.Count > 0) { log.AppendLine("Source parts for ", ResourceContainer.GetResourceName(type), ":"); foreach (PartSim partSim in sourcePartSet) { log.AppendLine(partSim.name, ":", partSim.partId); } } //DumpSourcePartSets(log, "after " + ResourceContainer.GetResourceName(type)); } // If we don't have sources for all the needed resources then return false without setting up any drains for (int i = 0; i < this.resourceConsumptions.Types.Count; i++) { int type = this.resourceConsumptions.Types[i]; HashSet<PartSim> sourcePartSet; if (!sourcePartSets.TryGetValue(type, out sourcePartSet) || sourcePartSet.Count == 0) { if (log != null) log.AppendLine("No source of ", ResourceContainer.GetResourceName(type)); isActive = false; return false; } } // Now we set the drains on the members of the sets and update the draining parts set for (int i = 0; i < this.resourceConsumptions.Types.Count; i++) { int type = this.resourceConsumptions.Types[i]; HashSet<PartSim> sourcePartSet = sourcePartSets[type]; ResourceFlowMode mode = (ResourceFlowMode)resourceFlowModes[type]; double consumption = resourceConsumptions[type]; double amount = 0d; double total = 0d; if (mode == ResourceFlowMode.ALL_VESSEL_BALANCE || mode == ResourceFlowMode.STAGE_PRIORITY_FLOW_BALANCE || mode == ResourceFlowMode.STAGE_STACK_FLOW_BALANCE || mode == ResourceFlowMode.STACK_PRIORITY_SEARCH) { foreach (PartSim partSim in sourcePartSet) total += partSim.resources[type]; } else amount = consumption / sourcePartSet.Count; // Loop through the members of the set foreach (PartSim partSim in sourcePartSet) { if (total != 0d) amount = consumption * partSim.resources[type] / total; if (log != null) log.Append("Adding drain of ", amount, " ", ResourceContainer.GetResourceName(type)) .AppendLine(" to ", partSim.name, ":", partSim.partId); partSim.resourceDrains.Add(type, amount); drainingParts.Add(partSim); } } return true; }
private void BuildDontStageLists(LogMsg log) { if (log != null) log.AppendLine("Creating list with capacity of ", (currentStage + 1)); dontStagePartsLists.Clear(); for (int i = 0; i <= currentStage; i++) { if (i < dontStagePartsLists.Count) { dontStagePartsLists[i].Clear(); } else { dontStagePartsLists.Add(new List<PartSim>()); } } for (int i = 0; i < allParts.Count; ++i) { PartSim partSim = allParts[i]; if (partSim.isEngine || !partSim.Resources.Empty) { if (log != null) log.AppendLine(partSim.name, ":", partSim.partId, " is engine or tank, decoupled = ", partSim.decoupledInStage); if (partSim.decoupledInStage < -1 || partSim.decoupledInStage > currentStage - 1) { if (log != null) log.AppendLine("decoupledInStage out of range"); } else { dontStagePartsLists[partSim.decoupledInStage + 1].Add(partSim); } } } for (int i = 1; i <= lastStage; i++) { if (dontStagePartsLists[i].Count == 0) { dontStagePartsLists[i] = dontStagePartsLists[i - 1]; } } }
public static EngineSim New(PartSim theEngine, ModuleEngines engineMod, double atmosphere, float machNumber, bool vectoredThrust, bool fullThrust, LogMsg log) { float maxFuelFlow = engineMod.maxFuelFlow; float minFuelFlow = engineMod.minFuelFlow; float thrustPercentage = engineMod.thrustPercentage; List <Transform> thrustTransforms = engineMod.thrustTransforms; List <float> thrustTransformMultipliers = engineMod.thrustTransformMultipliers; Vector3 vecThrust = CalculateThrustVector(vectoredThrust ? thrustTransforms : null, vectoredThrust ? thrustTransformMultipliers : null, log); FloatCurve atmosphereCurve = engineMod.atmosphereCurve; bool atmChangeFlow = engineMod.atmChangeFlow; FloatCurve atmCurve = engineMod.useAtmCurve ? engineMod.atmCurve : null; FloatCurve velCurve = engineMod.useVelCurve ? engineMod.velCurve : null; FloatCurve thrustCurve = engineMod.useThrustCurve ? engineMod.thrustCurve : null; float currentThrottle = engineMod.currentThrottle; float IspG = engineMod.g; bool throttleLocked = engineMod.throttleLocked || fullThrust; List <Propellant> propellants = engineMod.propellants; float thrustCurveRatio = engineMod.thrustCurveRatio; foreach (Propellant p in propellants) { if (p.ignoreForThrustCurve) { continue; } double ratio = p.totalResourceAvailable / p.totalResourceCapacity; if (ratio < thrustCurveRatio) { thrustCurveRatio = (float)ratio; } } bool active = engineMod.isOperational; //I do not know if this matters. RF and stock always have finalThrust. But stock uses resultingThrust in the mass flow calculations, so keep it. float resultingThrust = SimManager.hasInstalledRealFuels ? engineMod.finalThrust : engineMod.resultingThrust; bool isFlamedOut = engineMod.flameout; EngineSim engineSim = pool.Borrow(); engineSim.isp = 0.0; engineSim.maxMach = 0.0f; engineSim.actualThrust = 0.0; engineSim.partSim = theEngine; engineSim.isActive = active; engineSim.thrustVec = vecThrust; engineSim.isFlamedOut = isFlamedOut; engineSim.resourceConsumptionsForMass.Reset(); engineSim.resourceConsumptionsForIsp.Reset(); engineSim.resourceFlowModes.Reset(); engineSim.appliedForces.Clear(); double flowRate = 0.0; if (engineSim.partSim.hasVessel) { if (log != null) { log.AppendLine("hasVessel is true"); } float flowModifier = GetFlowModifier(atmChangeFlow, atmCurve, engineSim.partSim.part.atmDensity, velCurve, machNumber, thrustCurve, thrustCurveRatio, ref engineSim.maxMach, engineMod.flowMultCap, engineMod.flowMultCapSharpness); engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere); engineSim.thrust = GetThrust(Mathf.Lerp(minFuelFlow, maxFuelFlow, GetThrustPercent(thrustPercentage)) * flowModifier, engineSim.isp); engineSim.actualThrust = engineSim.isActive ? resultingThrust : 0.0; if (log != null) { log.buf.AppendFormat("flowMod = {0:g6}\n", flowModifier); log.buf.AppendFormat("isp = {0:g6}\n", engineSim.isp); log.buf.AppendFormat("thrust = {0:g6}\n", engineSim.thrust); log.buf.AppendFormat("actual = {0:g6}\n", engineSim.actualThrust); log.buf.AppendFormat("final = {0:g6}\n", engineMod.finalThrust); log.buf.AppendFormat("resulting = {0:g6}\n", engineMod.resultingThrust); } if (throttleLocked) { if (log != null) { log.AppendLine("throttleLocked is true, using thrust for flowRate"); } flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); } else { if (currentThrottle > 0.0f && engineSim.partSim.isLanded == false) { if (log != null) { log.AppendLine("throttled up and not landed, using actualThrust for flowRate"); } flowRate = GetFlowRate(engineSim.actualThrust, engineSim.isp); } else { if (log != null) { log.AppendLine("throttled down or landed, using thrust for flowRate"); } flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); } } } else { if (log != null) { log.buf.AppendLine("hasVessel is false"); } float flowModifier = GetFlowModifier(atmChangeFlow, atmCurve, CelestialBodies.SelectedBody.GetDensity(BuildAdvanced.Altitude), velCurve, machNumber, thrustCurve, thrustCurveRatio, ref engineSim.maxMach, engineMod.flowMultCap, engineMod.flowMultCapSharpness); engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere); engineSim.thrust = GetThrust(Mathf.Lerp(minFuelFlow, maxFuelFlow, GetThrustPercent(thrustPercentage)) * flowModifier, engineSim.isp); engineSim.actualThrust = 0d; if (log != null) { log.buf.AppendFormat("flowMod = {0:g6}\n", flowModifier); log.buf.AppendFormat("isp = {0:g6}\n", engineSim.isp); log.buf.AppendFormat("thrust = {0:g6}\n", engineSim.thrust); log.buf.AppendFormat("actual = {0:g6}\n", engineSim.actualThrust); log.AppendLine("no vessel, using thrust for flowRate"); } flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); } if (log != null) { log.buf.AppendFormat("flowRate = {0:g6}\n", flowRate); } float flowMass = 0f; for (int i = 0; i < propellants.Count; ++i) { Propellant propellant = propellants[i]; if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir") { continue; } flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id); } if (log != null) { log.buf.AppendFormat("flowMass = {0:g6}\n", flowMass); } for (int i = 0; i < propellants.Count; ++i) { Propellant propellant = propellants[i]; if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir") { continue; } double consumptionRate = propellant.ratio * flowRate / flowMass; if (log != null) { log.buf.AppendFormat( "Add consumption({0}, {1}:{2:d}) = {3:g6}\n", ResourceContainer.GetResourceName(propellant.id), theEngine.name, theEngine.partId, consumptionRate); } // Add all for mass engineSim.resourceConsumptionsForMass.Add(propellant.id, consumptionRate); if (!propellant.ignoreForIsp) { engineSim.resourceConsumptionsForIsp.Add(propellant.id, consumptionRate); } engineSim.resourceFlowModes.Add(propellant.id, (double)propellant.GetFlowMode()); } for (int i = 0; i < thrustTransforms.Count; i++) { Transform thrustTransform = thrustTransforms[i]; Vector3d direction = thrustTransform.forward.normalized; Vector3d position = thrustTransform.position; AppliedForce appliedForce = AppliedForce.New(direction * engineSim.thrust * thrustTransformMultipliers[i], position); engineSim.appliedForces.Add(appliedForce); } return(engineSim); }
public static RCSSim New(PartSim theEngine, ModuleRCS engineMod, double atmosphere, float machNumber, bool vectoredThrust, bool fullThrust, LogMsg log) { double maxFuelFlow = engineMod.maxFuelFlow; // double minFuelFlow = engineMod.minFuelFlow; float thrustPercentage = engineMod.thrustPercentage; List <Transform> thrustTransforms = engineMod.thrusterTransforms; // List<float> thrustTransformMultipliers = engineMod.th Vector3 vecThrust = CalculateThrustVector(vectoredThrust ? thrustTransforms : null, log); FloatCurve atmosphereCurve = engineMod.atmosphereCurve; // bool atmChangeFlow = engineMod.at // FloatCurve atmCurve = engineMod.useAtmCurve ? engineMod.atmCurve : null; // FloatCurve velCurve = engineMod.useVelCurve ? engineMod.velCurve : null; // float currentThrottle = engineMod.currentThrottle; double IspG = engineMod.G; // bool throttleLocked = engineMod.throttleLocked || fullThrust; List <Propellant> propellants = engineMod.propellants; bool active = engineMod.moduleIsEnabled; // float resultingThrust = engineMod.resultingThrust; bool isFlamedOut = engineMod.flameout; RCSSim engineSim = pool.Borrow(); engineSim.isp = 0.0; engineSim.maxMach = 0.0f; engineSim.actualThrust = 0.0; engineSim.partSim = theEngine; engineSim.isActive = active; engineSim.thrustVec = vecThrust; engineSim.isFlamedOut = isFlamedOut; engineSim.resourceConsumptions.Reset(); engineSim.resourceFlowModes.Reset(); engineSim.appliedForces.Clear(); double flowRate = 0.0; if (engineSim.partSim.hasVessel) { if (log != null) { log.AppendLine("hasVessel is true"); } engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere); engineSim.thrust = GetThrust(maxFuelFlow, engineSim.isp); engineSim.actualThrust = engineSim.isActive ? engineSim.thrust : 0.0; if (log != null) { log.buf.AppendFormat("isp = {0:g6}\n", engineSim.isp); log.buf.AppendFormat("thrust = {0:g6}\n", engineSim.thrust); log.buf.AppendFormat("actual = {0:g6}\n", engineSim.actualThrust); } if (true) { if (log != null) { log.AppendLine("throttleLocked is true, using thrust for flowRate"); } flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); } else { // if (currentThrottle > 0.0f && engineSim.partSim.isLanded == false) // { //// TODO: This bit doesn't work for RF engines //if (log != null) log.AppendLine("throttled up and not landed, using actualThrust for flowRate"); // flowRate = GetFlowRate(engineSim.actualThrust, engineSim.isp); // } // else // { // if (log != null) log.AppendLine("throttled down or landed, using thrust for flowRate"); // flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); // } } } else { if (log != null) { log.buf.AppendLine("hasVessel is false"); } engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere); engineSim.thrust = GetThrust(maxFuelFlow, engineSim.isp); engineSim.actualThrust = 0d; if (log != null) { log.buf.AppendFormat("isp = {0:g6}\n", engineSim.isp); log.buf.AppendFormat("thrust = {0:g6}\n", engineSim.thrust); log.buf.AppendFormat("actual = {0:g6}\n", engineSim.actualThrust); log.AppendLine("no vessel, using thrust for flowRate"); } flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); } if (log != null) { log.buf.AppendFormat("flowRate = {0:g6}\n", flowRate); } float flowMass = 0f; for (int i = 0; i < propellants.Count; ++i) { Propellant propellant = propellants[i]; if (!propellant.ignoreForIsp) { flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id); } } if (log != null) { log.buf.AppendFormat("flowMass = {0:g6}\n", flowMass); } for (int i = 0; i < propellants.Count; ++i) { Propellant propellant = propellants[i]; if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir") { continue; } double consumptionRate = propellant.ratio * flowRate / flowMass; if (log != null) { log.buf.AppendFormat( "Add consumption({0}, {1}:{2:d}) = {3:g6}\n", ResourceContainer.GetResourceName(propellant.id), theEngine.name, theEngine.partId, consumptionRate); } engineSim.resourceConsumptions.Add(propellant.id, consumptionRate); engineSim.resourceFlowModes.Add(propellant.id, (double)propellant.GetFlowMode()); } for (int i = 0; i < thrustTransforms.Count; i++) { Transform thrustTransform = thrustTransforms[i]; Vector3d direction = thrustTransform.forward.normalized; Vector3d position = thrustTransform.position; AppliedForce appliedForce = AppliedForce.New(direction * engineSim.thrust, position); engineSim.appliedForces.Add(appliedForce); } return(engineSim); }
public void GetSourceSet_Internal(int type, bool includeSurfaceMountedParts, List <PartSim> allParts, HashSet <PartSim> visited, HashSet <PartSim> allSources, ref int priMax, LogMsg log, String indent) { if (log != null) { log.Append(indent, "GetSourceSet_Internal(", ResourceContainer.GetResourceName(type), ") for ") .AppendLine(name, ":", partId); indent += " "; } // Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns as is. if (visited.Contains(this)) { if (log != null) { log.Append(indent, "Nothing added, already visited (", name, ":") .AppendLine(partId + ")"); } return; } if (log != null) { log.AppendLine(indent, "Adding this to visited"); } visited.Add(this); // Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed. // Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result. //MonoBehaviour.print("for each fuel line"); int lastCount = allSources.Count; for (int i = 0; i < this.fuelTargets.Count; i++) { PartSim partSim = this.fuelTargets[i]; if (partSim != null) { if (visited.Contains(partSim)) { if (log != null) { log.Append(indent, "Fuel target already visited, skipping (", partSim.name, ":") .AppendLine(partSim.partId, ")"); } } else { if (log != null) { log.Append(indent, "Adding fuel target as source (", partSim.name, ":") .AppendLine(partSim.partId, ")"); } partSim.GetSourceSet_Internal(type, includeSurfaceMountedParts, allParts, visited, allSources, ref priMax, log, indent); } } } if (fuelCrossFeed) { if (includeSurfaceMountedParts) { // check surface mounted fuel targets for (int i = 0; i < surfaceMountFuelTargets.Count; i++) { PartSim partSim = this.surfaceMountFuelTargets[i]; if (partSim != null) { if (visited.Contains(partSim)) { if (log != null) { log.Append(indent, "Surface part already visited, skipping (", partSim.name, ":") .AppendLine(partSim.partId, ")"); } } else { if (log != null) { log.Append(indent, "Adding surface part as source (", partSim.name, ":") .AppendLine(partSim.partId, ")"); } partSim.GetSourceSet_Internal(type, includeSurfaceMountedParts, allParts, visited, allSources, ref priMax, log, indent); } } } } lastCount = allSources.Count; //MonoBehaviour.print("for each attach node"); for (int i = 0; i < this.attachNodes.Count; i++) { AttachNodeSim attachSim = this.attachNodes[i]; if (attachSim.attachedPartSim != null) { if (attachSim.nodeType == AttachNode.NodeType.Stack) { if ((string.IsNullOrEmpty(noCrossFeedNodeKey) == false && attachSim.id.Contains(noCrossFeedNodeKey)) == false) { if (visited.Contains(attachSim.attachedPartSim)) { if (log != null) { log.Append(indent, "Attached part already visited, skipping (", attachSim.attachedPartSim.name, ":") .AppendLine(attachSim.attachedPartSim.partId, ")"); } } else { if (log != null) { log.Append(indent, "Adding attached part as source (", attachSim.attachedPartSim.name, ":") .AppendLine(attachSim.attachedPartSim.partId, ")"); } attachSim.attachedPartSim.GetSourceSet_Internal(type, includeSurfaceMountedParts, allParts, visited, allSources, ref priMax, log, indent); } } } } } } // If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel // type was not disabled) and it contains fuel, it adds itself. if (resources.HasType(type) && resourceFlowStates[type] > 0.0) { if (resources[type] > resRequestRemainingThreshold) { // Get the priority of this tank int pri = GetResourcePriority(); if (pri > priMax) { // This tank is higher priority than the previously added ones so we clear the sources // and set the priMax to this priority allSources.Clear(); priMax = pri; } // If this is the correct priority then add this to the sources if (pri == priMax) { if (log != null) { log.Append(indent, "Adding enabled tank as source (", name, ":") .AppendLine(partId, ")"); } allSources.Add(this); } } } else { if (log != null) { log.Append(indent, "Not fuel tank or disabled. HasType = ", resources.HasType(type)) .AppendLine(" FlowState = " + resourceFlowStates[type]); } } }
public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere, double mach, bool vectoredThrust, bool fullThrust, LogMsg log) { if (log != null) log.AppendLine("CreateEngineSims for ", this.name); var partMods = this.part.Modules; var numMods = partMods.Count; if (hasMultiModeEngine) { // A multi-mode engine has multiple ModuleEngines but only one is active at any point // The mode of the engine is the engineID of the ModuleEngines that is (are?) active string mode = part.GetModule<MultiModeEngine>().mode; for (int i = 0; i < numMods; i++) { //log.AppendLine("Module: ", partMods[i].moduleName); var engine = partMods[i] as ModuleEngines; if (engine != null && engine.engineID == mode) { if (log != null) log.AppendLine("Module: ", engine.moduleName); EngineSim engineSim = EngineSim.New( this, engine, atmosphere, (float)mach, vectoredThrust, fullThrust, log); allEngines.Add(engineSim); } } } else if (hasModuleEngines) { for (int i = 0; i < numMods; i++) { //log.AppendLine("Module: ", partMods[i].moduleName); var engine = partMods[i] as ModuleEngines; if (engine != null) { if (log != null) log.AppendLine("Module: ", engine.moduleName); EngineSim engineSim = EngineSim.New( this, engine, atmosphere, (float)mach, vectoredThrust, fullThrust, log); allEngines.Add(engineSim); } } } }
// This is the old recursive function for STACK_PRIORITY_SEARCH public void GetSourceSet_Old(int type, bool includeSurfaceMountedParts, List <PartSim> allParts, HashSet <PartSim> visited, HashSet <PartSim> allSources, LogMsg log, String indent) { if (log != null) { log.Append(indent, "GetSourceSet_Old(", ResourceContainer.GetResourceName(type), ") for ") .AppendLine(name, ":", partId); indent += " "; } // Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns as is. if (visited.Contains(this)) { if (log != null) { log.Append(indent, "Returning empty set, already visited (", name, ":") .AppendLine(partId + ")"); } return; } if (log != null) { log.AppendLine(indent, "Adding this to visited"); } visited.Add(this); // Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed. // Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result. //MonoBehaviour.print("for each fuel line"); int lastCount = allSources.Count; for (int i = 0; i < this.fuelTargets.Count; i++) { PartSim partSim = this.fuelTargets[i]; if (partSim != null) { if (visited.Contains(partSim)) { if (log != null) { log.Append(indent, "Fuel target already visited, skipping (", partSim.name, ":") .AppendLine(partSim.partId, ")"); } } else { if (log != null) { log.Append(indent, "Adding fuel target as source (", partSim.name, ":") .AppendLine(partSim.partId, ")"); } partSim.GetSourceSet_Old(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent); } } } // check surface mounted fuel targets if (includeSurfaceMountedParts) { for (int i = 0; i < surfaceMountFuelTargets.Count; i++) { PartSim partSim = this.surfaceMountFuelTargets[i]; if (partSim != null) { if (visited.Contains(partSim)) { if (log != null) { log.Append(indent, "Fuel target already visited, skipping (", partSim.name, ":") .AppendLine(partSim.partId, ")"); } } else { if (log != null) { log.Append(indent, "Adding fuel target as source (", partSim.name, ":") .AppendLine(partSim.partId, ")"); } partSim.GetSourceSet_Old(type, true, allParts, visited, allSources, log, indent); } } } } if (allSources.Count > lastCount) { if (log != null) { log.Append(indent, "Returning ", (allSources.Count - lastCount), " fuel target sources (") .AppendLine(this.name, ":", this.partId, ")"); } return; } // Rule 3: This rule has been removed and merged with rules 4 and 7 to fix issue with fuel tanks with disabled crossfeed // Rule 4: Part performs scan on each of its axially mounted neighbors. // Couplers (bicoupler, tricoupler, ...) are an exception, they only scan one attach point on the single attachment side, // skip the points on the side where multiple points are. [Experiment] // Again, the part creates union of scan lists from each of its neighbor and if it is not empty, returns this list. // The order in which mount points of a part are scanned appears to be fixed and defined by the part specification file. [Experiment] if (fuelCrossFeed) { lastCount = allSources.Count; //MonoBehaviour.print("for each attach node"); for (int i = 0; i < this.attachNodes.Count; i++) { AttachNodeSim attachSim = this.attachNodes[i]; if (attachSim.attachedPartSim != null) { if (attachSim.nodeType == AttachNode.NodeType.Stack) { if ((string.IsNullOrEmpty(noCrossFeedNodeKey) == false && attachSim.id.Contains(noCrossFeedNodeKey)) == false) { if (visited.Contains(attachSim.attachedPartSim)) { if (log != null) { log.Append(indent, "Attached part already visited, skipping (", attachSim.attachedPartSim.name, ":") .AppendLine(attachSim.attachedPartSim.partId, ")"); } } else { if (log != null) { log.Append(indent, "Adding attached part as source (", attachSim.attachedPartSim.name, ":") .AppendLine(attachSim.attachedPartSim.partId, ")"); } attachSim.attachedPartSim.GetSourceSet_Old(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent); } } } } } if (allSources.Count > lastCount) { if (log != null) { log.Append(indent, "Returning " + (allSources.Count - lastCount) + " attached sources (") .AppendLine(this.name, ":", this.partId, ")"); } return; } } // Rule 5: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel // type was not disabled [Experiment]) and it contains fuel, it returns itself. // Rule 6: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel // type was not disabled) but it does not contain the requested fuel, it returns empty list. [Experiment] if (resources.HasType(type) && resourceFlowStates[type] > 0.0) { if (resources[type] > SimManager.RESOURCE_MIN) { allSources.Add(this); if (log != null) { log.Append(indent, "Returning enabled tank as only source (", name, ":") .AppendLine(partId, ")"); } return; } } else { if (log != null) { log.Append(indent, "Not fuel tank or disabled. HasType = ", resources.HasType(type)) .AppendLine(" FlowState = " + resourceFlowStates[type]); } } // Rule 7: If the part is radially attached to another part and it is child of that part in the ship's tree structure, it scans its // parent and returns whatever the parent scan returned. [Experiment] [Experiment] if (parent != null && parentAttach == AttachModes.SRF_ATTACH) { if (fuelCrossFeed) { if (visited.Contains(parent)) { if (log != null) { log.Append(indent, "Parent part already visited, skipping (", parent.name, ":") .AppendLine(parent.partId, ")"); } } else { lastCount = allSources.Count; this.parent.GetSourceSet_Old(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent); if (allSources.Count > lastCount) { if (log != null) { log.Append(indent, "Returning ", (allSources.Count - lastCount), " parent sources (") .AppendLine(this.name, ":", this.partId, ")"); } return; } } } } // Rule 8: If all preceding rules failed, part returns empty list. if (log != null) { log.Append(indent, "Returning empty set, no sources found (", name, ":") .AppendLine(partId, ")"); } return; }
// This is a new function for STAGE_STACK_FLOW(_BALANCE) public void GetSourceSet(int type, bool includeSurfaceMountedParts, List<PartSim> allParts, HashSet<PartSim> visited, HashSet<PartSim> allSources, LogMsg log, String indent) { // Initial version of support for new flow mode // Call a modified version of the old GetSourceSet code that adds all potential sources rather than stopping the recursive scan // when certain conditions are met int priMax = int.MinValue; GetSourceSet_Internal(type, includeSurfaceMountedParts, allParts, visited, allSources, ref priMax, log, indent); if (log != null) log.AppendLine(allSources.Count, " parts with priority of ", priMax); }
public void SetupAttachNodes(Dictionary <Part, PartSim> partSimLookup, LogMsg log) { if (log != null) { log.AppendLine("SetupAttachNodes for ", name, ":", partId); } attachNodes.Clear(); for (int i = 0; i < part.attachNodes.Count; ++i) { AttachNode attachNode = part.attachNodes[i]; if (log != null) { log.AppendLine("AttachNode ", attachNode.id, " = ", (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null")); } if (attachNode.attachedPart != null && attachNode.id != "Strut") { PartSim attachedSim; if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim)) { if (log != null) { log.AppendLine("Adding attached node ", attachedSim.name, ":", attachedSim.partId); } attachNodes.Add(AttachNodeSim.New(attachedSim, attachNode.id, attachNode.nodeType)); } else { if (log != null) { log.AppendLine("No PartSim for attached part (", attachNode.attachedPart.partInfo.name, ")"); } } } } for (int i = 0; i < part.fuelLookupTargets.Count; ++i) { Part p = part.fuelLookupTargets[i]; if (p != null) { PartSim targetSim; if (partSimLookup.TryGetValue(p, out targetSim)) { if (log != null) { log.AppendLine("Fuel target: ", targetSim.name, ":", targetSim.partId); } fuelTargets.Add(targetSim); } else { if (log != null) { log.AppendLine("No PartSim for fuel target (", p.name, ")"); } } } } }
// This is the old recursive function for STACK_PRIORITY_SEARCH public void GetSourceSet_Old(int type, bool includeSurfaceMountedParts, List<PartSim> allParts, HashSet<PartSim> visited, HashSet<PartSim> allSources, LogMsg log, String indent) { if (log != null) { log.Append(indent, "GetSourceSet_Old(", ResourceContainer.GetResourceName(type), ") for ") .AppendLine(name, ":", partId); indent += " "; } // Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns as is. if (visited.Contains(this)) { if (log != null) log.Append(indent, "Returning empty set, already visited (", name, ":") .AppendLine(partId + ")"); return; } if (log != null) log.AppendLine(indent, "Adding this to visited"); visited.Add(this); // Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed. // Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result. //MonoBehaviour.print("for each fuel line"); int lastCount = allSources.Count; for (int i = 0; i < this.fuelTargets.Count; i++) { PartSim partSim = this.fuelTargets[i]; if (partSim != null) { if (visited.Contains(partSim)) { if (log != null) log.Append(indent, "Fuel target already visited, skipping (", partSim.name, ":") .AppendLine(partSim.partId, ")"); } else { if (log != null) log.Append(indent, "Adding fuel target as source (", partSim.name, ":") .AppendLine(partSim.partId, ")"); partSim.GetSourceSet_Old(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent); } } } // check surface mounted fuel targets if (includeSurfaceMountedParts) { for (int i = 0; i < surfaceMountFuelTargets.Count; i++) { PartSim partSim = this.surfaceMountFuelTargets[i]; if (partSim != null) { if (visited.Contains(partSim)) { if (log != null) log.Append(indent, "Fuel target already visited, skipping (", partSim.name, ":") .AppendLine(partSim.partId, ")"); } else { if (log != null) log.Append(indent, "Adding fuel target as source (", partSim.name, ":") .AppendLine(partSim.partId, ")"); partSim.GetSourceSet_Old(type, true, allParts, visited, allSources, log, indent); } } } } if (allSources.Count > lastCount) { if (log != null) log.Append(indent, "Returning ", (allSources.Count - lastCount), " fuel target sources (") .AppendLine(this.name, ":", this.partId, ")"); return; } // Rule 3: This rule has been removed and merged with rules 4 and 7 to fix issue with fuel tanks with disabled crossfeed // Rule 4: Part performs scan on each of its axially mounted neighbors. // Couplers (bicoupler, tricoupler, ...) are an exception, they only scan one attach point on the single attachment side, // skip the points on the side where multiple points are. [Experiment] // Again, the part creates union of scan lists from each of its neighbor and if it is not empty, returns this list. // The order in which mount points of a part are scanned appears to be fixed and defined by the part specification file. [Experiment] if (fuelCrossFeed) { lastCount = allSources.Count; //MonoBehaviour.print("for each attach node"); for (int i = 0; i < this.attachNodes.Count; i++) { AttachNodeSim attachSim = this.attachNodes[i]; if (attachSim.attachedPartSim != null) { if (attachSim.nodeType == AttachNode.NodeType.Stack) { if ((string.IsNullOrEmpty(noCrossFeedNodeKey) == false && attachSim.id.Contains(noCrossFeedNodeKey)) == false) { if (visited.Contains(attachSim.attachedPartSim)) { if (log != null) log.Append(indent, "Attached part already visited, skipping (", attachSim.attachedPartSim.name, ":") .AppendLine(attachSim.attachedPartSim.partId, ")"); } else { if (log != null) log.Append(indent, "Adding attached part as source (", attachSim.attachedPartSim.name, ":") .AppendLine(attachSim.attachedPartSim.partId, ")"); attachSim.attachedPartSim.GetSourceSet_Old(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent); } } } } } if (allSources.Count > lastCount) { if (log != null) log.Append(indent, "Returning " + (allSources.Count - lastCount) + " attached sources (") .AppendLine(this.name, ":", this.partId, ")"); return; } } // Rule 5: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel // type was not disabled [Experiment]) and it contains fuel, it returns itself. // Rule 6: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel // type was not disabled) but it does not contain the requested fuel, it returns empty list. [Experiment] if (resources.HasType(type) && resourceFlowStates[type] > 0.0) { if (resources[type] > SimManager.RESOURCE_MIN) { allSources.Add(this); if (log != null) log.Append(indent, "Returning enabled tank as only source (", name, ":") .AppendLine(partId, ")"); return; } } else { if (log != null) log.Append(indent, "Not fuel tank or disabled. HasType = ", resources.HasType(type)) .AppendLine(" FlowState = " + resourceFlowStates[type]); } // Rule 7: If the part is radially attached to another part and it is child of that part in the ship's tree structure, it scans its // parent and returns whatever the parent scan returned. [Experiment] [Experiment] if (parent != null && parentAttach == AttachModes.SRF_ATTACH) { if (fuelCrossFeed) { if (visited.Contains(parent)) { if (log != null) log.Append(indent, "Parent part already visited, skipping (", parent.name, ":") .AppendLine(parent.partId, ")"); } else { lastCount = allSources.Count; this.parent.GetSourceSet_Old(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent); if (allSources.Count > lastCount) { if (log != null) log.Append(indent, "Returning ", (allSources.Count - lastCount), " parent sources (") .AppendLine(this.name, ":", this.partId, ")"); return; } } } } // Rule 8: If all preceding rules failed, part returns empty list. if (log != null) log.Append(indent, "Returning empty set, no sources found (", name, ":") .AppendLine(partId, ")"); return; }
public bool SetPossibleResourceDrains(LogMsg log, List <PartSim> allParts, HashSet <PartSim> drainingParts) { //DumpSourcePartSets(log, "before clear"); foreach (HashSet <PartSim> sourcePartSet in fullSourcePartSets.Values) { sourcePartSet.Clear(); } //DumpSourcePartSets(log, "after clear"); for (int index = 0; index < this.resourceConsumptions.Types.Count; index++) { int type = this.resourceConsumptions.Types[index]; if (!fullSourcePartSets.TryGetValue(type, out HashSet <PartSim> sourcePartSet)) { sourcePartSet = new HashSet <PartSim>(); fullSourcePartSets.Add(type, sourcePartSet); } switch ((ResourceFlowMode)this.resourceFlowModes[type]) { case ResourceFlowMode.NO_FLOW: if (partSim.maxResources[type] > SimManager.RESOURCE_MIN) { sourcePartSet.Add(partSim); } break; case ResourceFlowMode.ALL_VESSEL: case ResourceFlowMode.ALL_VESSEL_BALANCE: for (int i = 0; i < allParts.Count; i++) { PartSim aPartSim = allParts[i]; if (aPartSim.maxResources[type] > SimManager.RESOURCE_MIN) { sourcePartSet.Add(aPartSim); } } break; case ResourceFlowMode.STAGE_PRIORITY_FLOW: case ResourceFlowMode.STAGE_PRIORITY_FLOW_BALANCE: //All vessel ordered by stage if (log != null) { log.Append("Find ", ResourceContainer.GetResourceName(type), " sources for ", partSim.name) .AppendLine(":", partSim.partId); } foreach (HashSet <PartSim> stagePartSet in stagePartSets.Values) { stagePartSet.Clear(); } var maxStage = -1; for (int i = 0; i < allParts.Count; i++) { var aPartSim = allParts[i]; //if (log != null) log.Append(aPartSim.name, ":" + aPartSim.partId, " contains ", aPartSim.resources[type]) // .AppendLine((aPartSim.resourceFlowStates[type] == 0) ? " (disabled)" : ""); if (aPartSim.maxResources[type] > SimManager.RESOURCE_MIN) { int stage = aPartSim.inverseStage; if (stage > maxStage) { maxStage = stage; } if (!stagePartSets.TryGetValue(stage, out HashSet <PartSim> tempPartSet)) { tempPartSet = new HashSet <PartSim>(); stagePartSets.Add(stage, tempPartSet); } tempPartSet.Add(aPartSim); } } //Add resource containers in order by stage for (int j = maxStage; j >= -1; j--) { //if (log != null) log.AppendLine("Testing stage ", j); if (stagePartSets.TryGetValue(j, out HashSet <PartSim> stagePartSet) && stagePartSet.Count > 0) { //if (log != null) log.AppendLine("Not empty"); // We have to copy the contents of the set here rather than copying the set reference or // bad things (tm) happen foreach (PartSim aPartSim in stagePartSet) { sourcePartSet.Add(aPartSim); } break; } } break; case ResourceFlowMode.STACK_PRIORITY_SEARCH: case ResourceFlowMode.STAGE_STACK_FLOW: case ResourceFlowMode.STAGE_STACK_FLOW_BALANCE: visited.Clear(); //Resource containers limited to current stage if (log != null) { log.Append("Find ", ResourceContainer.GetResourceName(type), " sources for ", partSim.name) .AppendLine(":", partSim.partId); } partSim.GetSourceSet(type, true, allParts, visited, sourcePartSet, true, log, ""); break; default: if (log != null) { log.Append("SetResourceDrains(", partSim.name, ":", partSim.partId) .AppendLine(") Unexpected flow type for ", ResourceContainer.GetResourceName(type), ")"); } break; } } // If we don't have sources for all the needed resources then return false without setting up any drains for (int i = 0; i < this.resourceConsumptions.Types.Count; i++) { int type = this.resourceConsumptions.Types[i]; if (!fullSourcePartSets.TryGetValue(type, out HashSet <PartSim> sourcePartSet) || sourcePartSet.Count == 0) { if (log != null) { log.AppendLine("No source of ", ResourceContainer.GetResourceName(type)); } return(false); } } // Now we set the drains on the members of the sets and update the draining parts set for (int i = 0; i < this.resourceConsumptions.Types.Count; i++) { HashSet <PartSim> sourcePartSet = fullSourcePartSets[resourceConsumptions.Types[i]]; // Loop through the members of the set foreach (PartSim partSim in sourcePartSet) { drainingParts.Add(partSim); } } return(true); }
public void SetupParent(Dictionary<Part, PartSim> partSimLookup, LogMsg log) { if (part.parent != null) { parent = null; if (partSimLookup.TryGetValue(part.parent, out parent)) { if (log != null) log.AppendLine("Parent part is ", parent.name, ":", parent.partId); if (part.attachMode == AttachModes.SRF_ATTACH && part.attachRules.srfAttach && part.fuelCrossFeed && part.parent.fuelCrossFeed) { if (log != null) log.Append("Added (", name, ":", partId) .AppendLine(", ", parent.name, ":", parent.partId, ") to surface mounted fuel targets."); parent.surfaceMountFuelTargets.Add(this); surfaceMountFuelTargets.Add(parent); } } else { if (log != null) log.AppendLine("No PartSim for parent part (", part.parent.partInfo.name, ")"); } } }
public void DumpSourcePartSets(LogMsg log, String msg) { if (log == null) return; log.AppendLine("DumpSourcePartSets ", msg); foreach (int type in sourcePartSets.Keys) { log.AppendLine("SourcePartSet for ", ResourceContainer.GetResourceName(type)); HashSet<PartSim> sourcePartSet = sourcePartSets[type]; if (sourcePartSet.Count > 0) { foreach (PartSim partSim in sourcePartSet) { log.AppendLine("Part ", partSim.name, ":", partSim.partId); } } else { log.AppendLine("No parts"); } } }
// This function prepares the simulation by creating all the necessary data structures it will // need during the simulation. All required data is copied from the core game data structures // so that the simulation itself can be run in a background thread without having issues with // the core game changing the data while the simulation is running. public bool PrepareSimulation(LogMsg _log, List <Part> parts, double theGravity, double theAtmosphere = 0, double theMach = 0, bool dumpTree = false, bool vectoredThrust = false, bool fullThrust = false) { log = _log; if (log != null) { log.AppendLine("PrepareSimulation started"); } _timer.Reset(); _timer.Start(); // Store the parameters in members for ease of access in other functions partList = parts; gravity = theGravity; atmosphere = theAtmosphere; mach = theMach; lastStage = StageManager.LastStage; maxMach = 1.0f; if (log != null) { log.AppendLine("lastStage = ", lastStage); } // Clear the lists for our simulation parts allParts.Clear(); allFuelLines.Clear(); drainingParts.Clear(); allEngines.Clear(); activeEngines.Clear(); drainingResources.Clear(); // A dictionary for fast lookup of Part->PartSim during the preparation phase partSimLookup.Clear(); if (partList.Count > 0 && partList[0].vessel != null) { vesselName = partList[0].vessel.vesselName; vesselType = partList[0].vessel.vesselType; } // First we create a PartSim for each Part (giving each a unique id) int partId = 1; for (int i = 0; i < partList.Count; ++i) { Part part = partList[i]; // If the part is already in the lookup dictionary then log it and skip to the next part if (partSimLookup.ContainsKey(part)) { if (log != null) { log.AppendLine("Part ", part.name, " appears in vessel list more than once"); } continue; } // Create the PartSim PartSim partSim = PartSim.New(part, partId, atmosphere, log); // Add it to the Part lookup dictionary and the necessary lists partSimLookup.Add(part, partSim); allParts.Add(partSim); if (partSim.isFuelLine) { allFuelLines.Add(partSim); } if (partSim.isEngine) { partSim.CreateEngineSims(allEngines, atmosphere, mach, vectoredThrust, fullThrust, log); } if (partSim.isRCS) { partSim.CreateRCSSims(allRCS, atmosphere, mach, vectoredThrust, fullThrust, log); } partId++; } for (int i = 0; i < allEngines.Count; ++i) { maxMach = Mathf.Max(maxMach, allEngines[i].maxMach); } UpdateActiveEngines(); // Now that all the PartSims have been created we can do any set up that needs access to other parts // First we set up all the parent links for (int i = 0; i < allParts.Count; i++) { PartSim partSim = allParts[i]; partSim.SetupParent(partSimLookup, log); } // Then, in the VAB/SPH, we add the parent of each fuel line to the fuelTargets list of their targets if (HighLogic.LoadedSceneIsEditor) { for (int i = 0; i < allFuelLines.Count; ++i) { PartSim partSim = allFuelLines[i]; CModuleFuelLine fuelLine = partSim.part.GetModule <CModuleFuelLine>(); if (fuelLine.target != null) { PartSim targetSim; if (partSimLookup.TryGetValue(fuelLine.target, out targetSim)) { if (log != null) { log.AppendLine("Fuel line target is ", targetSim.name, ":", targetSim.partId); } targetSim.fuelTargets.Add(partSim.parent); } else { if (log != null) { log.AppendLine("No PartSim for fuel line target (", partSim.part.partInfo.name, ")"); } } } else { if (log != null) { log.AppendLine("Fuel line target is null"); } } } } if (log != null) { log.AppendLine("SetupAttachNodes and count stages"); } for (int i = 0; i < allParts.Count; ++i) { PartSim partSim = allParts[i]; partSim.SetupAttachNodes(partSimLookup, log); if (partSim.decoupledInStage >= lastStage) { lastStage = partSim.decoupledInStage + 1; } } // And finally release the Part references from all the PartSims if (log != null) { log.AppendLine("ReleaseParts"); } for (int i = 0; i < allParts.Count; ++i) { allParts[i].ReleasePart(); } // And dereference the core's part list partList = null; _timer.Stop(); if (log != null) { log.AppendLine("PrepareSimulation: ", _timer.ElapsedMilliseconds, "ms"); log.Flush(); } Dump(); log = null; return(true); }
// This function prepares the simulation by creating all the necessary data structures it will // need during the simulation. All required data is copied from the core game data structures // so that the simulation itself can be run in a background thread without having issues with // the core game changing the data while the simulation is running. public bool PrepareSimulation(LogMsg _log, List<Part> parts, double theGravity, double theAtmosphere = 0, double theMach = 0, bool dumpTree = false, bool vectoredThrust = false, bool fullThrust = false) { log = _log; if (log != null) log.AppendLine("PrepareSimulation started"); _timer.Reset(); _timer.Start(); // Store the parameters in members for ease of access in other functions partList = parts; gravity = theGravity; atmosphere = theAtmosphere; mach = theMach; lastStage = StageManager.LastStage; maxMach = 1.0f; if (log != null) log.AppendLine("lastStage = ", lastStage); // Clear the lists for our simulation parts allParts.Clear(); allFuelLines.Clear(); drainingParts.Clear(); allEngines.Clear(); activeEngines.Clear(); drainingResources.Clear(); // A dictionary for fast lookup of Part->PartSim during the preparation phase partSimLookup.Clear(); if (partList.Count > 0 && partList[0].vessel != null) { vesselName = partList[0].vessel.vesselName; vesselType = partList[0].vessel.vesselType; } // First we create a PartSim for each Part (giving each a unique id) int partId = 1; for (int i = 0; i < partList.Count; ++i) { Part part = partList[i]; // If the part is already in the lookup dictionary then log it and skip to the next part if (partSimLookup.ContainsKey(part)) { if (log != null) log.AppendLine("Part ", part.name, " appears in vessel list more than once"); continue; } // Create the PartSim PartSim partSim = PartSim.New(part, partId, atmosphere, log); // Add it to the Part lookup dictionary and the necessary lists partSimLookup.Add(part, partSim); allParts.Add(partSim); if (partSim.isFuelLine) { allFuelLines.Add(partSim); } if (partSim.isEngine) { partSim.CreateEngineSims(allEngines, atmosphere, mach, vectoredThrust, fullThrust, log); } partId++; } for (int i = 0; i < allEngines.Count; ++i) { maxMach = Mathf.Max(maxMach, allEngines[i].maxMach); } UpdateActiveEngines(); // Now that all the PartSims have been created we can do any set up that needs access to other parts // First we set up all the parent links for (int i = 0; i < allParts.Count; i++) { PartSim partSim = allParts[i]; partSim.SetupParent(partSimLookup, log); } // Then, in the VAB/SPH, we add the parent of each fuel line to the fuelTargets list of their targets if (HighLogic.LoadedSceneIsEditor) { for (int i = 0; i < allFuelLines.Count; ++i) { PartSim partSim = allFuelLines[i]; CModuleFuelLine fuelLine = partSim.part.GetModule<CModuleFuelLine>(); if (fuelLine.target != null) { PartSim targetSim; if (partSimLookup.TryGetValue(fuelLine.target, out targetSim)) { if (log != null) log.AppendLine("Fuel line target is ", targetSim.name, ":", targetSim.partId); targetSim.fuelTargets.Add(partSim.parent); } else { if (log != null) log.AppendLine("No PartSim for fuel line target (", partSim.part.partInfo.name, ")"); } } else { if (log != null) log.AppendLine("Fuel line target is null"); } } } if (log != null) log.AppendLine("SetupAttachNodes and count stages"); for (int i = 0; i < allParts.Count; ++i) { PartSim partSim = allParts[i]; partSim.SetupAttachNodes(partSimLookup, log); if (partSim.decoupledInStage >= lastStage) { lastStage = partSim.decoupledInStage + 1; } } // And finally release the Part references from all the PartSims if (log != null) log.AppendLine("ReleaseParts"); for (int i = 0; i < allParts.Count; ++i) { allParts[i].ReleasePart(); } // And dereference the core's part list partList = null; _timer.Stop(); if (log != null) { log.AppendLine("PrepareSimulation: ", _timer.ElapsedMilliseconds, "ms"); log.Flush(); } Dump(); log = null; return true; }
public static EngineSim New(PartSim theEngine, ModuleEngines engineMod, double atmosphere, float machNumber, bool vectoredThrust, bool fullThrust, LogMsg log) { float maxFuelFlow = engineMod.maxFuelFlow; float minFuelFlow = engineMod.minFuelFlow; float thrustPercentage = engineMod.thrustPercentage; List <Transform> thrustTransforms = engineMod.thrustTransforms; List <float> thrustTransformMultipliers = engineMod.thrustTransformMultipliers; Vector3 vecThrust = CalculateThrustVector(vectoredThrust ? thrustTransforms : null, vectoredThrust ? thrustTransformMultipliers : null, log); FloatCurve atmosphereCurve = engineMod.atmosphereCurve; bool atmChangeFlow = engineMod.atmChangeFlow; FloatCurve atmCurve = engineMod.useAtmCurve ? engineMod.atmCurve : null; FloatCurve velCurve = engineMod.useVelCurve ? engineMod.velCurve : null; float currentThrottle = engineMod.currentThrottle; float IspG = engineMod.g; bool throttleLocked = engineMod.throttleLocked || fullThrust; List <Propellant> propellants = engineMod.propellants; bool active = engineMod.isOperational; float resultingThrust = engineMod.resultingThrust; bool isFlamedOut = engineMod.flameout; EngineSim engineSim = pool.Borrow(); engineSim.isp = 0.0; engineSim.maxMach = 0.0f; engineSim.actualThrust = 0.0; engineSim.partSim = theEngine; engineSim.isActive = active; engineSim.thrustVec = vecThrust; engineSim.isFlamedOut = isFlamedOut; engineSim.resourceConsumptions.Reset(); engineSim.maxResourceConsumptions.Reset(); engineSim.resourceFlowModes.Reset(); engineSim.appliedForces.Clear(); double flowRate = 0.0; double maxFlowRate = 0.0; if (engineSim.partSim.hasVessel) { if (log != null) { log.AppendLine("hasVessel is true"); } float flowModifier = GetFlowModifier(atmChangeFlow, atmCurve, engineSim.partSim.part.atmDensity, velCurve, machNumber, ref engineSim.maxMach); engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere); engineSim.fullThrust = GetThrust(maxFuelFlow * flowModifier, engineSim.isp); engineSim.thrust = GetThrust(Mathf.Lerp(minFuelFlow, maxFuelFlow, GetThrustPercent(thrustPercentage)) * flowModifier, engineSim.isp); engineSim.actualThrust = engineSim.isActive ? resultingThrust : 0.0; if (log != null) { log.buf.AppendFormat("flowMod = {0:g6}\n", flowModifier); log.buf.AppendFormat("isp = {0:g6}\n", engineSim.isp); log.buf.AppendFormat("thrust = {0:g6}\n", engineSim.thrust); log.buf.AppendFormat("actual = {0:g6}\n", engineSim.actualThrust); } if (throttleLocked) { if (log != null) { log.AppendLine("throttleLocked is true, using thrust for flowRate"); } flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); } else { if (currentThrottle > 0.0f && engineSim.partSim.isLanded == false) { // TODO: This bit doesn't work for RF engines if (log != null) { log.AppendLine("throttled up and not landed, using actualThrust for flowRate"); } flowRate = GetFlowRate(engineSim.actualThrust, engineSim.isp); } else { if (log != null) { log.AppendLine("throttled down or landed, using thrust for flowRate"); } flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); } } maxFlowRate = GetFlowRate(engineSim.fullThrust, engineSim.isp); } else { if (log != null) { log.buf.AppendLine("hasVessel is false"); } float altitude = BasicDeltaV.Instance.AtmosphereDepth; float flowModifier = GetFlowModifier(atmChangeFlow, atmCurve, BasicDeltaV.Instance.CurrentCelestialBody.GetDensity(BasicDeltaV.Instance.CurrentCelestialBody.GetPressure(altitude), BasicDeltaV.Instance.CurrentCelestialBody.GetTemperature(altitude)), velCurve, machNumber, ref engineSim.maxMach); engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere); engineSim.fullThrust = GetThrust(maxFuelFlow * flowModifier, engineSim.isp); engineSim.thrust = GetThrust(Mathf.Lerp(minFuelFlow, maxFuelFlow, GetThrustPercent(thrustPercentage)) * flowModifier, engineSim.isp); engineSim.actualThrust = 0d; if (log != null) { log.buf.AppendFormat("flowMod = {0:g6}\n", flowModifier); log.buf.AppendFormat("isp = {0:g6}\n", engineSim.isp); log.buf.AppendFormat("thrust = {0:g6}\n", engineSim.thrust); log.buf.AppendFormat("actual = {0:g6}\n", engineSim.actualThrust); log.AppendLine("no vessel, using thrust for flowRate"); } flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); maxFlowRate = GetFlowRate(engineSim.fullThrust, engineSim.isp); } if (log != null) { log.buf.AppendFormat("flowRate = {0:g6}\n", flowRate); } float flowMass = 0f; for (int i = 0; i < propellants.Count; ++i) { Propellant propellant = propellants[i]; if (!propellant.ignoreForIsp) { flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id); } } if (log != null) { log.buf.AppendFormat("flowMass = {0:g6}\n", flowMass); } for (int i = 0; i < propellants.Count; ++i) { Propellant propellant = propellants[i]; if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir") { continue; } double consumptionRate = propellant.ratio * flowRate / flowMass; if (log != null) { log.buf.AppendFormat( "Add consumption({0}, {1}:{2:d}) = {3:g6}\n", ResourceContainer.GetResourceName(propellant.id), theEngine.name, theEngine.partId, consumptionRate); } engineSim.resourceConsumptions.Add(propellant.id, consumptionRate); engineSim.resourceFlowModes.Add(propellant.id, (double)propellant.GetFlowMode()); double maxConsumptionRate = propellant.ratio * maxFlowRate / flowMass; engineSim.maxResourceConsumptions.Add(propellant.id, maxConsumptionRate); } for (int i = 0; i < thrustTransforms.Count; i++) { Transform thrustTransform = thrustTransforms[i]; Vector3d direction = thrustTransform.forward.normalized; Vector3d position = thrustTransform.position; AppliedForce appliedForce = AppliedForce.New(direction * engineSim.thrust * thrustTransformMultipliers[i], position); engineSim.appliedForces.Add(appliedForce); } return(engineSim); }
// This function runs the simulation and returns a newly created array of Stage objects public Stage[] RunSimulation(LogMsg _log) { log = _log; if (log != null) { log.AppendLine("RunSimulation started"); } _timer.Reset(); _timer.Start(); // Start with the last stage to simulate // (this is in a member variable so it can be accessed by AllowedToStage and ActivateStage) currentStage = lastStage; // Work out which engines would be active if just doing the staging and if this is different to the // currently active engines then generate an extra stage // Loop through all the engines bool anyActive = false; for (int i = 0; i < allEngines.Count; ++i) { EngineSim engine = allEngines[i]; if (log != null) { log.AppendLine("Testing engine mod of ", engine.partSim.name, ":", engine.partSim.partId); } bool bActive = engine.isActive; bool bStage = (engine.partSim.inverseStage >= currentStage); if (log != null) { log.AppendLine("bActive = ", bActive, " bStage = ", bStage); } if (HighLogic.LoadedSceneIsFlight) { if (bActive) { anyActive = true; } if (bActive != bStage) { // If the active state is different to the state due to staging if (log != null) { log.AppendLine("Need to do current active engines first"); } doingCurrent = true; } } else { if (bStage) { if (log != null) { log.AppendLine("Marking as active"); } engine.isActive = true; } } } // If we need to do current because of difference in engine activation and there actually are active engines // then we do the extra stage otherwise activate the next stage and don't treat it as current if (doingCurrent && anyActive) { currentStage++; } else { ActivateStage(); doingCurrent = false; } // Create a list of lists of PartSims that prevent decoupling BuildDontStageLists(log); if (log != null) { log.Flush(); } // Create the array of stages that will be returned Stage[] stages = new Stage[currentStage + 1]; int startStage = currentStage; // Loop through the stages while (currentStage >= 0) { if (log != null) { log.AppendLine("Simulating stage ", currentStage); log.Flush(); _timer.Reset(); _timer.Start(); } // Update active engines and resource drains UpdateResourceDrains(); // Update the masses of the parts to correctly handle "no physics" parts stageStartMass = UpdatePartMasses(); if (log != null) { allParts[0].DumpPartToLog(log, "", allParts); } // Create the Stage object for this stage Stage stage = new Stage(); stageTime = 0d; vecStageDeltaV = Vector3.zero; stageStartCom = ShipCom; stepStartMass = stageStartMass; stepEndMass = 0; CalculateThrustAndISP(); // Store various things in the Stage object stage.thrust = totalStageThrust; stage.thrustToWeight = totalStageThrust / (stageStartMass * gravity); stage.maxThrustToWeight = stage.thrustToWeight; stage.actualThrust = totalStageActualThrust; stage.actualThrustToWeight = totalStageActualThrust / (stageStartMass * gravity); CalculateRCS(gravity, false); stage.RCSIsp = RCSIsp; stage.RCSThrust = RCSThrust; stage.RCSdeltaVStart = RCSDeltaV; stage.RCSTWRStart = RCSTWR; stage.RCSBurnTime = RCSBurnTime; if (log != null) { log.AppendLine("stage.thrust = ", stage.thrust); log.AppendLine("StageMass = ", stageStartMass); log.AppendLine("Initial maxTWR = ", stage.maxThrustToWeight); } // calculate torque and associates stage.maxThrustTorque = totalStageThrustForce.TorqueAt(stageStartCom).magnitude; // torque divided by thrust. imagine that all engines are at the end of a lever that tries to turn the ship. // this numerical value, in meters, would represent the length of that lever. double torqueLeverArmLength = (stage.thrust <= 0) ? 0 : stage.maxThrustTorque / stage.thrust; // how far away are the engines from the CoM, actually? double thrustDistance = (stageStartCom - totalStageThrustForce.GetAverageForceApplicationPoint()).magnitude; // the combination of the above two values gives an approximation of the offset angle. double sinThrustOffsetAngle = 0; if (thrustDistance > 1e-7) { sinThrustOffsetAngle = torqueLeverArmLength / thrustDistance; if (sinThrustOffsetAngle > 1) { sinThrustOffsetAngle = 1; } } stage.thrustOffsetAngle = Math.Asin(sinThrustOffsetAngle) * 180 / Math.PI; // Calculate the total cost of the vessel at this point stage.totalCost = 0d; for (int i = 0; i < allParts.Count; ++i) { if (currentStage > allParts[i].decoupledInStage) { stage.totalCost += allParts[i].GetCost(currentStage); } } // The total mass is simply the mass at the start of the stage stage.totalMass = stageStartMass; // If we have done a previous stage if (currentStage < startStage) { // Calculate what the previous stage's mass and cost were by subtraction Stage prev = stages[currentStage + 1]; prev.cost = prev.totalCost - stage.totalCost; prev.mass = prev.totalMass - stage.totalMass; } // The above code will never run for the last stage so set those directly if (currentStage == 0) { stage.cost = stage.totalCost; stage.mass = stage.totalMass; } dontStageParts = dontStagePartsLists[currentStage]; if (log != null) { log.AppendLine("Stage setup took ", _timer.ElapsedMilliseconds, "ms"); if (dontStageParts.Count > 0) { log.AppendLine("Parts preventing staging:"); for (int i = 0; i < dontStageParts.Count; i++) { PartSim partSim = dontStageParts[i]; partSim.DumpPartToLog(log, ""); } } else { log.AppendLine("No parts preventing staging"); } log.Flush(); } // Now we will loop until we are allowed to stage int loopCounter = 0; while (!AllowedToStage()) { loopCounter++; //if (log != null) log.AppendLine("loop = ", loopCounter); // Calculate how long each draining tank will take to drain and run for the minimum time double resourceDrainTime = double.MaxValue; PartSim partMinDrain = null; foreach (PartSim partSim in drainingParts) { double time = partSim.TimeToDrainResource(log); if (time < resourceDrainTime) { resourceDrainTime = time; partMinDrain = partSim; } } if (log != null) { log.Append("Drain time = ", resourceDrainTime, " (", partMinDrain.name) .AppendLine(":", partMinDrain.partId, ")"); } foreach (PartSim partSim in drainingParts) { partSim.DrainResources(resourceDrainTime, log); } // Get the mass after draining stepEndMass = ShipMass; stageTime += resourceDrainTime; double stepEndTWR = totalStageThrust / (stepEndMass * gravity); /*if (log != null) * { * log.AppendLine("After drain mass = ", stepEndMass); * log.AppendLine("currentThrust = ", totalStageThrust); * log.AppendLine("currentTWR = ", stepEndTWR); * }*/ if (stepEndTWR > stage.maxThrustToWeight) { stage.maxThrustToWeight = stepEndTWR; } //if (log != null) log.AppendLine("newMaxTWR = ", stage.maxThrustToWeight); // If we have drained anything and the masses make sense then add this step's deltaV to the stage total if (resourceDrainTime > 0d && stepStartMass > stepEndMass && stepStartMass > 0d && stepEndMass > 0d) { vecStageDeltaV += vecThrust * (float)((currentisp * Units.GRAVITY * Math.Log(stepStartMass / stepEndMass)) / simpleTotalThrust); } // Update the active engines and resource drains for the next step UpdateResourceDrains(); // Recalculate the current thrust and isp for the next step CalculateThrustAndISP(); // Check if we actually changed anything if (stepStartMass == stepEndMass) { //MonoBehaviour.print("No change in mass"); break; } // Check to stop rampant looping if (loopCounter == 1000) { if (log != null) { log.AppendLine("exceeded loop count"); log.AppendLine("stageStartMass = " + stageStartMass); log.AppendLine("stepStartMass = " + stepStartMass); log.AppendLine("StepEndMass = " + stepEndMass); } break; } // The next step starts at the mass this one ended at stepStartMass = stepEndMass; } // Store more values in the Stage object and stick it in the array // Store the magnitude of the deltaV vector stage.deltaV = vecStageDeltaV.magnitude; stage.resourceMass = stageStartMass - stepEndMass; if (HighLogic.LoadedSceneIsEditor) //this is only needed in the VAB. { CalculateRCS(gravity, true); } stage.RCSdeltaVEnd = RCSDeltaV; stage.RCSTWREnd = RCSTWR; // Recalculate effective stage isp from the stage deltaV (flip the standard deltaV calculation around) // Note: If the mass doesn't change then this is a divide by zero if (stageStartMass != stepStartMass) { stage.isp = stage.deltaV / (Units.GRAVITY * Math.Log(stageStartMass / stepStartMass)); } else { stage.isp = 0; } // Zero stage time if more than a day (this should be moved into the window code) stage.time = (stageTime < SECONDS_PER_DAY) ? stageTime : 0d; stage.number = doingCurrent ? -1 : currentStage; // Set the stage number to -1 if doing current engines stage.totalPartCount = allParts.Count; stage.maxMach = maxMach; stages[currentStage] = stage; // Now activate the next stage currentStage--; doingCurrent = false; if (log != null) { // Log how long the stage took _timer.Stop(); log.AppendLine("Simulating stage took ", _timer.ElapsedMilliseconds, "ms"); stage.Dump(log); _timer.Reset(); _timer.Start(); } // Activate the next stage ActivateStage(); if (log != null) { // Log how long it took to activate _timer.Stop(); log.AppendLine("ActivateStage took ", _timer.ElapsedMilliseconds, "ms"); } } // Now we add up the various total fields in the stages for (int i = 0; i < stages.Length; i++) { // For each stage we total up the cost, mass, deltaV and time for this stage and all the stages above for (int j = i; j >= 0; j--) { stages[i].totalDeltaV += stages[j].deltaV; stages[i].totalResourceMass += stages[j].resourceMass; stages[i].totalTime += stages[j].time; stages[i].partCount = i > 0 ? stages[i].totalPartCount - stages[i - 1].totalPartCount : stages[i].totalPartCount; } // We also total up the deltaV for stage and all stages below for (int j = i; j < stages.Length; j++) { stages[i].inverseTotalDeltaV += stages[j].deltaV; } // Zero the total time if the value will be huge (24 hours?) to avoid the display going weird // (this should be moved into the window code) if (stages[i].totalTime > SECONDS_PER_DAY) { stages[i].totalTime = 0d; } } FreePooledObject(); _timer.Stop(); if (log != null) { log.AppendLine("RunSimulation: ", _timer.ElapsedMilliseconds, "ms"); log.Flush(); } log = null; return(stages); }