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"); } } }
public void DumpPartToBuffer(StringBuilder buffer, String prefix, List <PartSim> allParts = null) { buffer.Append(prefix); buffer.Append(name); buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", partId, decoupledInStage, inverseStage); //buffer.AppendFormat(", vesselName = '{0}'", vesselName); //buffer.AppendFormat(", vesselType = {0}", SimManager.GetVesselTypeString(vesselType)); //buffer.AppendFormat(", initialVesselName = '{0}'", initialVesselName); buffer.AppendFormat(", isNoPhys = {0}", isNoPhysics); buffer.AppendFormat(", baseMass = {0}", baseMass); buffer.AppendFormat(", baseMassForCoM = {0}", baseMassForCoM); buffer.AppendFormat(", fuelCF = {0}", fuelCrossFeed); buffer.AppendFormat(", noCFNKey = '{0}'", noCrossFeedNodeKey); buffer.AppendFormat(", isSep = {0}", isSepratron); for (int i = 0; i < resources.Types.Count; i++) { int type = resources.Types[i]; buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), resources[type]); } if (attachNodes.Count > 0) { buffer.Append(", attached = <"); attachNodes[0].DumpToBuffer(buffer); for (int i = 1; i < attachNodes.Count; i++) { buffer.Append(", "); attachNodes[i].DumpToBuffer(buffer); } buffer.Append(">"); } // Add more info here buffer.Append("]\n"); if (allParts != null) { String newPrefix = prefix + " "; for (int i = 0; i < allParts.Count; i++) { PartSim partSim = allParts[i]; if (partSim.parent == this) { partSim.DumpPartToBuffer(buffer, newPrefix, allParts); } } } }
public void DumpSourcePartSets(String msg) { MonoBehaviour.print("DumpSourcePartSets " + msg); foreach (int type in sourcePartSets.Keys) { MonoBehaviour.print("SourcePartSet for " + ResourceContainer.GetResourceName(type)); HashSet <PartSim> sourcePartSet = sourcePartSets[type]; if (sourcePartSet.Count > 0) { foreach (PartSim partSim in sourcePartSet) { MonoBehaviour.print("Part " + partSim.name + ":" + partSim.partId); } } else { MonoBehaviour.print("No parts"); } } }
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 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 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 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 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 EngineSim New(PartSim theEngine, double atmosphere, float machNumber, float maxFuelFlow, float minFuelFlow, float thrustPercentage, Vector3 vecThrust, FloatCurve atmosphereCurve, bool atmChangeFlow, FloatCurve atmCurve, FloatCurve velCurve, float currentThrottle, float IspG, bool throttleLocked, List <Propellant> propellants, bool active, float resultingThrust, List <Transform> thrustTransforms, LogMsg log) { 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.resourceConsumptions.Reset(); engineSim.resourceFlowModes.Reset(); engineSim.appliedForces.Clear(); double flowRate = 0.0; if (engineSim.partSim.hasVessel) { if (log != null) { log.buf.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.buf.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.buf.AppendLine("throttled up and not landed, using actualThrust for flowRate"); } flowRate = GetFlowRate(engineSim.actualThrust, engineSim.isp); } else { if (log != null) { log.buf.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, SimManager.Body.GetDensity(FlightGlobals.getStaticPressure(0, SimManager.Body), FlightGlobals.getExternalTemperature(0, SimManager.Body)), 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); } if (log != null) { log.buf.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()); } double thrustPerThrustTransform = engineSim.thrust / thrustTransforms.Count; 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 * thrustPerThrustTransform, position); engineSim.appliedForces.Add(appliedForce); } return(engineSim); }
public bool SetResourceDrains(List <PartSim> allParts, List <PartSim> allFuelLines, HashSet <PartSim> drainingParts) { LogMsg log = null; foreach (HashSet <PartSim> sourcePartSet in sourcePartSets.Values) { sourcePartSet.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 = new HashSet<PartSim>(); //MonoBehaviour.print("SetResourceDrains(" + name + ":" + partId + ") setting sources to just this"); sourcePartSet.Add(partSim); } break; case ResourceFlowMode.ALL_VESSEL: 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: foreach (HashSet <PartSim> stagePartSet in stagePartSets.Values) { stagePartSet.Clear(); } var maxStage = -1; //Logger.Log(type); for (int i = 0; i < allParts.Count; i++) { var aPartSim = allParts[i]; if (aPartSim.resources[type] <= SimManager.RESOURCE_MIN || aPartSim.resourceFlowStates[type] == 0) { continue; } int stage = aPartSim.DecouplerCount(); if (stage > maxStage) { maxStage = stage; } if (!stagePartSets.TryGetValue(stage, out sourcePartSet)) { sourcePartSet = new HashSet <PartSim>(); stagePartSets.Add(stage, sourcePartSet); } sourcePartSet.Add(aPartSim); } for (int j = 0; j <= maxStage; j++) { HashSet <PartSim> stagePartSet; if (stagePartSets.TryGetValue(j, out stagePartSet) && stagePartSet.Count > 0) { sourcePartSet = stagePartSet; } } break; case ResourceFlowMode.STACK_PRIORITY_SEARCH: visited.Clear(); if (SimManager.logOutput) { log = new LogMsg(); log.buf.AppendLine("Find " + ResourceContainer.GetResourceName(type) + " sources for " + partSim.name + ":" + partSim.partId); } partSim.GetSourceSet(type, allParts, visited, sourcePartSet, log, ""); if (SimManager.logOutput) { MonoBehaviour.print(log.buf); } break; default: MonoBehaviour.print("SetResourceDrains(" + partSim.name + ":" + partSim.partId + ") Unexpected flow type for " + ResourceContainer.GetResourceName(type) + ")"); break; } if (sourcePartSet.Count > 0) { sourcePartSets[type] = sourcePartSet; if (SimManager.logOutput) { log = new LogMsg(); log.buf.AppendLine("Source parts for " + ResourceContainer.GetResourceName(type) + ":"); foreach (PartSim partSim in sourcePartSet) { log.buf.AppendLine(partSim.name + ":" + partSim.partId); } MonoBehaviour.print(log.buf); } } } // 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 (SimManager.logOutput) { MonoBehaviour.print("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]; // Loop through the members of the set double amount = resourceConsumptions[type] / sourcePartSet.Count; foreach (PartSim partSim in sourcePartSet) { if (SimManager.logOutput) { MonoBehaviour.print( "Adding drain of " + amount + " " + ResourceContainer.GetResourceName(type) + " to " + partSim.name + ":" + partSim.partId); } partSim.resourceDrains.Add(type, amount); drainingParts.Add(partSim); } } return(true); }
public static EngineSim New(PartSim theEngine, double atmosphere, double machNumber, float maxFuelFlow, float minFuelFlow, float thrustPercentage, Vector3 vecThrust, FloatCurve atmosphereCurve, bool atmChangeFlow, FloatCurve atmCurve, FloatCurve velCurve, float currentThrottle, float IspG, bool throttleLocked, List <Propellant> propellants, bool active, bool correctThrust, List <Transform> thrustTransforms) { EngineSim engineSim = pool.Borrow(); StringBuilder buffer = null; //MonoBehaviour.print("Create EngineSim for " + theEngine.name); //MonoBehaviour.print("maxThrust = " + maxThrust); //MonoBehaviour.print("minThrust = " + minThrust); //MonoBehaviour.print("thrustPercentage = " + thrustPercentage); //MonoBehaviour.print("requestedThrust = " + requestedThrust); //MonoBehaviour.print("velocity = " + velocity); engineSim.partSim = theEngine; engineSim.isActive = active; //engineSim.thrust = (maxThrust - minThrust) * (thrustPercentage / 100f) + minThrust; //MonoBehaviour.print("thrust = " + thrust); engineSim.thrustVec = vecThrust; double flowRate = 0d; if (engineSim.partSim.hasVessel) { //MonoBehaviour.print("hasVessel is true"); //engineSim.actualThrust = engineSim.isActive ? resultingThrust : 0.0; engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere); //if (engineSim.isp == 0d) //{ // MonoBehaviour.print("Isp at " + engineSim.partSim.part.staticPressureAtm + " is zero. Flow rate will be NaN"); //} //if (correctThrust && realIsp == 0) //{ // float ispsl = atmosphereCurve.Evaluate(0); // if (ispsl != 0) // { // engineSim.thrust = engineSim.thrust * engineSim.isp / ispsl; // } // else // { // MonoBehaviour.print("Isp at sea level is zero. Unable to correct thrust."); // } // //MonoBehaviour.print("corrected thrust = " + thrust); //} //if (velocityCurve != null) //{ // engineSim.thrust *= velocityCurve.Evaluate((float)velocity); // //MonoBehaviour.print("thrust at velocity = " + thrust); //} float multiplier = 1; if (atmChangeFlow) { multiplier = (float)(engineSim.partSim.part.atmDensity / 1.225); if (atmCurve != null) { multiplier = atmCurve.Evaluate(multiplier); } } if (velCurve != null) { multiplier *= velCurve.Evaluate((float)machNumber); } if (throttleLocked) { //MonoBehaviour.print("throttleLocked is true"); //flowRate = engineSim.thrust / (engineSim.isp * 9.82); flowRate = Mathf.Lerp(minFuelFlow, maxFuelFlow, (thrustPercentage / 100f)) * multiplier; } else { if (engineSim.partSim.isLanded) { //MonoBehaviour.print("partSim.isLanded is true, mainThrottle = " + FlightInputHandler.state.mainThrottle); flowRate = Mathf.Lerp(minFuelFlow, maxFuelFlow, FlightInputHandler.state.mainThrottle * (thrustPercentage / 100f)) * multiplier; } else { if (currentThrottle > 0) { //MonoBehaviour.print("requestedThrust > 0"); //flowRate = requestedThrust / (engineSim.isp * 9.82) * multiplier; flowRate = Mathf.Lerp(minFuelFlow, maxFuelFlow, currentThrottle * (thrustPercentage / 100f)) * multiplier; } else { //MonoBehaviour.print("requestedThrust <= 0"); flowRate = Mathf.Lerp(minFuelFlow, maxFuelFlow, (thrustPercentage / 100f)) * multiplier; } } } } else { //MonoBehaviour.print("hasVessel is false"); engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere); if (engineSim.isp == 0d) { MonoBehaviour.print("Isp at " + atmosphere + " is zero. Flow rate will be NaN"); } //if (correctThrust) //{ // float ispsl = atmosphereCurve.Evaluate(0); // if (ispsl != 0) // { // engineSim.thrust = engineSim.thrust * engineSim.isp / ispsl; // } // else // { // MonoBehaviour.print("Isp at sea level is zero. Unable to correct thrust."); // } // //MonoBehaviour.print("corrected thrust = " + thrust); //} float multiplier = 1; if (atmChangeFlow) { //multiplier = (float)(engineSim.partSim.part.atmDensity / 1.225); multiplier = (float)atmosphere; // technically wrong but about the same for my Editor need if (atmCurve != null) { multiplier = atmCurve.Evaluate(multiplier); } } if (velCurve != null) { multiplier *= velCurve.Evaluate((float)machNumber); } flowRate = Mathf.Lerp(minFuelFlow, maxFuelFlow, (thrustPercentage / 100f)) * multiplier; } if (SimManager.logOutput) { buffer = new StringBuilder(1024); buffer.AppendFormat("flowRate = {0:g6}\n", flowRate); } engineSim.thrust = flowRate * (engineSim.isp * IspG); // TODO : look into the diff between the 2 in the old code (jet real thrust vs ideal thrust ?) engineSim.actualThrust = engineSim.thrust; float flowMass = 0f; for (int i = 0; i < propellants.Count; i++) { Propellant propellant = propellants[i]; flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id); } if (SimManager.logOutput) { buffer.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 (SimManager.logOutput) { buffer.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); } if (SimManager.logOutput) { MonoBehaviour.print(buffer); } double thrustPerThrustTransform = engineSim.thrust / thrustTransforms.Count; 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 * thrustPerThrustTransform, position); engineSim.appliedForces.Add(appliedForce); } return(engineSim); }