#########################################################################################
#
# 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$='
';
#########################################################################################
#########################################################################################
# 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();
#########################################################################################
#########################################################################################