/// <summary> /// This will return an image of the specified size with the thrust graph for this nozzle /// Totally jacked the image code from the wiki http://wiki.kerbalspaceprogram.com/wiki/Module_code_examples, with some modifications /// /// /// </summary> /// <param name="imgH">Hight of the desired output, in pixels</param> /// <param name="imgW">Width of the desired output, in pixels</param> /// <param name="burnTime">Chart for burn time in seconds</param> /// <returns></returns> public static Texture2D stackThrustPredictPic(int imgH, int imgW, int burnTime, FloatCurve atmoCurve, AdvSRBNozzle nozzle) { //First we set up the analyzer //Step 1: Figure out fuel sources nozzle.FuelStackSearcher(nozzle.FuelSourcesList); //Step 2: Set up mass variables float stackTotalMass = 0f; stackTotalMass = nozzle.fullStackFuelMass; float remStackFuel = nozzle.fullStackFuelMass; //stackTotalMass = CalcStackWetMass(FuelSourcesList, stackTotalMass); //float[] segmentFuelArray = new float[nozzle.FuelSourcesList.Count]; int i = 0; //foreach (Part p in nozzle.FuelSourcesList) //{ // segmentFuelArray[i] = p.GetResourceMass(); // i++; //} //float stackCurrentMass = stackTotalMass; //Now we set up the image maker Texture2D image = new Texture2D(imgW, imgH); int graphXmin = 19; int graphXmax = imgW - 20; int graphYmin = 19; //Step 3: Set Up color variables Color brightG = Color.black; brightG.r = .3372549f; brightG.g = 1; Color mediumG = Color.black; mediumG.r = 0.16862745f; mediumG.g = .5f; Color lowG = Color.black; lowG.r = 0.042156863f; lowG.g = .125f; Color MassColor = Color.blue; Color ThrustColor = Color.cyan; Color ExtraThrustColor = Color.yellow; //Step 4: Define text arrays KSF_CharArrayUtils.populateCharArrays(); //Step 5a: Define time markings (every 10 seconds gets a verticle line) System.Collections.Generic.List<int> timeLines = new System.Collections.Generic.List<int>(); double xScale = (imgW - 40) / (double)burnTime; //print("xScale: " + xScale); calcTimeLines((float)burnTime, 10, timeLines, (float)xScale, 20); //Step 5b: Define vertical line markings (9 total to give 10 sections) System.Collections.Generic.List<int> horzLines = new System.Collections.Generic.List<int>(); calcHorizLines(imgH - graphYmin, horzLines, 9, 20); //Step 6: Clear the background //Set all the pixels to black. If you don't do this the image contains random junk. for (int y = 0; y < image.height; y++) { for (int x = 0; x < image.width; x++) { image.SetPixel(x, y, Color.black); } } //Step 7a: Draw Time Lines for (int y = 0; y < image.height; y++) { for (int x = 0; x < image.width; x++) { if (timeLines.Contains(x) && y > graphYmin) image.SetPixel(x, y, lowG); } } //Step 7b: Draw Vert Lines for (int y = 0; y < image.height; y++) { for (int x = 0; x < image.width; x++) { if (horzLines.Contains(y) && x < graphXmax && x > graphXmin) image.SetPixel(x, y, lowG); } } //Step 7c: Draw Bounding Lines for (int y = 0; y < image.height; y++) { for (int x = 0; x < image.width; x++) { if ((x == graphXmin | x == graphXmax) && (y > graphYmin | y == graphYmin)) image.SetPixel(x, y, mediumG); if (y == graphYmin && graphXmax > x && graphXmin < x) image.SetPixel(x, y, mediumG); } } //Step 8a: Populate graphArray double simStep = .2; i = 0; //double peakThrustTime = 0; double peakThrustAmt = 0; //set up the array for the graphs int graphArraySize = Convert.ToInt16(Convert.ToDouble(burnTime) / simStep); double[,] graphArray = new double[graphArraySize, 4]; //one time setups //graphArray[i, 0] = stackMassFlow(FuelSourcesList, (float)(i * simStep), segmentFuelArray, simStep); graphArray[i, 0] = nozzle.MassFlow.Evaluate((float)(i * simStep)) * nozzle.fullStackFuelMass; graphArray[i, 1] = stackTotalMass; graphArray[i, 2] = 9.80665 * atmoCurve.Evaluate(1) * graphArray[i, 0]; graphArray[i, 3] = graphArray[i, 2] - (graphArray[i, 1] * 9.80665); remStackFuel -= (float)graphArray[i, 0]; //fForce = 9.81f * fCurrentIsp * fFuelFlowMass / TimeWarp.fixedDeltaTime; do { i++; // //graphArray[i, 0] = stackMassFlow(FuelSourcesList, (float)(i * simStep), segmentFuelArray, simStep); graphArray[i, 0] = nozzle.MassFlow.Evaluate((float)(i * simStep)) * nozzle.fullStackFuelMass; graphArray[i, 1] = graphArray[i - 1, 1] - (graphArray[i - 1, 0] * simStep); if (remStackFuel > 0) graphArray[i, 2] = 9.80665 * atmoCurve.Evaluate(1) * graphArray[i, 0]; else graphArray[i, 2] = 0; if (graphArray[i, 2] > 0) graphArray[i, 3] = graphArray[i, 2] - (graphArray[i, 1] * 9.80665); else graphArray[i, 3] = 0; if (graphArray[i, 2] > peakThrustAmt) { peakThrustAmt = graphArray[i, 2]; //peakThrustTime = i; } remStackFuel -= (float)graphArray[i, 0] * (float)simStep; //print("generating params i=" + i + " peak at " + Convert.ToInt16(Convert.ToDouble(simDuration) / simStep)); } while (i + 1 < graphArraySize); //Step 8b: Make scales for the y axis double yScaleMass = 1; double yScaleThrust = 1; int usableY; usableY = imgH - 20; yScaleMass = usableY / (Mathf.CeilToInt((float)(graphArray[0, 1] / 10)) * 10); float inter; inter = (float)peakThrustAmt / 100; //print("1: " + inter); inter = Mathf.CeilToInt(inter); //print("22: " + inter); inter = inter * 100; //print("3: " + inter); inter = usableY / inter; //print("4: " + inter); yScaleThrust = inter; //print(yScaleThrust + ":" + peakThrustAmt + ":" + usableY); Debug.Log("graphed scales"); //Step 8c: Graph the mass int lineWidth = 3; for (int x = graphXmin; x < graphXmax; x++) { int fx = fGraph(xScale, x - 20, yScaleMass, graphArray, simStep, graphArraySize, 1, 20); for (int y = fx; y < fx + lineWidth; y++) { image.SetPixel(x, y, MassColor); } } //Step 8d: Graph the thrust lineWidth = 3; for (int x = graphXmin; x < graphXmax; x++) { int fx = fGraph(xScale, x - 20, yScaleThrust, graphArray, simStep, graphArraySize, 2, 20); for (int y = fx; y < fx + lineWidth; y++) { image.SetPixel(x, y, ThrustColor); } } //Step 8e: Graph the thrust extra lineWidth = 2; for (int x = graphXmin; x < graphXmax; x++) { int fx = fGraph(xScale, x - 20, yScaleThrust, graphArray, simStep, graphArraySize, 3, 20); for (int y = fx; y < fx + lineWidth; y++) { image.SetPixel(x, y, ExtraThrustColor); } } //Step 9: Set up boxes for time //int i = 0; string s; i = 0; int length = 0; int pos = 0; int startpos = 0; Texture2D tex; do { s = ""; pos = timeLines[i]; i++; s = i * 10 + " s"; //print("composite string: " + s); length = calcStringPixLength(KSF_CharArrayUtils.convertStringToCharArray(s)); //print("length: " + length); startpos = Mathf.FloorToInt((float)pos - 0.5f * (float)length); Color[] region = image.GetPixels(startpos, 23, length, 11); for (int c = 0; c < region.Length; c++) { region[c] = brightG; } image.SetPixels(startpos, 23, length, 11, region); tex = convertCharArrayToTex(KSF_CharArrayUtils.convertStringToCharArray(s), length, brightG); Color[] region2 = tex.GetPixels(); image.SetPixels(startpos + 2, 25, length - 4, 7, region2); length = 0; } while (i < timeLines.Count); //set up boxes for horizontal lines, mass first startpos = 0; length = 0; pos = 0; i = 0; do { s = ""; pos = horzLines[i]; i++; s = ((Mathf.CeilToInt((float)(graphArray[0, 1] / 10)) * i)).ToString() + " t"; length = calcStringPixLength(KSF_CharArrayUtils.convertStringToCharArray(s)); startpos = Mathf.FloorToInt((float)pos - 5f); Color[] region = image.GetPixels(23, startpos, length, 11); for (int c = 0; c < region.Length; c++) { region[c] = brightG; } image.SetPixels(23, startpos, length, 11, region); tex = convertCharArrayToTex(KSF_CharArrayUtils.convertStringToCharArray(s), length, brightG); Color[] region2 = tex.GetPixels(); image.SetPixels(25, startpos + 2, length - 4, 7, region2); length = 0; } while (i < horzLines.Count); //set up boxes for horizontal lines, thrust startpos = 0; length = 0; pos = 0; i = 0; do { s = ""; pos = horzLines[i]; i++; s = ((Mathf.CeilToInt((float)(peakThrustAmt / 100)) * (i * 10))).ToString() + " k"; length = calcStringPixLength(KSF_CharArrayUtils.convertStringToCharArray(s)); startpos = Mathf.FloorToInt((float)pos - 5f); Color[] region = image.GetPixels(imgW - 60, startpos, length, 11); for (int c = 0; c < region.Length; c++) { region[c] = brightG; } image.SetPixels(imgW - 60, startpos, length, 11, region); tex = convertCharArrayToTex(KSF_CharArrayUtils.convertStringToCharArray(s), length, brightG); Color[] region2 = tex.GetPixels(); image.SetPixels(imgW - 58, startpos + 2, length - 4, 7, region2); length = 0; } while (i < horzLines.Count); image.Apply(); return image; }
public void segmentGUI() { if (DebugDetail > 0) Debug.Log("AdvSRB: in segmentGUI"); //this is the "Segment" mode of the GUI, which allows one to customize burn times for each segment string segGUIname = ""; //AdvSRBNozzle SRB; //must populate the buttons with the most current fuel sources //nozzle.FuelStackSearcher(nozzle.FuelSourcesList); //set length of internal text box to be 50 pix per segment segListLength = lNozzles.Count * 50; //automatically select first segment as current GUI if (lNozzles.Count > 0 && segCurrentGUI == null) { segCurrentGUI = lNozzles[0]; iSegments = 0; refreshNodeInfo = true; } segListVector = GUI.BeginScrollView(new Rect(GUImainRect.xMin + 10, GUImainRect.yMin + 10, 120, 420), segListVector, new Rect(0, 0, 100, segListLength + 30)); GUI.Label(new Rect(15, 0, 100, 15), "Nozzle End"); for (int i = 0; i < lNozzles.Count; i++) { SRB = lNozzles[iSegments].GetComponent<AdvSRBNozzle>(); ////if (SRB.GUIshortName != "") //// segGUIname = SRB.GUIshortName; ////else segGUIname = "Unknown"; //segGUIname = SRB.name; segGUIname = SRB.name; if (i == iSegments) { if (GUI.Button(new Rect(5, i * 50 + 20, 95, 40),"* " + segGUIname)) { if (lNozzles[i] != segCurrentGUI) { segPriorGUI = segCurrentGUI; segCurrentGUI = lNozzles[i]; refreshSegGraph = true; } } } else { if (GUI.Button(new Rect(5, i * 50 + 20, 95, 40), segGUIname)) { if (lNozzles[i] != segCurrentGUI) { segPriorGUI = segCurrentGUI; segCurrentGUI = lNozzles[i]; iSegments = i; refreshSegGraph = true; } } } } GUI.Label(new Rect(15, segListLength + 15, 100, 10), "Top o' Stack"); GUI.EndScrollView(); //if (GUI.Button(new Rect(GUImainRect.xMin + 10, GUImainRect.yMin + 445, 120, 20), "Apply to Symmetry")) //{ // AdvSRBSegment s; // AdvSRBSegment sb; // if (segCurrentGUI != null) // { // s = segCurrentGUI.GetComponent<AdvSRBSegment>(); // foreach (Part p in segCurrentGUI.symmetryCounterparts) // { // Debug.Log("Symmetry Found! " + p.partName); // sb = p.GetComponent<AdvSRBSegment>(); // sb.MassFlow = s.MassFlow; // sb.BurnProfile = s.BurnProfile; // } // } // refreshNodeInfo = true; //} if (isAutoNode) { if (GUI.Button(new Rect(GUImainRect.xMin + 10, GUImainRect.yMin + 470, 120, 20), "Go to Manual Node")) { isAutoNode = !isAutoNode; refreshNodeInfo = true; } } else { if (GUI.Button(new Rect(GUImainRect.xMin + 10, GUImainRect.yMin + 470, 120, 20), "Go to Auto Nodes")) { isAutoNode = !isAutoNode; refreshNodeInfo = true; } } SRB = segCurrentGUI.GetComponent<AdvSRBNozzle>(); SRB.StackSearchAndFuel(); //copy/paste functionality //if (segCurrentGUI != null) //{ // SRB = segCurrentGUI.GetComponent<AdvSRBSegment>(); // if (GUI.Button(new Rect(GUImainRect.xMin + 10, GUImainRect.yMin + 500, 55, 20), "Copy")) // { // //klipboard = SRB.AnimationCurveToString(SRB.MassFlow); // } // if (GUI.Button(new Rect(GUImainRect.xMin + 75, GUImainRect.yMin + 500, 55, 20), "Paste")) // { // //SRB.MassFlow = SRB.AnimationCurveFromString(klipboard); // //SRB.BurnProfile = SRB.AnimationCurveToString(SRB.MassFlow); // refreshNodeInfo = true; // refreshSegGraph = true; // } //} int width = 0; if (segCurrentGUI != null) { //SRB = segCurrentGUI.GetComponent<AdvSRBSegment>(); width = SRB.MassFlow.length * 60; if (refreshSegGraph) { System.Collections.Generic.List<Part> fSL = new System.Collections.Generic.List<Part>(); //filled once during OnActivate, is the master list fSL.Add(segCurrentGUI); segGraph = AdvSRBGraphUtils.stackThrustPredictPic(310, 490, Convert.ToInt16(simDuration), SRB.atmosphereCurve, SRB); refreshSegGraph = false; ; } GUI.Box(new Rect(GUImainRect.xMin + 140, GUImainRect.yMin + 190, 510, 330), segGraph); if (isAutoNode) { //populate the list box int listLength = 0; listLength = 30 * (autoTypeList.Count + 1); //draw the selection box to select which auto mode to use autoListVector = GUI.BeginScrollView(new Rect(GUImainRect.xMin + 140, GUImainRect.yMin + 10, 200, 170), autoListVector, new Rect(0, 0, 180, listLength)); for (int i = 0; i < autoTypeList.Count; i++) { if (GUI.Button(new Rect(5, i * 30 + 5, 175, 20), autoTypeList[i].shortName())) { autoTypeCurrent = i; } } GUI.EndScrollView(); GUI.Label(new Rect(GUImainRect.xMin + 350, GUImainRect.yMin + 10, 300, 40), autoTypeList[autoTypeCurrent].description()); autoTypeList[autoTypeCurrent].drawGUI(GUImainRect); ////if (autoTypeList[autoTypeCurrent].useWithSegment()) ////{ //// this is the button to compute the segment values //// if (GUI.Button(new Rect(GUImainRect.xMin + 500, GUImainRect.yMin + 140, 150, 20), "Evaluate for Segment")) //// { //// SRB.MassFlow = autoTypeList[autoTypeCurrent].computeCurve(nozzle.atmosphereCurve, segCurrentGUI); //// SRB.BurnProfile = SRB.AnimationCurveToString(SRB.MassFlow); //// refreshSegGraph = true; //// } ////} if (autoTypeList[autoTypeCurrent].useWithStack()) { //this is the button to compute stack values if (GUI.Button(new Rect(GUImainRect.xMin + 500, GUImainRect.yMin + 170, 150, 20), "Evaluate for Stack")) { //AdvSRBSegment s; SRB.MassFlow = autoTypeList[autoTypeCurrent].computeCurve(SRB.atmosphereCurve, SRB.fullStackFuelMass); SRB.BurnProfile = AdvSRBUtils.AnimationCurveToString(SRB.MassFlow); //foreach (Part p in nozzle.FuelSourcesList) //{ // //s = p.Modules.OfType<AdvSRBSegment>().FirstOrDefault(); // //s.MassFlow = autoTypeList[autoTypeCurrent].computeCurve(nozzle.atmosphereCurve, p); // //s.BurnProfile = s.AnimationCurveToString(s.MassFlow); //} refreshSegGraph = true; } } } else { nodeListVector = GUI.BeginScrollView(new Rect(GUImainRect.xMin + 140, GUImainRect.yMin + 10, 510, 40), nodeListVector, new Rect(0, 0, width + 70, 22)); if (segCurrentGUI != null) { SRB = segCurrentGUI.GetComponent<AdvSRBNozzle>(); for (int i = 0; i < SRB.MassFlow.length + 1; i++) { if (i < SRB.MassFlow.length) { if (i == nodeNumber) { if (GUI.Button(new Rect(3 + i * 60, 4, 54, 18), "*Node " + (i + 1))) { nodePriorNumber = nodeNumber; nodeNumber = i; refreshNodeInfo = true; } } else { if (GUI.Button(new Rect(3 + i * 60, 4, 54, 18), "Node " + (i + 1))) { nodePriorNumber = nodeNumber; nodeNumber = i; refreshNodeInfo = true; } } } else { if (GUI.Button(new Rect(3 + i * 60, 4, 64, 18), "Add Node")) { nodePriorNumber = nodeNumber; nodeNumber = i; SRB.MassFlow.AddKey(SRB.MassFlow.keys[i - 1].time + 10, 0); refreshNodeInfo = true; } } } } GUI.EndScrollView(); //set up node editing tools if (segCurrentGUI != null && refreshNodeInfo) { //SRB = segCurrentGUI.GetComponent<AdvSRBSegment>(); //lbMsgBox = "The currently selected segment is " + SRB.GUIshortName + " and the current node is Node " + (nodeNumber + 1); //removed May 4, 2014: asterisks make redundant tbTime = SRB.MassFlow.keys[nodeNumber].time.ToString(); tbValue = SRB.MassFlow.keys[nodeNumber].value.ToString(); tbInTan = SRB.MassFlow.keys[nodeNumber].inTangent.ToString(); tbOutTan = SRB.MassFlow.keys[nodeNumber].outTangent.ToString(); refreshNodeInfo = false; } else { if (segCurrentGUI == null) { lbMsgBox = "Please select a segment on the left to begin editing"; tbTime = ""; tbValue = ""; tbInTan = ""; tbOutTan = ""; } } GUI.Label(new Rect(GUImainRect.xMin + 162, GUImainRect.yMin + 80, 78, 15), "Node Time (s)"); GUI.Label(new Rect(GUImainRect.xMin + 292, GUImainRect.yMin + 80, 78, 15), "Node Value"); GUI.Label(new Rect(GUImainRect.xMin + 422, GUImainRect.yMin + 80, 78, 15), "In Tangent"); GUI.Label(new Rect(GUImainRect.xMin + 552, GUImainRect.yMin + 80, 78, 15), "Out Tangent"); tbTime = GUI.TextField(new Rect(140 + GUImainRect.xMin, 100 + GUImainRect.yMin, 120, 20), tbTime); tbValue = GUI.TextField(new Rect(270 + GUImainRect.xMin, 100 + GUImainRect.yMin, 120, 20), tbValue); tbInTan = GUI.TextField(new Rect(400 + GUImainRect.xMin, 100 + GUImainRect.yMin, 120, 20), tbInTan); tbOutTan = GUI.TextField(new Rect(530 + GUImainRect.xMin, 100 + GUImainRect.yMin, 120, 20), tbOutTan); GUI.Label(new Rect(GUImainRect.xMin + 140, GUImainRect.yMin + 60, 510, 15), convertMassFlowToThrust(Convert.ToSingle(tbValue), SRB.atmosphereCurve, lbMsgBox)); simDuration = GUI.TextField(new Rect(140 + GUImainRect.xMin, 160 + GUImainRect.yMin, 40, 20), simDuration); GUI.Label(new Rect(GUImainRect.xMin + 190, GUImainRect.yMin + 160, 150, 20), "Simulation Run Time (s)"); //save node changes if (segCurrentGUI != null) { //SRB = segCurrentGUI.GetComponent<AdvSRBSegment>(); if (GUI.Button(new Rect(GUImainRect.xMin + 490, GUImainRect.yMin + 160, 160, 20), "Save Changes to Node")) { Keyframe k = new Keyframe(); k.time = Convert.ToSingle(tbTime); k.value = Convert.ToSingle(tbValue); k.inTangent = Convert.ToSingle(tbInTan); k.outTangent = Convert.ToSingle(tbOutTan); SRB.MassFlow.RemoveKey(nodeNumber); SRB.MassFlow.AddKey(k); SRB.BurnProfile = AdvSRBUtils.AnimationCurveToString(SRB.MassFlow); refreshNodeInfo = true; refreshSegGraph = true; } if (GUI.Button(new Rect(GUImainRect.xMin + 140, GUImainRect.yMin + 130, 160, 20), "Smooth Tangents")) { SRB.MassFlow.SmoothTangents(nodeNumber, 1); SRB.BurnProfile = AdvSRBUtils.AnimationCurveToString(SRB.MassFlow); } if (GUI.Button(new Rect(GUImainRect.xMin + 320, GUImainRect.yMin + 130, 160, 20), "Flat In Tan")) { float deltaY; float deltaX; deltaY = SRB.MassFlow.keys[nodeNumber].value - SRB.MassFlow.keys[nodeNumber - 1].value; deltaX = SRB.MassFlow.keys[nodeNumber].time - SRB.MassFlow.keys[nodeNumber - 1].time; tbInTan = (deltaY / deltaX).ToString(); SRB.BurnProfile = AdvSRBUtils.AnimationCurveToString(SRB.MassFlow); } if (GUI.Button(new Rect(GUImainRect.xMin + 490, GUImainRect.yMin + 130, 160, 20), "Flat Out Tan")) { float deltaY; float deltaX; deltaY = SRB.MassFlow.keys[nodeNumber + 1].value - SRB.MassFlow.keys[nodeNumber].value; deltaX = SRB.MassFlow.keys[nodeNumber + 1].time - SRB.MassFlow.keys[nodeNumber].time; tbOutTan = (deltaY / deltaX).ToString(); SRB.BurnProfile = AdvSRBUtils.AnimationCurveToString(SRB.MassFlow); } if (nodeNumber != 0) { if (GUI.Button(new Rect(GUImainRect.xMin + 380, GUImainRect.yMin + 160, 100, 20), "Remove Node")) { SRB.MassFlow.RemoveKey(nodeNumber); SRB.BurnProfile = AdvSRBUtils.AnimationCurveToString(SRB.MassFlow); refreshNodeInfo = true; refreshSegGraph = true; } } //highlight the selected booster segment in the editor //segCurrentGUI.SetHighlight(true); } } } }