######################################################################################### # # AdvRecorder (TNTsim3D) # ######################################################################################### # # Started: 28 March 2005 # Updated: 31 November 2005 # # Authors: # Dave Breitwisch, MicroImages, Inc. # Jeremy Johnson, MicroImages, Inc. # # Minimum Version Required: TNTmips2005:71 # # To Run: # This script must be saved as an RVC object in the Landscape file that is being used # by TNTsim3D. # ######################################################################################### ######################################################################################### # # Purpose: # # The purpose of this script is to provide a user of TNTsim3D a way to record the # path that they create when using the simulator. Options are then given to smooth # the path using splining and averaging methods along with the direction of the view. # An option to create a movie of the path using popular codecs is also provided. # # Description of Controls: # # Basic Operations: # # Open Button: # Used to load a previously saved or generated path from a text file. # # Save Button: # Used to save the current path to a text file. # # Flight Recorder Panel: # # Record Button: # Saves the position and viewing direction of each frame created by TNTsim3D into # a set of arrays until the stop button is pressed. # # Play Button: # Loads the saved information in the arrays one frame at a time so TNTsim3D will # playback the recorded or loaded path. # # Pause Button: # Pauses TNTsim3D on the current frame while in play mode. # # Stop Button: # Stops playback and resets the current frame counter to the beginning. # # Loop Toggle: # Sets the playback to repeat the path when finished # # Smoothing Process Panel: # # Path Smoothing Controls: # # Enable Toggle: # Enable the script to smooth the x,y,z path and create a new view for each of # the frames depending on the path. # # Smoothing Ratio: # Ratio of the number of frames out to frames in. # 1 = Same number of frames out as in. # 2 = Twice the number of frames out as in. # 0.5 = Half the number of frames out as in. # # Thinning Percentage: # The percent of the path that is thinned out to uses as the basis for the # reconstructed smoother path. Current Range of Values = 0.01 to 10.00. # 0 = No thinning at all, the entire line is used. This will result in very # little smoothing of the path original. # 10 = 2 endpoints and 9 equally spaced points along the original line. # 50 = 2 endpoints and a point along the center of the original path. # 100 = Completely thinned (2 endpoints and a straight line) # # Viewer Direction Controls: # # Roll Factor: # Numeric value used in an algorithm to create a simulated roll in the viewer # position. The roll is based on the change of heading X number of frames # ahead (specified in next control). This is a scaling factor for that # algorithm. # 0 = No roll is created. The roll value will always be z # 1 = The default result of the algorithm is used # 2 = Twice the result of the algorithm is used # # Frames to Look Ahead: # Numeric value used by the Roll creation algorithm to determine how far ahead # to look to find the change in heading. The greater the value, the earlier # the view would start to roll in anticipation of the turn. # # View Direction Smoothing Controls: # # Enable Toggle: # Enable the script to smooth the roll, pitch, turn values of the current working # path. This can be done to smooth out artifacts that may be created by the view # creation in the Path Smoothing process. # # Frames Ahead: # Number of frames ahead to use when averaging out the components of the current # viewing frame. # # Frames Behind: # Number of frames back to use when averaging out the components of the current # viewing frame. # # Number of Times: # Number of passes through this averaging algorithm to smooth out the view. More # passes will create a smoother view, but detail will be gradually lost. # # Start Smoothing Button: # Starts the process of smoothing the path based on the user specified settings. # # Reload Previous Button: # Reloads the last set of values from the working array. These saved arrays # contain the original working data before the last smoothing process was started. # Effectively an "Undo" button. # # Create Movie Panel: # # Movie Type: # Type of movie file to create based on codecs on the machine (Ex: MPEG or AVI). # # Framerate: # Number of frames to create per second. Each frame is equal to one position on # the path. # # Movie Controls: # # Record Button: # Starts the recording process. The path is recreated in TNTsim3D and each frame # is saved and appended to the movie file according to the codec. # # Stop Button: # Stops the recording process. The movie file will contain all frames up to the # point where the process was stopped. # ######################################################################################### ######################################################################################### # Global Declarations ######################################################################################### ######################################################################################### # For a script to run under TNTsim3D, it must include an instance of class TNTSIM3D # named "TNTsim3D". ######################################################################################### class TNTSIM3D TNTsim3D; ############################################ # Numeric values used as indices or counters ############################################ numeric index = 0; numeric loop = 0; numeric savedPoints; numeric numPoints = 0; numeric numPointsInSlice; numeric finalIndexPos; numeric maxPoints = 10000000; ############################################ # Arrays to hold position and direction info ############################################ array numeric posX[maxPoints]; array numeric posY[maxPoints]; array numeric posZ[maxPoints]; array numeric pitch[maxPoints]; array numeric roll[maxPoints]; array numeric turn[maxPoints]; array numeric tmpposX[1]; array numeric tmpposY[1]; array numeric tmpposZ[1]; array numeric tmppitch[1]; array numeric tmproll[1]; array numeric tmpturn[1]; array numeric savedposX[1]; array numeric savedposY[1]; array numeric savedposZ[1]; array numeric savedpitch[1]; array numeric savedroll[1]; array numeric savedturn[1]; array numeric finalposX[1]; array numeric finalposY[1]; array numeric finalposZ[1]; array numeric finalpitch[1]; array numeric finalroll[1]; array numeric finalturn[1]; array numeric descArray[1]; ############################################ # Polyline to hold position information ############################################ class POLYLINE P; ############################################ # State of recorder: # 0 = idle, 1 = record, # 2 = play, 3 = movie generation ############################################ numeric state = 0; ############################################ # Dialog Controls ############################################ class GUI_CTRL_PUSHBUTTON open, save, start, reload; class GUI_CTRL_TOGGLEBUTTON record, play, pause, stop, movierecord, moviestop; class GUI_CTRL_TOGGLEBUTTON psToggle, psPrint, vsToggle, vsPrint; class GUI_CTRL_LABEL iFnameR, oFname, numberframes, frame; class GUI_CTRL_LABEL moviecurrentframe, movietotalframes; class GUI_CTRL_LABEL vcTRlbl, vcSFlbl, vcRFlbl, vcFAlbl, vsFAlbl, vsFBlbl, vsTSlbl; class GUI_CTRL_LABEL numFrmStrt, numFrmFnsh, savefile, smStatus; class GUI_CTRL_EDIT_NUMBER vcTRnum, vcSFnum, vcRFnum, vcFAnum, vsFAnum, vsFBnum, vsTSnum; class GUI_CTRL_GROUPBOX psCtrls, vcCtrls, vsCtrls; class GUI_CTRL_COMBOBOX movietypecombo, movieframeratecombo; class GUI_FORMDATA data; class GUI_DLG dlgwin; ############################################ # User Settings for path smoothing ############################################ numeric rollFactor; numeric lookAhead; numeric smoothAhead; numeric smoothBehind; numeric smoothFactor; ############################################ # Variables for movie generation ############################################ class GRDEVICE_MEM_RGB24 frameDevice; numeric movieHeight = 0; numeric movieWidth = 0; class Frame movieFrame; class GC gc; class Movie movie; class Color color; color.red = 100; color.green = 50; color.blue = 50; class RECT movierect; class POINT2D moviepoint; ######################################################################################### ######################################################################################### ######################################################################################### # Functions used to smooth non-stationary paths ######################################################################################### ######################################################################################### # Creates a Polyline from the (x,y,z) positions from the loaded path proc CreatePolyline( ) { local numeric i; local class POINT3D p3d; P.Clear(); P.Set3D(true); for i = 1 to numPoints { p3d.x = posX[i]; p3d.y = posY[i]; p3d.z = posZ[i]; P.AppendVertex3D(p3d); } } ######################################################################################### # Averages out the Z values. Required becasue the splining of the polyline uses linear # interpolation for the Z values and is not "included" in the splining method. proc SmoothZValues(numeric numCut) { local numeric points = P.GetNumPoints(); local array numeric tempZvalues[points]; local numeric m, n, diff, sum; for m = 1 to points { # Set the range to be even on both sides if ( m < numCut ) { diff = m - 1; } else if ( m > (points - numCut) ) { diff = points - m; } else { diff = numCut; } # Find the average Z value within the range of values sum = 0; for n = (diff * -1) to diff { sum = sum + tmpposZ[m + n]; } tempZvalues[m] = sum / (diff * 2 + 1); } # Save the new Z values for m = 1 to points { tmpposZ[m] = tempZvalues[m]; } } ######################################################################################### # Smooth the polyline using Thin and Splining methods proc SmoothLine( ) { local class POINT3D p; local numeric points, i; local numeric PopulateIterations = 3; local numeric origLen, origPoints, distperframe; smStatus.SetLabel("Smoothing Process Status: " + "Smoothing Line " + NumToStr(0) + "% complete"); # Calculate the stats from the original path origPoints = P.GetNumPoints(); origLen = P.ComputeLength(); distperframe = origLen / origPoints; local numeric thinPercent = vcTRnum.GetValue() / 100.0; local numeric numPointsCut = (thinPercent * origLen) / distperframe; # Thin the polyline based on the user input. 100% thins the entire line to a straight # line between its endpoints. 0% would not thin the line at all. Typical values # range between 0.01 and 10%. P.Thin("Minimum", thinPercent * origLen); smStatus.SetLabel("Smoothing Process Status: " + "Smoothing Line " + NumToStr(10) + "% complete"); # Spline the X,Y values to smooth the path and overpopulate the line P.Spline("Cubic", numPointsCut, 1, "DontMoveEnds"); smStatus.SetLabel("Smoothing Process Status: " + "Smoothing Line " + NumToStr(30) + "% complete"); P.Spline("Cubic", 100, 1, "DontMoveEnds"); smStatus.SetLabel("Smoothing Process Status: " + "Smoothing Line " + NumToStr(40) + "% complete"); # Re-Thin the line to decrease the effects on the Z values from the X,Y spline P.Thin("Minimum", thinPercent * origLen * 0.01); smStatus.SetLabel("Smoothing Process Status: " + "Smoothing Line " + NumToStr(50) + "% complete"); # Spline the Z value to smooth the elevation and overpopulate the line P.SplineZ("Cubic", numPointsCut, 1, "DontMoveEnds"); smStatus.SetLabel("Smoothing Process Status: " + "Smoothing Line " + NumToStr(60) + "% complete"); P.SplineZ("Cubic", 100, 1, "DontMoveEnds"); smStatus.SetLabel("Smoothing Process Status: " + "Smoothing Line " + NumToStr(70) + "% complete"); local numeric thinFactor = distperframe * (1/smoothFactor); local numeric adjustThinning = 1; local class POLYLINE tempLine; local numeric errCount = 0; local numeric thinError = 0.98; local numeric currNum, wantNum, needNum; # Based on a calculated guess, use a trial and error method to thin the smoothed # polyline so the output number of vertices closely matches the input number of vertices # and the smoothing ratio. while ( adjustThinning ) { tempLine = P; tempLine.Thin("Minimum", thinFactor); currNum = tempLine.GetNumPoints(); needNum = smoothFactor * origPoints; if (currNum > needNum) { if ( currNum < (needNum * (2 - thinError)) ) { adjustThinning = 0; } else { # Make thinFactor larger thinFactor = thinFactor * 1.01; adjustThinning = 1; } } else if (currNum < needNum) { if ( currNum > (needNum * thinError) ) { adjustThinning = 0; } else { # Make thinFactor smaller thinFactor = thinFactor * 0.99; adjustThinning = 1; } } else { adjustThinning = 0; } # Report a warning if it can not get within the margin of error errCount++; if (errCount > 100) { local string errMsg = "Warning: Unable to thin the line back to within \n"; errMsg = errMsg + NumToStr(thinError) + "% of the specified Smoothing Ratio\n"; errMsg = errMsg + "after " + NumToStr(errCount) + " tries.\n"; errMsg = errMsg + "The current result is the closet value it has calculated.\n"; errMsg = errMsg + "For more accurate results, adjust the 'thinError' variable \n"; errMsg = errMsg + "or the 'errCount > #' condition in script near this error message."; PopupMessage(errMsg); adjustThinning = 0; } } smStatus.SetLabel("Smoothing Process Status: " + "Smoothing Line " + NumToStr(80) + "% complete"); P = tempLine; points = P.GetNumPoints(); # Resize and save the data to the arrays ResizeArrayClear(tmpposX, points); ResizeArrayClear(tmpposY, points); ResizeArrayClear(tmpposZ, points); ResizeArrayClear(tmppitch, points); ResizeArrayClear(tmproll, points); ResizeArrayClear(tmpturn, points); local numeric n; for n = 1 to points { p = P.GetVertex3D(n-1); tmpposX[n] = p.x; tmpposY[n] = p.y; tmpposZ[n] = p.z; } smStatus.SetLabel("Smoothing Process Status: " + "Smoothing Line " + NumToStr(90) + "% complete"); # Use an averaging method to smooth out the Z values SmoothZValues(numPointsCut); smStatus.SetLabel("Smoothing Process Status: " + "Smoothing Line " + NumToStr(100) + "% complete"); } ######################################################################################### # Calculates the view (pitch, roll, turn) proc CreateFlightView( numeric points ) { local numeric i, x1, x2, y1, y2, z1, z2; local numeric fwd = lookAhead; local numeric Dist, Azim1, Azim2, Elev; for i = 1 to points - 1 { # Check for the end of the line if ((i + fwd) > points) { fwd = points - i; } # Find out where to look ahead to x1 = tmpposX[i]; y1 = tmpposY[i]; z1 = tmpposZ[i]; x2 = tmpposX[i + fwd]; y2 = tmpposY[i + fwd]; z2 = tmpposZ[i + fwd]; Displacement3Dd(x1, y1, z1, x2, y2, z2, Dist, Azim1, Elev); tmpturn[i] = Azim1; local numeric Rho, Theta, Phi; ConvertXYZtoSphericald(x2-x1, y2-y1, z2-z1, Rho, Theta, Phi); tmppitch[i] = 90 - Phi; x2 = tmpposX[i+1]; y2 = tmpposY[i+1]; z2 = tmpposZ[i+1]; Displacement3Dd(x1, y1, z1, x2, y2, z2, Dist, Azim2, Elev); # Handles right turn over 360-0 degree boundry if (Azim1 < 90 and Azim2 > 270) { Azim2 = Azim2 - 360; } # Handles left turn over 0-360 degree boundery if (Azim2 < 90 and Azim1 > 270) { Azim1 = Azim1 - 360; } tmproll[i] = ((Azim1 - Azim2) / 90) * 90 * rollFactor; } # Level out last frame tmppitch[points] = tmppitch[points-1]; tmproll[points] = tmproll[points-1]; tmpturn[points] = tmpturn[points-1]; } ######################################################################################### # Smooth Path using polyline proc SmoothViaPolyine() { local numeric points; CreatePolyline(); SmoothLine(); points = P.GetNumPoints()-1; CreateFlightView(points); numPoints=points; } ######################################################################################### ######################################################################################### # Functions used to manage data arrays ######################################################################################### ######################################################################################### # Keep a copy of the data so the working arrays can be rolled back if requested proc SaveArrays( ) { savedPoints = numPoints; ResizeArrayClear(savedposX, savedPoints); ResizeArrayClear(savedposY, savedPoints); ResizeArrayClear(savedposZ, savedPoints); ResizeArrayClear(savedpitch, savedPoints); ResizeArrayClear(savedroll, savedPoints); ResizeArrayClear(savedturn, savedPoints); local numeric i; for i = 1 to savedPoints { savedposX[i] = posX[i]; savedposY[i] = posY[i]; savedposZ[i] = posZ[i]; savedpitch[i] = pitch[i]; savedroll[i] = roll[i]; savedturn[i] = turn[i]; } } ######################################################################################### # Reset the original arrays with the temporary working data proc ResetArrays() { ResizeArrayClear(posX, numPoints); ResizeArrayClear(posY, numPoints); ResizeArrayClear(posZ, numPoints); ResizeArrayClear(pitch, numPoints); ResizeArrayClear(roll, numPoints); ResizeArrayClear(turn, numPoints); local numeric i; for i = 1 to numPoints { posX[i]=tmpposX[i]; posY[i]=tmpposY[i]; posZ[i]=tmpposZ[i]; pitch[i]=tmppitch[i]; roll[i]=tmproll[i]; turn[i]=tmpturn[i]; } } ######################################################################################### # Reload the original arrays with the saved working data proc ReloadArrays( ) { ResizeArrayClear(posX, savedPoints); ResizeArrayClear(posY, savedPoints); ResizeArrayClear(posZ, savedPoints); ResizeArrayClear(pitch, savedPoints); ResizeArrayClear(roll, savedPoints); ResizeArrayClear(turn, savedPoints); local numeric i; for i = 1 to savedPoints { posX[i] = savedposX[i]; posY[i] = savedposY[i]; posZ[i] = savedposZ[i]; pitch[i] = savedpitch[i]; roll[i] = savedroll[i]; turn[i] = savedturn[i]; } numPoints = savedPoints; numberframes.SetLabel(NumToStr(savedPoints)); movietotalframes.SetLabel(NumToStr(savedPoints)); numFrmStrt.SetLabel(NumToStr(savedPoints)); numFrmFnsh.SetLabel(""); smStatus.SetLabel("Smoothing Process Status: Idle"); } ######################################################################################### ######################################################################################### # Functions that determine stationary and non-stationary points ######################################################################################### ######################################################################################### # Returns the number of successive points that are stationary func NumIdenticalPositions(numeric startIndex, numeric required, endIndex) { local numeric length = 1; while (startIndex < endIndex ) { if ( ((startIndex + length) <= savedPoints ) && (savedposX[startIndex] == savedposX[startIndex + length]) && (savedposY[startIndex] == savedposY[startIndex + length]) && (savedposZ[startIndex] == savedposZ[startIndex + length]) ) { length ++; } else { if (length >= required) { return length; } else { return 0; } } } return 0; } ######################################################################################### # Creates an array that will store whether the points are stationary or non-stationary proc CreateDescriptorArray() { local numeric numRequired = 10; local numeric length; ResizeArrayClear(descArray, savedPoints); descArray[1] = 1; local numeric w; for w = 1 to savedPoints { length = NumIdenticalPositions(w, numRequired, savedPoints - 1); if (length >= numRequired) { descArray[w] = length * (-1); w = w + length; if (w <= savedPoints) { descArray[w] = 1; } } } for w = 1 to savedPoints { if ( (round( (w/savedPoints) * 100 ) % 10) == 0) { smStatus.SetLabel("Smoothing Process Status: " + "Analysing Path " + NumToStr(round((w/savedPoints) * 100)) + "% complete"); } if (descArray[w] == 1) { local numeric temp = 1; if ( w != savedPoints) { while ( (descArray[w + temp] >= 0) && ((w+temp) < savedPoints) ) { temp++; } } descArray[w] = temp; } } } ######################################################################################### ######################################################################################### # Functions used to manage segments of the flight path ######################################################################################### ######################################################################################### # Load the original arrays with a segement of the full path proc CreateSliceArray(numeric currIndex) { ResizeArrayClear(posX, numPointsInSlice); ResizeArrayClear(posY, numPointsInSlice); ResizeArrayClear(posZ, numPointsInSlice); ResizeArrayClear(pitch, numPointsInSlice); ResizeArrayClear(roll, numPointsInSlice); ResizeArrayClear(turn, numPointsInSlice); local numeric temp; for temp = 1 to numPointsInSlice { posX[temp] = savedposX[currIndex + temp - 1]; posY[temp] = savedposY[currIndex + temp - 1]; posZ[temp] = savedposZ[currIndex + temp - 1]; pitch[temp] = savedpitch[currIndex + temp - 1]; roll[temp] = savedroll[currIndex + temp - 1]; turn[temp] = savedturn[currIndex + temp - 1]; } } ######################################################################################### # Add the arrays to the final arrays that contain the final path proc AppendSliceArray() { ResizeArrayPreserve(finalposX, finalIndexPos + numPointsInSlice); ResizeArrayPreserve(finalposY, finalIndexPos + numPointsInSlice); ResizeArrayPreserve(finalposZ, finalIndexPos + numPointsInSlice); ResizeArrayPreserve(finalpitch, finalIndexPos + numPointsInSlice); ResizeArrayPreserve(finalroll, finalIndexPos + numPointsInSlice); ResizeArrayPreserve(finalturn, finalIndexPos + numPointsInSlice); local numeric t; for t = 1 to numPointsInSlice { finalposX[finalIndexPos + t] = posX[t]; finalposY[finalIndexPos + t] = posY[t]; finalposZ[finalIndexPos + t] = posZ[t]; finalpitch[finalIndexPos + t] = pitch[t]; finalroll[finalIndexPos + t] = roll[t]; finalturn[finalIndexPos + t] = turn[t]; } finalIndexPos = finalIndexPos + numPointsInSlice; } ######################################################################################### ######################################################################################### # Functions used to smooth the view direction of a flight path ######################################################################################### ######################################################################################### # Make sure the heading value is within bounds func CheckHeading(numeric currHead, numeric nextHead) { if (currHead > 270 and nextHead < 90) { nextHead = nextHead + 360; } else if (currHead < 90 and nextHead > 270) { nextHead = nextHead - 360; } return nextHead; } ######################################################################################### # Make sure the roll value is within bounds func CheckRoll(numeric currRoll, numeric nextRoll) { if (currRoll > 100 and nextRoll < -100) { nextRoll = nextRoll + 360; } else if (currRoll < -100 and nextRoll > 100) { nextRoll = nextRoll - 360; } return nextRoll; } ######################################################################################### # Smooth view direction of current working XYZ flight path by averaging nearby values proc SmoothViewViaXYZPath() { local numeric i, n; local numeric frmBack = smoothBehind; local numeric frmAhead = smoothAhead; ResizeArrayClear(tmpposX, numPoints); ResizeArrayClear(tmpposY, numPoints); ResizeArrayClear(tmpposZ, numPoints); ResizeArrayClear(tmppitch, numPoints); ResizeArrayClear(tmproll, numPoints); ResizeArrayClear(tmpturn, numPoints); numeric sumPitch, sumRoll, sumTurn; for i = 1 to numPoints { sumPitch = sumRoll = sumTurn = 0; tmpposX[i] = posX[i]; tmpposY[i] = posY[i]; tmpposZ[i] = posZ[i]; # Add the points behind for n = i - frmBack to i { if (n < 1) { sumPitch = sumPitch + pitch[1]; sumRoll = sumRoll + CheckRoll(roll[i], roll[1]); sumTurn = sumTurn + CheckHeading(turn[i], turn[1]); } else { sumPitch = sumPitch + pitch[n]; sumRoll = sumRoll + CheckRoll(roll[i], roll[n]); sumTurn = sumTurn + CheckHeading(turn[i], turn[n]); } } # Add the points ahead for n = i + 1 to i + frmAhead { if (n > numPoints) { sumPitch = sumPitch + pitch[numPoints]; sumRoll = sumRoll + CheckRoll(roll[i], roll[numPoints]); sumTurn = sumTurn + CheckHeading(turn[i], turn[numPoints]); } else { sumPitch = sumPitch + pitch[n]; sumRoll = sumRoll + CheckRoll(roll[i], roll[n]); sumTurn = sumTurn + CheckHeading(turn[i], turn[n]); } } # Save the average of the sum tmppitch[i] = sumPitch / (frmBack + frmAhead + 1); tmproll[i] = sumRoll / (frmBack + frmAhead + 1); tmpturn[i] = sumTurn / (frmBack + frmAhead + 1); if ( (round( (i/numPoints) * 100 ) % 10) == 0) { smStatus.SetLabel("Smoothing Process Status: " + "Smoothing View " + NumToStr(round((i/numPoints) * 100)) + "% complete"); } } } ######################################################################################### ######################################################################################### # Callback procedures for control dialog ######################################################################################### ######################################################################################### # Procedure to end movie recording and clean up. proc OnEndMovie() { MovieStop(movie); DestroyGC(gc); MovieExit(movie); FrameDestroy(movieFrame); TNTsim3D.StopRecording(); moviestop.SetEnabled(false); #grey out button moviestop.SetValue(0,0); #pop stop button if it was pushed movierecord.SetValue(0,0); #pop record button state = 0; #Don't use SetState() here since it would call EndMovie again index = 1; } ######################################################################################### # Process for setting the state of the recorder proc SetState(numeric newstate) { if (state == 3) { OnEndMovie(); } state = newstate; } ######################################################################################### # Procedure to start recording a movie and set up recording parameters. proc OnStartMovie() { TNTsim3D.StartRecording(); frameDevice = TNTsim3D.GetFrameDevice(); movieWidth = frameDevice.GetWidth(); movieHeight = frameDevice.GetHeight(); movieFrame = FrameCreate(movieWidth,movieHeight); gc = FrameCreateGC(movieFrame); ActivateGC(gc); movie = MovieInit(); SetColor(color); data = dlgwin.GetValues(); #test local string type$ = data.GetValueStr("MovieTypeCombo"); local string framerate$ = data.GetValueStr("MovieFramerateCombo"); MovieSetFormat(movie,type$); # Get type from combobox MovieSetFrameRate(movie,framerate$); # Get framerate from combobox MovieSetFrameWidth(movie,movieWidth); MovieSetFrameHeight(movie,movieHeight); # Make Output File string ext$; ext$ = MovieGetFileExt(movie); string filename$; filename$ = GetOutputFileName("","Create output file for movie",ext$); MovieStart(movie,filename$); movierect.Set(0,0, movieWidth-1, movieHeight-1); moviepoint.x = 1; moviepoint.y = 1; SetState(3); moviestop.SetEnabled(1); } ######################################################################################### # Called to enable the dialog controls when recording proc Enable () { save.SetEnabled(1); play.SetEnabled(1); pause.SetEnabled(1); stop.SetEnabled(1); start.SetEnabled(1); movierecord.SetEnabled(1); } ######################################################################################### # Called when the state is set to stop proc OnStop () { SetState(0); index = 1; stop.SetValue(1, 0); record.SetValue(0, 0); play.SetValue(0, 0); pause.SetValue(0, 0); frame.SetLabel(""); oFname.SetLabel("Path is now set for playback, smoothing, or movie recording"); } ######################################################################################### # Called to open a flight path from a text file. Called when the Open button on the # dialog is pressed proc OnOpen ( ) { ResizeArrayClear(posX, maxPoints); ResizeArrayClear(posY, maxPoints); ResizeArrayClear(posZ, maxPoints); ResizeArrayClear(pitch, maxPoints); ResizeArrayClear(roll, maxPoints); ResizeArrayClear(turn, maxPoints); local class FILEPATH filename = GetInputFileName("", "Select Input File", "txt"); if (!filename.Exists()) return; local class FILE file = fopen(filename); if (file == 0) return; local string string$ = "something"; numPoints = 0; # Read in the data from the text file while (string$ != "") { string$ = fgetline$(file); if (NumberTokens(string$, ",") != 6) continue; numPoints++; posX[numPoints] = StrToNum(GetToken(string$, ",", 1)); posY[numPoints] = StrToNum(GetToken(string$, ",", 2)); posZ[numPoints] = StrToNum(GetToken(string$, ",", 3)); pitch[numPoints] = StrToNum(GetToken(string$, ",", 4)); roll[numPoints] = StrToNum(GetToken(string$, ",", 5)); turn[numPoints] = StrToNum(GetToken(string$, ",", 6)); } # Set the counters and indices numberframes.SetLabel(NumToStr(numPoints)); movietotalframes.SetLabel(NumToStr(numPoints)); numFrmStrt.SetLabel(NumToStr(numPoints)); iFnameR.SetLabel("../"+filename.GetName()); start.SetEnabled(1); OnStop(); Enable(); } ######################################################################################### # Called to save the recorded flight path in a text file. Called when Save button on # the dialog is pressed proc OnSave ( ) { local class FILE file = GetOutputTextFile("", "Select Output File", "txt"); if (file == 0) return; local numeric idx; for (idx = 1; idx <= numPoints; idx++) fwritestring(file, sprintf("%f,%f,%f,%f,%f,%f\n", posX[idx], posY[idx], posZ[idx], pitch[idx], roll[idx], turn[idx])); OnStop(); oFname.SetLabel("Saved path is now set for playback, smoothing, or movie recording"); } ######################################################################################### # Called when the Record button is pressed. Sets the state to record. proc OnRecord () { # called when Record button is pressed; Enable(); # enable controls, set state to record, SetState(1); # initialize counters, and set status of controls index = 1; numPoints = 0; record.SetValue(1, 0); play.SetValue(0, 0); pause.SetValue(0, 0); stop.SetValue(0, 0); iFnameR.SetLabel("No File Selected"); } ######################################################################################### # Called when the Play button is pressed. Sets the state to play. proc OnPlay () { SetState(2); play.SetValue(1, 0); record.SetValue(0, 0); pause.SetValue(0, 0); stop.SetValue(0, 0); } ######################################################################################### # Called when the Pause button is pressed. Sets the state to pause. proc OnPause () { # called when Pause button is pressed; SetState(0); # set state to pause and set status of controls pause.SetValue(1, 0); stop.SetValue(0, 0); } ######################################################################################### # Resets the loop status when the checkbox is changed proc OnLoop () { loop = !loop; } ######################################################################################### # Resets the dialog controls when the checkbox is changed proc OnPathSmoothToggle() { local numeric value = psToggle.GetValue(); psCtrls.SetEnabled(value); vcCtrls.SetEnabled(value); vcTRlbl.SetEnabled(value); vcTRnum.SetEnabled(value); vcSFlbl.SetEnabled(value); vcSFnum.SetEnabled(value); vcRFlbl.SetEnabled(value); vcRFnum.SetEnabled(value); vcFAlbl.SetEnabled(value); vcFAnum.SetEnabled(value); psPrint.SetEnabled(value); } ######################################################################################### # Resets the dialog controls when the checkbox is changed proc OnViewSmoothToggle() { local numeric value = vsToggle.GetValue(); vsCtrls.SetEnabled(value); vsFAlbl.SetEnabled(value); vsFAnum.SetEnabled(value); vsFBlbl.SetEnabled(value); vsFBnum.SetEnabled(value); vsTSlbl.SetEnabled(value); vsTSnum.SetEnabled(value); vsPrint.SetEnabled(value); } ######################################################################################### # Main process called when the Start Smoothing button is pressed. Based on the user # parameters, it calls the appropriate functions to smooth the path and view of the data # that is currently located in the working arrays. proc OnStartSmoothing( ) { if (psToggle.GetValue() | vsToggle.GetValue()) { OnStop(); smStatus.SetLabel("Smoothing Process Status: Saving Previous Data"); start.SetEnabled(0); SaveArrays(); numFrmStrt.SetLabel(NumToStr(savedPoints)); numFrmFnsh.SetLabel(""); # Get the user specified values lookAhead = vcFAnum.GetValue(); smoothAhead = vsFAnum.GetValue(); smoothBehind = vsFBnum.GetValue(); smoothFactor = vcSFnum.GetValue(); rollFactor = vcRFnum.GetValue(); # Check if the user wants the path smoothed if (psToggle.GetValue()) { finalIndexPos = 0; numPointsInSlice = 0; # Search through path to identify stationary and non-stationary segments smStatus.SetLabel("Smoothing Process Status: Analysing Path 0% complete"); CreateDescriptorArray(); local numeric r = 1; while (r <= savedPoints) { # If non-stationary segment; cut, smooth, then paste if (descArray[r] > 0) { numPoints = numPointsInSlice = descArray[r]; CreateSliceArray(r); SmoothViaPolyine(); ResetArrays(); numPointsInSlice = numPoints; AppendSliceArray(); # If stationary segment; cut and paste } else if (descArray[r] < 0) { numPointsInSlice = descArray[r] * (-1); CreateSliceArray(r); AppendSliceArray(); } if ( (round( (r/savedPoints) * 100 ) % 10) == 0) { smStatus.SetLabel("Smoothing Process Status: " + "Smoothing Path " + NumToStr(round((r/savedPoints) * 100)) + "% complete"); } r++; } # Reload final results into the working arrays ResizeArrayClear(posX, finalIndexPos); ResizeArrayClear(posY, finalIndexPos); ResizeArrayClear(posZ, finalIndexPos); ResizeArrayClear(pitch, finalIndexPos); ResizeArrayClear(roll, finalIndexPos); ResizeArrayClear(turn, finalIndexPos); for r = 1 to finalIndexPos { posX[r] = finalposX[r]; posY[r] = finalposY[r]; posZ[r] = finalposZ[r]; pitch[r] = finalpitch[r]; roll[r] = finalroll[r]; turn[r] = finalturn[r]; } numPoints = finalIndexPos; ResizeArrayClear(finalposX, 1); ResizeArrayClear(finalposY, 1); ResizeArrayClear(finalposZ, 1); ResizeArrayClear(finalpitch, 1); ResizeArrayClear(finalroll, 1); ResizeArrayClear(finalturn, 1); } # Checks if the user wants the view smoothed local numeric i; if (vsToggle.GetValue()) { for i = 1 to vsTSnum.GetValue() { SmoothViewViaXYZPath(); ResetArrays(); } } numberframes.SetLabel(NumToStr(numPoints)); movietotalframes.SetLabel(NumToStr(numPoints)); numFrmFnsh.SetLabel(NumToStr(numPoints)); ResizeArrayClear(tmpposX, 1); ResizeArrayClear(tmpposY, 1); ResizeArrayClear(tmpposZ, 1); ResizeArrayClear(tmppitch, 1); ResizeArrayClear(tmproll, 1); ResizeArrayClear(tmpturn, 1); start.SetEnabled(1); reload.SetEnabled(1); oFname.SetLabel("Smoothed path is now set for playback, smoothing, or movie recording"); smStatus.SetLabel("Smoothing Process Status: Completed"); } else { PopupMessage("All settings are currently inactive. \nProcess was not initiated."); } } ######################################################################################### # Reload the working arrays with the previous data in the saved arrays proc OnReloadPrev() { OnStop(); ReloadArrays(); reload.SetEnabled(false); } ######################################################################################### ######################################################################################### ### SML function name predefined in TNTsim3D, called for each frame. ### Record or play frame in flight as designated by recorder state. ######################################################################################### proc OnFrame ( ) { # If recording, get current viewer position and orientation and store in arrays if (state == 1) { local class POINT3D viewer, orientation; TNTsim3D.GetSceneByOrientation(viewer, orientation); posX[index] = viewer.x; # viewer position posY[index] = viewer.y; posZ[index] = viewer.z; pitch[index] = orientation.x; # viewer orientation (x=pitch, roll[index] = orientation.y; # y = roll, z = heading azimuth turn[index] = orientation.z; index++; # increment counts numPoints++; numberframes.SetLabel(NumToStr(numPoints)); # update count labels on dialog movietotalframes.SetLabel(NumToStr(numPoints)); numFrmStrt.SetLabel(NumToStr(numPoints)); frame.SetLabel(NumToStr(index)); if (numPoints >= maxPoints) OnStop(); } # If playback, read stored viewer position and orientation from # arrays and use these to set current frame else if (state == 2) { local class POINT3D viewer, orientation; viewer.x = posX[index]; # viewer position viewer.y = posY[index]; viewer.z = posZ[index]; orientation.x = pitch[index]; # viewer orientation (x = pitch, orientation.y = roll[index]; # y = roll, z = heading azimuth orientation.z = turn[index]; TNTsim3D.SetSceneByOrientation(viewer, orientation); # set frame index++; frame.SetLabel(NumToStr(index)); # update frame count label on dialog if (index >= numPoints) { # increment frame count if (loop == 1) index = 1; # if Loop checkbox is on, restart playback else OnStop(); # otherwise stop } } # If generating a movie, read stored viewer position and orientation from # arrays and use to set the current frame # then grab the frame and push it into the current movie else if (state == 3) { moviecurrentframe.SetLabel(NumToStr(index)); local class POINT3D viewer, orientation; viewer.x = posX[index]; # viewer position viewer.y = posY[index]; viewer.z = posZ[index]; orientation.x = pitch[index]; # viewer orientation (x = pitch, orientation.y = roll[index]; # y = roll, z = heading azimuth orientation.z = turn[index]; TNTsim3D.SetSceneByOrientation(viewer, orientation); # set frame index++; frame.SetLabel(NumToStr(index)); # update frame count label on dialog # Don't get frame yet. The screen hasn't redrawn # Getting the frame here would make the first frame of the movie # be the last thing the viewer was looking at before loading the saved points if (index == 1) return; frameDevice = TNTsim3D.GetFrameDevice(); # Check to see if the device is the correct size if (frameDevice.GetHeight() != movieHeight || frameDevice.GetWidth() != movieWidth) { PopupMessage("Window Resize Detected. Movie generation stopped"); OnEndMovie(); return; } # copy rectangular image from in-memory graphics devide to the # current movie-frame's graphics context gc.CopyRect(frameDevice, movierect, moviepoint); MovieAddFrame(movie, movieFrame); if (index >= numPoints) OnEndMovie(); } } ######################################################################################### ######################################################################################### ### Dialog specification in XML ######################################################################################### string xml$; xml$=' AVI MPEG 23.976 (24000/1001) fps - NTSC encapsulated film rate 24 fps - Standard international cinema film rate 25 fps - PAL (625/50) video frame rate 29.97 (30000/1001) fps - NTSC video frame rate 30 fps - NTSC drop-frame (525/60) video frame rate 50 fps - Double frame rate / progressive PAL 59.94 (60000/1001) fps - Double frame rate NTSC 60 fps - Double frame rate drop-frame NTSC '; ######################################################################################### ######################################################################################### # Functions called when the script is selected in an open instance of TNTsim3D ######################################################################################### ######################################################################################### # Sets up the dialog and it's controls proc InitialSetup( ) { class XMLDOC dlgdoc; # class instance for the XML document numeric err = dlgdoc.Parse(xml$); # read the XML and parse into memory if (err < 0) { # if no XML document found, exit PopupError(err); Exit(); } class XMLNODE dlgnode; dlgnode = dlgdoc.GetElementByID("mainApp"); if (dlgnode == 0) { PopupMessage("Could not find dialog node in XML document"); Exit(); } dlgwin.SetXMLNode(dlgnode); dlgwin.CreateModeless(); numeric ret = dlgwin.Open(); if ( ret == -1 ) { Exit(); } # Setup XML controls record = dlgwin.GetCtrlByID("Record"); play = dlgwin.GetCtrlByID("Play"); pause = dlgwin.GetCtrlByID("Pause"); stop = dlgwin.GetCtrlByID("Stop"); save = dlgwin.GetCtrlByID("Save"); numberframes = dlgwin.GetCtrlByID("NumberFrames"); moviecurrentframe = dlgwin.GetCtrlByID("MovieCurrentFrame"); movietotalframes = dlgwin.GetCtrlByID("MovieTotalFrames"); frame = dlgwin.GetCtrlByID("Frame"); iFnameR = dlgwin.GetCtrlByID("InFileNameR"); oFname = dlgwin.GetCtrlByID("OutFileName"); movierecord = dlgwin.GetCtrlByID("RecordMovie"); moviestop = dlgwin.GetCtrlByID("StopMovie"); psCtrls = dlgwin.GetCtrlByID("PathSmCtrls"); psToggle = dlgwin.GetCtrlByID("PathSmToggle"); vcCtrls = dlgwin.GetCtrlByID("ViewCCtrls"); vcTRlbl = dlgwin.GetCtrlByID("VCTRNum"); vcTRnum = dlgwin.GetCtrlByID("VCTRNum"); vcSFlbl = dlgwin.GetCtrlByID("VCSFLbl"); vcSFnum = dlgwin.GetCtrlByID("VCSFNum"); vcRFlbl = dlgwin.GetCtrlByID("VCRFLbl"); vcRFnum = dlgwin.GetCtrlByID("VCRFNum"); vcFAlbl = dlgwin.GetCtrlByID("VCFALbl"); vcFAnum = dlgwin.GetCtrlByID("VCFANum"); vsCtrls = dlgwin.GetCtrlByID("ViewSmCtrls"); vsToggle = dlgwin.GetCtrlByID("ViewSmToggle"); vsFAlbl = dlgwin.GetCtrlByID("VSFALbl"); vsFAnum = dlgwin.GetCtrlByID("VSFANum"); vsFBlbl = dlgwin.GetCtrlByID("VSFBLbl"); vsFBnum = dlgwin.GetCtrlByID("VSFBNum"); vsTSlbl = dlgwin.GetCtrlByID("VSTSLbl"); vsTSnum = dlgwin.GetCtrlByID("VSTSNum"); numFrmStrt = dlgwin.GetCtrlByID("numFramesStart"); numFrmFnsh = dlgwin.GetCtrlByID("numFramesFinish"); smStatus = dlgwin.GetCtrlByID("SmoothingStatus"); movietypecombo = dlgwin.GetCtrlByID("MovieTypeCombo"); movieframeratecombo = dlgwin.GetCtrlByID("MovieFramerateCombo"); start = dlgwin.GetCtrlByID("StartSmoothing"); reload = dlgwin.GetCtrlByID("ReloadSaved"); } ######################################################################################### ######################################################################################### # Start of the Script ######################################################################################### InitialSetup(); ######################################################################################### #########################################################################################