c         
c   AlignFrcMtnData                          
c
c	Copyright by Dwight Meglan 1990
c
c                       
c	Purpose:	Align force and motion data.  Define the frame offset between
c		  		the two as well as the ratio of force plate data frames to motion
c				data frames.  Determine which force plate's data corresponds to 
c				which foot.  Assign the corresponding data to each foot.
c
c	Routines called:
c		GaitFrcMtnAlign
c		SimpleFrcMtnAlign
c
c	Common Block Variables USED by routine:
c	Block Name		Variable Name
c
c   None
c
c	Common Block Variables MODIFIED by routine:        
c	Block Name		Variable Name
c
c   None
c
      subroutine AlignFrcMtnData(CMND,FF,SF,JF,CF,OF)
      implicit none               
                                                           
      include 'a_const.inc'
      include 'a_struct.inc' 
      include 'a_prm.inc'
      include 'a_names.inc'
                                                                        
      record /CmndLine/     CMND                        
      record /force_flag/   FF
      record /segment_flag/ SF
      record /joint_flag/   JF
      record /cycle_flag/   CF
      record /option_flag/  OF

      integer*4 i,nopt,oend
      logical*4 UseSimple

      if ( .not. FF.Adjusted ) goto 1000
c
c     Check for options regarding alignment
c
      UseSimple= .false.
      do i= 1, CMND.NumOpt(2)
        if ( CMND.OptText(i,2) .eq. 'SIM' ) UseSimple= .true.
      end do

c
c     Branch to Gait alignment procedure (which can be complicated) if doing
c     the OSU or Helen Hayes marker sets. Otherwise use a straight forward
c     manual approach to assigning the force plate data to specific feet and
c     setting the offset between the first force data frame and the first
c     motion data time frame. Special section for foot marker set added by
c     dam on Feb 7 1993
c                                                          
      if ( (GP.analysis_type .eq. CKC_ANL)  .or. UseSimple ) then
        call SimpleFrcMtnAlign( CMND,FF,CF,OF )

      else if ( (GP.analysis_type .eq. F3DR_ANL) .or. 
     &          (GP.analysis_type .eq. F3DL_ANL)      ) then
        CMND.NumOpt(2)= CMND.NumOpt(2) + 1
        nopt= CMND.NumOpt(2)
        if ( nopt .gt. 1 ) then
          oend= CMND.OptEnd(nopt-1,2)
        else
          oend= CMND.CmndEnd(2)
        end if
        if ( GP.analysis_type .eq. F3DR_ANL ) then
          CMND.OptText(nopt,2)= 'RFT'
          CMND.Text(1)(oend+1:oend+6)= '/RFT=1'
        else
          CMND.OptText(nopt,2)= 'LFT'
          CMND.Text(1)(oend+1:oend+6)= '/LFT=1'
        end if
        CMND.OptStart(nopt,2)= oend + 2
        CMND.OptEnd(nopt,2)=   oend + 6
        CMND.OptNumOK(nopt,2)=   .true.
        CMND.OptIntVal(nopt,2)=  1
        CMND.OptRealVal(nopt,2)= 1.0
        call SimpleFrcMtnAlign( CMND,FF,CF,OF )

      else if ( (GP.analysis_type .eq. FULL_ANL) .or. 
     &          (GP.analysis_type .eq. CC_ANL)   .or. 
     &          (GP.analysis_type .eq. MAYO_ANL) .or. 
     &          (GP.analysis_type .eq. HH_ANL)       ) then
        call GaitFrcMtnAlign( CMND,FF,CF,OF )

      else
        write(6,100) ANLNames(GP.analysis_type)
        if ( OF.ToLog ) write(3,100) ANLNames(GP.analysis_type)
100     format('ERROR- Force to motion data alignment is not a valid process'/
     &         '       for the ',a20,' analysis')
      end if
      return

1000  write(6,1005)                                      
      if ( OF.ToLog ) write(3,1005)
1005  format(' Error- Force plate data must be CONditioned before',
     &       ' alignment can be done')
      return
      end
c
c  SimpleFrcMtnAlign
c
c	Copyright by Dwight Meglan 1992
c  
c
c	Purpose:	Align force and motion data.  Define the frame offset between
c		  		the two as well as the ratio of force plate data frames to motion
c				data frames.  Determine which force plate's data corresponds to 
c				which foot.  Assign the corresponding data to each foot. All
c           determination of which force plate corresponds to which foot is
c           done manually via the command line and the offset between the
c           force and motion data is assumed to come from the file data, i.e.
c           the motion and force data is assumed to have been simultaneously
c           sampled by a system which controlled both processes such as how
c           VICON C3D files and Motion Analysis Corporation P3D files combined
c           with ANA files work. The only question is whether the data when
c           written to disk by the acquisition system was truncated at
c           different times in the motion and force. This can happen with the
c           Motion Analysis files but not with the C3D files. 
c
c
c	Routines called:
c    EvalFrcAliOpt
c    GetFileName
c
c	Common Block Variables USED by routine:
c	Block Name		Variable Name
c
c  force_plate    FpDataOK
c  Param          FP.lfft_plt
c  Param          FP.num_lfft_plt
c  Param          FP.num_rtft_plt
c  Param          FP.rtft_plt
c  force_plate    FpData
c  Param          MP.first_frm
c  Force_Plate    rMtnFrmRate
c
c	Common Block Variables MODIFIED by routine:
c	Block Name		Variable Name
c
c  force_plate    FtGrl
c  Param          FP.av_frm_ratio
c  Param          FP.frm_offset
c  force_plate    FtGrlOK
c
      subroutine SimpleFrcMtnAlign(CMND,FF,CF,OF)
      implicit none
                                                           
      include 'a_const.inc'                                
      include 'a_struct.inc' 
      include 'a_prm.inc'
      include 'a_gls2.inc'
      include 'a_frc.inc'
      include 'a_names.inc'
                                                                        
      record /CmndLine/     CMND                        
      record /force_flag/   FF
      record /cycle_flag/   CF
      record /option_flag/  OF

      record /FrcMtnOpt/ FMOPT
                                             
      integer*4 pos,length,NumPlt(2),PltNum(MAX_PLT,2),i,ft,frm,load,j,side,
     &          FirstMtnFrm
      logical*4 error,NameOK,GetFileName
      character name*60,FrcFileType*3
              
      FF.aligned= .false.
      FF.individual_feet= .false.
c                                               
c     Evaluate the manual options for setting which plates "belong" to which
c     foot
c
      call EvalFrcAliOpt( CMND,FMOPT,OF,error )
      if ( error ) return
c           
c     Set up the floor contact information based upon which feet were specified
c     in the options
c                                                         
      if ( .not. CF.event ) then ! this is for non gait data files
        FirstMtnFrm= 1
        MP.num_rhs= 0
        MP.num_rto= 0
        MP.num_lhs= 0
        MP.num_lto= 0
        do frm= 1, MP.nfrm
          do side= rt, lf
            FlrCntct(frm,side)= .false.
          end do
        end do 
        if ( FP.num_rtft_plt .gt. 0 ) then
          do frm= 1, MP.nfrm
            FlrCntct(frm,rt)= .true.
          end do 
          MP.num_rhs= 1
          MP.num_rto= 1
          MP.rhs_frm(1)= 1
          MP.rto_frm(1)= MP.nfrm
        end if
        if ( FP.num_lfft_plt .gt. 0 ) then
          do frm= 1, MP.nfrm
            FlrCntct(frm,lf)= .true.    
          end do   
          MP.num_lhs= 1
          MP.num_lto= 1
          MP.lhs_frm(1)= 1
          MP.lto_frm(1)= MP.nfrm
        end if
        CF.event= .true.
      else ! gait data is being used
        if ( MP.first_ft .eq. rt ) then
          FirstMtnFrm= MP.rhs_frm(1)
          if ( MP.cycle_first_frm(1) .ne. FirstMtnFrm ) 
     &      write(6,700) FirstMtnFrm, MP.cycle_first_frm(1)
700       format(' NOTE- Right heel strike at frame # ',i3,' is the first'/
     &           '       frame of the gait cycle but the first frame indicated'/
     &           '       by the cycle limits is frame # ',i3)
        else
          FirstMtnFrm= MP.lhs_frm(1)
          if ( MP.cycle_first_frm(1) .ne. FirstMtnFrm ) 
     &      write(6,710) FirstMtnFrm, MP.cycle_first_frm(1)
710       format(' NOTE- Left heel strike at frame # ',i3,' is the first'/
     &           '       frame of the gait cycle but the first frame indicated'/
     &           '       by the cycle limits is frame # ',i3)
        end if
      end if
c       
c     Set the time Offset between the force and motion data
c     Test force file type and choose the appropriate alignment procedure
c           
c     C3D files have the motion and force already synched
c
c     ANA files may have a corresponding P3D motion file which may start at
c     at later frames because of how the motion data was saved. The force and
c     motion data is synched however and the offset can be derived from the
c     P3D file starting frame number and the av_frm_ratio.
c
      if ( rMtnFrmRate .lt. 0.0001 ) goto 1070 ! error
      FP.av_frm_ratio= float(FP.frm_rate)/rMtnFrmRate
      MP.cycle_length(1)= MP.nfrm
      NameOK= getfilename( GP.FrcFile,1,name,FrcFileType,length )
      if ( .not. NameOK ) then
        FrcFileType= 'ANA'
        write(6,200)
        if ( OF.ToLog ) write(3,200)
200     format(' NOTE- Trouble determining force file type in GetFileName'/
     &         '       Assuming force file is an ANA file')
      end if
      if ( FrcFileType .eq. 'C3D' ) then ! VICON AMASS file
          FP.frm_offset= 0
      else ! Motion Analysis file
          FP.frm_offset= nint(float(MP.first_frm+FirstMtnFrm-2)
     &                              *FP.av_frm_ratio)
      end if                                
c     
c     Assign the force data to the specific foot ground reaction force arrays
c     and set the arrays which indicate whether there is force on the foot at
c     each time frame.
c
      NumPlt(rt)= FP.num_rtft_plt
      NumPlt(lf)= FP.num_lfft_plt
      do i= 1, FP.num_rtft_plt
        PltNum(i,rt)= FP.rtft_plt(i)                
      end do                          
      do i= 1, FP.num_lfft_plt
        PltNum(i,lf)= FP.lfft_plt(i)
      end do
c
c     Sum the force plate data for the individual feet
c
      do ft= rt, lf
        if ( NumPlt(ft) .eq. 0 ) then     ! there is no force plate data
          do frm= 1, FP.nfrm      ! for this foot...     
            FtGrlOK(frm,ft)= .false.  
            do load= fx, mz
              FtGrl(load,frm,ft)= 0.0
            end do ! load                 
          end do ! frm
        else                                 ! do the combination of force
          do i= 1, FP.nfrm           ! plate data for the foot
            FtGrlOK(i,ft)= .true.    
            do load= fx, mz
              FtGrl(load,i,ft)= 0.0              
            end do ! load
            do j= 1, NumPlt(ft)
              if ( FpDataOK(i,PltNum(j,ft)) ) then
                do load= fx, mz
                  FtGrl(load,i,ft)= FtGrl(load,i,ft) +
     &                                FpData(load,i,PltNum(j,ft))
                end do ! load
              end if
            end do
            if ( abs(FtGrl(fz,i,ft)) .lt. FMOPT.FzThreshold ) 
     &        FtGrlOK(i,ft)= .false.
          end do ! i => cycle frm number
        end if ! Is there any good force plate data for this foot
      end do ! ft
      FF.aligned= .true.
      FF.individual_feet= .true.
      return

1070  write(6,1075) rMtnFrmRate
      if ( OF.ToLog ) write(3,1075) rMtnFrmRate
1075  format(' Error- Motion data frame rate is ',f10.6,'.'/
     &       '        It has to be greater than zero for individual foot forces'/
     &       '        to be determined.')
      return
      end
c
c     EvalFrcMtnOpt
c
c     Copyright by Dwight Meglan 1992
c     
c     Purpose: Set up the various options to control the manual
c              alignment of the force and motion data.
c
c     Check for options-                           
c        Manual options:
c        /RFT=  - manually indicates plates associated with right foot
c        /LFT=  - manually indicates plates associated with left foot
c        /FZM=  - Minimum fz force (Newtons) required on plate for plate
c                 reading to be considered valid (default= 5N)
c
      subroutine EvalFrcAliOpt( CMND,FMOPT,OF,error )
      implicit none                             

      include 'a_const.inc'
      include 'a_struct.inc'
      include 'a_prm.inc'
      include 'a_gls2.inc'
      include 'a_frc.inc'

      record /CmndLine/  CMND
      record /FrcMtnOpt/ FMOPT             
      record /Option_Flag/ OF

      logical*4 error

      integer*4 i,j
      logical*4 BadOption
      character Options(4)*3

      data Options / 'RFT','LFT','FZM','SIM' /

      error= .true.
      FMOPT.ManTrigFrm=   .false.                     
      FMOPT.ManFtPlt=     .false.
      FMOPT.SecondFt=     .false.
      FMOPT.OffSetWith2ndFt= .false.
      FMOPT.UseDelDetect= .false.        
      FMOPT.MzZero=       .false.
      FMOPT.TrigPct=      FRC_COND.trig_percent     
      FMOPT.HsTolPct=     FRC_COND.HS_tol_percent
      FMOPT.FzThreshold=  10.0 ! Newtons
      FMOPT.FzRange=      50.0 ! Newtons
      if ( CMND.NumOpt(2) .eq. 0 ) goto 1000
c                                       
c     Make sure options are valid
c 
      do i= 1, CMND.NumOpt(2)
        BadOption= .true.
        do j= 1, 4
          if ( CMND.OptText(i,2) .eq. Options(j) ) BadOption= .false.
        end do
        if ( BadOption ) goto 1010 ! error
      end do ! i                            
c                                               
c     check for manual assignment of plates to individual feet                
c                                           
      do i= 1, CMND.NumOpt(2)
        if ( (CMND.OptText(i,2) .eq. 'RFT') .or.
     &       (CMND.OptText(i,2) .eq. 'LFT')       ) FMOPT.ManFtPlt= .true.
      end do
      if ( FMOPT.ManFtPlt ) call SetFtPlt( CMND,OF,error )
      if ( error ) goto 1030 ! improper plate to foot assignment
c
c     Set the minimum fz threshold for indicating that the force plate is
c     active.
c
      if ( CMND.OptText(i,2) .eq. 'FZM' ) then ! set fz threshold
        if ( .not. CMND.OptNumOK(i,2) .or.                  
     &       ( CMND.OptRealVal(i,2) .lt. 0.0 ) ) goto 1100 ! error
        FMOPT.FzThreshold= CMND.OptRealVal(i,2)
      end if
c                                                               
c     Final check that force plate to foot assignment has been made
c
      if ( (FP.num_rtft_plt .eq. 0) .and. 
     &     (FP.num_lfft_plt .eq. 0)       ) goto 1020
      error= .false.
      return                                         

1000  write(6,1005) 
      if ( OF.ToLog ) write(3,1005) 
1005  format(' Error- No options are present for simple FRC ALI command'/
     &       '        At a minimum a /RFT=1 or /LFT=1 option must be given')
      return

                                         
1010  write(6,1015) CMND.OptText(i,2)
      if ( OF.ToLog ) write(3,1015) CMND.OptText(i,2)
1015  format(' Error- ',a3,' is not a valid option for simple FRC ALI'/
     &       '        Valid options are: RFT LFT FZM')
      return

1020  write(6,1025) 
      if ( OF.ToLog ) write(3,1025) 
1025  format(' Error- No options are present for simple FRC ALI command to'/
     &       '        assign force plate(s) to a specific foot'/
     &       '        At a minimum a /RFT=1 or /LFT=1 option must be given')
      return
                                                         
1030  return                                                           

1100  write(6,1105) CMND.OptText(i,2)
      if ( OF.ToLog ) write(3,1105) CMND.OptText(i,2)
1105  format(' Error- ',a3,' is not a valid minimum fz threshold'/
     &       '        The threshold must be greater than 0.0'/
     &       '        (At least 5 newtons is recommended...)')
      return
      end
c
c  GaitFrcMtnAlign
c
c	Copyright by Dwight Meglan 1992
c
c                       
c	Purpose:	Align force and motion data.  Define the frame offset between
c		  		the two as well as the ratio of force plate data frames to motion
c				data frames.  Determine which force plate's data corresponds to 
c				which foot.  Assign the corresponding data to each foot.  This 
c				routine has been designed to run fairly well with the automatic
c				settings included but because of the large variation in conditions
c				as to how the force plates have been struck (or not struck) by
c				the feet a large number of options are available to override or
c				fine tune the default settings.  This routine is really rather
c				messy and is defineitely not for the faint of heart when it comes
c				to modifying the code to your situation.  Study the derivation
c				section of ANZ before hacking away...
c
c           The Gait align
c           subroutine uses a general approach of searching for the
c           correspondence between the manually determined motion heel/foot 
c           strike and the force plate heel/foot strike to derive the alignment
c           of the force and motion.
c
c
c	Routines called:
c     AssignFpToFt
c     CheckFpToFt
c     DecideFpToFt
c     EvalFrcMtnOffset
c		EvalGaitFrcAliOpt
c		Find1stFpCntct
c
c	Common Block Variables USED by routine:
c	Block Name		Variable Name
c
c	Param			FP.lfft_plt
c	Param			FP.num_lfft_plt
c	Param			FP.num_rtft_plt
c	Param			FP.rtft_plt
c
c	Common Block Variables MODIFIED by routine:
c	Block Name		Variable Name
c
c    None
c
      subroutine GaitFrcMtnAlign(CMND,FF,CF,OF)
      implicit none
                                                           
      include 'a_const.inc'
      include 'a_struct.inc' 
      include 'a_prm.inc'
      include 'a_gls2.inc'
      include 'a_frc.inc'
      include 'a_names.inc'
                                                                        
      record /CmndLine/     CMND                        
      record /force_flag/   FF
      record /cycle_flag/   CF
      record /option_flag/  OF
              
      record /FrcMtnOpt/ FMOPT

      integer*4 TrigFrm(MAX_PLT),NumPltTrig,NumPltUsed,PltOrder(MAX_PLT),i
      logical*4 error,FtPltOK
      character SideName(2)*5

      data SideName /'right','left '/

      if ( ( (GP.analysis_type .eq. FULL_ANL) .or. 
     &       (GP.analysis_type .eq. HH_ANL)       ) .and.
     &     (.not. CF.event) ) goto 1007
c
c     Evaluate the various command line options
c
      call EvalGaitFrcAliOpt( CMND,FMOPT,TrigFrm,NumPltTrig,OF,error )
      if ( error ) return
c
c     Automatically find the frame #'s at which the force plate(s) become
c     active, i.e. that the vertical force rises above a certain threshold
c     indicating that something (hopefully a foot) has made contact with the
c     plate.
c
      call Find1stFpCntct( FMOPT,TrigFrm,NumPltTrig,NumPltUsed,
     &                     PltOrder,OF,error )
      if ( error ) return
c
c     If plate to foot assignment was done manually, then check whether
c     the plate which was struck first belongs to the plates assigned to
c     the first foot of the gait cycle indicated by the gait cycle info.
c     Don't do this if second foot/plate option has been chosen.
c             
      if ( FMOPT.ManFtPlt .and. (.not. FMOPT.SecondFt) ) then
        FtPltOK= .false.
        if ( MP.first_ft .eq. rt ) then
          do i= 1, FP.num_rtft_plt
            if ( PltOrder(1) .eq. FP.rtft_plt(i) ) FtPltOK= .true.
          end do                          
        else ! left foot first
          do i= 1, FP.num_lfft_plt
            if ( PltOrder(1) .eq. FP.lfft_plt(i) ) FtPltOK= .true.
          end do
        end if
        if ( .not. FtPltOK ) goto 1060 ! trouble...
      end if
c
c     Determine the time offset between the first frame of the motion data and
c     the first frame of the force plate data
c
      call EvalFrcMtnOffset(FMOPT,TrigFrm,PltOrder,OF,error)
      if ( error ) return
c
c     Decide which force plates correspond to which foot
c
      call DecideFpToFt(FMOPT,TrigFrm,NumPltUsed,PltOrder,OF)
c
c     Check to make sure that the grouping(s) of force plates to the feet
c     makes sense...
c
      call CheckFpToFt(FMOPT,TrigFrm,PltOrder,OF)
c
c     Group the force plate data together by foot and set the arrays which
c     describe whether there is valid ground reaction force data on the feet
c     at each time frame
c
      call AssignFpToFt(FMOPT,OF)
      FF.individual_feet= .true.
      FF.aligned= .true.
      return

1007  write(6,1009)                                      
      if ( OF.ToLog ) write(3,1009)
1009  format(' Error- For gait marker sets (OSU and Helen Hayes), the'/
     &       '        gait event information (CYC EVE) must have been input'/
     &       '        before alignment between force and motion data can',
     &              ' be carried out.')
      return

                                                            
1060  write(6,1065) PltOrder(1),SideName(MP.first_ft)
      if ( OF.ToLog ) write(3,1065) PltOrder(1),
     &                                      SideName(MP.first_ft)
1065  format(' Error- The trigger frame #''s of the force plates indicate'/
     &       '        that plate # ',i1,' is the first plate struck, but'/
     &       '        the gait cycle information indicates that the ',a5/
     &       '        foot strikes first and according to the manually assigned'/
     &       '        foot-force plate correspondence data the plate data'/
     &       '        belongs to the other foot.')
      return                                                            
      end                                                                  
c         
c   SetTrigFrm                          
c
c	Copyright by Dwight Meglan 1990
c
c	Purpose:	Extract plate-foot strike frame numbers from command line
c
c	Routines called:
c		None
c
c
c	Common Block Variables USED by routine:
c	Block Name		Variable Name
c
c	None
c
c	Common Block Variables MODIFIED by routine:
c	Block Name		Variable Name
c
c	None
c
      subroutine SetTrigFrm( CMND,OF,TrigFrm,NumPltsFound,error )
      implicit none

      include 'a_const.inc'
      include 'a_struct.inc'
      include 'a_prm.inc'

      record /CmndLine/    CMND
      record /option_flag/ OF
      integer*4 TrigFrm(MAX_PLT),NumPltsFound
      logical*4 error

      integer*4 PltFound(MAX_PLT),i,j,PltNum

      error= .true.
      NumPltsFound= 0
      do i= 1, CMND.NumOpt(2)
        if ( CMND.OptText(i,2)(1:2) .eq. 'HS' ) then
          PltNum= ichar(CMND.OptText(i,2)(3:3)) - ichar('0')
          do j= 1, NumPltsFound
            if ( PltNum .eq. PltFound(j) ) goto 1000 ! error- same plate more
          end do                                     !        than once
          if ( (PltNum .lt. 1) .or. (PltNum .gt. FP.num_plt) ) goto 1010
          if ( .not. CMND.OptNumOK(i,2) ) goto 1020 ! error
          if ( (CMND.OptIntVal(i,2) .lt. -1) .or.
     &         (CMND.OptIntVal(i,2) .gt. FP.nfrm) ) goto 1030
          NumPltsFound= NumPltsFound + 1
          TrigFrm(PltNum)= CMND.OptIntVal(i,2)
          if ( TrigFrm(PltNum) .eq. 0 ) TrigFrm(PltNum)= -1 ! no heel strike
          PltFound(NumPltsFound)= PltNum
        end if
      end do ! i
      if ( NumPltsFound .ne. FP.num_plt ) goto 1040
      error= .false.
      return

1000  write(6,1005) PltNum
      if ( OF.ToLog ) write(3,1005) PltNum
1005  format(' Error- Force plate #',i1,' is listed more than once in the'/
     &       '        options defining the frame #''s at which the feet struck'/
     &       '        the force plates.')
      return
      
1010  write(6,1015) PltNum,FP.num_plt
      if ( OF.ToLog ) write(3,1015) PltNum,FP.num_plt
1015  format(' Error- Force plate #',i1,' is listed in the options defining'/
     &       '        the foot strike frame #''s, but only plates 1 to ',i1/
     &       '        are present in the data.')
      return
      
1020  write(6,1025) PltNum
      if ( OF.ToLog ) write(3,1025) PltNum
1025  format(' Error- The foot strike frame # for force plate #',i1,' is not'/
     &       '        a valid number.  Option must have the form /HS1=123.')
      return
      
1030  write(6,1035) PltNum,FP.nfrm
      if ( OF.ToLog ) write(3,1035) PltNum,FP.nfrm
1035  format(' Error- The foot strike frame # ,',i5,', for force plate #',i1/
     &       '        is not a valid frame #.  Valid frame #''s are from 1 to',
     &       i5,'.')
      return
      
1040  write(6,1045) NumPltsFound,FP.num_plt
      if ( OF.ToLog ) write(3,1045) NumPltsFound,FP.num_plt
1045  format(' Error- Foot strike frame #''s were given for ',i1,' force'/
     &       '        plates in the options, but there are ',i1,' plates'/
     &       '        present in the data.  Foot strikes must be given for'/
     &       '        all plates present to use the manual definition of'/
     &       '        foot strikes option.')
      return
      end
c         
c   SetFtPlt
c
c	Copyright by Dwight Meglan 1990
c
c	Purpose:	Extract plate numbers and assign to feet from command line
c
c	Routines called:
c		None
c
c
c	Common Block Variables USED by routine:
c	Block Name		Variable Name
c
c	Force			FP.num_plt
c
c	Common Block Variables MODIFIED by routine:
c	Block Name		Variable Name
c                  
c	Param			FP.lfft_plt
c	Param			FP.num_lfft_plt
c	Param			FP.num_rtft_plt
c	Param			FP.rtft_plt
c
      subroutine SetFtPlt( CMND,OF,error )
      implicit none

      include 'a_const.inc'
      include 'a_struct.inc'
      include 'a_prm.inc'

      record /CmndLine/    CMND
      record /option_flag/ OF
      logical*4 error

      integer*4 i,j,k,Start,End,NumPlt,PltNum(MAX_PLT)
      character OptText*10
      logical*4 RftDone,LftDone
                             
      error= .true.
      FP.num_rtft_plt= 0
      FP.num_lfft_plt= 0
      RftDone= .false.
      LftDone= .false.                        
      do i= 1, CMND.NumOpt(2)
        if ( (CMND.OptText(i,2) .eq. 'RFT') .or.
     &       (CMND.OptText(i,2) .eq. 'LFT')       ) then    
          if ( .not. CMND.OptNumOK(i,2) ) goto 1000 ! error- no plate #'s
          OptText= '          '
          OptText= CMND.Text(1)(CMND.OptStart(i,2):CMND.OptEnd(i,2))
          Start= index(OptText,'=') + 1
          End=   CMND.OptEnd(i,2) - CMND.OptStart(i,2) + 1
          NumPlt= 0
          do j= Start, End
            NumPlt= NumPlt + 1
            PltNum(NumPlt)= ichar(OptText(j:j)) - ichar('0')
            if ( (PltNum(NumPlt) .lt. 1) .or. 
     &           (PltNum(NumPlt) .gt. FP.num_plt) ) goto 1010 ! error
            do k= 1, NumPlt-1                                      ! bad plt #
              if ( PltNum(NumPlt) .eq. PltNum(k) ) goto 1020 ! error- plt #
            end do ! k                                       ! given more than
          end do ! j                                         ! once
          if ( CMND.OptText(i,2) .eq. 'RFT' ) then
            if ( RftDone ) goto 1030 ! error- foot already done
            RftDone= .true.
            FP.num_rtft_plt= NumPlt
            do j= 1, NumPlt
              FP.rtft_plt(j)= PltNum(j)
            end do
          else
            if ( LftDone ) goto 1040 ! error- foot already done
            LftDone= .true.
            FP.num_lfft_plt= NumPlt
            do j= 1, NumPlt
              FP.lfft_plt(j)= PltNum(j)
            end do             
          end if ! which foot
        end if ! option is RFT or LFT
      end do ! i
      error= .false.
      return

1000  write(6,1005) CMND.OptText(i,2)
      if ( OF.ToLog ) write(3,1005) CMND.OptText(i,2)
1005  format(' Error- No plate #''s given for option ',a3)
      return

1010  write(6,1015) CMND.OptText(i,2),PltNum(NumPlt),FP.num_plt
      if ( OF.ToLog ) write(3,1015) CMND.OptText(i,2),PltNum(NumPlt),
     &                                      FP.num_plt 
1015  format(' Error- Invalid plate # given for option ',a3/
     &       '        Plate #',i2', given, but only plate #''s 1 to ',i1,
     &                ' are valid.')
      return
      
1020  write(6,1025) PltNum(k),CMND.OptText(i,2)
      if ( OF.ToLog ) write(3,1025) PltNum(k),CMND.OptText(i,2)
1025  format(' Error- Plate #',i1,' given more than once for option ',a3)
      return

1030  write(6,1035) 
      if ( OF.ToLog ) write(3,1035) 
1035  format(' Error- Plate #''s for right foot given more than once')
      return
      
1040  write(6,1045) 
      if ( OF.ToLog ) write(3,1045) 
1045  format(' Error- Plate #''s for left foot given more than once')
      return
      end
c
c     EvalGaitFrcMtnOpt
c
c     Copyright by Dwight Meglan 1992
c     
c     Purpose: Set up the various options to control the automatic and manual
c              alignment of the force and motion data.
c
c     Check for options-                           
c        Manual options:
c        /RFT=  - manually indicates plates associated with right foot
c        /LFT=  - manually indicates plates associated with left foot
c        /HS1=  - manually indicates force frame # at which heel strike
c                 occurs upon plate # 1
c        /HS2=  - same as above but for plate # 2
c        /HS#=  - same as above, valid #'s up to number of force plates
c        /SEC   - indicates that the first force plate triggered corresponds
c                 to the second foot to have a heelstrike in the motion
c                 data.  This is required for forces taken with the old GLS
c                 system if the motion data was sorted from first heelstrike
c                 on the 1st plate rather than on the 2nd plate. 
c        /2ND   - The second plate to show a foot on it is used along with the
c                 the frame number of the second heelstrike to calculate the 
c                 offset between the force and motion data frames
c        /FZM=  - Minimum fz force (Newtons) required on plate for plate
c                 reading to be considered valid (default= 5N)
c        /FZR=  - Minimum range of fz values abs(FzMax-FzMin) for plate to be
c                 considered active during data take
c        Automatic options: These values have defaults set by
c                           OPT FRC TRI % and OPT FRC SAM %.  Using the
c                           options overrides the defaults
c        /TRI=  - percent of maximum range of Fz of each plate considered
c                 to indicate that a heel strike has occured on the plate
c                 when the Fz changes by this amount or greater from one
c                 time frame to the next.    
c        /FST=  - percent of gait cycle within which if force plate is triggered
c                 relative to a heel strike then the plate is assumed to
c                 have been struck by the foot causing the heel strike.
c        /CHA   - Use change in level (del) detection of heel strike
c        /ABS   - Use absolute fz force level detection of heel strike
c
c        Extra options
c        /MZZ   - Zero the mz component on the first plate- because ours
c                 is messed to the point that the signal throws everything
c                 else way off
c
      subroutine EvalGaitFrcAliOpt( CMND,FMOPT,TrigFrm,NumPltTrig,OF,error )
      implicit none                             

      include 'a_const.inc'
      include 'a_struct.inc'
      include 'a_prm.inc'
      include 'a_gls2.inc'
      include 'a_frc.inc'

      record /CmndLine/  CMND
      record /FrcMtnOpt/ FMOPT             
      record /Option_Flag/ OF

      integer*4 TrigFrm(MAX_PLT),NumPltTrig
      logical*4 error

      integer*4 i,j
      logical*4 BadOption
      character Options(15)*3

      data Options / 'RFT','LFT','HS1','HS2','HS3','HS4','SEC','TRI','FST',
     &               'FZM','DLD','LVD','MZZ','FZR','2ND' /

      error= .true.
      FMOPT.ManTrigFrm=   .false.                     
      FMOPT.ManFtPlt=     .false.
      FMOPT.SecondFt=     .false.
      FMOPT.OffSetWith2ndFt= .false.
      FMOPT.UseDelDetect= .false.        
      FMOPT.MzZero=       .false.
      FMOPT.TrigPct=      FRC_COND.trig_percent     
      FMOPT.HsTolPct=     FRC_COND.HS_tol_percent
      FMOPT.FzThreshold=  10.0 ! Newtons
      FMOPT.FzRange=      50.0 ! Newtons
      if ( CMND.NumOpt(2) .eq. 0 ) then
        error= .false.
        return
      end if
c                                       
c     Make sure options are valid
c 
      do i= 1, CMND.NumOpt(2)
        BadOption= .true.
        do j= 1, 15 
          if ( CMND.OptText(i,2) .eq. Options(j) ) BadOption= .false.
        end do
        if ( BadOption ) goto 1010 ! error
      end do ! i
c
c     check for heelstrike frames #'s
c                                                                     
      do i= 1, CMND.NumOpt(2)
        if ( CMND.OptText(i,2)(1:2) .eq. 'HS' )  FMOPT.ManTrigFrm= .true.
      end do
      if ( FMOPT.ManTrigFrm ) then
        call SetTrigFrm( CMND,OF,TrigFrm,NumPltTrig,error )
        if ( error ) goto 1020 ! improper Trigger frame set...
      end if
c                                               
c     check for manual assignment of plates to individual feet                
c                           
      do i= 1, CMND.NumOpt(2)
        if ( (CMND.OptText(i,2) .eq. 'RFT') .or.
     &       (CMND.OptText(i,2) .eq. 'LFT')       ) FMOPT.ManFtPlt= .true.
      end do
      if ( FMOPT.ManFtPlt ) then
        call SetFtPlt( CMND,OF,error )
        if ( error ) goto 1030 ! improper plate to foot assignment
      end if
c
c     Check for the special case where the force plate data is shifted because
c     the motion data was sorted from the first plate struck rather than
c     the 2nd when the old GLS system was used to take the force plate data.
c     This is a real kludge!!!!!
c
      do i= 1, CMND.NumOpt(2)
        if ( CMND.OptText(i,2) .eq. 'SEC' ) FMOPT.SecondFt= .true.
      end do
c
c     Check for the special case where the second heelstrike is used to 
c     calculate the offset between the force and motion data frames.  This is
c     required if the force plate data is taken too late and the data starts
c     with the first foot already on the plates...
c     This is a real kludge!!!!!
c                                                 
      do i= 1, CMND.NumOpt(2) 
        if ( CMND.OptText(i,2) .eq. '2ND' ) FMOPT.OffsetWith2ndFt= .true.
      end do
c
c     Lastly check for options relating to automatic analysis
c             
      do i= 1, CMND.NumOpt(2)
        if ( CMND.OptText(i,2) .eq. 'TRI' ) then ! set trigger level
          if ( .not. CMND.OptNumOK(i,2) .or.
     &         ( CMND.OptRealVal(i,2) .lt. 0.0 ) .or.         
     &         ( CMND.OptRealVal(i,2) .gt. 100.0 )     ) goto 1040 ! error
          FMOPT.TrigPct= CMND.OptRealVal(i,2)
        else if ( CMND.OptText(i,2) .eq. 'FST' ) then ! set HS tolerance level
          if ( .not. CMND.OptNumOK(i,2) .or.
     &         ( CMND.OptRealVal(i,2) .lt. 0.0 ) .or.
     &         ( CMND.OptRealVal(i,2) .gt. 100.0 )    ) goto 1050 ! error
          FMOPT.HSTolPct= CMND.OptRealVal(i,2)
        else if ( CMND.OptText(i,2) .eq. 'FZM' ) then ! set fz threshold
          if ( .not. CMND.OptNumOK(i,2) .or.                  
     &         ( CMND.OptRealVal(i,2) .lt. 0.0 ) ) goto 1100 ! error
          FMOPT.FzThreshold= CMND.OptRealVal(i,2)
        else if ( CMND.OptText(i,2) .eq. 'FZR' ) then ! set fz threshold
          if ( .not. CMND.OptNumOK(i,2) .or.
     &         ( CMND.OptRealVal(i,2) .lt. 0.0 ) ) goto 1100 ! error
          FMOPT.FzRange= CMND.OptRealVal(i,2)
        else if ( CMND.OptText(i,2) .eq. 'CHA' ) then ! use del detect of HS
          FMOPT.UseDelDetect= .true.
        else if ( CMND.OptText(i,2) .eq. 'ABS' ) then ! use level detect of HS
          FMOPT.UseDelDetect= .false.
        else if ( CMND.OptText(i,2) .eq. 'MZZ' ) then ! Zero plate 1 mz
          FMOPT.MzZero= .true.
        end if
      end do ! i
      error= .false.
      return

1010  write(6,1015) CMND.OptText(i,2)
      if ( OF.ToLog ) write(3,1015) CMND.OptText(i,2)
1015  format(' Error- ',a3,' is not a valid option for FRC ALI'/
     &       '        Valid options are: RFT LFT HS# SEC TRI'/
     &       '                           FST FZM CHA ABS MZZ')
      return
                                                         
1020  return

1030  return

1040  write(6,1045) CMND.OptRealVal(i,2)
      if ( OF.ToLog ) write(3,1045) CMND.OptRealVal(i,2)
1045  format(' Error- ',f6.3,' is not a valid percentage for the force plate'/
     &       '        Fz trigger level.  A valid percentage is >0 and <100')
      return
      
1050  write(6,1045) CMND.OptRealVal(i,2)
      if ( OF.ToLog ) write(3,1055) CMND.OptRealVal(i,2)
1055  format(' Error- ',f6.3,' is not a valid percentage for the heel strike'/
     &       '        trigger tolerance level.  A valid percentage is >0 and <100')
      return

1100  write(6,1105) CMND.OptText(i,2)
      if ( OF.ToLog ) write(3,1105) CMND.OptText(i,2)
1105  format(' Error- ',a3,' is not a valid minimum fz threshold'/
     &       '        The threshold must be greater than 0.0'/
     &       '        (At least 5 newtons is recommended...)')
      return
      end
c
c
c     Find1stFpCntct
c
c     Copyright by Dwight Meglan 1992
c     
c     Purpose: Identify the frame #'s of the force plate data at which the
c              the fz force indicates that something is on the plate. This
c              detection can be carried by either the fz rising a threshold
c              value or the change in fz from one frame to next being greater
c              than a specified value.
c
c	Routines called:
c		Swap
c
c	Common Block Variables USED by routine:
c	Block Name		Variable Name
c
c	Param			FP.nfrm
c	Param			FP.num_plt
c	Force			FpData
c
c	Common Block Variables MODIFIED by routine:
c	Block Name		Variable Name
c
c   None
c                                     
      subroutine Find1stFpCntct( FMOPT,TrigFrm,NumPltTrig,NumPltUsed,
     &                           PltOrder,OF,error)
      implicit none
                                                           
      include 'a_const.inc'                                
      include 'a_struct.inc' 
      include 'a_prm.inc'
      include 'a_gls2.inc'
      include 'a_frc.inc'
      include 'a_names.inc'

      record /FrcMtnOpt/ FMOPT
      record /option_flag/ OF

      integer*4 NumPltTrig,TrigFrm(MAX_PLT),NumPltUsed,PltOrder(MAX_PLT)
      logical*4 error

      real*4    MinFz(MAX_PLT),MaxFz(MAX_PLT),DelFzMax,TrigLevel(MAX_PLT),
     &          DelFz                                      
      integer*4 plt,frm,TempTrigFrm(MAX_PLT),i,j
      logical*4 swapped
c
c     Find frame #'s at which heel strike occured on each plate, if they
c     have not been set manually.  NOTE that if the absolute value of Fz
c     for a plate does not rise above 10 N (~2 lbs) then it is assumed that
c     the plate was never struck by a foot.  A trigger frame number <0 
c     indicates that the plate was never struck.
c                                                                           
c     Determine point at which the difference in z force from one sample
c     to the next is greater than TrigPct% of the difference between max and
c     min z forces.  This is called the point where the foot first contacted the
c     plate.  One problem with using this simple minded approach is that
c     if there is a spike in the fz data or the person doesn't hit the plates
c     or they are always on the plate(s), then the heel strike frame number
c     will be wrong.  However, the line must be drawn as to where to stop
c     with the software algorithms and start with the data being taken properly
c     and this where I have drawn it.  There must a clear foot strike on
c     each plate or no foot strike at all during the sample period and there
c     should not be any spikes in the fz data. 
c
      error= .true.
      do plt= 1, FP.num_plt
        MinFz(plt)= FpData(fz,1,plt)
        MaxFz(plt)= FpData(fz,1,plt)
        do frm= 2, FP.nfrm
          if ( FpData(fz,frm,plt) .lt. MinFz(plt) )
     &      MinFz(plt)= FpData(fz,frm,plt)
          if ( FpData(fz,frm,plt) .gt. MaxFz(plt) )
     &      MaxFz(plt)= FpData(fz,frm,plt)
        end do ! frm
      end do ! plt
      if ( .not. FMOPT.ManTrigFrm ) then ! find the trigger frame #'s
        NumPltTrig= 0                
        do plt= 1, FP.num_plt                     
          DelFzMax= abs( MaxFz(plt) - MinFz(plt) )
          TrigLevel(plt)= (FMOPT.TrigPct/100.0)*DelFzMax         
          if ( DelFzMax .lt. FMOPT.FzRange ) then  ! plate not struck by either foot
            TrigFrm(plt)= -1
          else ! find the heel strike
            frm= 2             
            if ( FMOPT.UseDelDetect ) then
              DelFz= abs( FpData(fz,2,plt) - FpData(fz,1,plt) )
              do while( (DelFz .lt. TrigLevel(plt)) .and.          
     &                  (frm .lt. FP.nfrm)        )
                frm= frm + 1           
                DelFz= abs( FpData(fz,frm,plt) - FpData(fz,frm-1,plt) )
              end do
            else ! use level detect of heel strike
              do while( ( -FpData(fz,frm,plt) .lt. TrigLevel(plt)) .and.
     &                  (frm .lt. FP.nfrm)        )
                frm= frm + 1           
              end do
            end if
            if ( frm .ge. FP.nfrm ) then ! no heel strike found
              TrigFrm(plt)= -1
            else
              TrigFrm(plt)= frm - 1
              NumPltTrig= NumPltTrig + 1
            end if
          end if ! DelFzMax > 10 N
          if ( TrigFrm(plt) .lt. 0 ) then
            write(6,100) plt
            if ( OF.ToLog ) write(3,100) plt
100         format(' Plate # ',i1,' not struck by either foot.')
          end if
        end do ! plt
        write(6,105)              ! Write out a summary
        if ( OF.ToLog ) write(3,105)
105     format(/' Plate #   Foot strike   Fz Trigger Level    Min Fz     Max Fz'/
     &         '             frame #       ( Newtons )        (N)        (N)'/)
        do plt= 1, FP.num_plt                     
          write(6,110) plt,TrigFrm(plt),TrigLevel(plt),MinFz(plt),MaxFz(plt)
          if ( OF.ToLog ) write(3,110) plt,TrigFrm(plt),TrigLevel(plt),
     &                                         MinFz(plt),MaxFz(plt)
110       format(3x,i1,9x,i5,7x,f11.4,5x,f11.5,1x,f11.5)
        end do ! plt
      end if ! trigger frames have not been set manually
      if ( NumPltTrig .le. 0 ) goto 1080 ! error- at least 1 plate must
c                                                 be triggered
c     Determine the order in which the plates were triggered
c
      NumPltUsed= 0
      do plt= 1, FP.num_plt
        if ( TrigFrm(plt) .gt. 0 ) then
          NumPltUsed= NumPltUsed + 1
          PltOrder(NumPltUsed)=  plt
          TempTrigFrm(NumPltUsed)= TrigFrm(plt)
        end if
      end do ! plt
      i= NumPltUsed
      swapped= .true.
      do while ( (i .gt. 0) .and. swapped )
        j= 1  
        swapped= .false.
        do while ( j .lt. i )
          if ( TempTrigFrm(j) .gt. TempTrigFrm(j+1) ) then
            call swap ( TempTrigFrm(j), TempTrigFrm(j+1) )
            call swap ( PltOrder(j) , PltOrder(j+1) )
            swapped= .true.
          end if                       
          j= j + 1
        end do     
        i= i - 1
      end do 
      write(6,211) (PltOrder(i),i=1,NumPltUsed)
211   format(/' Plate trigger order: ',4i3/)
      error= .false.
      return

1080  write(6,1085)                 
      if ( OF.ToLog ) write(3,1085) 
1085  format(' Error- No plates were struck according to the foot strike'/
     &       '        detection algorithm.  Try lowering the force plate'/
     &       '        trigger level using the /TRI= option.')
      return
      end
c                   
c
c     EvalFrcMtnOffset
c
c     Copyright by Dwight Meglan 1992
c     
c     Purpose: Determine the offset (in terms of number of force plate data
c              frames) between the first frame of force data corresponding to
c              the first heel/foot strike on any of the force plates and the
c              first heel/foot strike indicated by the manually input gait
c              events based on the foot motion data.
c                        
c     Determine the offset between the frame number of the force plate
c     data corresponding to the first plate contacted and the frame number
c     of the motion data at which the  first heel strike occured.
c
c	Routines called:
c		None
c
c	Common Block Variables USED by routine:
c	Block Name		Variable Name
c
c	Param			FP.frm_rate
c	Param			FP.nfrm
c	Param			FP.num_plt
c	Param			MP.first_ft
c  Param       MP.lhs_frm
c  Param       MP.rhs_frm
c  Param       rMtnFrmRate
c
c	Common Block Variables MODIFIED by routine:
c	Block Name		Variable Name
c
c	Param			FP.av_frm_ratio
c	Param			FP.frm_offset
c	Param			FP.lfft_plt
c	Param			FP.num_lfft_plt
c	Param			FP.num_rtft_plt
c	Param			FP.rtft_plt
c
      subroutine EvalFrcMtnOffset(FMOPT,TrigFrm,PltOrder,OF,error)
      implicit none  
                                                           
      include 'a_const.inc'                                
      include 'a_struct.inc' 
      include 'a_prm.inc'
      include 'a_gls2.inc'
      include 'a_frc.inc'
      include 'a_names.inc'

      record /FrcMtnOpt/ FMOPT
      record /option_flag/ OF
                                                     
      integer*4 TrigFrm(MAX_PLT),PltOrder(MAX_PLT)
      logical*4 error

      integer*4 OffSet,pos,length
      logical*4 NameOK,getfilename
      character FrcFileType*3,name*60
 
      error= .true.
      if ( rMtnFrmRate .lt. 0.0001 ) goto 1070 ! error
      FP.av_frm_ratio= float(FP.frm_rate)/rMtnFrmRate
c
c     Test force file type and choose the appropriate alignment procedure
c
c     C3D files have the motion and force already synched
c
c     ANA files may have a corresponding P3D motion file which may start at
c     at later frames because of how the motion data was saved. The force and
c     motion data is synched however and the offset can be derived from the
c     P3D file starting frame number and the av_frm_ratio.
c
c     All other file types are "leftovers" from Ohio State's old data 
c     acquisition programs which were not synched with the video. These require
c     some extra fooling around to try to align the force and motion.
c  
      NameOK= getfilename( GP.FrcFile,1,name,FrcFileType,length )
      if ( .not. NameOK ) then
        FrcFileType= 'ANA'
        write(6,200)
        if ( OF.ToLog ) write(3,200)
200     format(' NOTE- Trouble determining force file type in GetFileName'/
     &         '       Assuming force file is an ANA file')
      end if
c
c     OSU force file of the "old" kind
c
      if ( (FrcFileType .ne. 'C3D') .and. (FrcFileType .ne. 'ANA') ) then
        if ( FMOPT.SecondFt .and. (FP.num_plt .ge. 3) ) then
          FP.frm_offset= TrigFrm( PltOrder(2) ) 
        else if ( FMOPT.OffsetWith2ndFt ) then
          if ( MP.first_ft .eq. lf ) then
            OffSet= MP.rhs_frm(1) - MP.lhs_frm(1) + 1
          else
            OffSet= MP.lhs_frm(1) - MP.rhs_frm(1) + 1
          end if      
          FP.frm_offset= TrigFrm(PltOrder(2)) - Offset*FP.av_frm_ratio
        else
          FP.frm_offset= TrigFrm( PltOrder(1) ) 
        end if       
        if ( .not. FMOPT.ManFtPlt ) then
          if ( MP.first_ft .eq. rt ) then
            FP.num_rtft_plt= 1       
            FP.num_lfft_plt= 0                             
            if ( FMOPT.SecondFt .and. (FP.num_plt .ge. 3) ) then
              FP.rtft_plt(1)= PltOrder(2)
            else
              FP.rtft_plt(1)= PltOrder(1)
            end if
          else           
            FP.num_rtft_plt= 0
            FP.num_lfft_plt= 1
            if ( FMOPT.SecondFt .and. (FP.num_plt .ge. 3) ) then
              FP.lfft_plt(1)= PltOrder(2)
            else
              FP.lfft_plt(1)= PltOrder(1)
            end if
          end if                      
        end if
c
c     VICON AMASS file
c
      else if ( FrcFileType .eq. 'C3D' ) then
        if ( MP.first_ft .eq. rt ) then
          FP.frm_offset= (MP.rhs_frm(1)-1)*FP.av_frm_ratio+1
        else
          FP.frm_offset= (MP.lhs_frm(1)-1)*FP.av_frm_ratio+1
        end if
c
c     Motion Analysis File
c
      else if ( FrcFileType .eq. 'ANA' ) then
        if ( MP.first_ft .eq. rt ) then
          FP.frm_offset= (MP.first_frm+MP.rhs_frm(1)-2)*
     &                        FP.av_frm_ratio + 1
        else
          FP.frm_offset= (MP.first_frm+MP.lhs_frm(1)-2)*
     &                        FP.av_frm_ratio + 1
        end if
      end if                          
      error= .false.
      return

1070  write(6,1075) rMtnFrmRate
      if ( OF.ToLog ) write(3,1075) rMtnFrmRate
1075  format(' Error- Motion data frame rate is ',f10.6,'.'/
     &       '        It has to be greater than zero for individual foot forces'/
     &       '        to be determined.')
      return
      end
c
c     DecideFpToFt
c
c     Copyright by Dwight Meglan 1992
c
c     Purpose:
c     Figure out which plate readings correspond to which foot, if this
c     has not been set manually in the options.  Basically, check the proximity
c     of the force plate trigger frames to each of the 3 heel strikes which
c     are present the single gait cycle that is being analyzed.  If the
c     trigger frame is with in a percentage of the cycle, set by HsTolPct
c     (HeelStrkeTolerancePercent), of the heel strike then the plate is
c     associated with the foot.  Also, check whether 2 plates were hit by
c     the same foot by checking if their trigger frames are within SameFtPct
c     of the cycle of one another.  This scheme will NOT detect the case
c     where the second foot hits the same plate as the first foot or the
c     case where the 2nd HS of the first foot hits the same plate as the
c     second foot hit.  In these cases the manual specification of which
c     force plates are associated with which feet should be used.
c     As part of this process, decide whether
c     the force data during double stance can be used to compute joint loads
c     (i.e., can the individual resultant forces under each foot be determined
c     during this phase of gait).  This will not be possible if both feet
c     landed on the same plate during double stance.
c                                                   
c     NOTE!  If plt_per_ft for the second foot is 0, then it has not touched
c     a force plate at all.  Also, it is not possible for plt_per_ft
c     of the first foot to be 0.
c
c
c	Routines called:
c		None
c
c	Common Block Variables USED by routine:
c	Block Name		Variable Name
c
c	Param			FP.av_frm_ratio
c	Param			FP.nfrm
c  Param       FP.num_plt
c	Param			MP.cycle_length
c	Param			MP.first_ft
c	Param			MP.lhs_frm
c	Param			MP.rhs_frm     
c
c	Common Block Variables MODIFIED by routine:
c	Block Name		Variable Name
c
c	Force_Plate		FtGrlOK
c	Param			FP.dbl_stance_OK
c	Param			FP.first_frm
c	Param			FP.last_frm
c	Param			FP.lfft_plt
c	Param			FP.num_lfft_plt
c	Param			FP.num_rtft_plt
c	Param			FP.rtft_plt                              
c             
      subroutine DecideFpToFt(FMOPT,TrigFrm,NumPltUsed,PltOrder,OF)
      implicit none
                                                           
      include 'a_const.inc'
      include 'a_struct.inc' 
      include 'a_prm.inc'
      include 'a_gls2.inc'
      include 'a_frc.inc'
      include 'a_names.inc'               
                                                                        
      record /FrcMtnOpt/    FMOPT
      record /option_flag/  OF
              
      integer*4 TrigFrm(MAX_PLT),NumPltUsed,PltOrder(MAX_PLT)
                                       
      integer*4 HSTolNumFrm,MidHsFrm,i,plt,dif,itemp,j
      logical*4 AddPlate
c
c     Set force plate data frame limits relative to gait cycle events
c
      if ( FMOPT.SecondFt .and. (FP.num_plt .ge. 3) ) then
        FP.first_frm= TrigFrm(PltOrder(2))
      else
        FP.first_frm= TrigFrm(PltOrder(1))
      end if
      FP.last_frm= FP.first_frm + MP.cycle_length(1)*
     &                                      FP.av_frm_ratio
c
c     Associate force plates with feet by checking how closely the force plates
c     are triggered (i.e., struck by a foot) relative to the motion data
c     determined gait events.
c
c     First determine the tolerance zone of the number of frames from the foot
c     strike where if the plate trigger occurs then that the plate is 
c     considered to be associated with that foot
c
      if ( .not. FMOPT.ManFtPlt ) then
        FP.num_rtft_plt= 0
        FP.num_lfft_plt= 0
        HSTolNumFrm= ( FP.last_frm-FP.first_frm )*(FMOPT.HSTolPct/100.)
        if ( MP.first_ft .eq. rt ) then
          MidHsFrm= FP.first_frm + 
     &              (MP.lhs_frm(1)-MP.rhs_frm(1)+1)
     &             *FP.av_frm_ratio                            
        else
          MidHsFrm= FP.first_frm +                                    
     &              (MP.rhs_frm(1)-MP.lhs_frm(1)+1)        
     &             *FP.av_frm_ratio                                   
        end if   
c
c       Check each plate against the heelstrike frames and the tolerance zone
c                           
        do i= 2, NumPltUsed
          if ( FMOPT.SecondFt .and. (FP.num_plt .ge. 3) .and. (i .eq. 2) ) then
            plt= 1             
          else
            plt= i
          end if 
c
c         Check plate against first and second footstrikes of the first foot
c         of the gait cycle
c
          dif= abs( TrigFrm(PltOrder(plt)) - FP.first_frm ) ! first HS
          if ( (dif .le. HSTolNumFrm) .or. 
     &         ((plt .eq. 1) .and. (FP.first_frm .lt. 0)) ) then      
            if ( MP.first_ft .eq. rt ) then
              FP.num_rtft_plt= FP.num_rtft_plt + 1
              FP.rtft_plt(FP.num_rtft_plt)= PltOrder(plt)
            else                        
              FP.num_lfft_plt= FP.num_lfft_plt + 1
              FP.lfft_plt(FP.num_lfft_plt)= PltOrder(plt)
            end if
          else ! go ahead and check plate against 2nd HS of first foot
            dif= abs( TrigFrm(PltOrder(plt)) - FP.last_frm ) ! third HS 
            if ( dif .le. HSTolNumFrm ) then
              if ( MP.first_ft .eq. rt ) then
                FP.num_rtft_plt= FP.num_rtft_plt + 1
                FP.rtft_plt(FP.num_rtft_plt)= PltOrder(plt)
              else        
                FP.num_lfft_plt= FP.num_lfft_plt + 1
                FP.lfft_plt(FP.num_lfft_plt)= PltOrder(plt)
              end if
            end if ! plate is within HS tolerance
          end if ! plate is struck by first foot
c
c         Check the plate against the footstrike of the second foot of the
c         gait cycle
c
          dif= abs( TrigFrm(PltOrder(plt)) - MidHsFrm ) ! second foot HS
          if ( dif .le. HSTolNumFrm ) then  
            if ( MP.first_ft .eq. rt ) then
              FP.num_lfft_plt= FP.num_lfft_plt + 1
              FP.lfft_plt(FP.num_lfft_plt)= PltOrder(plt)
            else                                  
              FP.num_rtft_plt= FP.num_rtft_plt + 1
              FP.rtft_plt(FP.num_rtft_plt)= PltOrder(plt)
            end if
          end if
        end do ! plt
c
c       Ohio State Specifc Error Fix !!!
c
c       Swap plate to foot assignments and set second foot number of plates to 0
c
        if ( FMOPT.SecondFt .and. (FP.num_plt .eq. 2) ) then 
          do i= 1, MAX_PLT          
            itemp= FP.rtft_plt(i)
            FP.rtft_plt(i)= FP.lfft_plt(i)
            FP.lfft_plt(i)= itemp
          end do
          itemp= FP.num_rtft_plt
          FP.num_rtft_plt= FP.num_lfft_plt
          FP.num_lfft_plt= itemp
          if ( MP.first_ft .eq. rt ) then
            FP.num_rtft_plt= 0
          else
            FP.num_lfft_plt= 0
          end if
        end if ! if Second foot actually strikes plates first
      end if ! plate to foot correspondence has not been done manually
c
c     Special case of 3 or more force plates and second plate is struck by 
c     first heelstrike recorded in gait events
c
      if ( FMOPT.SecondFt .and. (FP.num_plt .ge. 3) ) then
        AddPlate= .true.
        if ( MP.first_ft .eq. rt ) then
          do i= 1, FP.num_lfft_plt
            if ( FP.lfft_plt(i) .eq. 1 ) AddPlate= .false.
          end do
          if ( AddPlate ) then
            FP.num_lfft_plt= FP.num_lfft_plt + 1
            FP.lfft_plt(FP.num_lfft_plt)= 1
          end if
        else ! left heelstrike first
          do i= 1, FP.num_rtft_plt
            if ( FP.rtft_plt(i) .eq. 1 ) AddPlate= .false.
          end do
          if ( AddPlate ) then
            FP.num_rtft_plt= FP.num_rtft_plt + 1
            FP.rtft_plt(FP.num_rtft_plt)= 1
          end if
        end if
      end if
c
c     Check validity of force plate data during double support
c
      FP.dbl_stance_OK= .true.
c      if ( (FP.num_rtft_plt .eq. 0) .or. 
c     &     (FP.num_lfft_plt .eq. 0)      ) then
c        FP.dbl_stance_OK= .false.
c      else
        do i= 1, FP.num_rtft_plt
          do j= 1, FP.num_lfft_plt
            if ( FP.rtft_plt(i) .eq. FP.lfft_plt(j) )
     &          FP.dbl_stance_OK= .false.
          end do ! j         
        end do ! i
c      end if
      if ( FP.num_rtft_plt .gt. 0 ) then
        write(6,120) (FP.rtft_plt(i),i=1,FP.num_rtft_plt)
        if ( OF.ToLog ) write(3,120) (FP.rtft_plt(i),i=1,FP.num_rtft_plt)
120     format(/' The right foot struck plate(s) # : ',4i2)
      else
        write(6,121)
        if ( OF.ToLog ) write(3,121)
121     format(/' The right foot struck plate(s) # : none')
      end if
      if ( FP.num_lfft_plt .gt. 0 ) then
        write(6,125) (FP.lfft_plt(j),j=1,FP.num_lfft_plt)
        if ( OF.ToLog ) write(3,125) (FP.lfft_plt(j),j=1,FP.num_lfft_plt)
125     format( ' The left foot struck plate(s) #  : ',4i2)
      else
        write(6,126)
        if ( OF.ToLog ) write(3,126)
126     format(' The left foot struck plate(s) # : none')
      end if
      return
      end
c
c
c     CheckFpToFt
c
c     Copyright by Dwight Meglan 1992
c
c     Purpose:
c     Do a quick check of what the force plate information says is the order
c     in which the feet struck the plates.  Calculate the y position of
c     the center of pressure of the applied force to the first two force
c     plates that are triggered and use this to decide which foot hit which
c     plate.  NOTE that this assumes that the feet do not cross over.  Also
c     the direction of travel is corrected for in determining the order
c     of foot strike by checking the x position of the center of pressure.
c     NOTE also that this routine only makes sense if there are 2 or more
c     force plates present...
c                                
c	Routines called:
c		None
c
c	Common Block Variables USED by routine:
c	Block Name		Variable Name
c
c	Param			FP.nfrm
c	Param			FP.num_plt
c  Force       FpData
c                                    
c	Common Block Variables MODIFIED by routine:
c	Block Name		Variable Name
c
c	  None
c                            
      subroutine CheckFpToFt(FMOPT,TrigFrm,PltOrder,OF)
      implicit none
                                                           
      include 'a_const.inc'
      include 'a_struct.inc' 
      include 'a_prm.inc'
      include 'a_gls2.inc'
      include 'a_frc.inc'
      include 'a_names.inc'                              
                                                                        
      record /FrcMtnOpt/    FMOPT
      record /option_flag/  OF
              
      integer*4 TrigFrm(MAX_PLT),PltOrder(MAX_PLT)
                                       
      integer*4 i,plt,frm,CalcFirstFt
      real*4    FtPos(2,2)
      character SideName(2)*5

      data SideName /'right','left '/

      if ( FP.num_plt .lt. 2 ) return
c
c     Select a frame of data from the first two force plates which 5 frames
c     past the trigger frame
c
      do i= 1, 2
        plt= PltOrder(i)
        if ( PltOrder(i) .le. 0 ) plt= 1
        frm= TrigFrm(plt) + 5
        do while ( (frm .lt. FP.nfrm) .and.
     &             (abs(FpData(fz,frm,plt)) .lt. 0.01) )
          frm= frm + 1               
        end do      
        FtPos(x,i)= -FpData(my,frm,plt)/FpData(fz,frm,plt)
        FtPos(y,i)=  FpData(mx,frm,plt)/FpData(fz,frm,plt)
      end do
      CalcFirstFt= lf
c
c     Give some results     
c                                    
      if ( FtPos(x,1) .lt. FtPos(x,2) ) then ! patient moves forward
        if ( FtPos(y,1) .lt. FtPos(y,2) ) CalcFirstFt= rt
        write(6,130)
        if ( OF.ToLog ) write(3,130)
130     format(/' The force plate data shows patient moving from back to front')
      else ! the patient is moving from front to back of lab
        if ( FtPos(y,1) .gt. FtPos(y,2) ) CalcFirstFt= rt
        write(6,135)
        if ( OF.ToLog ) write(3,135)
135     format(/' The force plate data shows patient moving from front to back')
      end if
      write(6,115) SideName(CalcFirstFt),SideName(MP.first_ft)
      if ( OF.ToLog ) write(3,115) SideName(CalcFirstFt),
     &                                     SideName(MP.first_ft)
115   format(/' The force plates show ',a5,' foot struck plates first.'/
     &        ' The gait events show  ',a5,' foot as first foot sorted.')
      if ( FMOPT.SecondFt .and. (FP.num_plt .eq. 2) ) then
        write(6,116)
        if ( OF.ToLog ) write(3,116)
116     format('   However, since the second foot struck the force plates'/
     &         '   first, the estimate of direction and sides on plates may'/
     &         '   be unreliable...')
      else if ( FMOPT.SecondFt .and. (FP.num_plt .ge. 3) ) then
        write(6,117)
        if ( OF.ToLog ) write(3,117)
117     format('   However, a FoRCe ALIgn option is set to indicate that the'/
     &         '   the first heelstrike indicated in the gait events is'/
     &         '   actually the SECcond foot to strike a force plate')
      end if
      return
      end
c
c                     
c     AssignFpToFt
c
c     Copyright by Dwight Meglan 1992
c
c     Purpose:
c     Split the force plate load data between the feet.  The force
c     plate data is checked to see if a foot touches more than one plate
c     and if so combines the data from these plates to form a single set
c     of load data describing the foot resultant.             
c     Since all force plate data has already been transformed from the plate
c     LCS to the global CS ( in the subroutine ConditionFrcData ),
c     the resultant forces and moments can be obtained
c     by just summing the load data from the force plates that the foot
c     touches.
c      
c	Routines called:
c		None
c
c	Common Block Variables USED by routine:
c	Block Name		Variable Name
c
c	Param			FP.nfrm
c	Param			FP.num_plt
c	Param			FP.lfft_plt
c	Param			FP.num_lfft_plt
c	Param			FP.num_rtft_plt
c	Param			FP.rtft_plt
c	Force			FpData
c	Param			MP.first_ft
c	Param			MP.lhs_frm
c	Param			MP.lto_frm
c	Param			MP.rhs_frm
c	Param			MP.rto_frm
c
c	Common Block Variables MODIFIED by routine:
c	Block Name		Variable Name
c
c	Force_Plate		FtGrlOK
c	Param		   	FP.av_frm_ratio
c	Param	    		FP.dbl_stance_OK
c	Force_Plate		FtGrl
c
      subroutine AssignFpToFt(FMOPT,OF)
      implicit none
                                                           
      include 'a_const.inc'
      include 'a_struct.inc' 
      include 'a_prm.inc'
      include 'a_gls2.inc'
      include 'a_frc.inc'
      include 'a_names.inc'                                 
                                                                        
      record /FrcMtnOpt/    FMOPT
      record /option_flag/  OF
              
      integer*4 NumPlt(2),i,PltNum(MAX_PLT,2),DblFirstFrm,DblLastFrm,
     &          FirstHSFrm,FirstTOFrm,foot2,ft,frm,load,MtnFrm,j,LastHSFrm
c
c     Setup information foot ordering on plates
c
      NumPlt(rt)= FP.num_rtft_plt
      NumPlt(lf)= FP.num_lfft_plt
      do i= 1, FP.num_rtft_plt
        PltNum(i,rt)= FP.rtft_plt(i)                
      end do                                     
      do i= 1, FP.num_lfft_plt
        PltNum(i,lf)= FP.lfft_plt(i)
      end do
      if ( MP.first_ft .eq. rt ) then ! determine 1st and last frames
        DblFirstFrm= MP.lhs_frm(1)  ! of double stance
        DblLastFrm=  MP.rto_frm(1)
        FirstHSFrm=  MP.rhs_frm(1)
        FirstTOFrm=  MP.lto_frm(1)
        LastHSFrm=   MP.rhs_frm(2)
        foot2= lf
      else
        DblFirstFrm= MP.rhs_frm(1)
        DblLastFrm=  MP.lto_frm(1)
        FirstHSFrm=  MP.lhs_frm(1)
        FirstTOFrm=  MP.rto_frm(1)
        LastHSFrm=   MP.lhs_frm(2)
        foot2= rt
      end if
c
c     Sum the force plate data for the individual feet
c
      do ft= rt, lf
        if ( NumPlt(ft) .eq. 0 ) then     ! there is no force plate data
          do frm= 1, FP.nfrm      ! for this foot...     
            FtGrlOK(frm,ft)= .false.  
            do load= fx, mz
              FtGrl(load,frm,ft)= 0.0
            end do ! load                 
          end do ! frm                  
        else                                 ! do the combination of force
          do i= 1, FP.nfrm           ! plate data for the foot
            MtnFrm= FirstHSFrm + 
     &              float(i-FP.first_frm+1)/FP.av_frm_ratio
            FtGrlOK(i,ft)= .true.    
            do load= fx, mz
              FtGrl(load,i,ft)= 0.0              
            end do ! load
            if ( i .ge. FP.first_frm ) then
              if ( (MtnFrm .lt. FirstTOFrm) .and.  (ft .eq. foot2) .and.
     &             (.not. FMOPT.SecondFt) )  FtGrlOK(i,ft)= .false.
              if ( (MtnFrm .ge. DblFirstFrm) .and.       
     &             (MtnFrm .le. DblLastFrm)  .and. 
     &             (.not. FP.dbl_stance_OK)   ) FtGrlOK(i,ft)= .false.
              if ( FtGrlOK(i,ft) ) then
                do j= 1, NumPlt(ft)
                  if ( FpDataOK(i,PltNum(j,ft)) ) then
                    do load= fx, mz
                      if ( (.not. FMOPT.MzZero) .or. (PltNum(j,ft) .ne. 1) .or.
     &                     (load .ne. mz) )      
     &                  FtGrl(load,i,ft)= FtGrl(load,i,ft) +
     &                                      FpData(load,i,PltNum(j,ft))
                    end do ! load
                  end if
                end do ! j => plt                
              end if ! force plate data good for this part of the gait cycle
              if ( -FtGrl(fz,i,ft) .lt. FMOPT.FzThreshold ) 
     &          FtGrlOK(i,ft)= .false.
            else
              FtGrlOK(i,ft)= .false.
            end if
          end do ! i => cycle frm number
c
c         Try to trap for single motion frame misalignment and force plate
c         thresholding (FtGrlOK) that has been too aggressive and made
c         the force plate data invalid too early
c                     
          do i= 1, FP.nfrm          
            MtnFrm= FirstHSFrm +                  
     &              float(i-FP.first_frm+1)/FP.av_frm_ratio
            if ( ft .ne. foot2 ) then
              if ( (MtnFrm .eq. FirstHSFrm) .and. 
     &              FtGrlOK(MtnFrm+1,ft) .and.
     &              (.not. FtGrlOK(MtnFrm,ft))  ) then
                do frm= i-1, i + FP.av_frm_ratio + 1
                  FtGrlOK(frm,ft)= .true.
                end do
              end if
              if ( (MtnFrm .eq. DblLastFrm) .and. 
     &              FtGrlOK(MtnFrm-1,ft) .and.
     &              (.not. FtGrlOK(MtnFrm,ft))  ) then
                do frm= i - FP.av_frm_ratio - 1, i + 1
                  FtGrlOK(frm,ft)= .true.
                end do
              end if
              if ( MtnFrm .eq. LastHSFrm ) then
                do frm= i-1, i+1
                  FtGrlOK(frm,ft)= .true.
                end do
              end if
            else                   
              if ( (MtnFrm .eq. FirstTOFrm) .and. 
     &              FtGrlOK(MtnFrm-1,ft) .and.
     &              (.not. FtGrlOK(MtnFrm,ft))  ) then
                do frm= i- FP.av_frm_ratio - 1, i+1
                  FtGrlOK(frm,ft)= .true.
                end do
              end if
              if ( (MtnFrm .eq. DblFirstFrm) .and. 
     &              FtGrlOK(MtnFrm+1,ft) .and.
     &              (.not. FtGrlOK(MtnFrm,ft))  ) then
                do frm= i-1, i + FP.av_frm_ratio+1
                  FtGrlOK(frm,ft)= .true.
                end do                            
              end if                         
            end if
          end do
        end if ! Is there any good force plate data for this foot
      end do ! ft 
      return                  
      end 
                                        
