Repository: FloatingArrayDesign/MoorDyn (language: python)

Issue:
Added Pre-Compiled Files
Hi,

I have added the pre-compiled files from the WEC-SIM folder for easy access and use so that some users won't have to compile the software themselves. Hope this is okay!

many thanks,

Thomas


Discussion / hints:
My 2 cents: This is not a good idea but better adding [Releases](https://github.com/mattEhall/MoorDyn/releases)
Thomas, thanks for this. 
I think this is something removed in a prior pull request - @sanguinariojoe, what's your reason again?

I'd lean toward prioritizing convenience/access (by including binaries) over worry about best practices at this point...
Well, first I should mention I removed the binaries from the dev branch, this PR points towards master.

Regarding binaries in git, in general you DO NOT want to track them. Along this line we can differentiate 2 kinds of binaries:

 - Images or similar resources. Those binaries would not change, ever
 - Compiled binaries and automatically generated files, that will change at every single commit (or they will become obsolete, which is even worst)

The former can be safely added to the repo while the latter is the source of many problems. First, git has to track every single bite changed at every single commit. With time this will make the git repo giant and sluggish. Second, the more "random" changes you introduce the higher chances you have to create a hash collision, rendering the git repository broken forever (this is anyway very unlikely to happen). Finally, it is making branches and forks useless, e.g.

 1. I fork the project
 2. I make changes, so I recompile and add the library
 3. Meanwhile @mattEhall makes some other changes, obviously adding a different version of the compiled library
 4. I submit my PR and it cannot be merged because of conflicts.

Might be the case (or not) that there are conflicts to be fixed in the source code files, and they would be indeed hard to fix, but manageable anyway. But how can anyone fix the conflicts on the compiled library??? There is no way to workaround such problem, it is mandatory to start the work all over again. So bad...
@TGiles1998, I think @sanguinariojoe has made a really solid case here, and I should add those binaries into a release rather than merging this pull request. Let's leave this open until I get it done!
Thanks to both of you,
Matt
Can we close this?

Just for the record... The v2 candidate in https://github.com/mattEhall/MoorDyn/pull/31 will autodeploy releases with Windows installers. And example here:

https://github.com/sanguinariojoe/MoorDyn/releases/tag/nightly

(And installing in Python will be as simple as typing `pip install moordyn`)
 

Fix patch:
diff --git a/.gitignore b/.gitignore
index c70c06ab..2d9bcde7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,8 +53,14 @@ compile/SO/test*
 .eggs
 moordyn.egg-info
 tests/__pycache__
+/out/build
+/.vs/MoorDyn/FileContentIndex
+/.vs
+/CMakeSettings.json
 
 # Compilation artifacts #
 #########################
 build
 dist
+
+/source/.vscode
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d89b348d..a53a3bc8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.10)
 set(MOORDYN_MAJOR_VERSION 2)
 set(MOORDYN_MINOR_VERSION 1)
-set(MOORDYN_PATCH_VERSION 1)
+set(MOORDYN_PATCH_VERSION 2)
 set(MOORDYN_VERSION ${MOORDYN_MAJOR_VERSION}.${MOORDYN_MINOR_VERSION})
 project(Moordyn VERSION ${MOORDYN_VERSION})
 
diff --git a/README.md b/README.md
index e9a0e620..3b0557d0 100644
--- a/README.md
+++ b/README.md
@@ -65,4 +65,6 @@ version 2 development:
 We must also express our gratitude to some other developers that have
 contributed
 
+From [Kelson Marine](https://kelsonmarine.com):
   - [David Joseph Anderson](https://davidjosephanderson.com/)
+  - [Alex Kinley](https://github.com/AlexWKinley)
diff --git a/compile/DLL/makefile b/compile/DLL/makefile
index 587c8b61..90b19eec 100644
--- a/compile/DLL/makefile
+++ b/compile/DLL/makefile
@@ -45,6 +45,7 @@ HEADERS = ../../source/Body.h \
           ../../source/MoorDyn.h \
           ../../source/Rod.h \
           ../../source/Waves.h \
+		  ../../source/Seafloor.h \
           ../../source/Body.hpp \
           ../../source/Connection.hpp \
           ../../source/IO.hpp \
@@ -56,12 +57,13 @@ HEADERS = ../../source/Body.h \
           ../../source/Rod.hpp \
           ../../source/State.hpp \
           ../../source/Time.hpp \
-          ../../source/Waves.hpp
+          ../../source/Waves.hpp \
+		  ../../source/Seafloor.hpp
 
 all: libmoordyn2.dll
 
-libmoordyn2.dll: MoorDyn.o MoorDyn2.o Line.o Connection.o Rod.o Body.o Log.o IO.o Misc.o State.o Time.o Waves.o kiss_fftr.o kiss_fft.o
-	$(CXX) $(LFLAGS) -o libmoordyn2.dll MoorDyn.o MoorDyn2.o Line.o Rod.o Body.o Connection.o Log.o IO.o Misc.o State.o Time.o Waves.o kiss_fftr.o kiss_fft.o
+libmoordyn2.dll: MoorDyn.o MoorDyn2.o Line.o Connection.o Rod.o Body.o Log.o IO.o Misc.o State.o Time.o Waves.o Seafloor.o kiss_fftr.o kiss_fft.o
+	$(CXX) $(LFLAGS) -o libmoordyn2.dll MoorDyn.o MoorDyn2.o Line.o Rod.o Body.o Connection.o Log.o IO.o Misc.o State.o Time.o Waves.o Seafloor.o kiss_fftr.o kiss_fft.o
 
 MoorDyn.o: ../../source/MoorDyn.cpp $(HEADERS)
 	$(CXX) $(CPPFLAGS) ../../source/MoorDyn.cpp
@@ -96,6 +98,9 @@ Time.o: ../../source/Time.cpp $(HEADERS)
 Waves.o: ../../source/Waves.cpp $(HEADERS)
 	$(CXX) $(CPPFLAGS) ../../source/Waves.cpp
 
+Seafloor.o: ../../source/Seafloor.cpp $(HEADERS)
+	$(CXX) $(CPPFLAGS) ../../source/Seafloor.cpp
+
 Log.o: ../../source/Log.cpp $(HEADERS)
 	$(CXX) $(CPPFLAGS) ../../source/Log.cpp
 
diff --git a/compile/DYLIB/makefile b/compile/DYLIB/makefile
index 48b7db5b..432f91c9 100644
--- a/compile/DYLIB/makefile
+++ b/compile/DYLIB/makefile
@@ -54,6 +54,7 @@ HEADERS = ../../source/Body.h \
           ../../source/MoorDyn.h \
           ../../source/Rod.h \
           ../../source/Waves.h \
+		  ../../source/Seafloor.h \
           ../../source/Body.hpp \
           ../../source/Connection.hpp \
           ../../source/IO.hpp \
@@ -65,12 +66,13 @@ HEADERS = ../../source/Body.h \
           ../../source/Rod.hpp \
           ../../source/State.hpp \
           ../../source/Time.hpp \
-          ../../source/Waves.hpp
+          ../../source/Waves.hpp \
+		  ../../source/Seafloor.hpp
 
 all: libmoordyn2.dylib
 
-libmoordyn2.dylib: MoorDyn.o MoorDyn2.o Line.o Connection.o Body.o Rod.o State.o Time.o Waves.o Log.o IO.o Misc.o kiss_fft.o kiss_fftr.o
-	$(CXX) $(LFLAGS) -o libmoordyn2.dylib MoorDyn.o MoorDyn2.o Line.o Connection.o Body.o Rod.o State.o Time.o Waves.o Log.o IO.o Misc.o kiss_fft.o kiss_fftr.o
+libmoordyn2.dylib: MoorDyn.o MoorDyn2.o Line.o Connection.o Body.o Rod.o State.o Time.o Waves.o Seafloor.o Log.o IO.o Misc.o kiss_fft.o kiss_fftr.o
+	$(CXX) $(LFLAGS) -o libmoordyn2.dylib MoorDyn.o MoorDyn2.o Line.o Connection.o Body.o Rod.o State.o Time.o Waves.o Seafloor.o Log.o IO.o Misc.o kiss_fft.o kiss_fftr.o
 
 MoorDyn.o: ../../source/MoorDyn.cpp $(HEADERS)
 	$(CXX) $(CPPFLAGS) ../../source/MoorDyn.cpp
@@ -105,6 +107,9 @@ Time.o: ../../source/Time.cpp $(HEADERS)
 Waves.o: ../../source/Waves.cpp $(HEADERS)
 	$(CXX) $(CPPFLAGS) ../../source/Waves.cpp
 
+Seafloor.o: ../../source/Seafloor.cpp $(HEADERS)
+	$(CXX) $(CPPFLAGS) ../../source/Seafloor.cpp
+
 Log.o: ../../source/Log.cpp $(HEADERS)
 	$(CXX) $(CPPFLAGS) ../../source/Log.cpp
 
diff --git a/compile/SO/makefile b/compile/SO/makefile
index b2b656d1..31908670 100644
--- a/compile/SO/makefile
+++ b/compile/SO/makefile
@@ -46,6 +46,7 @@ HEADERS = ../../source/Body.h \
           ../../source/MoorDyn.h \
           ../../source/Rod.h \
           ../../source/Waves.h \
+		  ../../source/Seafloor.h \
           ../../source/Body.hpp \
           ../../source/Connection.hpp \
           ../../source/IO.hpp \
@@ -57,7 +58,8 @@ HEADERS = ../../source/Body.h \
           ../../source/Rod.hpp \
           ../../source/State.hpp \
           ../../source/Time.hpp \
-          ../../source/Waves.hpp
+          ../../source/Waves.hpp \
+		  ../../source/Seafloor.hpp
 
 all: libmoordyn.so
 
@@ -65,8 +67,8 @@ libmoordyn.so: libmoordyn.so.2.0.0
 	rm -f libmoordyn.so
 	ln -s libmoordyn.so.2.0.0 libmoordyn.so
 
-libmoordyn.so.2.0.0: MoorDyn.o MoorDyn2.o Line.o Connection.o Body.o Rod.o State.o Time.o Waves.o Log.o IO.o Misc.o kiss_fft.o kiss_fftr.o
-	$(CXX) $(LFLAGS) -o libmoordyn.so.2.0.0 MoorDyn.o MoorDyn2.o Line.o Connection.o Body.o Rod.o State.o Time.o Waves.o Log.o IO.o Misc.o kiss_fft.o kiss_fftr.o
+libmoordyn.so.2.0.0: MoorDyn.o MoorDyn2.o Line.o Connection.o Body.o Rod.o State.o Time.o Waves.o Seafloor.o Log.o IO.o Misc.o kiss_fft.o kiss_fftr.o
+	$(CXX) $(LFLAGS) -o libmoordyn.so.2.0.0 MoorDyn.o MoorDyn2.o Line.o Connection.o Body.o Rod.o State.o Time.o Waves.o Seafloor.o Log.o IO.o Misc.o kiss_fft.o kiss_fftr.o
 
 MoorDyn.o: ../../source/MoorDyn.cpp $(HEADERS)
 	$(CXX) $(CPPFLAGS) ../../source/MoorDyn.cpp
@@ -101,6 +103,9 @@ Time.o: ../../source/Time.cpp $(HEADERS)
 Waves.o: ../../source/Waves.cpp $(HEADERS)
 	$(CXX) $(CPPFLAGS) ../../source/Waves.cpp
 
+Seafloor.o: ../../source/Seafloor.cpp $(HEADERS)
+	$(CXX) $(CPPFLAGS) ../../source/Seafloor.cpp
+
 Log.o: ../../source/Log.cpp $(HEADERS)
 	$(CXX) $(CPPFLAGS) ../../source/Log.cpp
 
diff --git a/docs/starting.rst b/docs/starting.rst
index 22ee4b47..1f3fb7d1 100644
--- a/docs/starting.rst
+++ b/docs/starting.rst
@@ -68,9 +68,11 @@ will be documented separatelly
 Windows
 ^^^^^^^
 
-In this tutorial we are installing Eigen3 and MoorDyn in the default folders
-(C:\Program Files (x86)\Eigen3 and C:\Program Files (x86)\Moordyn),
-using for that the latest versions available downloaded with Git.
+In this tutorial we will install Eigen3 and MoorDyn in the default folders
+(C:\Program Files (x86)\Eigen3 and C:\Program Files (x86)\Moordyn).
+We will acquire the latest versions available with Git and build them
+using CMake. This documentation assumes that you are building
+in an MSYS2 build environment.
 
 Please, install all the tools we need:
 
@@ -95,20 +97,21 @@ The same holds for CMake:
 
 The installation of MSYS2 is pretty well documented in
 `the project web page <https://www.msys2.org/>`_. However, we need some
-additional tools, so after it, and running "MSYS MinGW 64-bit", please type
+additional tools, so after running "MSYS MinGW 64-bit", please type
 the following command
 
 .. code-block:: bash
+pacman -S 
 
-  pacman -S mingw-w64-x86_64-python-setuptools mingw-w64-x86_64-python-pip
+  pacman -S mingw-w64-x86_64-python-setuptools mingw-w64-x86_64-python-pip mingw64/mingw-w64-x86_64-make mingw-w64-x86_64-gcc mingw-w64-x86_64-gdb mingw-w64-x86_64-cmake
 
-Now we are making all the MinGW stack available along the whole system, adding
+Now we need to make the MinGW stack available across the whole system by adding
 it to the PATH environment variable.
 To this end, execute "System" from the Windows Init menu, and look for
 "environment".
 Then click on "Edit the system environment variables" and in the popping up
 window on "Enviroment Variables..."
-Do double click on Path (in the System variables box), and add a new entry:
+Double click on Path (in the System variables box), and add a new entry:
 "C:\msys64\mingw64\bin"
 
 .. figure:: win_msys2_env.png
@@ -116,9 +119,9 @@ Do double click on Path (in the System variables box), and add a new entry:
 
    Adding MinGW to the PATH
 
-Now we are ready to work! First we are creating a folder where we are
-downloading and compiling the codes, let's say C:\MoorDyn.
-Create such a folder, and do right click inside, selecting "Git GUI Here". In
+Now we are ready to work! First we must create a folder where we will
+download and compile the MoorDyn code, let's say C:\MoorDyn.
+Create such a folder, and right click inside, selecting "Git GUI Here". In
 the Git window select "Clone Existing Repository".
 
 .. figure:: win_git_gui.png
@@ -126,7 +129,7 @@ the Git window select "Clone Existing Repository".
 
    The Git GUI to clone repositories
 
-We are starting with Eigen3, so in the first box set
+We are starting with Eigen3, so in the first box of the window that pops up set
 "https://gitlab.com/libeigen/eigen.git", and in the second "C:\MoorDyn\eigen":
 
 .. figure:: win_git_eigen.png
@@ -144,12 +147,15 @@ Now you can repeat, setting "https://github.com/mattEhall/MoorDyn.git", and
    Cloning MoorDyn repository
 
 Now, create two additional folders in C:\MoorDyn named eigen.build and
-MoorDyn.build.
-It is time to start CMake from the Windows Init menu. To prepare Eigen3 set
+MoorDyn.build. As suggested by the names, these folders are where we will
+actually build the source code we just cloned from GitHub. To do this, we'll
+be using CMake as our build tool.
+
+Start CMake from the Windows Init menu. To prepare Eigen3 set
 "C:\MoorDyn\eigen" in the source box and "C:\MoorDyn\eigen.build" in the
 binaries box, and press "Configure".
 The first time you configure a new project, CMake will ask you for the toolchain
-to use. Please, select "MinGW Makefiles":
+to use. Select "MinGW Makefiles":
 
 .. figure:: win_cmake_selectcompiler.png
    :alt: Selecting the MinGW generator
@@ -158,8 +164,11 @@ to use. Please, select "MinGW Makefiles":
 
 Click on "Finish" and let CMake work. After a short while you will see a lot of
 new red boxes.
-Do not worry, they are red because they are new, they are not errors.
-Remember to set CMAKE_BUILD_TYPE as "Release".
+Don't worry, these are not errors - they are red because they are new, and you
+must specify some additional parameters for CMake.
+Remember to set CMAKE_BUILD_TYPE as "Release" (unless you are working on the
+source code, in which case you may wish to set the build type to "Debug" so
+as to run the built program through a debugger).
 It is also recommended to disable BUILD_TESTING, EIGEN_BUILD_DOC and
 EIGEN_BUILD_TESTING:
 
@@ -172,7 +181,7 @@ Press "Configure" once again, and then "Generate". Now you can close CMake.
 
 Now, since we are installing Eigen in C:\Program Files (x86)\Eigen3, we need
 to execute a Command Prompt with administrative rights.
-To this end, look for "cmd" in the Windows Init menu and do right click on
+Search for "cmd" in the Windows Init menu and right click on
 "Command Prompt", selecting Run as Administrator:
 
 .. figure:: win_cmd_admin.png
@@ -188,8 +197,9 @@ Now you just need to type the following commands:
   mingw32-make
   mingw32-make install
 
-We will use a cmd with administrative rights later on, so do not close it.
-Now we are installing MoorDyn following a very similar process.
+We will need to use cmd with administrative rights later on, so do not close it.
+
+Now we will install MoorDyn following a very similar process.
 Launch CMake again, and set "C:\MoorDyn\MoorDyn" in the source box and
 "C:\MoorDyn\MoorDyn.build" in the binaries box, clicking "Configure" afterwards.
 Select again the "MinGW Makefiles" for the generator.
@@ -202,9 +212,9 @@ enable FORTRAN_WRAPPER and PYTHON_WRAPPER:
    Configuration options for MoorDyn
 
 You can also enable MATLAB_WRAPPER if you have Matlab installed in your system.
-We are ready, click "Configure" once more and the "Generate".
+We are ready, click "Configure" once more and then "Generate".
 
-Now you can recover your Command Prompt (which has adminsitrative rights), and
+Now go back to your Command Prompt from earlier (which has adminsitrative rights), and
 type the following commands:
 
 .. code-block:: bash
@@ -226,7 +236,7 @@ option and type
 Linux and MAC
 ^^^^^^^^^^^^^
 
-First of all, use you package manager to install the following packages
+First of all, use your package manager to install the following packages
 
 * `Git <https://git-scm.com/>`_
 * `CMake <https://cmake.org/>`_
diff --git a/docs/structure.rst b/docs/structure.rst
index ccfd40ff..76625dc0 100644
--- a/docs/structure.rst
+++ b/docs/structure.rst
@@ -6,6 +6,11 @@ Model Structure
 
 MoorDyn Objects
 ---------------
+MoorDyn models the dynamics of mooring structures in an object-oriented programming (OOP) approach.
+The overall simulation is managed by the MoorDyn2 class. This class reads inputs, allows models to
+be loaded and saved, contains various environmental parameters (e.g. time-steps sizes and filepaths)
+and most importantly contains pointers to all the Objects representing physical parts of the model
+(Lines, Bodies, Rods, Connections). 
 
 Lines
 ^^^^^
@@ -33,6 +38,71 @@ MoorDyn keeps a dictionary of line types to describe the cross-sectional
 to identify it, and contains all the properties aside from length and discretization that
 are needed to describe a mooring line in MoorDyn.
 
+Line objects maintain the following Data Members:
+
+General:
+- env: a pointer to the global environment object
+- waves: a pointer to the global object storing information about waves/currents
+- t: the simulation time, as a real
+
+Specific to each Line:
+- N: int indicating the number of line segments in N
+- UnstrLen: real indicating the unstretched length of the line
+- d: real describing the line diameter
+- rho: real describing the linear density of the line
+- E: Young's modulus of the line (Pa)
+- EI: real Bending stiffness (Nm^2)
+- c: real damping coefficient (Ns). This can be the literal damping coefficient, or a negative value representing
+fraction of critical damping
+- Can: real normal added mass coefficient
+- Cat: real axial added mass coefficient
+- Cdn: real normal drag coefficient w/r/t frontal area
+- Cdt: real axial drag coefficient w/r/t surface area
+- BAin: real axial-internal damping
+- A: real cross sectional area
+- nEApoints: number of values in stress-strain lookup table
+- stiffXs: x-array for stress-strain lookup table
+- stiffYs: y-array of reals for stress-strain lookup table
+- nEIpoints: number of values in bent-stiffness lookup table
+- bstiffXs: x-array for bent-stiffness lookup table
+- bstiffYs: y-array of reals for bent-stiffness lookup table
+
+State:
+- r: a vector of 3D node positions for the nodes representing the line.
+- rd: a vector of 3D velocities for each of the nodes representing the line
+- q: a vector of 3D tangent vectors for each node
+- qs: a vector of 3D tangent vectors for each segment
+- l: a vector of unstretched line segment lengths (as reals)
+- lstr: a vector of stretched segment lengths (as reals)
+- ldstr: a vector of reals describing the rate of stretch for each segment
+- Kurv: a vector of reals describing the curvature at node points
+- M: a vector of 3x3 matrices describing the mass+added mass of each node in the line
+- V: a vector of line-segment volumes as reals
+
+Forces are computed at every node in the line. Hence, all line forces are represented as std::vectors of 3D force vectors:
+- T: a vector of 3D vectors describing segment tensions
+- Td: a vector of 3D vectors describing segment Damping forces
+- Bs: a vector of 3D vectors describing bending stiffness forces
+- W: a vector of 3D vectors describing weight (gravity) forces
+- Dp: a vector of 3D vectors describing node drag (transversal)
+- Dq: a vector of 3D vectors describing node drag (axial)
+- Ap: a vector of 3D vectors describing added-mass forcing (Transversal)
+- Aq: a vector of 3D vectors describing mass-forcing (axial)
+- B: a vector of 3D vectors describing node bottom contact force
+- Fnet: a vector of 3D vectors describing total force on each node in the line
+
+Waves:
+- F: a vector of reals indicating volume of each segment submerged (1 = fully submerged, 0 = out of water)
+- zeta: vector of reals describing free-surface elevations
+- PDyn: vecctor of reals describing dynamic pressures
+- U: vector of 3D vectors describing wave velocities
+- Ud: vector of 3D vectors describing wave accelerations
+
+Misc.
+- endTypeA, endTypeB: indicates whether ends are pinned or cantilevered to rod
+- endMomentA, endMomentB: 3D moment vectors at ends, to be applied to attached Rod/Body
+- outfile: pointer to outfile to write to
+- channels: which channels to write to the outfile
 
 Points
 ^^^^^^
@@ -53,8 +123,44 @@ There are three types of Points:
 Free Points facilitate more advanced mooring systems. They can be used to connect two 
 or more mooring lines together, to create multi-segmented lines or junctions such as in a 
 bridle mooring configuration. If a free Point is given nonzero volume or mass properties,
-it can also represent a clump weight or float.  
+it can also represent a clump weight or float. 
+
+In the C++ API, "Points" are represented as Connection objects (as their principle purpose
+is to connect different lines/bodies/rods together). Currently, every line must have 2
+Connections at each endpoint. Connection objects expose a public member, Connection::attachment,
+that contains 1. a pointer to the Line object attached to the point and 2. a field indicating
+which "end" (A or B) of the line is attached to the point.
+
+Connections Objects have the following data members:
+
+General:
+- env: a pointer to a global struct holding environmental settings
+- waves: a pointer to a global object representing Waves in the system
 
+Specific to each Connection:
+- attached: a vector of attachments, describing all lines attached to the Connection
+- conM: the mass of the connection as a real
+- conV: the volume of the connection as a real
+- conF: a 3D vector of forces on the connection
+- conCdA: Drag coefficient of the connection
+- conCa: Added mass coefficient of the connection
+
+State:
+- r: 3D node position
+- rd: 3D node velocity
+- FNet: 3D force vector on node
+- M: 3x3 mass + added mass matrix
+
+Waves:
+- zeta: real representing free-surface elevation
+- PDyn: dynamic pressure
+- U: Wave velocities
+- Ud: Wave accelerations
+
+Misc:
+- number: connection ID (unique int)
+- type: Connection type, one of moordyn::Connection::types
+- WaterKin: Flag indicating whether wave/current kinematics will be considered:
 
 Rods 
 ^^^^
@@ -101,6 +207,51 @@ and Line objects, the core Body object properties are as follows:
 - added mass coefficient in each direction
 - added mass moment of inertia coefficient about each axis
 
+In the C++ API, Bodies are represented as a standalone class. This class can have different behaviors
+depending on it's "type" (in the intuitive sense, not in the C++ sense) - there are three possible types 
+for bodies:
+- COUPLED: the body position is controlled by the calling program.
+- FREE: the body position is free to move, controlled by the hydrodynamic forces implemented in MoorDyn
+- FIXED: the body is fixed, either to a particular location or to a connected, moving entity.
+
+Body objects have the following data members:
+
+General:
+- env: a pointer to a global struct holding environmental settings
+- waves: a pointer to a global object representing Waves in the system
+
+Unique to Body:
+Attachments:
+- attachedC: a vector of pointers to Connection objects, indicating all the connections attached to the body
+- attachedR: a vector of pointers to Rod objects attached to the body. 
+- rConnectRel: a vector<vec> of 3d vectors describing the attachment points locations for Connections
+- r6RodRel: a vector<vec6> of 6D vectors describing the attachment points and orientation of eac rod.
+
+Body Properties (set upon call to Body::setup()). Note that these are all set to zero for all Body types
+other than FREE, as they are not relevant to COUPLED or FIXED scenarios:
+- body_r6: 6D reference point for the body.
+- body_rCG: 3D location of body center-of-gravity
+- bodyM: real number describing the body's mass
+- bodyV: real number describing the body's volume
+- bodyI: 3d Vector describing the inertia diagonal components of the body
+- bodyCdA: 6D vector describing the body's drag coefficients
+- bodyCa: 6D vector describing added-mass coefficients
+
 
+Values describing the body's state:
+- r6: 6D vector describing body's position
+- v6: 6D vector describing body's velocity
+- r_ves: 6D vector describing fairlead position if a coupled body (may be different than overall r6)
+- rd_ves: 6D vector describing fairlead velocity if a coupled body (may be different than overall v6)
+- F6net: 6D vector describing total force/moment vector on node
+- M: 6x6 matrix representing total body mass + added mass
+- m0: 6x6 matrix representing 'starting mass' matrix of body, not taking into accound rod elements.
+- OrMat: 3x3 matrix representing orientation of the body
+- U: 3D vector of wave velocity at reference point
+- Ud: 3D vector of wave acceleration at reference point
+- outfile: pointer to the main output file for the body
 
+Misc:
+- number: a unique int id identifying the body
+- type: FREE, COUPLED, or FIXED, describing the "type" of body to be modeled
 
diff --git a/docs/usage.rst b/docs/usage.rst
index 7cd4e929..42e65792 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -169,6 +169,7 @@ The list of possible options is:
  - g – gravitational constant (m/s^2)*
  - rhoW – water density (kg/m^3)*
  - WtrDpth – water depth (m)*
+ - SeafloorFile – Relative path of a 3D Seafloor file
  - kBot – bottom stiffness constant (Pa/m) 
  - cBot – bottom damping constant (Pa-s/m)
  - dtIC – period for analyzing convergence of dynamic relaxation IC generation (s)
@@ -181,8 +182,33 @@ custom values for the sake of consistency.`
 
 The bottom contact parameters, kBot and cBot, result in a pressure which is then applied to the cross-sectional area (d*l) 
 of each contacting line segment to give a resulting vertical contact force for each segment.
-
  
+Seafloor File
+^^^^^^^^^^^^^
+
+If you need the seafloor have different depths at different locations, it is possible to create and use a 3D Seafloor file.
+This file allows you to define a square grid of points and define depths at each of these points.
+
+code-block:: none
+  num_x_points num_y_points
+  x_1 x_2 ... x_num_x_points
+  y_1 y_2 ... y_num_y_points
+  x_pos y_pos depth
+  x_pos y_pos depth
+  x_pos y_pos depth
+  etc, etc
+
+The two values on the first line define the number of points in each axis of the grid.
+The second line defines the actual locations along the x axis for the x grid points.
+The third line defines the locations along the y axis for the y grid points.
+The remaining lines are (x, y, z) coordinates for the seafloor on grid points.
+The order of these points is not important.
+It is also important that the x_pos be a value found in line 2 and y_pos be a value found in line 3.
+What happens if one of these points does not fall on the grid is not defined and may overwrite other depth values.
+
+If some part of the simulation fall outside of the defined grid area, it will use the depth of the nearest grid edge.
+
+
 Outputs
 ^^^^^^^
 
diff --git a/source/Body.cpp b/source/Body.cpp
index 46e6314a..8e89baef 100644
--- a/source/Body.cpp
+++ b/source/Body.cpp
@@ -227,7 +227,7 @@ Body::initialize()
 };
 
 void
-Body::setEnv(EnvCond* env_in, moordyn::Waves* waves_in)
+Body::setEnv(EnvCondRef env_in, moordyn::WavesRef waves_in)
 {
 	env = env_in;     // set pointer to environment settings object
 	waves = waves_in; // set pointer to Waves  object
@@ -240,9 +240,12 @@ Body::setDependentStates()
 	// dependent lines, yeah?)
 	for (unsigned int i = 0; i < attachedC.size(); i++) {
 		// this is making a "fake" state vector for the connect, describing its
-		// position and velocity
+		// position (rConnect) and velocity (rdConnect)
 		vec rConnect, rdConnect;
 
+		// Get connection location from rConnectRel for ith connected Connection
+		// and calculate the kinematics of that connection based on the kinematics
+		// of the Body:
 		transformKinematics(rConnectRel[i],
 		                    OrMat,
 		                    r6(Eigen::seqN(0, 3)),
@@ -344,7 +347,7 @@ Body::initiateStep(vec6 r, vec6 rd)
 		rd_ves = vec6::Zero();
 		return;
 	}
-	LOGERR << "The body is not a coupled/fixed one" << endl;
+	LOGERR << "Body " << number << "is not of type COUPLED or FIXED." << endl;
 	throw moordyn::invalid_value_error("Invalid body type");
 }
 
@@ -358,7 +361,7 @@ Body::updateFairlead(real time)
 		v6 = rd_ves;
 
 		// calculate orientation matrix based on latest angles
-		OrMat = RotXYZ(r6[3], r6[4], r6[5]);
+		OrMat = RotXYZ(r6(Eigen::seqN(3, 3)));
 
 		// set positions of any dependent connections and rods
 		setDependentStates();
@@ -392,12 +395,11 @@ Body::getStateDeriv()
 	}
 
 	// Get contributions from attached connections (and lines attached to
-	// them)
-
+	// them) and store in FNet:
 	doRHS();
 
 	// solve for accelerations in [M]{a}={f}
-	// For small systems, which are anyway larger than 4x4, we can use the
+	// For small systems, which are larger than 4x4, we can use the
 	// ColPivHouseholderQR algorithm, which is working with every single
 	// matrix, retaining a very good accuracy, and becoming yet faster
 	// See:
@@ -417,7 +419,7 @@ Body::doRHS()
 	// "winding" close to 2pi, and maybe prevent it if it risks compromising
 	// angle assumptions <<<<<<
 
-	// clear before re-summing
+	// clear Mass and Force matrix before re-summing
 	F6net = vec6::Zero();
 	M = mat6::Zero();
 
diff --git a/source/Body.hpp b/source/Body.hpp
index e8713545..94f5d876 100644
--- a/source/Body.hpp
+++ b/source/Body.hpp
@@ -30,6 +30,11 @@
 
 /** @file Body.hpp
  * C++ API for the moordyn::Body object
+ * 
+ * Body objects provide generic 6DOF rigid-body representations based on a
+ * lumped-parameter model of translational and rotational properties. Rod
+ * and Line objects may be added to body objects at any location. This allows
+ * the user to simulate a variety of submerged structures.
  */
 
 #pragma once
@@ -49,6 +54,8 @@ using namespace std;
 namespace moordyn {
 
 class Waves;
+typedef std::shared_ptr<Waves> WavesRef;
+
 class Connection;
 class Rod;
 
@@ -56,17 +63,20 @@ class Rod;
  * @brief A rigid body
  *
  * Some really basic dynamics are implemented for rigid bodiesout of the box,
- * which can be anyway extended through the usage of coupled ones.
+ * which can be extended through the usage of coupled ones.
  *
  * In the configuration file the options are:
  *
  * Name/ID, X0, Y0, Z0, Xcg, Ycg, Zcg, M, V, IX, IY, IZ, CdA-x,y,z Ca-x,y,z
+ * 
+ * moordyn::Body exctends the io::IO class, allowing it to perform input/output in a
+ * consistent manner.
  */
 class Body : public io::IO
 {
   public:
 	/** @brief Costructor
-	 * @param log Logging handler
+	 * @param log Logging handler defining where/how results should be logged.
 	 */
 	Body(moordyn::Log* log);
 
@@ -76,9 +86,9 @@ class Body : public io::IO
 
 	// ENVIRONMENTAL STUFF
 	/// Global struct that holds environmental settings
-	EnvCond* env;
+	EnvCondRef env;
 	/// global Waves object
-	moordyn::Waves* waves;
+	WavesRef waves;
 
 	// unique to Body:
 	/// Connections attached to this body
@@ -132,9 +142,9 @@ class Body : public io::IO
 	/// orientation)
 	mat OrMat;
 
-	/// wave velocities at body reference point
+	/// wave velocity at body reference point
 	vec U;
-	/// wave accelerations
+	/// wave acceleration at body reference point 
 	vec Ud;
 
 	/// Pointer to moordyn::MoorDyn::outfileMain
@@ -177,7 +187,7 @@ class Body : public io::IO
 	/// Type of body
 	types type;
 
-	/** @brief Setup a rigid body
+	/** @brief Setup/initialize a rigid body. Called after instantiating a new Body in MoorDyn2.cpp
 	 * @param number Body ID
 	 * @param type Body type
 	 * @param r6 6dof position
@@ -209,7 +219,7 @@ class Body : public io::IO
 
 	/** @brief Attach a rod to the body
 	 * @param rod The rod
-	 * @param coords The fixation point (6dof)
+	 * @param coords vector indicating start (vals 0-2) and end (valse 3-5) of rod
 	 * @throw moordyn::invalid_value_error If @p rod is NULL
 	 */
 	void addRod(Rod* rod, vec6 coords);
@@ -250,7 +260,7 @@ class Body : public io::IO
 	 * @param env_in Global struct that holds environmental settings
 	 * @param waves_in Global Waves object
 	 */
-	void setEnv(EnvCond* env_in, moordyn::Waves* waves_in);
+	void setEnv(EnvCondRef env_in, moordyn::WavesRef waves_in);
 
 	/** @brief set the states (positions and velocities) to all the attached
 	 * entities
@@ -330,7 +340,7 @@ class Body : public io::IO
 	 */
 	void initiateStep(vec6 r, vec6 rd);
 
-	/** @brief Sets the kinematics
+	/** @brief Sets the kinematics based on the position and velocity of the fairlead.
 	 *
 	 * This function is meant only for coupled or fixed bodies
 	 * @param time Local time within the time step (from 0 to dt)
@@ -339,7 +349,7 @@ class Body : public io::IO
 	 */
 	void updateFairlead(real time);
 
-	/** @brief Set the latest states to the body
+	/** @brief Set the states to the body to the position r and velocity rd
 	 * @param r The position
 	 * @param rd The velocity
 	 */
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 7e2366c2..3268c410 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -13,7 +13,7 @@ set(MOORDYN_SRCS
     Waves.cpp
     kiss_fft.cpp
     kiss_fftr.cpp
-)
+    Seafloor.cpp)
 
 set(MOORDYN_HEADERS
     Body.hpp
@@ -36,6 +36,8 @@ set(MOORDYN_HEADERS
     MoorDyn.h
     Rod.h
     Waves.h
+    Seafloor.hpp
+    Seafloor.h
 )
 
 set(MOORDYN_PUBLIC_DEPS "")
diff --git a/source/Connection.cpp b/source/Connection.cpp
index 0e53ae63..42853f2f 100644
--- a/source/Connection.cpp
+++ b/source/Connection.cpp
@@ -47,6 +47,7 @@ namespace moordyn {
 
 Connection::Connection(moordyn::Log* log)
   : io::IO(log)
+  , seafloor(nullptr)
   , WaterKin(0)
 {
 }
@@ -145,7 +146,8 @@ Connection::initialize()
 		pos = r;
 		vel = rd;
 
-		if (-env->WtrDpth > r[2]) {
+		const real waterDepth = seafloor ? seafloor->getDepthAt(r[0], r[1]) : -env->WtrDpth;
+		if (waterDepth > r[2]) {
 			LOGERR << "Error: water depth is shallower than Point " << number
 			       << "." << endl;
 			throw moordyn::invalid_value_error("Invalid water depth");
@@ -212,10 +214,11 @@ Connection::GetConnectionOutput(OutChanProps outChan)
 }
 
 void
-Connection::setEnv(EnvCond* env_in, moordyn::Waves* waves_in)
+Connection::setEnv(EnvCondRef env_in, moordyn::WavesRef waves_in, moordyn::SeafloorRef seafloor_in)
 {
 	env = env_in;     // set pointer to environment settings object
 	waves = waves_in; // set pointer to Waves  object
+	seafloor = seafloor_in;
 }
 
 void
diff --git a/source/Connection.hpp b/source/Connection.hpp
index 3c130d9e..9d44a98f 100644
--- a/source/Connection.hpp
+++ b/source/Connection.hpp
@@ -36,6 +36,7 @@
 
 #include "Misc.hpp"
 #include "IO.hpp"
+#include "Seafloor.hpp"
 #include <utility>
 
 #ifdef USE_VTK
@@ -49,6 +50,7 @@ namespace moordyn {
 
 class Line;
 class Waves;
+typedef std::shared_ptr<Waves> WavesRef;
 
 /** @class Connection Connection.hpp
  * @brief A connection for a line endpoint
@@ -56,9 +58,9 @@ class Waves;
  * Each line must have 2 connections at each endpoint, which are used to define
  * how those points are moving. There are 3 basic types of connections:
  *
- *  - Fixed: The point is indeed fixed, either to a unmovable point (i.e. an
- *           anchor) or to a Body
- *  - Free: The point freely moves, with its own translation degrees of freedom,
+ *  - Fixed: The point is fixed, either to a unmovable point (i.e. an anchor
+ *           or to a Body
+ *  - Free: The point freely moves, with its own translational degrees of freedom,
  *          to provide a connection point between multiple mooring lines or an
  *          unconnected termination point of a Line, which could have a clump
  *          weight or float via the point's mass and volume parameters
@@ -88,9 +90,10 @@ class Connection : public io::IO
   private:
 	// ENVIRONMENTAL STUFF
 	/// Global struct that holds environmental settings
-	EnvCond* env;
+	EnvCondRef env;
 	/// global Waves object
-	moordyn::Waves* waves;
+	moordyn::WavesRef waves;
+	moordyn::SeafloorRef seafloor;
 
 	/// Lines attached to this connection node
 	std::vector<attachment> attached;
@@ -196,7 +199,7 @@ class Connection : public io::IO
 	types type;
 
 	/** @brief flag indicating whether wave/current kinematics will be
-	 * considered for this linec
+	 * considered for this Connection. 
 	 *
 	 * - 0: none, or use value set externally for each node of the object
 	 * - 1: interpolate from stored
@@ -288,7 +291,7 @@ class Connection : public io::IO
 	 * @param env_in Global struct that holds environmental settings
 	 * @param waves_in Global Waves object
 	 */
-	void setEnv(EnvCond* env_in, moordyn::Waves* waves_in);
+	void setEnv(EnvCondRef env_in, moordyn::WavesRef waves_in, moordyn::SeafloorRef seafloor_in);
 
 	/** @brief Multiply the drag by a factor
 	 *
diff --git a/source/Line.cpp b/source/Line.cpp
index ccdfead4..ef18e674 100644
--- a/source/Line.cpp
+++ b/source/Line.cpp
@@ -221,10 +221,11 @@ Line::setup(int number_in,
 };
 
 void
-Line::setEnv(EnvCond* env_in, moordyn::Waves* waves_in)
+Line::setEnv(EnvCondRef env_in, moordyn::WavesRef waves_in, moordyn::SeafloorRef seafloor_in)
 {
 	env = env_in;
 	waves = waves_in;
+	seafloor = seafloor_in;
 }
 
 std::pair<std::vector<vec>, std::vector<vec>>
@@ -296,7 +297,7 @@ Line::initialize()
 		// output internal damping force
 		if (channels.find("c") != string::npos) {
 			for (unsigned int i = 1; i <= N; i++) {
-				*outfile << "Seg" << i << "cx \t Node" << i << "cy \t Node" << i
+				*outfile << "Seg" << i << "cx \t Seg" << i << "cy \t Seg" << i
 				         << "cz \t ";
 			}
 		}
@@ -363,7 +364,7 @@ Line::initialize()
 			}
 			// output internal damping force?
 			if (channels.find("c") != string::npos) {
-				for (unsigned int i = 0; i < N; i++)
+				for (unsigned int i = 0; i < 3 * N; i++)
 					*outfile << "(N) \t";
 			}
 			// output segment tensions?
@@ -382,7 +383,7 @@ Line::initialize()
 					*outfile << "(-/s) \t";
 			}
 			// output seabed contact force?
-			if (channels.find("D") != string::npos) {
+			if (channels.find("b") != string::npos) {
 				for (unsigned int i = 0; i <= 3 * N + 2; i++)
 					*outfile << "(N) \t";
 			}
@@ -395,8 +396,7 @@ Line::initialize()
 	// corresponding Connection or Rod objects calling "setEndState",
 	// so now we can proceed with figuring out the positions of the nodes along
 	// the line.
-
-	if (-env->WtrDpth > r[0][2]) {
+	if (getWaterDepth(r[0][0], r[0][1]) > r[0][2]) {
 		LOGERR << "Water depth is shallower than Line " << number << " anchor"
 		       << endl;
 		throw moordyn::invalid_value_error("Invalid water depth");
@@ -473,7 +473,8 @@ Line::initialize()
 
 	// if conditions are ideal, try to calculate initial line profile using
 	// catenary routine (from FAST v.7)
-	if (-r[0][2] == env->WtrDpth) {
+	real waterDepth = getWaterDepth(r[0][0], r[0][1]);
+	if (r[0][2] == waterDepth) {
 		real XF = dir(Eigen::seqN(0, 2)).norm(); // horizontal spread
 		real ZF = dir[2];
 		real LW = ((rho - env->rho_w) * A) * env->g;
@@ -585,7 +586,7 @@ Line::storeWaterKin(real dt,
 	LOGDBG << "Setting up wave variables for Line " << number
 	       << "!  ---------------------" << endl
 	       << "   nt=" << ntWater << ", and WaveDT=" << dtWater
-	       << ", env->WtrDpth=" << env->WtrDpth << endl;
+	       << ", average water depth=" << avgWaterDepth() << endl;
 
 	// resize the new time series vectors
 	zetaTS.assign(N + 1, std::vector<moordyn::real>(ntWater, 0.0));
@@ -604,7 +605,52 @@ Line::storeWaterKin(real dt,
 		u_in[i] = UTS[i];
 		ud_in[i] = UdTS[i];
 	}
-};
+}
+
+real
+Line::calcSubSeg(unsigned int firstNodeIdx, unsigned int secondNodeIdx)
+{
+	if (WaterKin != WAVES_NONE) {
+		// For now, the assumption is that everythings submerged except in still
+		// water state
+		LOGMSG << "ERROR in calcSubSeg: waves not set to WAVES_NONE\n";
+		return 1.0;  
+	}
+	real firstNodeZ = r[firstNodeIdx][2];
+	real secondNodeZ = r[secondNodeIdx][2];
+	if (firstNodeZ <= 0.0 && secondNodeZ < 0.0) {
+		return 1.0;  // Both nodes below water; segment must be too
+	} else if (firstNodeZ > 0.0 && secondNodeZ > 0.0) {
+		return 0.0;  // Both nodes above water; segment must be too
+	} else {
+		// Segment partially submerged - figure out which node is above water
+		vec lowerEnd = firstNodeZ < 0.0 ? r[firstNodeIdx] : r[secondNodeIdx];
+		vec upperEnd = firstNodeZ < 0.0 ? r[secondNodeIdx] : r[firstNodeIdx];
+
+		// segment submergence is calculated by calculating submergence of
+		// hypotenuse across segment from upper corner to lower corner
+		// To calculate this, we need the coordinates of these corners.
+		// first step is to get vector from lowerEnd to upperEnd
+		vec segmentAxis = upperEnd - lowerEnd;
+
+		// Next, find normal vector in z-plane, i.e. the normal vecto that
+		// points "up" the most. See the following stackexchange:
+		// https://math.stackexchange.com/questions/2283842/
+		vec upVec(0, 0, 1);  // the global up-unit vector
+		vec normVec = segmentAxis.cross(upVec.cross(segmentAxis));
+		normVec.normalize();
+
+		// make sure normal vector has length equal to radius of segment
+		real radius = d / 2;
+		scalevector(normVec, radius, normVec);
+
+		// Calculate and return submerged ratio:
+		lowerEnd = lowerEnd - normVec;
+		upperEnd = upperEnd + normVec;
+
+		return fabs(lowerEnd[2]) / (fabs(lowerEnd[2]) + upperEnd[2]);
+	}
+}
 
 void
 Line::setState(std::vector<vec> pos, std::vector<vec> vel)
@@ -797,12 +843,23 @@ Line::getStateDeriv()
 			F[i] = 1.0; // set VOF value to one for now (everything submerged -
 			            // eventually this should be element-based!!!) <<<<
 		}
-	} else if (WaterKin !=
-	           WAVES_NONE) // Hopefully WaterKin is set to zero, meaning no
-	                       // waves or set externally, otherwise it's an error
+	} else if (WaterKin != WAVES_NONE) {
+		// Hopefully WaterKin is set to zero, meaning no
+		// waves or set externally, otherwise it's an error	
 		cout << "ERROR: We got a problem with WaterKin not being 0,1,2."
 		     << endl;
-
+	} else {
+		// If in still water, iterate over all the segments and calculate
+		// volume of segment submerged. This is later used to calculate
+		// v_i, the *nodal* submerged volumes, which is then used to
+		// to calculate buoyancy.
+		for (unsigned int i = 0; i < N; i++) {
+			//waves->getWaveKin(
+			//    r[i][0], r[i][1], r[i][2], U[i], Ud[i], zeta[i], PDyn[i]);
+			F[i] = calcSubSeg(i, i + 1);
+		}
+	}
+	
 	//============================================================================================
 
 	// calculate mass matrix
@@ -814,7 +871,7 @@ Line::getStateDeriv()
 			m_i = pi / 8. * d * d * l[0] * rho;
 			v_i = 1. / 2. * F[i] * V[i];
 		} else if (i == N) {
-			m_i = pi / 8. * d * d * l[N - 2] * rho;
+			m_i = pi / 8. * d * d * l[N - 1] * rho;
 			v_i = 1. / 2. * F[i - 1] * V[i - 1];
 		} else {
 			m_i = pi / 8. * (d * d * rho * (l[i] + l[i - 1]));
@@ -1113,18 +1170,19 @@ Line::getStateDeriv()
 		// bottom contact (stiffness and damping, vertical-only for now) -
 		// updated for general case of potentially anchor or fairlead end in
 		// contact
-		if (r[i][2] < -env->WtrDpth) {
+		const real waterDepth = getWaterDepth(r[i][0], r[i][1]);
+		if (r[i][2] < waterDepth) {
 			if (i == 0)
 				B[i][2] =
-				    ((-env->WtrDpth - r[i][2]) * env->kb - rd[i][2] * env->cb) *
+				    ((waterDepth - r[i][2]) * env->kb - rd[i][2] * env->cb) *
 				    0.5 * d * (l[i]);
 			else if (i == N)
 				B[i][2] =
-				    ((-env->WtrDpth - r[i][2]) * env->kb - rd[i][2] * env->cb) *
+				    ((waterDepth - r[i][2]) * env->kb - rd[i][2] * env->cb) *
 				    0.5 * d * (l[i - 1]);
 			else
 				B[i][2] =
-				    ((-env->WtrDpth - r[i][2]) * env->kb - rd[i][2] * env->cb) *
+				    ((waterDepth - r[i][2]) * env->kb - rd[i][2] * env->cb) *
 				    0.5 * d * (l[i - 1] + l[i]);
 
 			// new rough-draft addition of seabed friction
@@ -1165,6 +1223,21 @@ Line::getStateDeriv()
 		else
 			Fnet[i] = T[i] - T[i - 1] + Td[i] - Td[i - 1];
 		Fnet[i] += W[i] + (Dp[i] + Dq[i] + Ap[i] + Aq[i]) + B[i] + Bs[i];
+
+		if (i == 0 && r[i][2] > 0.0) {
+			// LOGMSG << "ABOVE_WATER\n";
+		}
+
+		if (i == 0) {
+			// LOGMSG << "Node weight: " << W[i] << '\n';
+			// LOGMSG << "F[i]: " << F[i] << '\n';
+		/*	LOGMSG << "Node drag (transverse): " << Dp[i] << '\n';
+			LOGMSG << "Node drag (axial): " << Dq[i] << '\n';
+        	LOGMSG << "Node added mass (transverse): " << Ap[i] << '\n';
+			LOGMSG << "Node added mass (axial): " << Aq[i] << '\n';
+			LOGMSG << "Node bottom contact force: " << B[i] << '\n';
+			LOGMSG << "Node bending stiffness force: " << Bs[i] << '\n';*/
+		}
 	}
 
 	//	if (t > 5)
@@ -1283,7 +1356,7 @@ Line::Output(real time)
 		if (channels.find("c") != string::npos) {
 			for (unsigned int i = 0; i < N; i++) {
 				for (int J = 0; J < 3; J++)
-					*outfile << Td[i][J] + Td[i][J] + Td[i][J] << "\t ";
+					*outfile << Td[i][J] << "\t ";
 			}
 		}
 		// output segment strains?
diff --git a/source/Line.hpp b/source/Line.hpp
index 5c119ac7..626c9904 100644
--- a/source/Line.hpp
+++ b/source/Line.hpp
@@ -34,6 +34,7 @@
 
 #pragma once
 
+#include "Seafloor.hpp"
 #include "Misc.hpp"
 #include "IO.hpp"
 #include <utility>
@@ -48,6 +49,7 @@ using namespace std;
 namespace moordyn {
 
 class Waves;
+typedef std::shared_ptr<Waves> WavesRef;
 
 /** @class Line Line.hpp
  * @brief A mooring line
@@ -58,18 +60,18 @@ class Waves;
  * [connect (node 0)] - seg 0 - [node 1] - ... - seg n-1 - [connect (node N)]
  *
  * Depending on the length of the line, \f$ l \f$, the number of segments,
- * \f$ n \f$, an the material density and Young's modulus, \f$ \rho \f$ &
- * \f$ E \f$, a natural scillation period can be defined:
+ * \f$ n \f$, and the material density and Young's modulus, \f$ \rho \f$ &
+ * \f$ E \f$, a natural oscillation period can be defined:
  *
  * \f$ T = \frac{l}{\pi n} \sqrt{\frac{\rho}{E}} \f$
  *
- * Thus, the integration time step (moordyn::MoorDyn.dtM0) shall be smaller than
- * such natural period to avoid numerical instabilities
+ * The integration time step (moordyn::MoorDyn.dtM0) should be smaller than
+ * this natural period to avoid numerical instabilities
  */
 class Line : public io::IO
 {
   public:
-	/** @brief Costructor
+	/** @brief Constructor
 	 * @param log Logging handler
 	 */
 	Line(moordyn::Log* log);
@@ -79,7 +81,8 @@ class Line : public io::IO
 	~Line();
 
   private:
-	/** @brief Get the non-linear Young's modulus
+	/** @brief Get the non-linear Young's modulus. This is interpolated from a
+	 * curve provided in the input file.
 	 * @param l_stretched The actual length of the segment
 	 * @param l_unstretched The unstretched length of the segment
 	 */
@@ -95,12 +98,29 @@ class Line : public io::IO
 	 * @param curv The curvature
 	 */
 	real getNonlinearEI(real curv);
+	
+	/** @brief Finds the depth of the water at some (x, y) point. Either using env->WtrDpth or the 3D seafloor if available
+	 * @param x x coordinate
+	 * @param y y coordinate
+	 * @return A negative number representing the sea floor depth at the given location
+	*/
+	inline real getWaterDepth(real x, real y) {
+		return seafloor ? seafloor->getDepthAt(x, y) : -env->WtrDpth;
+	}
 
+	/** @brief A single value representing the average water depth
+	 * 
+	*/
+	inline real avgWaterDepth() {
+		return seafloor ? seafloor->getAverageDepth() : -env->WtrDpth;
+	}
 	// ENVIRONMENTAL STUFF
 	/// Global struct that holds environmental settings
-	EnvCond* env;
+	EnvCondRef env;
 	/// global Waves object
-	moordyn::Waves* waves;
+	moordyn::WavesRef waves;
+	/// Object containing the 3d seafloor info
+	moordyn::SeafloorRef seafloor;
 
 	/// Number of line segments
 	unsigned int N;
@@ -305,8 +325,9 @@ class Line : public io::IO
 	/** @brief Set the environmental data
 	 * @param env_in Global struct that holds environmental settings
 	 * @param waves_in Global Waves object
+	 * @param seafloor_in Global 3D Seafloor object
 	 */
-	void setEnv(EnvCond* env_in, moordyn::Waves* waves_in);
+	void setEnv(EnvCondRef env_in, moordyn::WavesRef waves_in, moordyn::SeafloorRef seafloor_in);
 
 	/** @brief Compute the stationary Initial Condition (IC)
 	 * @param return The states, i.e. the positions of the internal nodes
@@ -541,6 +562,15 @@ class Line : public io::IO
 	                   std::vector<std::vector<vec>> u,
 	                   std::vector<std::vector<vec>> ud);
 
+	/** @brief calculate the volume of the segment between firstNodeIdx and secondNodeIdx submerged
+	* 
+	* This must be used with adjacent nodes for accurate results. It is currently only implemented
+	* for the still water case.
+	* @param firstNodeIdx Index of the first node of the segment
+	* @param secondNodeIdx Index of the second node of the segment
+	*/
+	real calcSubSeg(unsigned int firstNodeIdx, unsigned int secondNodeIdx);
+
 	/** @brief Get the drag coefficients
 	 * @return The normal (transversal) and tangential (axial) drag coefficients
 	 */
diff --git a/source/Misc.hpp b/source/Misc.hpp
index 53db09c5..9abb0214 100644
--- a/source/Misc.hpp
+++ b/source/Misc.hpp
@@ -561,6 +561,15 @@ typedef enum
 	WAVES_KIN = 6,
 } waves_settings;
 
+
+typedef enum
+{
+	/// Flat seafloor:
+	SEAFLOOR_FLAT = 0,
+	/// 3D seafloor
+	SEAFLOOR_3D = 1,
+} seafloor_settings;
+
 // Current options: 0 - no currents or set externally (as part of WaveKin =0 or
 // 1 approach) [default]
 //                  1 - read in steady current profile, grid approach
@@ -584,6 +593,8 @@ typedef enum
 	CURRENTS_STEADY_NODE = 3,
 	/// dynamic current profile, node approach
 	CURRENTS_DYNAMIC_NODE = 4,
+	/// 4D current profile
+	CURRENTS_4D = 5
 } currents_settings;
 
 /** @brief Are the waves settings grid based?
@@ -861,14 +872,14 @@ interp4(const vector<vector<vector<vector<T>>>>& values,
 	unsigned int k0 = k > 0 ? k - 1 : 0;
 	unsigned int w0 = w > 0 ? w - 1 : 0;
 
-	T c000 = values[i0][j0][k0][w0] * fw + values[i0][j0][k0][w] * (1. - fw);
-	T c001 = values[i0][j0][k][w0] * fw + values[i0][j0][k][w] * (1. - fw);
-	T c010 = values[i0][j][k0][w0] * fw + values[i0][j][k0][w] * (1. - fw);
-	T c011 = values[i0][j][k][w0] * fw + values[i0][j][k][w] * (1. - fw);
-	T c100 = values[i][j0][k0][w0] * fw + values[i0][j0][k0][w] * (1. - fw);
-	T c101 = values[i][j0][k][w0] * fw + values[i0][j0][k][w] * (1. - fw);
-	T c110 = values[i][j][k0][w0] * fw + values[i0][j][k0][w] * (1. - fw);
-	T c111 = values[i][j][k][w0] * fw + values[i0][j0][k0][w] * (1. - fw);
+	T c000 = values[i0][j0][k0][w0] * (1. - fw) + values[i0][j0][k0][w] * fw;
+	T c001 = values[i0][j0][k][w0] * (1. - fw) + values[i0][j0][k][w] * fw;
+	T c010 = values[i0][j][k0][w0] * (1. - fw) + values[i0][j][k0][w] * fw;
+	T c011 = values[i0][j][k][w0] * (1. - fw) + values[i0][j][k][w] * fw;
+	T c100 = values[i][j0][k0][w0] * (1. - fw) + values[i][j0][k0][w] * fw;
+	T c101 = values[i][j0][k][w0] * (1. - fw) + values[i][j0][k][w] * fw;
+	T c110 = values[i][j][k0][w0] * (1. - fw) + values[i][j][k0][w] * fw;
+	T c111 = values[i][j][k][w0] * (1. - fw) + values[i][j][k][w] * fw;
 
 	T c00 = c000 * (1. - fx) + c100 * fx;
 	T c01 = c001 * (1. - fx) + c101 * fx;
@@ -1144,6 +1155,9 @@ typedef struct
 	double kb;
 	/// bottom damping   (Pa/m/s)
 	double cb;
+	/// Bottom modeling mode (0=flat, 1=3d...)<<<
+	moordyn::seafloor_settings SeafloorMode;
+
 	/// wave kinematics flag (0=off, >0=on...)<<<
 	moordyn::waves_settings WaveKin;
 	/// current flag (0=off, >0=on...)<<<
@@ -1166,6 +1180,8 @@ typedef struct
 	int writeLog;
 } EnvCond;
 
+typedef std::shared_ptr<EnvCond> EnvCondRef;
+
 typedef struct _LineProps // (matching Line Dictionary inputs)
 {
 	string type;
diff --git a/source/MoorDyn.cpp b/source/MoorDyn.cpp
index 05910fe0..ef97b6a9 100644
--- a/source/MoorDyn.cpp
+++ b/source/MoorDyn.cpp
@@ -103,36 +103,43 @@ MoorDynInit(const double x[], const double xd[], const char* infilename)
 		OwnConsoleWindow = 1;
 
 		// allocate a console for this app
-		AllocConsole();
-
-		// set the screen buffer to be big enough to let us scroll text
-		static const WORD MAX_CONSOLE_LINES = 500;
-		CONSOLE_SCREEN_BUFFER_INFO coninfo;
-		GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
-		coninfo.dwSize.Y = MAX_CONSOLE_LINES;
-		SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE),
-		                           coninfo.dwSize);
-
-		// redirect unbuffered STDOUT to the console
-		// lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);
-		lStdHandle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
-		hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
-		fp = _fdopen(hConHandle, "w");
-		*stdout = *fp;
-		setvbuf(stdout, NULL, _IONBF, 0);
-
-		// redirect unbuffered STDERR to the console
-		lStdHandle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
-		hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
-		fp = _fdopen(hConHandle, "w");
-		*stderr = *fp;
-		setvbuf(stderr, NULL, _IONBF, 0);
-
-		// make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog
-		// point to console as well
-		std::ios::sync_with_stdio();
-
-		std::cout << "(MoorDyn-initiated console window)" << std::endl;
+		if (AllocConsole()) {
+			// set the screen buffer to be big enough to let us scroll text
+			static const WORD MAX_CONSOLE_LINES = 500;
+			CONSOLE_SCREEN_BUFFER_INFO coninfo;
+			GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),
+			                           &coninfo);
+			coninfo.dwSize.Y = MAX_CONSOLE_LINES;
+			SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE),
+			                           coninfo.dwSize);
+
+			// redirect unbuffered STDOUT to the console
+			// lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);
+			lStdHandle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
+			hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
+			fp = _fdopen(hConHandle, "w");
+			*stdout = *fp;
+			setvbuf(stdout, NULL, _IONBF, 0);
+
+			// redirect unbuffered STDERR to the console
+			lStdHandle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
+			hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
+			fp = _fdopen(hConHandle, "w");
+			*stderr = *fp;
+			setvbuf(stderr, NULL, _IONBF, 0);
+
+			// make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog
+			// point to console as well
+			std::ios::sync_with_stdio();
+
+			std::cout << "(MoorDyn-initiated console window)" << std::endl;
+		} else {
+			// This is not a likely scenario, but we've run into some situations
+			// where you can neither get the console nor allocate a console.
+			// So just fall back to using whatever cout and cerr were before.
+			std::cout << "AllocConsole failed" << std::endl;
+			OwnConsoleWindow = 0;
+		}
 	}
 #endif
 
diff --git a/source/MoorDyn2.cpp b/source/MoorDyn2.cpp
index c4cdef75..34bd60b6 100644
--- a/source/MoorDyn2.cpp
+++ b/source/MoorDyn2.cpp
@@ -69,8 +69,10 @@ moordyn::MoorDyn::MoorDyn(const char* infilename)
   , dtM0(0.001)
   , dtOut(0.0)
   , _t_integrator(NULL)
+  , env(std::make_shared<EnvCond>())
   , GroundBody(NULL)
-  , waves(NULL)
+  , waves(nullptr)
+  , seafloor(nullptr)
   , nX(0)
   , nXtra(0)
   , npW(0)
@@ -105,19 +107,19 @@ moordyn::MoorDyn::MoorDyn(const char* infilename)
 	LOGDBG << "The basename is " << _basename << endl;
 	LOGDBG << "The basepath is " << _basepath << endl;
 
-	env.g = 9.8;
-	env.WtrDpth = 0.;
-	env.rho_w = 1025.;
-	env.kb = 3.0e6;
-	env.cb = 3.0e5;
-	env.WaveKin = moordyn::WAVES_NONE;
-	env.Current = moordyn::CURRENTS_NONE;
-	env.dtWave = 0.25;
-	env.WriteUnits = 1; // by default, write units line
-	env.writeLog = 0;   // by default, don't write out a log file
-	env.FrictionCoefficient = 0.0;
-	env.FricDamp = 200.0;
-	env.StatDynFricScale = 1.0;
+	env->g = 9.8;
+	env->WtrDpth = 0.;
+	env->rho_w = 1025.;
+	env->kb = 3.0e6;
+	env->cb = 3.0e5;
+	env->WaveKin = moordyn::WAVES_NONE;
+	env->Current = moordyn::CURRENTS_NONE;
+	env->dtWave = 0.25;
+	env->WriteUnits = 1; // by default, write units line
+	env->writeLog = 0;   // by default, don't write out a log file
+	env->FrictionCoefficient = 0.0;
+	env->FricDamp = 200.0;
+	env->StatDynFricScale = 1.0;
 
 	const moordyn::error_id err = ReadInFile();
 	MOORDYN_THROW(err, "Exception while reading the input file");
@@ -144,7 +146,6 @@ moordyn::MoorDyn::~MoorDyn()
 	delete _t_integrator;
 
 	delete GroundBody;
-	delete waves;
 	for (auto obj : LinePropList)
 		delete obj;
 	for (auto obj : RodPropList)
@@ -378,11 +379,20 @@ moordyn::MoorDyn::Init(const double* x, const double* xd, bool skip_ic)
 
 	// store passed WaveKin value to enable waves in simulation if applicable
 	// (they're not enabled during IC gen)
-	env.WaveKin = WaveKinTemp;
+	env->WaveKin = WaveKinTemp;
 	moordyn::error_id err = MOORDYN_SUCCESS;
 	string err_msg;
 	try {
-		waves->setup(&env, _t_integrator, _basepath.c_str());
+		// TODO - figure out how i want to do this better
+		// because this is horrible. the solution is probably to move EnvCond
+		// to its own .hpp and .cpp file so that it can contain the Seafloor and
+		// can itself be queries about the seafloor in general
+		real tmp = env->WtrDpth;
+		if (seafloor) {
+			env->WtrDpth = -seafloor->getAverageDepth();
+		}
+		waves->setup(env, _t_integrator, _basepath.c_str());
+		env->WtrDpth = tmp;
 	}
 	MOORDYN_CATCHER(err, err_msg);
 	if (err != MOORDYN_SUCCESS)
@@ -391,7 +401,7 @@ moordyn::MoorDyn::Init(const double* x, const double* xd, bool skip_ic)
 	// @mth: new approach to be implemented
 	// ------------------------- calculate wave time series if needed
 	// -------------------
-	// 	if (env.WaveKin == 2)
+	// 	if (env->WaveKin == 2)
 	// 	{
 	// 		for (int l=0; l<LineList.size(); l++)
 	// 			LineList[l]->makeWaveKinematics( 0.0 );
@@ -417,7 +427,7 @@ moordyn::MoorDyn::Init(const double* x, const double* xd, bool skip_ic)
 		outfileMain << channel.Name << "\t ";
 	outfileMain << endl;
 
-	if (env.WriteUnits > 0) {
+	if (env->WriteUnits > 0) {
 		// --- units ---
 		outfileMain << "(s)"
 		            << "\t ";
@@ -493,7 +503,7 @@ moordyn::MoorDyn::Step(const double* x,
 	}
 
 	// -------------------- do time stepping -----------------------
-	if (env.WaveKin == WAVES_EXTERNAL) {
+	if (env->WaveKin == WAVES_EXTERNAL) {
 		// extrapolate velocities from accelerations
 		// (in future could extrapolote from most recent two points,
 		// (U_1 and U_2)
@@ -644,28 +654,13 @@ moordyn::MoorDyn::ReadInFile()
 
 	// We are really interested in looking for the writeLog option, to start
 	// logging as soon as possible
-	ifstream in_file(_filepath);
 	vector<string> in_txt;
-	string line;
-	if (!in_file.is_open()) {
-		LOGERR << "Error: unable to open file '" << _filepath << "'" << endl;
+	if (readFileIntoBuffers(in_txt) != MOORDYN_SUCCESS) {
+		// BREAK
 		return MOORDYN_INVALID_INPUT_FILE;
 	}
-	while (in_file.good()) {
-		string line_txt;
-		getline(in_file, line_txt);
-		in_txt.push_back(line_txt);
-	}
-	in_file.close();
-	while (i < in_txt.size()) {
-		// Skip until we find the options header line
-		if ((in_txt[i].find("---") == string::npos) ||
-		    !moordyn::str::has(moordyn::str::upper(in_txt[i]), { "OPTIONS" })) {
-			i++;
-			continue;
-		}
-
-		i++;
+	// Skip until we find the options header line
+	if ((i = findStartOfSection(in_txt, { "OPTIONS" })) != -1) {
 		// Look for writeLog option entries until the end of section or file
 		while ((in_txt[i].find("---") == string::npos) && (i < in_txt.size())) {
 			vector<string> entries = moordyn::str::split(in_txt[i], ' ');
@@ -679,7 +674,7 @@ moordyn::MoorDyn::ReadInFile()
 				i++;
 				continue;
 			}
-			env.writeLog = atoi(entries[0].c_str());
+			env->writeLog = atoi(entries[0].c_str());
 			const moordyn::error_id err = SetupLog();
 			if (err != MOORDYN_SUCCESS)
 				return err;
@@ -688,29 +683,6 @@ moordyn::MoorDyn::ReadInFile()
 		}
 	}
 
-	// factor by which to boost drag coefficients during dynamic relaxation IC
-	// generation
-	ICDfac = 5.0;
-	// convergence analysis time step for IC generation
-	ICdt = 1.0;
-	// max time for IC generation
-	ICTmax = 120;
-	// threshold for relative change in tensions to call it converged
-	ICthresh = 0.001;
-	// temporary wave kinematics flag used to store input value while keeping
-	// env.WaveKin=0 for IC gen
-	WaveKinTemp = WAVES_NONE;
-	// assume no wave kinematics points are passed in externally, unless
-	// ExernalWaveKinInit is called later
-	npW = 0;
-	// default value for desired mooring model time step
-	dtM0 = 0.001;
-	// default time integration scheme
-	string t_integrator_name = "RK2";
-
-	// string containing which channels to write to output
-	vector<string> outchannels;
-
 	// make a "ground body" that will be the parent of all fixed objects
 	// (connections and rods)
 	LOGDBG << "Creating the ground body of type " << Body::TypeName(Body::FIXED)
@@ -732,1123 +704,620 @@ moordyn::MoorDyn::ReadInFile()
 	nX = 0;
 
 	// Now we can parse the whole input file
-	i = 0;
-	while (i < in_txt.size()) {
+	if ((i = findStartOfSection(in_txt, { "LINE DICTIONARY", "LINE TYPES" })) !=
+	    -1) {
 		// look for the section header line
-		if (in_txt[i].find("---") == string::npos) {
+		LOGDBG << "   Reading line types:" << endl;
+
+		// parse until the next header or the end of the file
+		while ((in_txt[i].find("---") == string::npos) && (i < in_txt.size())) {
+			LineProps* obj = readLineProps(in_txt[i]);
+			if (obj)
+				LinePropList.push_back(obj);
+			else {
+				delete obj; // Skips error lines... TODO update this
+				return MOORDYN_INVALID_INPUT;
+			}
 			i++;
-			continue;
 		}
+	}
 
-		if (moordyn::str::has(moordyn::str::upper(in_txt[i]),
-		                      { "LINE DICTIONARY", "LINE TYPES" })) {
-			LOGDBG << "   Reading line types:" << endl;
-
-			// skip following two lines (label line and unit line)
-			i += 3;
-			// parse until the next header or the end of the file
-			while ((in_txt[i].find("---") == string::npos) &&
-			       (i < in_txt.size())) {
-				vector<string> entries = moordyn::str::split(in_txt[i], ' ');
-				if (entries.size() < 10) {
-					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
-					       << endl
-					       << "'" << in_txt[i] << "'" << endl
-					       << "10 fields are required, but just "
-					       << entries.size() << " are provided" << endl;
-					return MOORDYN_INVALID_INPUT;
-				}
+	if ((i = findStartOfSection(in_txt, { "ROD DICTIONARY", "ROD TYPES" })) !=
+	    -1) {
+		LOGDBG << "   Reading rod types:" << endl;
 
-				LineProps* obj = new LineProps();
-
-				obj->type = entries[0];
-				obj->d = atof(entries[1].c_str());
-				obj->w = atof(entries[2].c_str());
-				obj->Cdn = atof(entries[6].c_str());
-				obj->Can = atof(entries[7].c_str());
-				obj->Cdt = atof(entries[8].c_str());
-				obj->Cat = atof(entries[9].c_str());
-
-				moordyn::error_id err;
-				err = read_curve(entries[3].c_str(),
-				                 &(obj->EA),
-				                 &(obj->nEApoints),
-				                 obj->stiffXs,
-				                 obj->stiffYs);
-				if (err)
-					return err;
-				err = read_curve(entries[4].c_str(),
-				                 &(obj->c),
-				                 &(obj->nCpoints),
-				                 obj->dampXs,
-				                 obj->dampYs);
-				if (err)
-					return err;
-				err = read_curve(entries[5].c_str(),
-				                 &(obj->EI),
-				                 &(obj->nEIpoints),
-				                 obj->bstiffXs,
-				                 obj->bstiffYs);
-				if (err)
-					return err;
-
-				LOGDBG << "\t'" << obj->type << "'"
-				       << " - with id " << LinePropList.size() << endl
-				       << "\t\td   : " << obj->d << endl
-				       << "\t\tw   : " << obj->w << endl
-				       << "\t\tCdn : " << obj->Cdn << endl
-				       << "\t\tCan : " << obj->Can << endl
-				       << "\t\tCdt : " << obj->Cdt << endl
-				       << "\t\tCat : " << obj->Cat << endl;
+		// parse until the next header or the end of the file
+		while ((in_txt[i].find("---") == string::npos) && (i < in_txt.size())) {
+			RodProps* obj = readRodProps(in_txt[i]);
 
-				LinePropList.push_back(obj);
-				i++;
+			if (obj)
+				RodPropList.push_back(obj);
+			else {
+				delete obj;
+				return MOORDYN_INVALID_INPUT;
 			}
+			i++;
 		}
+	}
 
-		if (moordyn::str::has(moordyn::str::upper(in_txt[i]),
-		                      { "ROD DICTIONARY", "ROD TYPES" })) {
-			LOGDBG << "   Reading rod types:" << endl;
-
-			// skip following two lines (label line and unit line)
-			i += 3;
-			// parse until the next header or the end of the file
-			while ((in_txt[i].find("---") == string::npos) &&
-			       (i < in_txt.size())) {
-				vector<string> entries = moordyn::str::split(in_txt[i], ' ');
-				if (entries.size() < 7) {
-					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
-					       << endl
-					       << "'" << in_txt[i] << "'" << endl
-					       << "7 fields are required, but just "
-					       << entries.size() << " are provided" << endl;
-					return MOORDYN_INVALID_INPUT;
-				}
+	if ((i = findStartOfSection(
+	         in_txt, { "BODIES", "BODY LIST", "BODY PROPERTIES" })) != -1) {
+		LOGDBG << "   Reading body list:" << endl;
 
-				RodProps* obj = new RodProps();
-				obj->type = entries[0];
-				obj->d = atof(entries[1].c_str());
-				obj->w = atof(entries[2].c_str());
-				obj->Cdn = atof(entries[3].c_str());
-				obj->Can = atof(entries[4].c_str());
-				obj->Cdt = atof(entries[5].c_str());
-				obj->Cat = atof(entries[6].c_str());
-
-				LOGDBG << "\t'" << obj->type << "'"
-				       << " - with id " << RodPropList.size() << endl
-				       << "\t\td   : " << obj->d << endl
-				       << "\t\tw   : " << obj->w << endl
-				       << "\t\tCdn : " << obj->Cdn << endl
-				       << "\t\tCan : " << obj->Can << endl
-				       << "\t\tCdt : " << obj->Cdt << endl
-				       << "\t\tCat : " << obj->Cat << endl;
+		// parse until the next header or the end of the file
+		while ((in_txt[i].find("---") == string::npos) && (i < in_txt.size())) {
+			Body* obj = readBody(in_txt[i]);
 
-				RodPropList.push_back(obj);
-				i++;
+			if (obj) {
+				BodyList.push_back(obj);
+			} else {
+				delete obj;
+				return MOORDYN_INVALID_INPUT;
 			}
+			i++;
 		}
+	}
 
-		if (moordyn::str::has(moordyn::str::upper(in_txt[i]),
-		                      { "BODIES", "BODY LIST", "BODY PROPERTIES" })) {
-			LOGDBG << "   Reading body list:" << endl;
-
-			// skip following two lines (label line and unit line)
-			i += 3;
-			// parse until the next header or the end of the file
-			while ((in_txt[i].find("---") == string::npos) &&
-			       (i < in_txt.size())) {
-				vector<string> entries = moordyn::str::split(in_txt[i], ' ');
-				if (entries.size() < 14) {
-					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
-					       << endl
-					       << "'" << in_txt[i] << "'" << endl
-					       << "14 fields are required, but just "
-					       << entries.size() << " are provided" << endl;
-					return MOORDYN_INVALID_INPUT;
-				}
+	if ((i = findStartOfSection(in_txt,
+	                            { "POINTS",
+	                              "POINT LIST",
+	                              "CONNECTION PROPERTIES",
+	                              "NODE PROPERTIES" })) != -1) {
+		LOGDBG << "   Reading point list:" << endl;
 
-				const int number = atoi(entries[0].c_str());
-				Body::types type;
-				vec6 r6;
-				for (unsigned int I = 0; I < 6; I++)
-					r6[I] = atof(entries[2 + I].c_str());
-				double M = atof(entries[8].c_str());
-				double V = atof(entries[11].c_str());
-
-				vec rCG, Inert;
-				vec6 CdA = vec6::Zero();
-				vec6 Ca = vec6::Zero();
-
-				vector<string> entries_rCG =
-				    moordyn::str::split(entries[9], '|');
-				if (entries_rCG.size() == 1) {
-					// if only one entry, it is the z coordinate
-					rCG[0] = 0.0;
-					rCG[1] = 0.0;
-					rCG[2] = atof(entries_rCG[0].c_str());
-				} else if (entries_rCG.size() == 3) {
-					rCG[0] = atof(entries_rCG[0].c_str());
-					rCG[1] = atof(entries_rCG[1].c_str());
-					rCG[2] = atof(entries_rCG[2].c_str());
-				} else {
-					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
-					       << endl
-					       << "'" << in_txt[i] << "'" << endl
-					       << "CG entry (col 10) must have 1 or 3 numbers"
-					       << endl;
-					return MOORDYN_INVALID_INPUT;
-				}
+		// parse until the next header or the end of the file
+		while ((in_txt[i].find("---") == string::npos) && (i < in_txt.size())) {
+			vector<string> entries = moordyn::str::split(in_txt[i], ' ');
+			if (entries.size() < 9) {
+				LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
+				       << endl
+				       << "'" << in_txt[i] << "'" << endl
+				       << "9 fields are required, but just " << entries.size()
+				       << " are provided" << endl;
+				return MOORDYN_INVALID_INPUT;
+			}
 
-				vector<string> entries_I =
-				    moordyn::str::split(entries[10], '|');
-				if (entries_I.size() == 1) {
-					// if only one entry, use it for all directions
-					Inert[0] = atof(entries_I[0].c_str());
-					Inert[1] = Inert[0];
-					Inert[2] = Inert[0];
-				} else if (entries_I.size() == 3) {
-					Inert[0] = atof(entries_I[0].c_str());
-					Inert[1] = atof(entries_I[1].c_str());
-					Inert[2] = atof(entries_I[2].c_str());
-				} else {
-					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
-					       << endl
-					       << "'" << in_txt[i] << "'" << endl
-					       << "Inertia entry (col 11) must have 1 or 3 numbers"
-					       << endl;
-					return MOORDYN_INVALID_INPUT;
+			int number = atoi(entries[0].c_str());
+			double M = atof(entries[5].c_str());
+			double V = atof(entries[6].c_str());
+			double CdA;
+			double Ca;
+			vec r0;
+			vec F = vec::Zero();
+			if (entries.size() >=
+			    12) // case with optional force inputs (12 total entries)
+			{
+				for (int I = 0; I < 3; I++) {
+					r0[I] = atof(entries[2 + I].c_str());
+					F[I] = atof(entries[7 + I].c_str());
 				}
+				CdA = atof(entries[10].c_str());
+				Ca = atof(entries[11].c_str());
+			} else // case without optional force inputs (9 total entries)
+			{
+				for (int I = 0; I < 3; I++)
+					r0[I] = atof(entries[2 + I].c_str());
+
+				CdA = atof(entries[7].c_str());
+				Ca = atof(entries[8].c_str());
+			}
 
-				vector<string> entries_CdA =
-				    moordyn::str::split(entries[12], '|');
-				if (entries_CdA.size() == 1) {
-					// if only one entry, use it for all directions
-					CdA[0] = atof(entries_CdA[0].c_str());
-					CdA[1] = CdA[0];
-					CdA[2] = CdA[0];
-				} else if (entries_CdA.size() == 3) {
-					CdA[0] = atof(entries_CdA[0].c_str());
-					CdA[1] = atof(entries_CdA[1].c_str());
-					CdA[2] = atof(entries_CdA[2].c_str());
-				} else {
+			Connection::types type;
+			char let1[10], num1[10], let2[10], num2[10], let3[10];
+			char typeWord[10];
+			strncpy(typeWord, entries[1].c_str(), 9);
+			typeWord[9] = '\0';
+			// divided outWord into letters and numbers
+			str::decomposeString(typeWord, let1, num1, let2, num2, let3);
+
+			if (!strcmp(let1, "ANCHOR") || !strcmp(let1, "FIXED") ||
+			    !strcmp(let1, "FIX")) {
+				// it is fixed  (this would just be used if someone wanted
+				// to temporarly fix a body that things were attached to)
+				type = Connection::FIXED;
+			} else if (!strcmp(let1, "BODY")) {
+				type = Connection::FIXED;
+				if (!strlen(num1)) {
 					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
 					       << endl
 					       << "'" << in_txt[i] << "'" << endl
-					       << "CdA entry (col 13) must have 1 or 3 numbers"
-					       << endl;
+					       << "no number provided for Rod " << number
+					       << " Body attachment" << endl;
 					return MOORDYN_INVALID_INPUT;
 				}
-
-				vector<string> entries_Ca =
-				    moordyn::str::split(entries[13], '|');
-				if (entries_Ca.size() == 1) {
-					// if only one entry, use it for all directions
-					Ca[0] = atof(entries_Ca[0].c_str());
-					Ca[1] = Ca[0];
-					Ca[2] = Ca[0];
-				} else if (entries_Ca.size() == 3) {
-					Ca[0] = atof(entries_Ca[0].c_str());
-					Ca[1] = atof(entries_Ca[1].c_str());
-					Ca[2] = atof(entries_Ca[2].c_str());
-				} else {
+				unsigned int bodyID = atoi(num1);
+				if (!bodyID || (bodyID > BodyList.size())) {
 					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
 					       << endl
 					       << "'" << in_txt[i] << "'" << endl
-					       << "Ca entry (col 14) must have 1 or 3 numbers"
-					       << endl;
+					       << "There is not " << bodyID << " bodies" << endl;
 					return MOORDYN_INVALID_INPUT;
 				}
+			} else if (!strcmp(let1, "FAIRLEAD") || !strcmp(let1, "VESSEL") ||
+			           !strcmp(let1, "VES") || !strcmp(let1, "COUPLED") ||
+			           !strcmp(let1, "CPLD")) {
+				// if a fairlead, add to list and add
+				type = Connection::COUPLED;
+				CpldConIs.push_back(ConnectionList.size());
+			} else if (!strcmp(let1, "CONNECT") || !strcmp(let1, "CON") ||
+			           !strcmp(let1, "FREE")) {
+				// if a connect, add to list and add states for it
+				type = Connection::FREE;
+				FreeConIs.push_back(ConnectionList.size());
+				ConnectStateIs.push_back(
+				    nX); // assign start index of this connect's states
+				nX += 6; // add 6 state variables for each connect
+			} else {
+				LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
+				       << endl
+				       << "'" << in_txt[i] << "'" << endl
+				       << "Unrecognized connection type '" << let1 << "'"
+				       << endl;
+				return MOORDYN_INVALID_INPUT;
+			}
 
-				char let1[10], num1[10], let2[10], num2[10], let3[10];
-				char typeWord[10];
-				strncpy(typeWord, entries[1].c_str(), 9);
-				typeWord[9] = '\0';
-				// divided outWord into letters and numbers
-				str::decomposeString(typeWord, let1, num1, let2, num2, let3);
+			// make default water depth at least the depth of the lowest
+			// node (so water depth input is optional)
+			// TODO - this probably doesn't care about 3d seafloor?
+			if (r0[2] < -env->WtrDpth)
+				env->WtrDpth = -r0[2];
+
+			LOGDBG << "\t'" << number << "'"
+			       << " - of type " << Connection::TypeName(type) << " with id "
+			       << ConnectionList.size() << endl;
+
+			// now make Connection object!
+			Connection* obj = new Connection(_log);
+			obj->setup(number, type, r0, M, V, F, CdA, Ca);
+			ConnectionList.push_back(obj);
+
+			// depending on type, assign the Connection to its respective
+			// parent body
+			if (!strcmp(let1, "ANCHOR") || !strcmp(let1, "FIXED") ||
+			    !strcmp(let1, "FIX"))
+				GroundBody->addConnection(obj, r0);
+			else if (!strcmp(let1, "BODY")) {
+				int bodyID = atoi(num1);
+				BodyList[bodyID - 1]->addConnection(obj, r0);
+			}
+			LOGDBG << endl;
 
-				if (!strcmp(let1, "ANCHOR") || !strcmp(let1, "FIXED") ||
-				    !strcmp(let1, "FIX")) {
-					// it is fixed  (this would just be used if someone wanted
-					// to temporarly fix a body that things were attached to)
-					type = Body::FIXED;
-				} else if (!strcmp(let1, "COUPLED") ||
-				           !strcmp(let1, "VESSEL") || !strcmp(let1, "VES") ||
-				           !strcmp(let1, "CPLD")) {
-					// it is coupled - controlled from outside
-					type = Body::COUPLED;
-					CpldBodyIs.push_back(BodyList.size());
-				} else {
-					// it is free - controlled by MoorDyn
-					type = Body::FREE;
-					FreeBodyIs.push_back(BodyList.size());
-					BodyStateIs.push_back(
-					    nX);  // assign start index of this body's states
-					nX += 12; // add 12 state variables for the body
-				}
+			i++;
+		}
+	}
+
+	if ((i = findStartOfSection(
+	         in_txt, { "RODS", "ROD LIST", "ROD PROPERTIES" })) != -1) {
+		LOGDBG << "   Reading rod list:" << endl;
+
+		// parse until the next header or the end of the file
+		while ((in_txt[i].find("---") == string::npos) && (i < in_txt.size())) {
+			Rod* obj = readRod(in_txt[i]);
+			RodList.push_back(obj);
+
+			i++;
+		}
+	}
+
+	if ((i = findStartOfSection(
+	         in_txt, { "LINES", "LINE LIST", "LINE PROPERTIES" })) != -1) {
+		LOGDBG << "   Reading line list: " << endl;
+
+		if (!LinePropList.size()) {
+			LOGERR << "Reading lines without defined line types" << endl;
+			return MOORDYN_INVALID_INPUT;
+		}
+		if (!ConnectionList.size()) {
+			LOGERR << "Reading lines without defined connections" << endl;
+			return MOORDYN_INVALID_INPUT;
+		}
+
+		// parse until the next header or the end of the file
+		while ((in_txt[i].find("---") == string::npos) && (i < in_txt.size())) {
+			vector<string> entries = moordyn::str::split(in_txt[i], ' ');
+			if (entries.size() < 7) {
+				LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
+				       << endl
+				       << "'" << in_txt[i] << "'" << endl
+				       << "7 fields are required, but just " << entries.size()
+				       << " are provided" << endl;
+				return MOORDYN_INVALID_INPUT;
+			}
+
+			int number = atoi(entries[0].c_str());
+			string type = entries[1];
+			double UnstrLen = atof(entries[4].c_str());
+			int NumSegs = atoi(entries[5].c_str()); // addition vs. MAP
+			string outchannels = entries[6];
+
+			int TypeNum = -1;
+			for (unsigned int J = 0; J < LinePropList.size(); J++) {
+				if (LinePropList[J]->type == type)
+					TypeNum = J;
+			}
+			if (TypeNum == -1) {
+				LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
+				       << endl
+				       << "'" << in_txt[i] << "'" << endl
+				       << "Unrecognized line type " << type << endl;
+				return MOORDYN_INVALID_INPUT;
+			}
+
+			// Make the output file (if queried)
+			if ((outchannels.size() > 0) &&
+			    (strcspn(outchannels.c_str(), "pvUDctsd") <
+			     strlen(outchannels.c_str()))) {
+				// if 1+ output flag chars are given and they're valid
 				stringstream oname;
-				oname << _basepath << _basename << "_Body" << number << ".out";
+				oname << _basepath << _basename << "_Line" << number << ".out";
 				outfiles.push_back(make_shared<ofstream>(oname.str()));
 				if (!outfiles.back()->is_open()) {
 					LOGERR << "Cannot create the output file '" << oname.str()
 					       << endl;
 					return MOORDYN_INVALID_OUTPUT_FILE;
 				}
-
-				Body* obj = new Body(_log);
-				LOGDBG << "\t'" << number << "'"
-				       << " - of type " << Body::TypeName(type) << " with id "
-				       << BodyList.size() << endl;
-				obj->setup(number,
-				           type,
-				           r6,
-				           rCG,
-				           M,
-				           V,
-				           Inert,
-				           CdA,
-				           Ca,
-				           outfiles.back());
-				BodyList.push_back(obj);
-				i++;
-			}
-		}
-
-		if (moordyn::str::has(moordyn::str::upper(in_txt[i]),
-		                      { "RODS", "ROD LIST", "ROD PROPERTIES" })) {
-			LOGDBG << "   Reading rod list:" << endl;
-
-			// skip following two lines (label line and unit line)
-			i += 3;
-			// parse until the next header or the end of the file
-			while ((in_txt[i].find("---") == string::npos) &&
-			       (i < in_txt.size())) {
-				vector<string> entries = moordyn::str::split(in_txt[i], ' ');
-				if (entries.size() < 8) {
-					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
-					       << endl
-					       << "'" << in_txt[i] << "'" << endl
-					       << "8 fields are required, but just "
-					       << entries.size() << " are provided" << endl;
-					return MOORDYN_INVALID_INPUT;
-				}
-
-				int number = atoi(entries[0].c_str());
-				string RodType = entries[1];
-				vec6 endCoords;
-				for (unsigned int I = 0; I < 6; I++)
-					endCoords[I] = atof(entries[3 + I].c_str());
-				int NumSegs = atoi(entries[9].c_str());
-				string outchannels = entries[10];
-
+			} else
+				outfiles.push_back(NULL);
+
+			LOGDBG << "\t'" << number << "'"
+			       << " - of class " << type << " (" << TypeNum << ")"
+			       << " with id " << LineList.size() << endl;
+
+			Line* obj = new Line(_log);
+			obj->setup(number,
+			           LinePropList[TypeNum],
+			           UnstrLen,
+			           NumSegs,
+			           outfiles.back(),
+			           outchannels);
+			LineList.push_back(obj);
+			LineStateIs.push_back(
+			    nX);                 // assign start index of this Line's states
+			nX += 6 * (NumSegs - 1); // add 6 state variables for each
+			                         // internal node of this line
+
+			for (unsigned int I = 0; I < 2; I++) {
+				const EndPoints end_point = I == 0 ? ENDPOINT_A : ENDPOINT_B;
 				char let1[10], num1[10], let2[10], num2[10], let3[10];
 				char typeWord[10];
-				strncpy(typeWord, entries[2].c_str(), 9);
+				strncpy(typeWord, entries[2 + I].c_str(), 9);
 				typeWord[9] = '\0';
 				// divided outWord into letters and numbers
 				str::decomposeString(typeWord, let1, num1, let2, num2, let3);
 
-				Rod::types type;
-				if (!strcmp(let1, "ANCHOR") || !strcmp(let1, "FIXED") ||
-				    !strcmp(let1, "FIX")) {
-					// it is fixed  (this would just be used if someone wanted
-					// to temporarly fix a body that things were attached to)
-					type = Rod::FIXED;
-				} else if (!strcmp(let1, "PINNED") || !strcmp(let1, "PIN")) {
-					// it is pinned
-					type = Rod::PINNED;
-					FreeRodIs.push_back(
-					    RodList.size()); // add this pinned rod to the free list
-					                     // because it is half free
-					RodStateIs.push_back(
-					    nX); // assign start index of this rod's states
-					nX += 6; // add 6 state variables for each pinned rod
-				} else if (!strcmp(let1, "BODY")) {
-					if (!strlen(num1)) {
-						LOGERR << "Error in " << _filepath << ":" << i + 1
-						       << "..." << endl
-						       << "'" << in_txt[i] << "'" << endl
-						       << "no number provided for Rod " << number
-						       << " Body attachment" << endl;
-						return MOORDYN_INVALID_INPUT;
-					}
-					unsigned int bodyID = atoi(num1);
-					if (!bodyID || (bodyID > BodyList.size())) {
-						LOGERR << "Error in " << _filepath << ":" << i + 1
-						       << "..." << endl
-						       << "'" << in_txt[i] << "'" << endl
-						       << "There is not " << bodyID << " bodies"
-						       << endl;
-						return MOORDYN_INVALID_INPUT;
-					}
-
-					if (!strcmp(let2, "PINNED") || !strcmp(let2, "PIN")) {
-						// it is pinned
-						type = Rod::PINNED;
-						FreeRodIs.push_back(
-						    RodList.size()); // add this pinned rod to the free
-						                     // list because it is half free
-						RodStateIs.push_back(
-						    nX); // assign start index of this rod's states
-						nX += 6; // add 6 state variables for each pinned rod
-					} else {
-						type = Rod::FIXED;
-					}
-				} else if (!strcmp(let1, "VESSEL") || !strcmp(let1, "VES") ||
-				           !strcmp(let1, "COUPLED") || !strcmp(let1, "CPLD")) {
-					// if a rigid fairlead, add to list and add
-					type = Rod::COUPLED;
-					CpldRodIs.push_back(
-					    RodList.size()); // index of fairlead in RodList vector
-				} else if (!strcmp(let1, "VESPIN") ||
-				           !strcmp(let1, "CPLDPIN")) {
-					// if a pinned fairlead, add to list and add
-					type = Rod::CPLDPIN;
-					CpldRodIs.push_back(
-					    RodList.size()); // index of fairlead in RodList vector
-					FreeRodIs.push_back(
-					    RodList.size()); // also add this pinned rod to the free
-					                     // list because it is half free
-					RodStateIs.push_back(
-					    nX); // assign start index of this rod's states
-					nX += 6; // add 6 state variables for each pinned rod
-				} else if (!strcmp(let1, "CONNECT") || !strcmp(let1, "CON") ||
-				           !strcmp(let1, "FREE")) {
-					type = Rod::FREE;
-					FreeRodIs.push_back(
-					    RodList.size()); // add this free rod to the free list
-					RodStateIs.push_back(
-					    nX);  // assign start index of this rod's states
-					nX += 12; // add 12 state variables for each free rod
-				} else {
+				if (!strlen(num1)) {
 					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
 					       << endl
 					       << "'" << in_txt[i] << "'" << endl
-					       << "Unrecognized connection type '" << let1 << "'"
+					       << "No number provided for the 1st connection index"
 					       << endl;
 					return MOORDYN_INVALID_INPUT;
 				}
+				unsigned int id = atoi(num1);
 
-				int TypeNum = -1;
-				for (unsigned int J = 0; J < RodPropList.size(); J++) {
-					if (RodPropList[J]->type == RodType)
-						TypeNum = J;
-				}
-				if (TypeNum == -1) {
-					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
-					       << endl
-					       << "'" << in_txt[i] << "'" << endl
-					       << "Unrecognized rod type " << RodType << endl;
-					return MOORDYN_INVALID_INPUT;
-				}
-
-				// Make the output file (if queried)
-				if ((outchannels.size() > 0) &&
-				    (strcspn(outchannels.c_str(), "pvUDctsd") <
-				     strlen(outchannels.c_str()))) {
-					// if 1+ output flag chars are given and they're valid
-					stringstream oname;
-					oname << _basepath << _basename << "_Rod" << number
-					      << ".out";
-					outfiles.push_back(make_shared<ofstream>(oname.str()));
-					if (!outfiles.back()->is_open()) {
-						LOGERR << "Cannot create the output file '"
-						       << oname.str() << endl;
-						return MOORDYN_INVALID_OUTPUT_FILE;
-					}
-				} else
-					outfiles.push_back(NULL);
-
-				LOGDBG << "\t'" << number << "'"
-				       << " - of class " << RodType << " (" << TypeNum << ")"
-				       << " and type " << Rod::TypeName(type) << " with id "
-				       << RodList.size() << endl;
-
-				Rod* obj = new Rod(_log);
-				obj->setup(number,
-				           type,
-				           RodPropList[TypeNum],
-				           endCoords,
-				           NumSegs,
-				           outfiles.back(),
-				           outchannels);
-				RodList.push_back(obj);
-
-				// depending on type, assign the Rod to its respective parent
-				// body
-				if (!strcmp(let1, "ANCHOR") || !strcmp(let1, "FIXED") ||
-				    !strcmp(let1, "FIX"))
-					GroundBody->addRod(obj, endCoords);
-				else if (!strcmp(let1, "PINNED") || !strcmp(let1, "PIN"))
-					GroundBody->addRod(obj, endCoords);
-				else if (!strcmp(let1, "BODY")) {
-					unsigned int bodyID = atoi(num1);
-					BodyList[bodyID - 1]->addRod(obj, endCoords);
-				}
-				LOGDBG << endl;
-
-				i++;
-			}
-		}
-
-		if (moordyn::str::has(moordyn::str::upper(in_txt[i]),
-		                      { "POINTS",
-		                        "POINT LIST",
-		                        "CONNECTION PROPERTIES",
-		                        "NODE PROPERTIES" })) {
-			LOGDBG << "   Reading point list:" << endl;
-
-			// skip following two lines (label line and unit line)
-			i += 3;
-			// parse until the next header or the end of the file
-			while ((in_txt[i].find("---") == string::npos) &&
-			       (i < in_txt.size())) {
-				vector<string> entries = moordyn::str::split(in_txt[i], ' ');
-				if (entries.size() < 9) {
-					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
-					       << endl
-					       << "'" << in_txt[i] << "'" << endl
-					       << "9 fields are required, but just "
-					       << entries.size() << " are provided" << endl;
-					return MOORDYN_INVALID_INPUT;
-				}
-
-				int number = atoi(entries[0].c_str());
-				double M = atof(entries[5].c_str());
-				double V = atof(entries[6].c_str());
-				double CdA;
-				double Ca;
-				vec r0;
-				vec F = vec::Zero();
-				if (entries.size() >=
-				    12) // case with optional force inputs (12 total entries)
-				{
-					for (int I = 0; I < 3; I++) {
-						r0[I] = atof(entries[2 + I].c_str());
-						F[I] = atof(entries[7 + I].c_str());
+				if (!strcmp(let1, "R") || !strcmp(let1, "ROD")) {
+					if (!id || id > RodList.size()) {
+						LOGERR << "Error in " << _filepath << ":" << i + 1
+						       << "..." << endl
+						       << "'" << in_txt[i] << "'" << endl
+						       << "There are not " << id << " rods" << endl;
+						return MOORDYN_INVALID_INPUT;
 					}
-					CdA = atof(entries[10].c_str());
-					Ca = atof(entries[11].c_str());
-				} else // case without optional force inputs (9 total entries)
-				{
-					for (int I = 0; I < 3; I++)
-						r0[I] = atof(entries[2 + I].c_str());
-
-					CdA = atof(entries[7].c_str());
-					Ca = atof(entries[8].c_str());
-				}
-
-				Connection::types type;
-				char let1[10], num1[10], let2[10], num2[10], let3[10];
-				char typeWord[10];
-				strncpy(typeWord, entries[1].c_str(), 9);
-				typeWord[9] = '\0';
-				// divided outWord into letters and numbers
-				str::decomposeString(typeWord, let1, num1, let2, num2, let3);
-
-				if (!strcmp(let1, "ANCHOR") || !strcmp(let1, "FIXED") ||
-				    !strcmp(let1, "FIX")) {
-					// it is fixed  (this would just be used if someone wanted
-					// to temporarly fix a body that things were attached to)
-					type = Connection::FIXED;
-				} else if (!strcmp(let1, "BODY")) {
-					type = Connection::FIXED;
-					if (!strlen(num1)) {
+					if (!strcmp(let2, "A"))
+						RodList[id - 1]->addLine(obj, end_point, ENDPOINT_A);
+					else if (!strcmp(let2, "B"))
+						RodList[id - 1]->addLine(obj, end_point, ENDPOINT_B);
+					else {
 						LOGERR << "Error in " << _filepath << ":" << i + 1
 						       << "..." << endl
 						       << "'" << in_txt[i] << "'" << endl
-						       << "no number provided for Rod " << number
-						       << " Body attachment" << endl;
+						       << "Rod end (A or B) must be specified" << endl;
 						return MOORDYN_INVALID_INPUT;
 					}
-					unsigned int bodyID = atoi(num1);
-					if (!bodyID || (bodyID > BodyList.size())) {
+				} else if (!strlen(let1) || !strcmp(let1, "C") ||
+				           !strcmp(let1, "CON")) {
+					if (!id || id > ConnectionList.size()) {
 						LOGERR << "Error in " << _filepath << ":" << i + 1
 						       << "..." << endl
 						       << "'" << in_txt[i] << "'" << endl
-						       << "There is not " << bodyID << " bodies"
+						       << "There are not " << id << " connections"
 						       << endl;
 						return MOORDYN_INVALID_INPUT;
 					}
-				} else if (!strcmp(let1, "FAIRLEAD") ||
-				           !strcmp(let1, "VESSEL") || !strcmp(let1, "VES") ||
-				           !strcmp(let1, "COUPLED") || !strcmp(let1, "CPLD")) {
-					// if a fairlead, add to list and add
-					type = Connection::COUPLED;
-					CpldConIs.push_back(ConnectionList.size());
-				} else if (!strcmp(let1, "CONNECT") || !strcmp(let1, "CON") ||
-				           !strcmp(let1, "FREE")) {
-					// if a connect, add to list and add states for it
-					type = Connection::FREE;
-					FreeConIs.push_back(ConnectionList.size());
-					ConnectStateIs.push_back(
-					    nX); // assign start index of this connect's states
-					nX += 6; // add 6 state variables for each connect
+					ConnectionList[id - 1]->addLine(obj, end_point);
 				} else {
 					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
 					       << endl
 					       << "'" << in_txt[i] << "'" << endl
-					       << "Unrecognized connection type '" << let1 << "'"
-					       << endl;
+					       << "Unrecognized connection type " << let1 << endl;
 					return MOORDYN_INVALID_INPUT;
 				}
-
-				// make default water depth at least the depth of the lowest
-				// node (so water depth input is optional)
-				if (r0[2] < -env.WtrDpth)
-					env.WtrDpth = -r0[2];
-
-				LOGDBG << "\t'" << number << "'"
-				       << " - of type " << Connection::TypeName(type)
-				       << " with id " << ConnectionList.size() << endl;
-
-				// now make Connection object!
-				Connection* obj = new Connection(_log);
-				obj->setup(number, type, r0, M, V, F, CdA, Ca);
-				ConnectionList.push_back(obj);
-
-				// depending on type, assign the Connection to its respective
-				// parent body
-				if (!strcmp(let1, "ANCHOR") || !strcmp(let1, "FIXED") ||
-				    !strcmp(let1, "FIX"))
-					GroundBody->addConnection(obj, r0);
-				else if (!strcmp(let1, "BODY")) {
-					int bodyID = atoi(num1);
-					BodyList[bodyID - 1]->addConnection(obj, r0);
-				}
-				LOGDBG << endl;
-
-				i++;
 			}
-		}
+			LOGDBG << endl;
 
-		if (moordyn::str::has(moordyn::str::upper(in_txt[i]),
-		                      { "LINES", "LINE LIST", "LINE PROPERTIES" })) {
-			LOGDBG << "   Reading line list: " << endl;
+			i++;
+		}
+	}
 
-			if (!LinePropList.size()) {
-				LOGERR << "Reading lines without defined line types" << endl;
+	if ((i = findStartOfSection(in_txt, { "FAILURE" })) != -1) {
+		LOGDBG << "   Reading failure conditions:" << endl;
+		// parse until the next header or the end of the file
+		while ((in_txt[i].find("---") == string::npos) && (i < in_txt.size())) {
+			vector<string> entries = moordyn::str::split(in_txt[i], ' ');
+			if (entries.size() < 4) {
+				LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
+				       << endl
+				       << "'" << in_txt[i] << "'" << endl
+				       << "4 fields are required, but just " << entries.size()
+				       << " are provided" << endl;
 				return MOORDYN_INVALID_INPUT;
 			}
-			if (!ConnectionList.size()) {
-				LOGERR << "Reading lines without defined connections" << endl;
+
+			FailProps* obj = new FailProps();
+			obj->rod = NULL;
+			obj->conn = NULL;
+			obj->status = false;
+			FailList.push_back(obj);
+
+			char let1[10], num1[10], let2[10], num2[10], let3[10];
+			char typeWord[10];
+			strncpy(typeWord, entries[0].c_str(), 9);
+			typeWord[9] = '\0';
+			// divided outWord into letters and numbers
+			str::decomposeString(typeWord, let1, num1, let2, num2, let3);
+
+			if (!strlen(num1)) {
+				LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
+				       << endl
+				       << "'" << in_txt[i] << "'" << endl
+				       << "No number provided for Node Failure" << endl;
 				return MOORDYN_INVALID_INPUT;
 			}
 
-			// skip following two lines (label line and unit line)
-			i += 3;
-			// parse until the next header or the end of the file
-			while ((in_txt[i].find("---") == string::npos) &&
-			       (i < in_txt.size())) {
-				vector<string> entries = moordyn::str::split(in_txt[i], ' ');
-				if (entries.size() < 7) {
+			const unsigned int id = atoi(num1);
+			if (!strcmp(let1, "R") || !strcmp(let1, "ROD")) {
+				if (!id || id > RodList.size()) {
 					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
 					       << endl
 					       << "'" << in_txt[i] << "'" << endl
-					       << "7 fields are required, but just "
-					       << entries.size() << " are provided" << endl;
+					       << "There are not " << id << " rods" << endl;
 					return MOORDYN_INVALID_INPUT;
 				}
-
-				int number = atoi(entries[0].c_str());
-				string type = entries[1];
-				double UnstrLen = atof(entries[4].c_str());
-				int NumSegs = atoi(entries[5].c_str()); // addition vs. MAP
-				string outchannels = entries[6];
-
-				int TypeNum = -1;
-				for (unsigned int J = 0; J < LinePropList.size(); J++) {
-					if (LinePropList[J]->type == type)
-						TypeNum = J;
-				}
-				if (TypeNum == -1) {
+				obj->rod = RodList[id - 1];
+				if (!strcmp(let2, "A"))
+					obj->rod_end_point = ENDPOINT_A;
+				else if (!strcmp(let2, "B"))
+					obj->rod_end_point = ENDPOINT_B;
+				else {
 					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
 					       << endl
 					       << "'" << in_txt[i] << "'" << endl
-					       << "Unrecognized line type " << type << endl;
+					       << "Failure end (A or B) must be specified" << endl;
 					return MOORDYN_INVALID_INPUT;
 				}
-
-				// Make the output file (if queried)
-				if ((outchannels.size() > 0) &&
-				    (strcspn(outchannels.c_str(), "pvUDctsd") <
-				     strlen(outchannels.c_str()))) {
-					// if 1+ output flag chars are given and they're valid
-					stringstream oname;
-					oname << _basepath << _basename << "_Line" << number
-					      << ".out";
-					outfiles.push_back(make_shared<ofstream>(oname.str()));
-					if (!outfiles.back()->is_open()) {
-						LOGERR << "Cannot create the output file '"
-						       << oname.str() << endl;
-						return MOORDYN_INVALID_OUTPUT_FILE;
-					}
-				} else
-					outfiles.push_back(NULL);
-
-				LOGDBG << "\t'" << number << "'"
-				       << " - of class " << type << " (" << TypeNum << ")"
-				       << " with id " << LineList.size() << endl;
-
-				Line* obj = new Line(_log);
-				obj->setup(number,
-				           LinePropList[TypeNum],
-				           UnstrLen,
-				           NumSegs,
-				           outfiles.back(),
-				           outchannels);
-				LineList.push_back(obj);
-				LineStateIs.push_back(
-				    nX); // assign start index of this Line's states
-				nX += 6 * (NumSegs - 1); // add 6 state variables for each
-				                         // internal node of this line
-
-				for (unsigned int I = 0; I < 2; I++) {
-					const EndPoints end_point =
-					    I == 0 ? ENDPOINT_A : ENDPOINT_B;
-					char let1[10], num1[10], let2[10], num2[10], let3[10];
-					char typeWord[10];
-					strncpy(typeWord, entries[2 + I].c_str(), 9);
-					typeWord[9] = '\0';
-					// divided outWord into letters and numbers
-					str::decomposeString(
-					    typeWord, let1, num1, let2, num2, let3);
-
-					if (!strlen(num1)) {
-						LOGERR
-						    << "Error in " << _filepath << ":" << i + 1 << "..."
-						    << endl
-						    << "'" << in_txt[i] << "'" << endl
-						    << "No number provided for the 1st connection index"
-						    << endl;
-						return MOORDYN_INVALID_INPUT;
-					}
-					unsigned int id = atoi(num1);
-
-					if (!strcmp(let1, "R") || !strcmp(let1, "ROD")) {
-						if (!id || id > RodList.size()) {
-							LOGERR << "Error in " << _filepath << ":" << i + 1
-							       << "..." << endl
-							       << "'" << in_txt[i] << "'" << endl
-							       << "There are not " << id << " rods" << endl;
-							return MOORDYN_INVALID_INPUT;
-						}
-						if (!strcmp(let2, "A"))
-							RodList[id - 1]->addLine(
-							    obj, end_point, ENDPOINT_A);
-						else if (!strcmp(let2, "B"))
-							RodList[id - 1]->addLine(
-							    obj, end_point, ENDPOINT_B);
-						else {
-							LOGERR << "Error in " << _filepath << ":" << i + 1
-							       << "..." << endl
-							       << "'" << in_txt[i] << "'" << endl
-							       << "Rod end (A or B) must be specified"
-							       << endl;
-							return MOORDYN_INVALID_INPUT;
-						}
-					} else if (!strlen(let1) || !strcmp(let1, "C") ||
-					           !strcmp(let1, "CON")) {
-						if (!id || id > ConnectionList.size()) {
-							LOGERR << "Error in " << _filepath << ":" << i + 1
-							       << "..." << endl
-							       << "'" << in_txt[i] << "'" << endl
-							       << "There are not " << id << " connections"
-							       << endl;
-							return MOORDYN_INVALID_INPUT;
-						}
-						ConnectionList[id - 1]->addLine(obj, end_point);
-					} else {
-						LOGERR << "Error in " << _filepath << ":" << i + 1
-						       << "..." << endl
-						       << "'" << in_txt[i] << "'" << endl
-						       << "Unrecognized connection type " << let1
-						       << endl;
-						return MOORDYN_INVALID_INPUT;
-					}
+			} else if (!strlen(let1) || !strcmp(let1, "C") ||
+			           !strcmp(let1, "CON")) {
+				if (!id || id > ConnectionList.size()) {
+					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
+					       << endl
+					       << "'" << in_txt[i] << "'" << endl
+					       << "There are not " << id << " connections" << endl;
+					return MOORDYN_INVALID_INPUT;
 				}
-				LOGDBG << endl;
-
-				i++;
+				obj->conn = ConnectionList[id - 1];
+				;
+			} else {
+				LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
+				       << endl
+				       << "'" << in_txt[i] << "'" << endl
+				       << "Unrecognized connection type " << let1 << endl;
+				return MOORDYN_INVALID_INPUT;
 			}
-		}
-
-		if (moordyn::str::has(moordyn::str::upper(in_txt[i]), { "FAILURE" })) {
-			LOGDBG << "   Reading failure conditions:" << endl;
 
-			// skip following two lines (label line and unit line)
-			i += 3;
-			// parse until the next header or the end of the file
-			while ((in_txt[i].find("---") == string::npos) &&
-			       (i < in_txt.size())) {
-				vector<string> entries = moordyn::str::split(in_txt[i], ' ');
-				if (entries.size() < 4) {
+			vector<string> lineNums = moordyn::str::split(entries[1], ',');
+			obj->lines.reserve(lineNums.size());
+			obj->line_end_points.reserve(lineNums.size());
+			for (unsigned int il = 0; il < lineNums.size(); il++) {
+				const unsigned int line_id = atoi(lineNums[il].c_str());
+				if (!line_id || line_id > LineList.size()) {
 					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
 					       << endl
 					       << "'" << in_txt[i] << "'" << endl
-					       << "4 fields are required, but just "
-					       << entries.size() << " are provided" << endl;
+					       << "There are not " << line_id << " lines" << endl;
 					return MOORDYN_INVALID_INPUT;
 				}
+				obj->lines.push_back(LineList[line_id - 1]);
+				obj->line_end_points.push_back(ENDPOINT_A);
+			}
+
+			obj->time = atof(entries[2].c_str());
+			obj->ten = atof(entries[3].c_str());
+
+			LOGDBG << "fail time is " << obj->time << " s" << endl;
+			LOGDBG << "fail ten is " << obj->ten << " N" << endl;
 
-				FailProps* obj = new FailProps();
-				obj->rod = NULL;
-				obj->conn = NULL;
-				obj->status = false;
-				FailList.push_back(obj);
+			i++;
+		}
+	}
 
+	if ((i = findStartOfSection(in_txt, { "OPTIONS" })) != -1) {
+		LOGDBG << "   Reading options:" << endl;
+		// Parse options until the next header or the end of the file
+		while ((in_txt[i].find("---") == string::npos) && (i < in_txt.size())) {
+			readOptionsLine(in_txt, i);
+			i++;
+		}
+	}
+
+	if ((i = findStartOfSection(in_txt, { "OUTPUT" })) != -1) {
+		LOGDBG << "   Reading output options:" << endl;
+		// parse until the next header or the end of the file
+		while ((in_txt[i].find("---") == string::npos) && (i < in_txt.size())) {
+			vector<string> entries = moordyn::str::split(in_txt[i], ' ');
+
+			for (unsigned int j = 0; j < entries.size();
+			     j++) // loop through each word on each line
+			{
 				char let1[10], num1[10], let2[10], num2[10], let3[10];
 				char typeWord[10];
-				strncpy(typeWord, entries[0].c_str(), 9);
 				typeWord[9] = '\0';
+				strncpy(typeWord, entries[j].c_str(), 9);
 				// divided outWord into letters and numbers
 				str::decomposeString(typeWord, let1, num1, let2, num2, let3);
 
-				if (!strlen(num1)) {
-					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
-					       << endl
-					       << "'" << in_txt[i] << "'" << endl
-					       << "No number provided for Node Failure" << endl;
-					return MOORDYN_INVALID_INPUT;
+				// declare dummy struct to be copied onto end of vector (and
+				// filled in later);
+				OutChanProps dummy;
+				strncpy(dummy.Name, typeWord, 10);
+
+				// figure out what type of output it is and process
+				// accordingly
+				// TODO: add checks of first char of num1,2, let1,2,3 not
+				// being NULL to below and handle errors (e.g. invalid line
+				// number)
+
+				// The length of dummy.Units is hardcoded to 10 in
+				// Misc.h
+				const int UnitsSize = 9;
+				dummy.Units[UnitsSize] = '\0';
+
+				// fairlead tension case (changed to just be for single
+				// line, not all connected lines)
+				if (!strcmp(let1, "FAIRTEN")) {
+					dummy.OType = 1;
+					dummy.QType = Ten;
+					strncpy(dummy.Units, moordyn::UnitList[Ten], UnitsSize);
+					dummy.ObjID = atoi(num1);
+					dummy.NodeID = LineList[dummy.ObjID - 1]->getN();
 				}
-
-				const unsigned int id = atoi(num1);
-				if (!strcmp(let1, "R") || !strcmp(let1, "ROD")) {
-					if (!id || id > RodList.size()) {
-						LOGERR << "Error in " << _filepath << ":" << i + 1
-						       << "..." << endl
-						       << "'" << in_txt[i] << "'" << endl
-						       << "There are not " << id << " rods" << endl;
-						return MOORDYN_INVALID_INPUT;
+				// achor tension case (changed to just be for single line,
+				// not all connected lines)
+				else if (!strcmp(let1, "ANCHTEN")) {
+					dummy.OType = 1;
+					dummy.QType = Ten;
+					strncpy(dummy.Units, moordyn::UnitList[Ten], UnitsSize);
+					dummy.ObjID = atoi(num1);
+					dummy.NodeID = 0;
+				}
+				// more general case
+				else {
+					// get object type and node number if applicable
+					// Line case:  L?N?xxxx
+					if (!strcmp(let1, "L")) {
+						dummy.OType = 1;
+						dummy.NodeID = atoi(num2);
 					}
-					obj->rod = RodList[id - 1];
-					if (!strcmp(let2, "A"))
-						obj->rod_end_point = ENDPOINT_A;
-					else if (!strcmp(let2, "B"))
-						obj->rod_end_point = ENDPOINT_B;
-					else {
-						LOGERR << "Error in " << _filepath << ":" << i + 1
-						       << "..." << endl
-						       << "'" << in_txt[i] << "'" << endl
-						       << "Failure end (A or B) must be specified"
-						       << endl;
-						return MOORDYN_INVALID_INPUT;
+					// Connect case:   C?xxx or Con?xxx
+					else if (!strcmp(let1, "C") || !strcmp(let1, "CON")) {
+						dummy.OType = 2;
+						dummy.NodeID = -1;
+						strncpy(let3, let2, 10);
 					}
-				} else if (!strlen(let1) || !strcmp(let1, "C") ||
-				           !strcmp(let1, "CON")) {
-					if (!id || id > ConnectionList.size()) {
-						LOGERR << "Error in " << _filepath << ":" << i + 1
+					// Rod case:   R?xxx or Rod?xxx
+					else if (!strcmp(let1, "R") || !strcmp(let1, "ROD")) {
+						dummy.OType = 3;
+						dummy.NodeID = atoi(num2);
+					}
+					// should do fairlead option also!
+
+					else {
+						LOGWRN << "Warning in " << _filepath << ":" << i + 1
 						       << "..." << endl
 						       << "'" << in_txt[i] << "'" << endl
-						       << "There are not " << id << " connections"
+						       << "invalid output specifier: " << let1
+						       << ".  Type must be oneof L, C/Con or R/Rod"
 						       << endl;
-						return MOORDYN_INVALID_INPUT;
+						dummy.OType = -1;
+						continue;
 					}
-					obj->conn = ConnectionList[id - 1];
-					;
-				} else {
-					LOGERR << "Error in " << _filepath << ":" << i + 1 << "..."
-					       << endl
-					       << "'" << in_txt[i] << "'" << endl
-					       << "Unrecognized connection type " << let1 << endl;
-					return MOORDYN_INVALID_INPUT;
-				}
 
-				vector<string> lineNums = moordyn::str::split(entries[1], ',');
-				obj->lines.reserve(lineNums.size());
-				obj->line_end_points.reserve(lineNums.size());
-				for (unsigned int il = 0; il < lineNums.size(); il++) {
-					const unsigned int line_id = atoi(lineNums[il].c_str());
-					if (!line_id || line_id > LineList.size()) {
-						LOGERR << "Error in " << _filepath << ":" << i + 1
+					// object number
+					dummy.ObjID = atoi(num1);
+
+					if (!strcmp(let3, "PX")) {
+						// cout << "SETTING QTYPE to " << PosX << endl;
+						dummy.QType = PosX;
+						strncpy(
+						    dummy.Units, moordyn::UnitList[PosX], UnitsSize);
+					} else if (!strcmp(let3, "PY")) {
+						dummy.QType = PosY;
+						strncpy(
+						    dummy.Units, moordyn::UnitList[PosY], UnitsSize);
+					} else if (!strcmp(let3, "PZ")) {
+						dummy.QType = PosZ;
+						strncpy(
+						    dummy.Units, moordyn::UnitList[PosZ], UnitsSize);
+					} else if (!strcmp(let3, "VX")) {
+						dummy.QType = VelX;
+						strncpy(
+						    dummy.Units, moordyn::UnitList[VelX], UnitsSize);
+					} else if (!strcmp(let3, "VY")) {
+						dummy.QType = VelY;
+						strncpy(
+						    dummy.Units, moordyn::UnitList[VelY], UnitsSize);
+					} else if (!strcmp(let3, "VZ")) {
+						dummy.QType = VelZ;
+						strncpy(
+						    dummy.Units, moordyn::UnitList[VelZ], UnitsSize);
+					} else if (!strcmp(let3, "AX")) {
+						dummy.QType = AccX;
+						strncpy(
+						    dummy.Units, moordyn::UnitList[AccX], UnitsSize);
+					} else if (!strcmp(let3, "Ay")) {
+						dummy.QType = AccY;
+						strncpy(
+						    dummy.Units, moordyn::UnitList[AccY], UnitsSize);
+					} else if (!strcmp(let3, "AZ")) {
+						dummy.QType = AccZ;
+						strncpy(
+						    dummy.Units, moordyn::UnitList[AccZ], UnitsSize);
+					} else if (!strcmp(let3, "T") || !strcmp(let3, "TEN")) {
+						dummy.QType = Ten;
+						strncpy(dummy.Units, moordyn::UnitList[Ten], UnitsSize);
+					} else if (!strcmp(let3, "FX")) {
+						dummy.QType = FX;
+						strncpy(dummy.Units, moordyn::UnitList[FX], UnitsSize);
+					} else if (!strcmp(let3, "FY")) {
+						dummy.QType = FY;
+						strncpy(dummy.Units, moordyn::UnitList[FY], UnitsSize);
+					} else if (!strcmp(let3, "FZ")) {
+						dummy.QType = FZ;
+						strncpy(dummy.Units, moordyn::UnitList[FZ], UnitsSize);
+					} else {
+						LOGWRN << "Warning in " << _filepath << ":" << i + 1
 						       << "..." << endl
 						       << "'" << in_txt[i] << "'" << endl
-						       << "There are not " << line_id << " lines"
+						       << "invalid quantity specifier: " << let3
 						       << endl;
-						return MOORDYN_INVALID_INPUT;
+						dummy.OType = -1;
+						continue;
 					}
-					obj->lines.push_back(LineList[line_id - 1]);
-					obj->line_end_points.push_back(ENDPOINT_A);
 				}
 
-				obj->time = atof(entries[2].c_str());
-				obj->ten = atof(entries[3].c_str());
-
-				LOGDBG << "fail time is " << obj->time << " s" << endl;
-				LOGDBG << "fail ten is " << obj->ten << " N" << endl;
-
-				i++;
-			}
-		}
-
-		if (moordyn::str::has(moordyn::str::upper(in_txt[i]), { "OPTIONS" })) {
-			LOGDBG << "   Reading options:" << endl;
-
-			i++;
-			// Parse options until the next header or the end of the file
-			while ((in_txt[i].find("---") == string::npos) &&
-			       (i < in_txt.size())) {
-				vector<string> entries = moordyn::str::split(in_txt[i], ' ');
-				if (entries.size() < 2) {
-					i++;
-					LOGWRN << "Ignoring option line " << i << endl;
-					continue;
+				// some name adjusting for special cases (maybe should
+				// handle this elsewhere...)
+				if ((dummy.OType == 3) && (dummy.QType == Ten)) {
+					if (dummy.NodeID > 0)
+						strncpy(dummy.Name, "TenEndB", 10);
+					else
+						strncpy(dummy.Name, "TenEndA", 10);
 				}
 
-				LOGDBG << "\t" << entries[1] << " = " << entries[0] << endl;
-				const string value = entries[0];
-				const string name = entries[1];
-				if (name == "writeLog") {
-					// Already registered
-					i++;
-					continue;
-				}
-				// DT is old way, should phase out
-				else if ((name == "dtM") || (name == "DT"))
-					dtM0 = atof(entries[0].c_str());
-				else if (name == "tScheme")
-					t_integrator_name = entries[0];
-				else if ((name == "g") || (name == "gravity"))
-					env.g = atof(entries[0].c_str());
-				else if ((name == "Rho") || (name == "rho") ||
-				         (name == "WtrDnsty"))
-					env.rho_w = atof(entries[0].c_str());
-				else if (name == "WtrDpth")
-					env.WtrDpth = atof(entries[0].c_str());
-				else if ((name == "kBot") || (name == "kb"))
-					env.kb = atof(entries[0].c_str());
-				else if ((name == "cBot") || (name == "cb"))
-					env.cb = atof(entries[0].c_str());
-				else if ((name == "dtIC") || (name == "ICdt"))
-					ICdt = atof(entries[0].c_str());
-				else if ((name == "TmaxIC") || (name == "ICTmax"))
-					ICTmax = atof(entries[0].c_str());
-				else if ((name == "CdScaleIC") || (name == "ICDfac"))
-					ICDfac = atof(entries[0].c_str());
-				else if ((name == "threshIC") || (name == "ICthresh"))
-					ICthresh = atof(entries[0].c_str());
-				else if (name == "WaveKin") {
-					WaveKinTemp =
-					    (moordyn::waves_settings)atoi(entries[0].c_str());
-					if ((WaveKinTemp < WAVES_NONE) || (WaveKinTemp > WAVES_KIN))
-						LOGWRN << "Unknown WaveKin option value " << WaveKinTemp
-						       << endl;
-				} else if (name == "dtWave")
-					env.dtWave = atoi(entries[0].c_str());
-				else if (name == "Currents") {
-					env.Current =
-					    (moordyn::currents_settings)atoi(entries[0].c_str());
-					if ((env.Current < CURRENTS_NONE) ||
-					    (env.Current > CURRENTS_DYNAMIC_NODE))
-						LOGWRN << "Unknown Currents option value "
-						       << env.Current << endl;
-				} else if (name == "WriteUnits")
-					env.WriteUnits = atoi(entries[0].c_str());
-				else if (name == "FrictionCoefficient")
-					env.FrictionCoefficient = atof(entries[0].c_str());
-				else if (name == "FricDamp")
-					env.FricDamp = atof(entries[0].c_str());
-				else if (name == "StatDynFricScale")
-					env.StatDynFricScale = atof(entries[0].c_str());
-				// output writing period (0 for at every call)
-				else if (name == "dtOut")
-					dtOut = atof(entries[0].c_str());
-				else
-					LOGWRN << "Warning: Unrecognized option '" << name << "'"
-					       << endl;
-
-				i++;
+				if ((dummy.OType > 0) && (dummy.QType > 0))
+					outChans.push_back(dummy);
 			}
-		}
-
-		if (moordyn::str::has(moordyn::str::upper(in_txt[i]), { "OUTPUT" })) {
-			LOGDBG << "   Reading output options:" << endl;
 
 			i++;
-			// parse until the next header or the end of the file
-			while ((in_txt[i].find("---") == string::npos) &&
-			       (i < in_txt.size())) {
-				vector<string> entries = moordyn::str::split(in_txt[i], ' ');
-
-				for (unsigned int j = 0; j < entries.size();
-				     j++) // loop through each word on each line
-				{
-					char let1[10], num1[10], let2[10], num2[10], let3[10];
-					char typeWord[10];
-					typeWord[9] = '\0';
-					strncpy(typeWord, entries[j].c_str(), 9);
-					// divided outWord into letters and numbers
-					str::decomposeString(
-					    typeWord, let1, num1, let2, num2, let3);
-
-					// declare dummy struct to be copied onto end of vector (and
-					// filled in later);
-					OutChanProps dummy;
-					strncpy(dummy.Name, typeWord, 10);
-
-					// figure out what type of output it is and process
-					// accordingly
-					// TODO: add checks of first char of num1,2, let1,2,3 not
-					// being NULL to below and handle errors (e.g. invalid line
-					// number)
-
-					// The length of dummy.Units is hardcoded to 10 in
-					// Misc.h
-					const int UnitsSize = 9;
-					dummy.Units[UnitsSize] = '\0';
-
-					// fairlead tension case (changed to just be for single
-					// line, not all connected lines)
-					if (!strcmp(let1, "FAIRTEN")) {
-						dummy.OType = 1;
-						dummy.QType = Ten;
-						strncpy(dummy.Units, moordyn::UnitList[Ten], UnitsSize);
-						dummy.ObjID = atoi(num1);
-						dummy.NodeID = LineList[dummy.ObjID - 1]->getN();
-					}
-					// achor tension case (changed to just be for single line,
-					// not all connected lines)
-					else if (!strcmp(let1, "ANCHTEN")) {
-						dummy.OType = 1;
-						dummy.QType = Ten;
-						strncpy(dummy.Units, moordyn::UnitList[Ten], UnitsSize);
-						dummy.ObjID = atoi(num1);
-						dummy.NodeID = 0;
-					}
-					// more general case
-					else {
-						// get object type and node number if applicable
-						// Line case:  L?N?xxxx
-						if (!strcmp(let1, "L")) {
-							dummy.OType = 1;
-							dummy.NodeID = atoi(num2);
-						}
-						// Connect case:   C?xxx or Con?xxx
-						else if (!strcmp(let1, "C") || !strcmp(let1, "CON")) {
-							dummy.OType = 2;
-							dummy.NodeID = -1;
-							strncpy(let3, let2, 10);
-						}
-						// Rod case:   R?xxx or Rod?xxx
-						else if (!strcmp(let1, "R") || !strcmp(let1, "ROD")) {
-							dummy.OType = 3;
-							dummy.NodeID = atoi(num2);
-						}
-						// should do fairlead option also!
-
-						else {
-							LOGWRN << "Warning in " << _filepath << ":" << i + 1
-							       << "..." << endl
-							       << "'" << in_txt[i] << "'" << endl
-							       << "invalid output specifier: " << let1
-							       << ".  Type must be oneof L, C/Con or R/Rod"
-							       << endl;
-							dummy.OType = -1;
-							continue;
-						}
-
-						// object number
-						dummy.ObjID = atoi(num1);
-
-						if (!strcmp(let3, "PX")) {
-							// cout << "SETTING QTYPE to " << PosX << endl;
-							dummy.QType = PosX;
-							strncpy(dummy.Units,
-							        moordyn::UnitList[PosX],
-							        UnitsSize);
-						} else if (!strcmp(let3, "PY")) {
-							dummy.QType = PosY;
-							strncpy(dummy.Units,
-							        moordyn::UnitList[PosY],
-							        UnitsSize);
-						} else if (!strcmp(let3, "PZ")) {
-							dummy.QType = PosZ;
-							strncpy(dummy.Units,
-							        moordyn::UnitList[PosZ],
-							        UnitsSize);
-						} else if (!strcmp(let3, "VX")) {
-							dummy.QType = VelX;
-							strncpy(dummy.Units,
-							        moordyn::UnitList[VelX],
-							        UnitsSize);
-						} else if (!strcmp(let3, "VY")) {
-							dummy.QType = VelY;
-							strncpy(dummy.Units,
-							        moordyn::UnitList[VelY],
-							        UnitsSize);
-						} else if (!strcmp(let3, "VZ")) {
-							dummy.QType = VelZ;
-							strncpy(dummy.Units,
-							        moordyn::UnitList[VelZ],
-							        UnitsSize);
-						} else if (!strcmp(let3, "AX")) {
-							dummy.QType = AccX;
-							strncpy(dummy.Units,
-							        moordyn::UnitList[AccX],
-							        UnitsSize);
-						} else if (!strcmp(let3, "Ay")) {
-							dummy.QType = AccY;
-							strncpy(dummy.Units,
-							        moordyn::UnitList[AccY],
-							        UnitsSize);
-						} else if (!strcmp(let3, "AZ")) {
-							dummy.QType = AccZ;
-							strncpy(dummy.Units,
-							        moordyn::UnitList[AccZ],
-							        UnitsSize);
-						} else if (!strcmp(let3, "T") || !strcmp(let3, "TEN")) {
-							dummy.QType = Ten;
-							strncpy(
-							    dummy.Units, moordyn::UnitList[Ten], UnitsSize);
-						} else if (!strcmp(let3, "FX")) {
-							dummy.QType = FX;
-							strncpy(
-							    dummy.Units, moordyn::UnitList[FX], UnitsSize);
-						} else if (!strcmp(let3, "FY")) {
-							dummy.QType = FY;
-							strncpy(
-							    dummy.Units, moordyn::UnitList[FY], UnitsSize);
-						} else if (!strcmp(let3, "FZ")) {
-							dummy.QType = FZ;
-							strncpy(
-							    dummy.Units, moordyn::UnitList[FZ], UnitsSize);
-						} else {
-							LOGWRN << "Warning in " << _filepath << ":" << i + 1
-							       << "..." << endl
-							       << "'" << in_txt[i] << "'" << endl
-							       << "invalid quantity specifier: " << let3
-							       << endl;
-							dummy.OType = -1;
-							continue;
-						}
-					}
-
-					// some name adjusting for special cases (maybe should
-					// handle this elsewhere...)
-					if ((dummy.OType == 3) && (dummy.QType == Ten)) {
-						if (dummy.NodeID > 0)
-							strncpy(dummy.Name, "TenEndB", 10);
-						else
-							strncpy(dummy.Name, "TenEndA", 10);
-					}
-
-					if ((dummy.OType > 0) && (dummy.QType > 0))
-						outChans.push_back(dummy);
-				}
-
-				i++;
-			}
 		}
-
-		i++;
 	}
 
 	// do some input validity checking?
-	// should there be a flag in the input file that clearly distingiushes the
-	// coupling type?
+	// should there be a flag in the input file that clearly distingiushes
+	// the coupling type?
 	// <<<<< I guess it's implied by whether bodies are coupled or not??
 
-	// TODO: make sure things are consistent for only ONE coupling type (body
-	// centric or fairlead centric)
+	// TODO: make sure things are consistent for only ONE coupling type
+	// (body centric or fairlead centric)
 	// <<<<<<<<<<<<<<<< also do checks when time step function is called...
 
 	LOGMSG << "Generated entities:" << endl
@@ -1872,13 +1341,15 @@ moordyn::MoorDyn::ReadInFile()
 	// Setup the time integrator
 	moordyn::error_id err = MOORDYN_SUCCESS;
 	string err_msg;
-	try {
-		_t_integrator = create_time_scheme(t_integrator_name, _log);
-	}
-	MOORDYN_CATCHER(err, err_msg);
-	if (err != MOORDYN_SUCCESS) {
-		LOGERR << err_msg << endl;
-		return err;
+	if (!_t_integrator) {
+		try {
+			_t_integrator = create_time_scheme("RK2", _log);
+		}
+		MOORDYN_CATCHER(err, err_msg);
+		if (err != MOORDYN_SUCCESS) {
+			LOGERR << err_msg << endl;
+			return err;
+		}
 	}
 	LOGMSG << "Time integrator = " << _t_integrator->GetName() << endl;
 	_t_integrator->SetGround(GroundBody);
@@ -1892,27 +1363,526 @@ moordyn::MoorDyn::ReadInFile()
 		_t_integrator->AddLine(obj);
 
 	// Setup the waves and populate them
-	waves = new moordyn::Waves(_log);
+	waves = std::make_shared<moordyn::Waves>(_log);
 	try {
-		waves->setup(&env, _t_integrator, _basepath.c_str());
+		// TODO - figure out how i want to do this better
+		// because this is horrible. the solution is probably to move EnvCond
+		// to its own .hpp and .cpp file so that it can contain the Seafloor and
+		// can itself be queries about the seafloor in general
+		real tmp = env->WtrDpth;
+		if (seafloor) {
+			env->WtrDpth = -seafloor->getAverageDepth();
+		}
+		waves->setup(env, _t_integrator, _basepath.c_str());
+		env->WtrDpth = tmp;
 	}
 	MOORDYN_CATCHER(err, err_msg);
 	if (err != MOORDYN_SUCCESS)
 		return err;
 
-	GroundBody->setEnv(&env, waves);
+	GroundBody->setEnv(env, waves);
 	for (auto obj : BodyList)
-		obj->setEnv(&env, waves);
+		obj->setEnv(env, waves);
 	for (auto obj : RodList)
-		obj->setEnv(&env, waves);
+		obj->setEnv(env, waves, seafloor);
 	for (auto obj : ConnectionList)
-		obj->setEnv(&env, waves);
+		obj->setEnv(env, waves, seafloor);
 	for (auto obj : LineList)
-		obj->setEnv(&env, waves);
+		obj->setEnv(env, waves, seafloor);
 
 	return MOORDYN_SUCCESS;
 }
 
+moordyn::error_id
+moordyn::MoorDyn::readFileIntoBuffers(vector<string>& in_txt)
+{
+	ifstream in_file(_filepath);
+	if (!in_file.is_open()) {
+		LOGERR << "Error: unable to open file '" << _filepath << "'\n";
+		return MOORDYN_INVALID_INPUT_FILE;
+	}
+
+	while (in_file.good()) {
+		string line_txt;
+		getline(in_file, line_txt);
+		in_txt.push_back(line_txt);
+	}
+	in_file.close();
+	return MOORDYN_SUCCESS;
+}
+
+int
+moordyn::MoorDyn::findStartOfSection(vector<string>& in_txt,
+                                     vector<string> sectionName)
+{
+	int i = 0;
+	while (i < in_txt.size() &&
+	       ((in_txt[i].find("---") == string::npos) ||
+	        !moordyn::str::has(moordyn::str::upper(in_txt[i]), sectionName))) {
+		i++;
+	}
+
+	if (i == in_txt.size())
+		return -1; // indicates section not found
+	if (sectionName[0] == "OPTIONS")
+		i++; // Increment once to get to the options
+	else
+		i += 3; // need to also skip label line and unit line
+
+	return i;
+}
+LineProps*
+moordyn::MoorDyn::readLineProps(string inputText)
+{
+	vector<string> entries = moordyn::str::split(inputText, ' ');
+
+	if (!checkNumberOfEntriesInLine(entries, 10)) {
+		return nullptr;
+	}
+
+	LineProps* obj = new LineProps();
+
+	obj->type = entries[0];
+	obj->d = atof(entries[1].c_str());
+	obj->w = atof(entries[2].c_str());
+	obj->Cdn = atof(entries[6].c_str());
+	obj->Can = atof(entries[7].c_str());
+	obj->Cdt = atof(entries[8].c_str());
+	obj->Cat = atof(entries[9].c_str());
+
+	moordyn::error_id err;
+	err = read_curve(entries[3].c_str(),
+	                 &(obj->EA),
+	                 &(obj->nEApoints),
+	                 obj->stiffXs,
+	                 obj->stiffYs);
+	if (err)
+		return nullptr;
+	err = read_curve(entries[4].c_str(),
+	                 &(obj->c),
+	                 &(obj->nCpoints),
+	                 obj->dampXs,
+	                 obj->dampYs);
+	if (err)
+		return nullptr;
+	err = read_curve(entries[5].c_str(),
+	                 &(obj->EI),
+	                 &(obj->nEIpoints),
+	                 obj->bstiffXs,
+	                 obj->bstiffYs);
+	if (err)
+		return nullptr;
+
+	LOGDBG << "\t'" << obj->type << "'"
+	       << " - with id " << LinePropList.size() << endl
+	       << "\t\td   : " << obj->d << endl
+	       << "\t\tw   : " << obj->w << endl
+	       << "\t\tCdn : " << obj->Cdn << endl
+	       << "\t\tCan : " << obj->Can << endl
+	       << "\t\tCdt : " << obj->Cdt << endl
+	       << "\t\tCat : " << obj->Cat << endl;
+	return obj;
+}
+
+RodProps*
+moordyn::MoorDyn::readRodProps(string inputText)
+{
+	vector<string> entries = moordyn::str::split(inputText, ' ');
+	if (!checkNumberOfEntriesInLine(entries, 7)) {
+		return nullptr;
+	}
+
+	RodProps* obj = new RodProps();
+	obj->type = entries[0];
+	obj->d = atof(entries[1].c_str());
+	obj->w = atof(entries[2].c_str());
+	obj->Cdn = atof(entries[3].c_str());
+	obj->Can = atof(entries[4].c_str());
+	obj->Cdt = atof(entries[5].c_str());
+	obj->Cat = atof(entries[6].c_str());
+
+	LOGDBG << "\t'" << obj->type << "'"
+	       << " - with id " << RodPropList.size() << endl
+	       << "\t\td   : " << obj->d << endl
+	       << "\t\tw   : " << obj->w << endl
+	       << "\t\tCdn : " << obj->Cdn << endl
+	       << "\t\tCan : " << obj->Can << endl
+	       << "\t\tCdt : " << obj->Cdt << endl
+	       << "\t\tCat : " << obj->Cat << endl;
+	return obj;
+}
+
+Body*
+moordyn::MoorDyn::readBody(string inputText)
+{
+	vector<string> entries = moordyn::str::split(inputText, ' ');
+	if (!checkNumberOfEntriesInLine(entries, 14)) {
+		LOGERR << "Error in " << _filepath << ":" << '\n';
+		return nullptr;
+	}
+
+	const int number = atoi(entries[0].c_str());
+	Body::types type;
+	vec6 r6;
+	for (unsigned int I = 0; I < 6; I++)
+		r6[I] = atof(entries[2 + I].c_str());
+	double M = atof(entries[8].c_str());
+	double V = atof(entries[11].c_str());
+
+	vec rCG, Inert;
+	vec6 CdA = vec6::Zero();
+	vec6 Ca = vec6::Zero();
+
+	vector<string> entries_rCG = moordyn::str::split(entries[9], '|');
+	if (entries_rCG.size() == 1) {
+		// if only one entry, it is the z coordinate
+		rCG[0] = 0.0;
+		rCG[1] = 0.0;
+		rCG[2] = atof(entries_rCG[0].c_str());
+	} else if (entries_rCG.size() == 3) {
+		rCG[0] = atof(entries_rCG[0].c_str());
+		rCG[1] = atof(entries_rCG[1].c_str());
+		rCG[2] = atof(entries_rCG[2].c_str());
+	} else {
+		LOGERR << "Error in " << _filepath << ":" << endl
+		       << "'" << inputText << "'" << endl
+		       << "CG entry (col 10) must have 1 or 3 numbers" << endl;
+		return nullptr;
+	}
+
+	vector<string> entries_I = moordyn::str::split(entries[10], '|');
+	if (entries_I.size() == 1) {
+		// if only one entry, use it for all directions
+		Inert[0] = atof(entries_I[0].c_str());
+		Inert[1] = Inert[0];
+		Inert[2] = Inert[0];
+	} else if (entries_I.size() == 3) {
+		Inert[0] = atof(entries_I[0].c_str());
+		Inert[1] = atof(entries_I[1].c_str());
+		Inert[2] = atof(entries_I[2].c_str());
+	} else {
+		LOGERR << "Error in " << _filepath << endl
+		       << "'" << inputText << "'" << endl
+		       << "Inertia entry (col 11) must have 1 or 3 numbers" << endl;
+		return nullptr;
+	}
+
+	vector<string> entries_CdA = moordyn::str::split(entries[12], '|');
+	if (entries_CdA.size() == 1) {
+		// if only one entry, use it for all directions
+		CdA[0] = atof(entries_CdA[0].c_str());
+		CdA[1] = CdA[0];
+		CdA[2] = CdA[0];
+	} else if (entries_CdA.size() == 3) {
+		CdA[0] = atof(entries_CdA[0].c_str());
+		CdA[1] = atof(entries_CdA[1].c_str());
+		CdA[2] = atof(entries_CdA[2].c_str());
+	} else {
+		LOGERR << "Error in " << _filepath << endl
+		       << "'" << inputText << "'" << endl
+		       << "CdA entry (col 13) must have 1 or 3 numbers" << endl;
+		return nullptr;
+	}
+
+	vector<string> entries_Ca = moordyn::str::split(entries[13], '|');
+	if (entries_Ca.size() == 1) {
+		// if only one entry, use it for all directions
+		Ca[0] = atof(entries_Ca[0].c_str());
+		Ca[1] = Ca[0];
+		Ca[2] = Ca[0];
+	} else if (entries_Ca.size() == 3) {
+		Ca[0] = atof(entries_Ca[0].c_str());
+		Ca[1] = atof(entries_Ca[1].c_str());
+		Ca[2] = atof(entries_Ca[2].c_str());
+	} else {
+		LOGERR << "Error in " << _filepath << endl
+		       << "'"
+		       << "'" << endl
+		       << "Ca entry (col 14) must have 1 or 3 numbers" << endl;
+		return nullptr;
+	}
+
+	char let1[10], num1[10], let2[10], num2[10], let3[10];
+	char typeWord[10];
+	strncpy(typeWord, entries[1].c_str(), 9);
+	typeWord[9] = '\0';
+	// divided outWord into letters and numbers
+	str::decomposeString(typeWord, let1, num1, let2, num2, let3);
+
+	if (!strcmp(let1, "ANCHOR") || !strcmp(let1, "FIXED") ||
+	    !strcmp(let1, "FIX")) {
+		// it is fixed  (this would just be used if someone wanted
+		// to temporarly fix a body that things were attached to)
+		type = Body::FIXED;
+	} else if (!strcmp(let1, "COUPLED") || !strcmp(let1, "VESSEL") ||
+	           !strcmp(let1, "VES") || !strcmp(let1, "CPLD")) {
+		// it is coupled - controlled from outside
+		type = Body::COUPLED;
+		CpldBodyIs.push_back(BodyList.size());
+	} else {
+		// it is free - controlled by MoorDyn
+		type = Body::FREE;
+		FreeBodyIs.push_back(BodyList.size());
+		BodyStateIs.push_back(nX); // assign start index of this body's states
+		nX += 12;                  // add 12 state variables for the body
+	}
+	stringstream oname;
+	oname << _basepath << _basename << "_Body" << number << ".out";
+	outfiles.push_back(make_shared<ofstream>(oname.str()));
+	if (!outfiles.back()->is_open()) {
+		LOGERR << "Cannot create the output file '" << oname.str() << endl;
+		return nullptr;
+	}
+
+	Body* obj = new Body(_log);
+	LOGDBG << "\t'" << number << "'"
+	       << " - of type " << Body::TypeName(type) << " with id "
+	       << BodyList.size() << endl;
+	obj->setup(number, type, r6, rCG, M, V, Inert, CdA, Ca, outfiles.back());
+	return obj;
+}
+
+Rod*
+moordyn::MoorDyn::readRod(string inputText)
+{
+
+	vector<string> entries = moordyn::str::split(inputText, ' ');
+	if (!checkNumberOfEntriesInLine(entries, 11)) {
+		return nullptr;
+	}
+
+	int number = atoi(entries[0].c_str());
+	string RodType = entries[1];
+	vec6 endCoords;
+	for (unsigned int I = 0; I < 6; I++)
+		endCoords[I] = atof(entries[3 + I].c_str());
+	int NumSegs = atoi(entries[9].c_str());
+	string outchannels = entries[10];
+
+	char let1[10], num1[10], let2[10], num2[10], let3[10];
+	char typeWord[10];
+	strncpy(typeWord, entries[2].c_str(), 9);
+	typeWord[9] = '\0';
+	// divided outWord into letters and numbers
+	str::decomposeString(typeWord, let1, num1, let2, num2, let3);
+
+	Rod::types type;
+	if (!strcmp(let1, "ANCHOR") || !strcmp(let1, "FIXED") ||
+	    !strcmp(let1, "FIX")) {
+		// it is fixed  (this would just be used if someone wanted
+		// to temporarly fix a body that things were attached to)
+		type = Rod::FIXED;
+	} else if (!strcmp(let1, "PINNED") || !strcmp(let1, "PIN")) {
+		// it is pinned
+		type = Rod::PINNED;
+		FreeRodIs.push_back(RodList.size()); // add this pinned rod to the free
+		                                     // list because it is half free
+		RodStateIs.push_back(nX); // assign start index of this rod's states
+		nX += 6;                  // add 6 state variables for each pinned rod
+	} else if (!strcmp(let1, "BODY")) {
+		if (!strlen(num1)) {
+			LOGERR << "Error in " << _filepath << ":"
+			       << "'" << inputText << "'" << endl
+			       << "no number provided for Rod " << number
+			       << " Body attachment" << endl;
+			return nullptr;
+		}
+		unsigned int bodyID = atoi(num1);
+		if (!bodyID || (bodyID > BodyList.size())) {
+			LOGERR << "Error in " << _filepath << ":"
+			       << "'" << inputText << "'" << endl
+			       << "There is not " << bodyID << " bodies" << endl;
+			return nullptr;
+		}
+
+		if (!strcmp(let2, "PINNED") || !strcmp(let2, "PIN")) {
+			// it is pinned
+			type = Rod::PINNED;
+			FreeRodIs.push_back(
+			    RodList.size());      // add this pinned rod to the free
+			                          // list because it is half free
+			RodStateIs.push_back(nX); // assign start index of this rod's states
+			nX += 6; // add 6 state variables for each pinned rod
+		} else {
+			type = Rod::FIXED;
+		}
+	} else if (!strcmp(let1, "VESSEL") || !strcmp(let1, "VES") ||
+	           !strcmp(let1, "COUPLED") || !strcmp(let1, "CPLD")) {
+		// if a rigid fairlead, add to list and add
+		type = Rod::COUPLED;
+		CpldRodIs.push_back(
+		    RodList.size()); // index of fairlead in RodList vector
+	} else if (!strcmp(let1, "VESPIN") || !strcmp(let1, "CPLDPIN")) {
+		// if a pinned fairlead, add to list and add
+		type = Rod::CPLDPIN;
+		CpldRodIs.push_back(
+		    RodList.size()); // index of fairlead in RodList vector
+		FreeRodIs.push_back(
+		    RodList.size());      // also add this pinned rod to the free
+		                          // list because it is half free
+		RodStateIs.push_back(nX); // assign start index of this rod's states
+		nX += 6;                  // add 6 state variables for each pinned rod
+	} else if (!strcmp(let1, "CONNECT") || !strcmp(let1, "CON") ||
+	           !strcmp(let1, "FREE")) {
+		type = Rod::FREE;
+		FreeRodIs.push_back(
+		    RodList.size());      // add this free rod to the free list
+		RodStateIs.push_back(nX); // assign start index of this rod's states
+		nX += 12;                 // add 12 state variables for each free rod
+	} else {
+		LOGERR << "Error in " << _filepath << ":"
+		       << "'" << inputText << "'" << endl
+		       << "Unrecognized connection type '" << let1 << "'" << endl;
+		return nullptr;
+	}
+
+	int TypeNum = -1;
+	for (unsigned int J = 0; J < RodPropList.size(); J++) {
+		if (RodPropList[J]->type == RodType)
+			TypeNum = J;
+	}
+	if (TypeNum == -1) {
+		LOGERR << "Error in " << _filepath << ":"
+		       << "'" << inputText << "'" << endl
+		       << "Unrecognized rod type " << RodType << endl;
+		return nullptr;
+	}
+
+	// Make the output file (if queried)
+	if ((outchannels.size() > 0) && (strcspn(outchannels.c_str(), "pvUDctsd") <
+	                                 strlen(outchannels.c_str()))) {
+		// if 1+ output flag chars are given and they're valid
+		stringstream oname;
+		oname << _basepath << _basename << "_Rod" << number << ".out";
+		outfiles.push_back(make_shared<ofstream>(oname.str()));
+		if (!outfiles.back()->is_open()) {
+			LOGERR << "Cannot create the output file '" << oname.str() << endl;
+			return nullptr;
+		}
+	} else
+		outfiles.push_back(NULL);
+
+	LOGDBG << "\t'" << number << "'"
+	       << " - of class " << RodType << " (" << TypeNum << ")"
+	       << " and type " << Rod::TypeName(type) << " with id "
+	       << RodList.size() << endl;
+
+	Rod* obj = new Rod(_log);
+	obj->setup(number,
+	           type,
+	           RodPropList[TypeNum],
+	           endCoords,
+	           NumSegs,
+	           outfiles.back(),
+	           outchannels);
+
+	// depending on type, assign the Rod to its respective parent
+	// body
+	if (!strcmp(let1, "ANCHOR") || !strcmp(let1, "FIXED") ||
+	    !strcmp(let1, "FIX"))
+		GroundBody->addRod(obj, endCoords);
+	else if (!strcmp(let1, "PINNED") || !strcmp(let1, "PIN"))
+		GroundBody->addRod(obj, endCoords);
+	else if (!strcmp(let1, "BODY")) {
+		unsigned int bodyID = atoi(num1);
+		BodyList[bodyID - 1]->addRod(obj, endCoords);
+	}
+	LOGDBG << endl;
+
+	return obj;
+}
+
+void
+moordyn::MoorDyn::readOptionsLine(vector<string>& in_txt, int i)
+{
+	vector<string> entries = moordyn::str::split(in_txt[i], ' ');
+	if (entries.size() < 2) {
+		LOGWRN << "Ignoring option line " << i
+		       << " due to unspecified value or option type" << endl;
+		return;
+	}
+
+	LOGDBG << "\t" << entries[1] << " = " << entries[0] << endl;
+	const string value = entries[0];
+	const string name = entries[1];
+
+	// DT is old way, should phase out
+	if ((name == "dtM") || (name == "DT"))
+		dtM0 = atof(entries[0].c_str());
+	else if (name == "tScheme") {
+		moordyn::error_id err = MOORDYN_SUCCESS;
+		string err_msg;
+		try {
+			_t_integrator = create_time_scheme(entries[0], _log);
+		}
+		MOORDYN_CATCHER(err, err_msg);
+		if (err != MOORDYN_SUCCESS) {
+			LOGWRN << "Defaulting to RK2 time integration";
+			LOGERR << err_msg << endl;
+		}
+	} else if ((name == "g") || (name == "gravity"))
+		env->g = atof(entries[0].c_str());
+	else if ((name == "Rho") || (name == "rho") || (name == "WtrDnsty"))
+		env->rho_w = atof(entries[0].c_str());
+	else if (name == "WtrDpth")
+		env->WtrDpth = atof(entries[0].c_str());
+	else if ((name == "kBot") || (name == "kb"))
+		env->kb = atof(entries[0].c_str());
+	else if ((name == "cBot") || (name == "cb"))
+		env->cb = atof(entries[0].c_str());
+	else if ((name == "dtIC") || (name == "ICdt"))
+		ICdt = atof(entries[0].c_str());
+	else if ((name == "TmaxIC") || (name == "ICTmax"))
+		ICTmax = atof(entries[0].c_str());
+	else if ((name == "CdScaleIC") || (name == "ICDfac"))
+		ICDfac = atof(entries[0].c_str());
+	else if ((name == "threshIC") || (name == "ICthresh"))
+		ICthresh = atof(entries[0].c_str());
+	else if (name == "WaveKin") {
+		WaveKinTemp = (moordyn::waves_settings)atoi(entries[0].c_str());
+		if ((WaveKinTemp < WAVES_NONE) || (WaveKinTemp > WAVES_KIN))
+			LOGWRN << "Unknown WaveKin option value " << WaveKinTemp << endl;
+	} else if (name == "dtWave")
+		env->dtWave = atoi(entries[0].c_str());
+	else if (name == "Currents") {
+		env->Current = (moordyn::currents_settings)atoi(entries[0].c_str());
+		if ((env->Current < CURRENTS_NONE) || (env->Current > CURRENTS_4D))
+			LOGWRN << "Unknown Currents option value " << env->Current << endl;
+	} else if (name == "WriteUnits")
+		env->WriteUnits = atoi(entries[0].c_str());
+	else if (name == "FrictionCoefficient")
+		env->FrictionCoefficient = atof(entries[0].c_str());
+	else if (name == "FricDamp")
+		env->FricDamp = atof(entries[0].c_str());
+	else if (name == "StatDynFricScale")
+		env->StatDynFricScale = atof(entries[0].c_str());
+	// output writing period (0 for at every call)
+	else if (name == "dtOut")
+		dtOut = atof(entries[0].c_str());
+	else if (name == "SeafloorFile") {
+		env->SeafloorMode = seafloor_settings::SEAFLOOR_3D;
+		this->seafloor = make_shared<moordyn::Seafloor>(_log);
+		std::string filepath = entries[0];
+		this->seafloor->setup(env, filepath);
+	}
+	else
+		LOGWRN << "Warning: Unrecognized option '" << name << "'" << endl;
+}
+
+bool
+moordyn::MoorDyn::checkNumberOfEntriesInLine(vector<string> entries,
+                                             int supposedNumberOfEntries)
+{
+	if (entries.size() < supposedNumberOfEntries) {
+		LOGERR << "Error in " << _filepath << ":" << endl
+		       << supposedNumberOfEntries << " fields are required, but just "
+		       << entries.size() << " are provided" << endl;
+		return false;
+	}
+
+	return true;
+}
+
 void
 moordyn::MoorDyn::detachLines(FailProps* failure)
 {
@@ -1922,7 +1892,8 @@ moordyn::MoorDyn::detachLines(FailProps* failure)
 		       << endl;
 		throw moordyn::unhandled_error("Invalid failure data");
 	} else if (!failure->rod && !failure->conn) {
-		LOGERR << "The failure criteria is missing either a rod or a connection"
+		LOGERR << "The failure criteria is missing either a rod or a "
+		          "connection"
 		       << endl;
 		throw moordyn::unhandled_error("Invalid failure data");
 	}
@@ -1946,7 +1917,7 @@ moordyn::MoorDyn::detachLines(FailProps* failure)
 	// now make Connection object!
 	Connection* obj = new Connection(_log);
 	obj->setup(ConnectionList.size() + 1, type, r0, M, V, F, CdA, Ca);
-	obj->setEnv(&env, waves);
+	obj->setEnv(env, waves, seafloor);
 	ConnectionList.push_back(obj);
 
 	// Kinematics of old attachment point
@@ -1960,8 +1931,8 @@ moordyn::MoorDyn::detachLines(FailProps* failure)
 		std::tie(pos, vel) = failure->conn->getState();
 	}
 
-	// detach lines from old Rod or Connection, and get kinematics of the old
-	// attachment point
+	// detach lines from old Rod or Connection, and get kinematics of the
+	// old attachment point
 	for (unsigned int i = 0; i < failure->lines.size(); i++) {
 		if (failure->rod)
 			failure->line_end_points[i] = failure->rod->removeLine(
@@ -2136,7 +2107,15 @@ MoorDyn_GetWaves(MoorDyn system)
 {
 	if (!system)
 		return NULL;
-	return (MoorDynWaves)(((moordyn::MoorDyn*)system)->GetWaves());
+	return (MoorDynWaves)(((moordyn::MoorDyn*)system)->GetWaves().get());
+}
+
+MoorDynSeafloor DECLDIR MoorDyn_GetSeafloor(MoorDyn system) 
+{
+	if (!system)
+		return NULL;
+	return (MoorDynSeafloor)(((moordyn::MoorDyn*)system)->GetSeafloor().get());
+	
 }
 
 int DECLDIR
diff --git a/source/MoorDyn2.h b/source/MoorDyn2.h
index ce58ca7b..32fb56a8 100644
--- a/source/MoorDyn2.h
+++ b/source/MoorDyn2.h
@@ -37,6 +37,7 @@
 
 #include "MoorDynAPI.h"
 #include "Waves.h"
+#include "Seafloor.h"
 #include "Connection.h"
 #include "Rod.h"
 #include "Line.h"
@@ -210,6 +211,14 @@ extern "C"
 	 */
 	MoorDynWaves DECLDIR MoorDyn_GetWaves(MoorDyn system);
 
+	/** @brief Get the 3D seafloor instance
+	 *
+	 * The seafloor instance is only not null if a SeafloorPath was given as an option.
+	 * @param system The Moordyn system
+	 * @return The Seafloor instance, NULL if errors happened or there is no 3D seafloor
+	 */
+	MoorDynSeafloor DECLDIR MoorDyn_GetSeafloor(MoorDyn system);
+
 	/** @brief Initializes the external Wave kinetics
 	 *
 	 * This is useless unless WaveKin option is set to 1 in the input file. If
diff --git a/source/MoorDyn2.hpp b/source/MoorDyn2.hpp
index 57beff91..b2ded9a4 100644
--- a/source/MoorDyn2.hpp
+++ b/source/MoorDyn2.hpp
@@ -45,6 +45,7 @@
 #include "Connection.hpp"
 #include "Rod.hpp"
 #include "Body.hpp"
+#include "Seafloor.hpp"
 
 #ifdef USE_VTK
 #include <vtkSmartPointer.h>
@@ -159,7 +160,15 @@ class MoorDyn : public io::IO
 	 * env.Currents is not CURRENTS_NONE
 	 * @return The wave knematics instance
 	 */
-	inline moordyn::Waves* GetWaves() const { return waves; }
+	inline moordyn::WavesRef GetWaves() const { return waves; }
+	
+	/** @brief Get the 3D seafloor instance
+	 * 
+	 * The Seafloor instance is used to represent a floor of varying depth over
+	 * a rectilinear grid. It allows for finding the seafloor height at any (x, y)
+	 * point which it calculated using a bilinear interpolation.
+	 */
+	inline moordyn::SeafloorRef GetSeafloor() const { return seafloor; }
 
 	/** @brief Initializes the external Wave kinetics
 	 *
@@ -354,6 +363,38 @@ class MoorDyn : public io::IO
 	 */
 	moordyn::error_id ReadInFile();
 
+	moordyn::error_id readFileIntoBuffers(vector<string> &in_txt);
+
+	int findStartOfSection(vector<string> &in_txt, vector<string> sectionName);
+
+	/** @brief Helper function to cread a new line property given a line from
+	* the input file.
+	* 
+	* @param inputText a string from the Line Properties section of input file
+	*/
+	LineProps* readLineProps(string inputText);
+
+	/** @brief Helper function to cread a new rod property given a line from
+	* the input file.
+	* 
+	* @param inputText a string from the Rod Properties section of input file
+	*/
+	RodProps* readRodProps(string inputText);
+
+	/** @brief Helper function to cread a new rod given a line from
+	* the input file.
+	* 
+	* @param inputText a string from the Rod List section of input file
+	*/
+	Rod* readRod(string inputText);
+
+	Body* readBody(string inputText);
+
+	void readOptionsLine(vector<string>& in_txt, int index);
+
+	bool checkNumberOfEntriesInLine(vector<string> entries,
+	                                int supposedNumberOfEntries);
+
 	/** @brief Get the forces
 	 * @param f The forces array
 	 * @return MOORDYN_SUCCESS If the forces are correctly set, an error code
@@ -434,11 +475,13 @@ class MoorDyn : public io::IO
 	TimeScheme* _t_integrator;
 
 	/// General options of the Mooryng system
-	EnvCond env;
+	EnvCondRef env;
 	/// The ground body, which is unique
 	Body* GroundBody;
 	/// Waves object that will be created to hold water kinematics info
-	Waves* waves = NULL;
+	WavesRef waves = nullptr;
+	/// 3D Seafloor object that gets shared with the lines and other things that need it
+	moordyn::SeafloorRef seafloor;
 
 	/// array of pointers to hold line library types
 	vector<LineProps*> LinePropList;
@@ -523,14 +566,14 @@ class MoorDyn : public io::IO
 		// env.writeLog = 1 -> MOORDYN_WRN_LEVEL
 		// env.writeLog = 2 -> MOORDYN_MSG_LEVEL
 		// env.writeLog >= 3 -> MOORDYN_DBG_LEVEL
-		int log_level = MOORDYN_ERR_LEVEL - env.writeLog;
+		int log_level = MOORDYN_ERR_LEVEL - env->writeLog;
 		if (log_level >= MOORDYN_ERR_LEVEL)
 			log_level = MOORDYN_NO_OUTPUT;
 		if (log_level < MOORDYN_DBG_LEVEL)
 			log_level = MOORDYN_DBG_LEVEL;
 		GetLogger()->SetLogLevel(log_level);
 
-		if (env.writeLog > 0) {
+		if (env->writeLog > 0) {
 			moordyn::error_id err = MOORDYN_SUCCESS;
 			string err_msg;
 			stringstream filepath;
diff --git a/source/Rod.cpp b/source/Rod.cpp
index f50cf445..15c63f70 100644
--- a/source/Rod.cpp
+++ b/source/Rod.cpp
@@ -54,6 +54,7 @@ namespace moordyn {
 
 Rod::Rod(moordyn::Log* log)
   : io::IO(log)
+  , seafloor(nullptr)
 {
 }
 
@@ -1016,9 +1017,10 @@ Rod::doRHS()
 			// contact
 			B[i][0] = 0.0;
 			B[i][1] = 0.0;
-			if (r[i][2] < -env->WtrDpth)
+			real waterDepth = getWaterDepth(r[i][0], r[i][1]);
+			if (r[i][2] < waterDepth)
 				B[i][2] =
-				    ((-env->WtrDpth - r[i][2]) * env->kb - rd[i][2] * env->cb) *
+				    ((waterDepth - r[i][2]) * env->kb - rd[i][2] * env->cb) *
 				    d * dL;
 			else {
 				B[i][2] = 0.0;
diff --git a/source/Rod.hpp b/source/Rod.hpp
index 82c475a3..9ea38180 100644
--- a/source/Rod.hpp
+++ b/source/Rod.hpp
@@ -36,6 +36,7 @@
 
 #include "Misc.hpp"
 #include "IO.hpp"
+#include "Seafloor.hpp"
 #include <vector>
 #include <utility>
 
@@ -49,6 +50,7 @@ using namespace std;
 namespace moordyn {
 
 class Waves;
+typedef std::shared_ptr<Waves> WavesRef;
 class Line;
 
 /** @class Rod Rod.hpp
@@ -75,9 +77,11 @@ class Rod : public io::IO
   private:
 	// ENVIRONMENTAL STUFF
 	/// Global struct that holds environmental settings
-	EnvCond* env;
+	EnvCondRef env;
 	/// global Waves object
-	moordyn::Waves* waves;
+	moordyn::WavesRef waves;
+	/// Object containing the 3d seafloor info
+	moordyn::SeafloorRef seafloor;
 
 	/// Attached lines to the rod
 	typedef struct _attachment
@@ -223,6 +227,15 @@ class Rod : public io::IO
 	 * @}
 	 */
 
+	/** @brief Finds the depth of the water at some (x, y) point. Either using env->WtrDpth or the 3D seafloor if available
+	 * @param x x coordinate
+	 * @param y y coordinate
+	 * @return A negative number representing the sea floor depth at the given location
+	*/
+	inline real getWaterDepth(real x, real y) {
+		return seafloor ? seafloor->getDepthAt(x, y) : -env->WtrDpth;
+	}
+
   public:
 	/** @brief Types of rods
 	 */
@@ -323,10 +336,11 @@ class Rod : public io::IO
 	 * @param env_in Global struct that holds environmental settings
 	 * @param waves_in Global Waves object
 	 */
-	inline void setEnv(EnvCond* env_in, moordyn::Waves* waves_in)
+	inline void setEnv(EnvCondRef env_in, moordyn::WavesRef waves_in, moordyn::SeafloorRef seafloor_in)
 	{
 		env = env_in;
 		waves = waves_in;
+		seafloor = seafloor_in;
 	}
 
 	/** @brief Initialize the rod state
diff --git a/source/Seafloor.cpp b/source/Seafloor.cpp
new file mode 100644
index 00000000..409ea0b6
--- /dev/null
+++ b/source/Seafloor.cpp
@@ -0,0 +1,177 @@
+#include "Seafloor.hpp"
+#include "Seafloor.h"
+#include <limits>
+
+namespace moordyn {
+
+Seafloor::Seafloor(moordyn::Log* log)
+  : LogUser(log)
+  , averageDepth(0)
+  , minDepth(-std::numeric_limits<real>::infinity())
+{
+}
+
+Seafloor::~Seafloor() {}
+
+unsigned int
+calcInsertIndex(std::vector<real>& list, real value)
+{
+	for (unsigned int i = 0; i < list.size(); i++) {
+		if (list[i] > value) {
+			return i == 0 ? 0 : i - 1;
+		}
+	}
+	return (unsigned int)(list.size() - 1);
+}
+void
+Seafloor::setup(EnvCondRef env, const string& filepath)
+{
+	// Initialize grid lengths:
+	nx = 0;
+	ny = 0;
+
+	if ((env->SeafloorMode == moordyn::SEAFLOOR_FLAT)) {
+		// In the case where we have a flat seafloor, we shouldn't
+		// actually build this object. In case we attempt to, the
+		// setup function will simply return
+		LOGMSG << "No seafloor file required. Assuming flat seafloor.\n";
+		return;
+	}
+
+	if ((env->SeafloorMode) == moordyn::SEAFLOOR_3D) {
+		LOGDBG << "Seafloor set to 3D mode.";
+		// const string SeafloorFilename =
+		//     (string)folder + "/seafloor_profile_3d.txt";
+		LOGMSG << "Reading seafloor from " << filepath << '\n';
+
+		vector<string> fLines; // Buffer to load file into line-by-line
+		string fLine;          // Buffer to process each line of input file
+
+		// Read file into buffers:
+		ifstream f(filepath);
+		if (!f.is_open()) {
+			LOGERR << "Cannot read the file " << filepath << '\n';
+			throw moordyn::input_file_error("Failure reading depths file");
+		}
+
+		while (getline(f, fLine)) {
+			fLines.push_back(fLine);
+		}
+		f.close();
+
+		if (fLines.size() < 4) {
+			// Basic input checking. Input file should contain
+			// 1st line indicating dimensions of x/y axes,
+			// 2nd and 3rd line denoting axis tick vals,
+			// and at least one line specifying a particular xyz val
+			LOGERR << "The file '" << filepath
+			       << "' should have at least 4 lines\n";
+			throw moordyn::input_file_error("Invalid file format");
+		}
+
+		// Setup nx, ny
+		vector<string> entries = moordyn::str::split(fLines[0]);
+		nx = stoi(entries[0]);
+		ny = stoi(entries[1]);
+
+		// Setup px (x-axis ticks):
+		entries = moordyn::str::split(fLines[1]);
+		if (entries.size() != nx) {
+			LOGERR << "There should be " << nx << " entries in line 2"
+			       << " of the input file.\n";
+			throw moordyn::input_file_error("Invalid no. of x ticks");
+		}
+		for (string entry : entries) {
+			px.push_back(stof(entry));
+		}
+
+		// Setup py (y-axis ticks):
+		entries = moordyn::str::split(fLines[2]);
+		if (entries.size() != ny) {
+			LOGERR << "There should be " << ny << " entries in line 3"
+			       << " of the input file.\n";
+			throw moordyn::input_file_error("Invalid no. of y ticks");
+		}
+		for (string entry : entries) {
+			py.push_back(stof(entry));
+		}
+
+		// initialize depths grid (full of zeroes to begin with):
+		depthGrid =
+		    std::vector<std::vector<real>>(nx, std::vector<real>(ny, 0.0));
+
+		real depthTotal = 0.0;
+		for (unsigned int i = 3; i < fLines.size(); i++) {
+			// This loop iterates all the (x,y,z) entries in the input file
+			entries = moordyn::str::split(fLines[i]);
+			if (entries.size() != 3) {
+				LOGERR << "Line " << i + 1 << " of '" << filepath
+				       << "' should have 3 entries (x, y, z)\n";
+				throw moordyn::input_file_error("Invalid file format");
+			}
+
+			real xPos = stof(entries[0]);
+			real yPos = stof(entries[1]);
+			real depth = stof(entries[2]);
+			unsigned int xIdx = calcInsertIndex(px, xPos);
+			unsigned int yIdx = calcInsertIndex(py, yPos);
+			depthGrid[xIdx][yIdx] = depth;
+			depthTotal += depth;
+			if (depth > minDepth) {
+				minDepth = depth;
+			}
+		}
+		averageDepth = depthTotal / (real)(nx * ny);
+
+	} else {
+		// Handle case where we specified an inappropriate flag
+		// for seafloor depths:
+		LOGERR << "ERROR: Invalid Seafloor depth mode specified. "
+		       << "EXITING.";
+		throw moordyn::invalid_value_error("Invalid depth mode\n.");
+	}
+}
+
+real
+Seafloor::getDepthAt(real x, real y)
+{
+	real fx, fy;
+
+	auto ix = interp_factor(px, x, fx);
+	auto iy = interp_factor(py, y, fy);
+
+	return interp2(depthGrid, ix, iy, fx, fy);
+}
+
+}
+
+#define CHECK_SEAFLOOR(s)                                                      \
+	if (!s) {                                                                  \
+		cerr << "Null seafloor instance received in " << __FUNC_NAME__ << " (" \
+		     << XSTR(__FILE__) << ":" << __LINE__ << ")" << endl;              \
+		return MOORDYN_INVALID_VALUE;                                          \
+	}
+
+int DECLDIR
+MoorDyn_GetDepthAt(MoorDynSeafloor seafloor, double x, double y, double* depth)
+{
+	CHECK_SEAFLOOR(seafloor);
+	*depth = ((moordyn::Seafloor*)seafloor)->getDepthAt(x, y);
+	return MOORDYN_SUCCESS;
+}
+
+int DECLDIR
+MoorDyn_GetAverageDepth(MoorDynSeafloor seafloor, double* avgDepth)
+{
+	CHECK_SEAFLOOR(seafloor);
+	*avgDepth = ((moordyn::Seafloor*)seafloor)->getAverageDepth();
+	return MOORDYN_SUCCESS;
+}
+
+int DECLDIR
+MoorDyn_GetMinDepth(MoorDynSeafloor seafloor, double* minDepth)
+{
+	CHECK_SEAFLOOR(seafloor);
+	*minDepth = ((moordyn::Seafloor*)seafloor)->getMinimumDepth();
+	return MOORDYN_SUCCESS;
+}
\ No newline at end of file
diff --git a/source/Seafloor.h b/source/Seafloor.h
new file mode 100644
index 00000000..205f7880
--- /dev/null
+++ b/source/Seafloor.h
@@ -0,0 +1,53 @@
+/** @file Seafloor.h
+ * C API for the moordyn::Seafloor object
+ */
+
+#ifndef SEAFLOOR_H
+#define SEAFLOOR_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+	/// A 3D Seafloor instance
+	typedef struct __MoorDynSeafloor* MoorDynSeafloor;
+
+	/** @brief Get the depth of the seafloor at some x and y
+	 * @param seafloor The Seafloor instance
+	 * @param x The x coordinate
+	 * @param y The y coordinate
+	 * @param depth The output seafloor depth at that (x, y) point
+	 * @return 0 If the data is correctly set, an error code otherwise
+	 * (see @ref moordyn_errors)
+	 */
+	int DECLDIR MoorDyn_GetDepthAt(MoorDynSeafloor seafloor,
+	                               double x,
+	                               double y,
+	                               double* depth);
+
+	/** @brief Get the average of depth of the seafloor
+	 * This value is calculated as the average value of every depth point.
+	 * If the rectilinear grid is not even, this average may not be the actual
+	 * average depth of the surface the data describes.
+	 * @param seafloor The Seafloor instance
+	 * @param avgDepth The output average seafloor depth
+	 * @return 0 If the data is correctly set, an error code otherwise
+	 * (see @ref moordyn_errors)
+	 */
+	int DECLDIR MoorDyn_GetAverageDepth(MoorDynSeafloor seafloor,
+	                                    double* avgDepth);
+
+	/** @brief The depth of the seafloor at the shallowest point
+	 * @param seafloor The Seafloor instance
+	 * @param minDepth The output minimum seafloor depth
+	 * @return 0 If the data is correctly set, an error code otherwise
+	 * (see @ref moordyn_errors)
+	 */
+	int DECLDIR MoorDyn_GetMinDepth(MoorDynSeafloor seafloor, double* minDepth);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // SEAFLOOR_H
diff --git a/source/Seafloor.hpp b/source/Seafloor.hpp
new file mode 100644
index 00000000..c91aa2e0
--- /dev/null
+++ b/source/Seafloor.hpp
@@ -0,0 +1,85 @@
+/**
+* Copyright (c) 2023, Kelson Marine Co.
+* 
+* This class is used to represent a three-dimensional
+* seafloor for use in a MoorDyn simulation.
+/
+
+/** @file Seafloor.hpp
+* C++ API for 3D seafloor object.
+*/
+#pragma once
+
+#include "Misc.hpp"
+#include "Log.hpp"
+#include <vector>
+#include <map>
+
+namespace moordyn {
+/** @class Seafloor Seafloor.hpp
+* @brief 
+*/
+class Seafloor : LogUser
+{
+  public:
+	Seafloor(moordyn::Log* log);
+	~Seafloor();
+
+
+	/** @brief Setup the seafloor 
+	 *
+	 * Always call this function after the construtor
+	 * @param env The enviromental options
+	 * @param folder The root folder where the wave data can be found
+	 * @throws moordyn::input_file_error If an input file cannot be read, or if
+	 * a file is ill-formatted
+	 * @throws moordyn::invalid_value_error If invalid values are found
+	 * @throws moordyn::mem_error If there were roblems allocating memory
+	 * @throws moordyn::output_file_error If data cannot be written in \p folder
+	 */
+	void setup(EnvCondRef env, const string& filepath);
+
+	/** @brief Get the depth at a particular x/y coordinate
+	* This should default to nearest edge depth if beyond the
+	* grid where depths have been explicitly defined.
+	* 
+	* @param x The x-coordinate of the point being assessed
+	* @param y The x-coordinate of the point being assessed
+	*/
+	real getDepthAt(real x, real y);
+
+	/** @brief The average of the depth at all the grid points
+	 * 
+	*/
+	real getAverageDepth() {
+		return averageDepth;
+	}
+	
+	/** @brief The depth of the seafloor at the shallowest point
+	 * Potentially useful for optimizing collision against the seafloor
+	*/
+	real getMinimumDepth() {
+		return minDepth;
+	}
+  private:
+	/// number of grid points (ticks) in x direction
+	unsigned int nx;
+	/// number of grid points (ticks) in y direction
+	unsigned int ny;
+
+	/// grid x coordinate arrays (indicating tick values)
+	std::vector<real> px;
+	/// grid y coordinate arrays (indicating tick values)
+	std::vector<real> py;
+
+	/// Seafloor depth grid (nx by ny grid of z vals)
+	std::vector<std::vector<real>> depthGrid;
+
+	/// the average of the depth at the grid points
+	real averageDepth;
+	/// the minimum depth of a grid point
+	real minDepth;
+};
+
+typedef std::shared_ptr<Seafloor> SeafloorRef;
+}
diff --git a/source/State.hpp b/source/State.hpp
index e178dd69..aec7a07f 100644
--- a/source/State.hpp
+++ b/source/State.hpp
@@ -92,7 +92,7 @@ class StateVar
 /** @class StateVarDeriv Time.hpp
  * @brief Generic state variables derivative
  *
- * This is holding the velocitites and accelerations
+ * This is holding the velocities and accelerations
  */
 template<class T>
 class StateVarDeriv
diff --git a/source/Waves.cpp b/source/Waves.cpp
index 180256ef..49376480 100644
--- a/source/Waves.cpp
+++ b/source/Waves.cpp
@@ -219,7 +219,7 @@ Waves::allocateKinematicsArrays()
 }
 
 void
-Waves::setup(EnvCond* env, TimeScheme* t, const char* folder)
+Waves::setup(EnvCondRef env, TimeScheme* t, const char* folder)
 {
 	dtWave = env->dtWave;
 	rho_w = env->rho_w;
@@ -259,6 +259,10 @@ Waves::setup(EnvCond* env, TimeScheme* t, const char* folder)
 			LOGDBG << "Current only: option TBD4 - "
 			       << "read in dynamic current profile, node approach "
 			       << "(current_profile_dynamic.txt)" << endl;
+		else if (env->Current == moordyn::CURRENTS_4D)
+			LOGDBG << "Current only: option 4D - read in current profile, grid "
+			       << "approach (current_profile_4d.txt"
+			       << endl;
 		else {
 			LOGDBG << "Invald current input settings (must be 0-4)" << endl;
 			throw moordyn::invalid_value_error("Invalid settings");
@@ -358,7 +362,7 @@ Waves::setup(EnvCond* env, TimeScheme* t, const char* folder)
 		}
 
 		// Interpolate/check frequency data
-		real dw = std::numeric_limits<real>::max();
+		real dw = std::numeric_limits<real>::max();  // Change in wave freq.
 		for (unsigned int i = 1; i < wavefreqs.size(); i++)
 			if (wavefreqs[i] - wavefreqs[i - 1] < dw)
 				dw = wavefreqs[i] - wavefreqs[i - 1];
@@ -758,31 +762,284 @@ Waves::setup(EnvCond* env, TimeScheme* t, const char* folder)
 					az[0][0][iz][it] = 0.0;
 				}
 			}
+		}
+		else {
+			// fill in output arrays
+			real ft;
+			unsigned iti = 1;
+			for (unsigned int iz = 0; iz < nz; iz++) {
+				for (unsigned int it = 0; it < nt; it++) {
+					// need to set iti, otherwise it will lock to final t after
+					// one pass through initially the upper index we check
+					// should always be one timestep ahead of it
+					iti = it + 1;
+					iti = interp_factor(UProfileT, iti, it * dtWave, ft);
+					ux[0][0][iz][it] = UProfileUx[iz][iti] * ft +
+					                   UProfileUx[iz][iti - 1] * (1. - ft);
+					uy[0][0][iz][it] = UProfileUy[iz][iti] * ft +
+					                   UProfileUy[iz][iti - 1] * (1. - ft);
+					uz[0][0][iz][it] = UProfileUz[iz][iti] * ft +
+					                   UProfileUz[iz][iti - 1] * (1. - ft);
+					// TODO: approximate fluid accelerations using finite
+					//       differences
+					ax[0][0][iz][it] = 0.0;
+					ay[0][0][iz][it] = 0.0;
+					az[0][0][iz][it] = 0.0;
+				}
+			}
+		}
+	} else if (env->Current == CURRENTS_4D) {
+		const string CurrentsFilename =
+		    (string)folder + "current_profile_4d.txt";
+		LOGMSG << "Reading 4d currents dynamic profile from '" << CurrentsFilename
+		       << "'..." << endl;
+
+		vector<string> lines;
+		string line;
+
+		ifstream f(CurrentsFilename);
+		if (!f.is_open()) {
+			LOGERR << "Cannot read the file '" << CurrentsFilename << "'"
+			       << endl;
+			throw moordyn::input_file_error("Invalid file");
+		}
+		while (getline(f, line)) {
+			lines.push_back(line);
+		}
+		f.close();
+
+		// A better check here is that the grid is at least as large as the
+		// waves grid, i.e. nxin * nyin * nzin * ntin >= size of waves
+		if (lines.size() < 7) { // TODO: Remove this check? Depends on final format
+			LOGERR << "The file '" << CurrentsFilename
+			       << "' should have at least 7 lines" << endl;
+			throw moordyn::input_file_error("Invalid file format");
+		}
+
+		// The first line must contain the number of x, y, z, t values in the input
+		// meshgrid (1-indexed, space delimited)
+		vector<string> gridDimensions = moordyn::str::split(lines[0]);
+		auto nxCurGrid = atoi(gridDimensions[0].c_str());
+		auto nyCurGrid = atoi(gridDimensions[1].c_str());
+		auto nzCurGrid = atoi(gridDimensions[2].c_str());
+		auto ntCurGrid = atoi(gridDimensions[3].c_str());
+
+
+		// Next four rows specify grid points x, y, t, z
+		vector<real> UProfileX;
+		vector<real> UProfileY;
+		vector<real> UProfileZ;
+		vector<real> UProfileT;
+
+		// Need to keep track of 
+
+		// And a map to keep track of coord->idx mapping...
+		map<string, int> XMap;
+		map<string, int> YMap;
+		map<string, int> ZMap;
+		map<string, int> TMap;
+
+		auto entry = moordyn::str::split(lines[1]);
+		for (int i = 0; i < nxCurGrid; i++) {
+			UProfileX.push_back(atof(entry[i].c_str()));
+			XMap[entry[i]] = i;
+		}
+
+		entry = moordyn::str::split(lines[2]);
+		for (int i = 0; i < nyCurGrid; i++) {
+			UProfileY.push_back(atof(entry[i].c_str()));
+			YMap[entry[i]] = i;
+		}
+
+		entry = moordyn::str::split(lines[3]);
+		for (int i = 0; i < nzCurGrid; i++) {
+			UProfileZ.push_back(atof(entry[i].c_str()));
+			ZMap[entry[i]] = i;
+		}
+		
+		entry = moordyn::str::split(lines[4]);
+		for (int i = 0; i < ntCurGrid; i++) {
+			UProfileT.push_back(atof(entry[i].c_str()));
+			TMap[entry[i]] = i;
+		}
+
+		// Need 3 4D grids - one for each component of current velocity:
+		vector<vector<vector<vector<real>>>> currentGridUx =
+			init4DArray(nxCurGrid, nyCurGrid, nzCurGrid, ntCurGrid);
+		vector<vector<vector<vector<real>>>> currentGridUy =
+			init4DArray(nxCurGrid, nyCurGrid, nzCurGrid, ntCurGrid);
+		vector<vector<vector<vector<real>>>> currentGridUz =
+			init4DArray(nxCurGrid, nyCurGrid, nzCurGrid, ntCurGrid);
+		
+		// Number of points in the current grid (important for iteration):
+		unsigned int nCurGridPoints =
+			nxCurGrid * nyCurGrid * nzCurGrid * ntCurGrid;
+
+		// Need to ensure that there is an entry for each gridpoint:
+		if (lines.size() < nCurGridPoints + 5) {
+			LOGERR << "The file'" << CurrentsFilename
+				   << "' should have a line for each gridpoint\n";
+			throw moordyn::input_file_error("Invalid file format\n");
+		}
+
+		// Read values into 4D array
+		for (int i = 6; i < lines.size(); i++) {
+			vector<string> entry = moordyn::str::split(lines[i]);
+
+			// Need to get indices (don't match coord values)
+			auto ix = XMap[entry[0]];
+			auto iy = YMap[entry[1]];
+			auto iz = ZMap[entry[2]];
+			auto it = TMap[entry[3]];
+
+			// Need to set velocity components in three separate vectors
+			// at the coordinate above
+			currentGridUx[ix][iy][iz][it] = atof(entry[4].c_str());
+			currentGridUy[ix][iy][iz][it] = atof(entry[5].c_str());
+			currentGridUz[ix][iy][iz][it] = atof(entry[6].c_str());
+		}
+
+
+		// check data
+		if ((nx * ny * nz != 0) && !nt) {
+			LOGERR << "At least one time step of current data read from '"
+			       << CurrentsFilename << "', but nt = 0" << endl;
+			throw moordyn::invalid_value_error("No time data");
+		}
+
+		// interpolate and add data to wave kinematics grid
+		if (nx * ny * nz == 0) {
+			// A grid hasn't been set up yet, make it based on the read-in z
+			// values. In this case, since we have 4D currents, we need to
+			// modify the CURRENTS_DYNAMIC case to add x and y axes to the
+			// current grid as well.
+			nx = UProfileX.size();
+			px.assign(nx, 0.0);
+			ny = UProfileY.size();
+			py.assign(ny, 0.0);
+			nz = UProfileZ.size();
+			pz.assign(nz, 0.0);
+			for (unsigned int i = 0; i < nx; i++)
+				px[i] = UProfileX[i];
+			for (unsigned int i = 0; i < ny; i++)
+				py[i] = UProfileY[i];
+			for (unsigned int i = 0; i < nz; i++)
+				pz[i] = UProfileZ[i];
+
+			// set the time step size to be the smallest interval in the
+			// inputted times
+			dtWave = std::numeric_limits<real>::max();
+			for (unsigned int i = 1; i < ntCurGrid; i++)
+				if (UProfileT[i] - UProfileT[i - 1] < dtWave)
+					dtWave = UProfileT[i] - UProfileT[i - 1];
+			nt = floor(UProfileT[ntCurGrid - 1] / dtWave) + 1;  // set number of timesteps
+
+			try {
+				allocateKinematicsArrays();
+			} catch (...) {
+				throw;
+			}
+
+			// fill in output arrays
+			real fx;
+			real fy;
+			real fz;
+			real ft;
+			unsigned ixi = 1;
+			for (unsigned int ix = 0; ix < nx; ix++) {
+				unsigned iyi = 1;
+				ixi = interp_factor(UProfileX, ixi, px[ix], fx);
+				for (unsigned int iy = 0; iy < ny; iy++) {
+					unsigned izi = 1;
+					iyi = interp_factor(UProfileY, iyi, py[iy], fy);
+					for (unsigned int iz = 0; iz < nz; iz++) {
+						unsigned iti = 1;
+						izi = interp_factor(UProfileZ, izi, pz[iz], fz);
+						for (unsigned int it = 0; it < nt; it++) {
+							iti = interp_factor(UProfileT, iti, it*dtWave, ft);
+							ux[ix][iy][iz][it] += interp4(currentGridUx,
+							                              ixi,
+							                              iyi,
+							                              izi,
+							                              iti,
+							                              fx,
+							                              fy,
+							                              fz,
+							                              ft);
+							uy[ix][iy][iz][it] += interp4(currentGridUy,
+							                              ixi,
+							                              iyi,
+							                              izi,
+							                              iti,
+							                              fx,
+							                              fy,
+							                              fz,
+							                              ft);
+							uz[ix][iy][iz][it] += interp4(currentGridUz,
+							                              ixi,
+							                              iyi,
+							                              izi,
+							                              iti,
+							                              fx,
+							                              fy,
+							                              fz,
+							                              ft);
+							ax[ix][iy][iz][it] = 0.0;  // TODO: approximate acceleration using finite differences
+							ay[ix][iy][iz][it] = 0.0;
+							az[ix][iy][iz][it] = 0.0;
+						}
+					}
+				}
+			}
 		} else // otherwise interpolate read in data and add to existing grid
 		       // (dtWave, px, etc are already set in the grid)
 		{
+			real fx;
+			real fy;
 			real fz;
-			unsigned izi = 1;
-			for (unsigned int iz = 0; iz < nz; iz++) {
-				izi = interp_factor(UProfileZ, izi, pz[iz], fz);
-				real ft;
-				unsigned iti = 1;
-				for (unsigned int it = 0; it < nt; it++) {
-					iti = it + 1; // Set initial iti
-					iti = interp_factor(UProfileT, iti, it * dtWave, ft);
-					for (unsigned int ix = 0; ix < nx; ix++) {
-						for (unsigned int iy = 0; iy < ny; iy++) {
-							ux[ix][iy][iz][it] +=
-							    interp2(UProfileUx, izi, iti, fz, ft);
-							uy[ix][iy][iz][it] +=
-							    interp2(UProfileUy, izi, iti, fz, ft);
-							uz[ix][iy][iz][it] +=
-							    interp2(UProfileUz, izi, iti, fz, ft);
-							// TODO: approximate fluid accelerations using
-							//       finite differences
-							ax[0][0][iz][it] = 0.0;
-							ay[0][0][iz][it] = 0.0;
-							az[0][0][iz][it] = 0.0;
+			real ft;
+			unsigned ixi = 1;
+			for (unsigned int ix = 0; ix < nx; ix++) {
+				unsigned iyi = 1;
+				ixi = interp_factor(UProfileX, ixi, px[ix], fx);
+				for (unsigned int iy = 0; iy < ny; iy++) {
+					unsigned izi = 1;
+					iyi = interp_factor(UProfileY, iyi, py[iy], fy);
+					for (unsigned int iz = 0; iz < nz; iz++) {
+						unsigned iti = 1;
+						izi = interp_factor(UProfileZ, izi, pz[iz], fz);
+						for (unsigned int it = 0; it < nt; it++) {
+							iti = interp_factor(UProfileT, iti, it*dtWave, ft);
+							ux[ix][iy][iz][it] += interp4(currentGridUx,
+							                              ixi,
+							                              iyi,
+							                              izi,
+							                              iti,
+							                              fx,
+							                              fy,
+							                              fz,
+							                              ft);
+							uy[ix][iy][iz][it] += interp4(currentGridUy,
+							                              ixi,
+							                              iyi,
+							                              izi,
+							                              iti,
+							                              fx,
+							                              fy,
+							                              fz,
+							                              ft);
+							uz[ix][iy][iz][it] += interp4(currentGridUz,
+							                              ixi,
+							                              iyi,
+							                              izi,
+							                              iti,
+							                              fx,
+							                              fy,
+							                              fz,
+							                              ft);
+							ax[ix][iy][iz][it] = 0.0;  // TODO: approximate acceleration using finite differences
+							ay[ix][iy][iz][it] = 0.0;
+							az[ix][iy][iz][it] = 0.0;
 						}
 					}
 				}
diff --git a/source/Waves.hpp b/source/Waves.hpp
index 35a1929b..4f0795f2 100644
--- a/source/Waves.hpp
+++ b/source/Waves.hpp
@@ -38,6 +38,7 @@
 #include "Log.hpp"
 #include "Time.hpp"
 #include <vector>
+#include <map>
 
 namespace moordyn {
 
@@ -224,7 +225,7 @@ class Waves : public LogUser
 	 * @throws moordyn::mem_error If there were roblems allocating memory
 	 * @throws moordyn::output_file_error If data cannot be written in \p folder
 	 */
-	void setup(EnvCond* env, TimeScheme* t, const char* folder = "Mooring/");
+	void setup(EnvCondRef env, TimeScheme* t, const char* folder = "Mooring/");
 
 	/** @brief Get the velocity, acceleration, wave height and dynamic pressure
 	 * at a specific positon and time
@@ -260,4 +261,9 @@ class Waves : public LogUser
 std::vector<real>
 gridAxisCoords(Waves::coordtypes coordtype, vector<string>& entries);
 
+
+/**
+* Renaming the shared pointer to be a little more ergonomic
+*/
+typedef std::shared_ptr<Waves> WavesRef;
 } // ::moordyn


Test patch:
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index a8c29b9b..38ccc63a 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -16,7 +16,7 @@ if(WIN32)
     endif()
 endif()
 
-set(TESTS "minimal;lifting;pendulum;time_schemes;io;bodies_and_rods;wavekin;quasi_static_chain")
+set(TESTS "minimal;lifting;pendulum;time_schemes;io;bodies_and_rods;wavekin;quasi_static_chain;wavekin_4;tripod;seafloor")
 if(USE_VTK)
     set(TESTS "${TESTS};vtk")
 endif()
diff --git a/tests/Mooring/3D_seafloor/hanging_lines.txt b/tests/Mooring/3D_seafloor/hanging_lines.txt
new file mode 100644
index 00000000..aa9d3fc7
--- /dev/null
+++ b/tests/Mooring/3D_seafloor/hanging_lines.txt
@@ -0,0 +1,55 @@
+--------------------- MoorDyn Input File -------------------------------------------------------
+Trying to simulate a single chain swinging in seawater. Outputs should be p,t so that we
+can get the position and tension for the line!
+----------------------- LINE TYPES --------------------------------------------------------------
+TypeName   Diam    Mass/m     EA         BA/-zeta    EI         Cd     Ca     CdAx    CaAx
+(name)     (m)     (kg/m)     (N)        (N-s/-)     (N-m^2)    (-)    (-)    (-)     (-)
+chain       13.332E-3  1.1        7.51E6     -0.5        0        0.0    0.0      0.0     1.0
+----------------------- POINTS -------------------------------------------------------------------
+Node      Type      X        Y         Z        M        V         CdA   CA
+(-)       (-)      (m)      (m)       (m)      (kg)     (m^3)     (m^2)  (-)
+1 fixed -50 0.0 -16.6 0 0 0 0
+2 free -50 0.0 -26.6 0 0 0 0
+3 fixed -40 0.0 -16.6 0 0 0 0
+4 free -40 0.0 -26.6 0 0 0 0
+5 fixed -30 0.0 -16.6 0 0 0 0
+6 free -30 0.0 -26.6 0 0 0 0
+7 fixed -20 0.0 -16.6 0 0 0 0
+8 free -20 0.0 -26.6 0 0 0 0
+9 fixed -10 0.0 -16.6 0 0 0 0
+10 free -10 0.0 -26.6 0 0 0 0
+11 fixed 0 0.0 -16.6 0 0 0 0
+12 free 0 0.0 -26.6 0 0 0 0
+13 fixed 10 0.0 -16.6 0 0 0 0
+14 free 10 0.0 -26.6 0 0 0 0
+15 fixed 20 0.0 -16.6 0 0 0 0
+16 free 20 0.0 -26.6 0 0 0 0
+17 fixed 30 0.0 -16.6 0 0 0 0
+18 free 30 0.0 -26.6 0 0 0 0
+19 fixed 40 0.0 -16.6 0 0 0 0
+20 free 40 0.0 -26.6 0 0 0 0
+-------------------------- LINES -----------------------------------------------------------------
+Line     LineType NodeA     NodeB  UnstrLen  NumSegs     Flags/Outputs
+(-)      (-)       (-)       (-)   (m)         (-)          (-)
+1 chain 1 2 10 10 p,t
+2 chain 3 4 10 10 p,t
+3 chain 5 6 10 10 p,t
+4 chain 7 8 10 10 p,t
+5 chain 9 10 10 10 p,t
+6 chain 11 12 10 10 p,t
+7 chain 13 14 10 10 p,t
+8 chain 15 16 10 10 p,t
+9 chain 17 18 10 10 p,t
+10 chain 19 20 10 10 p,t
+-------------------------- SOLVER OPTIONS----------------------------------------------------------
+2         writeLog     - Write a log file
+0.00050   dtM          - time step to use in mooring integration
+3.0e6     kb           - bottom stiffness
+3.0e5     cb           - bottom damping
+4000      WtrDpth      - water depth
+4.0       ICDfac       - factor by which to scale drag coefficients during dynamic relaxation IC gen
+0.0000015 threshIC     - threshold for IC convergence
+0.0       TmaxIC       - threshold for IC convergence
+0.00001   dtIC         - Time lapse between convergence tests (s)
+0         WaveKin      - No waves
+------------------------- need this line -------------------------------------- 
diff --git a/tests/Mooring/3D_seafloor/make_3d_seafloor.py b/tests/Mooring/3D_seafloor/make_3d_seafloor.py
new file mode 100644
index 00000000..87be8949
--- /dev/null
+++ b/tests/Mooring/3D_seafloor/make_3d_seafloor.py
@@ -0,0 +1,25 @@
+import numpy as np
+BASE_DEPTH = -30
+x = np.linspace(-50.0, 50.0, num=100)
+y = np.linspace(-2.0, 2.0, num=5)
+
+# Define 2d grid for seafloor:
+xx, yy = np.meshgrid(x,y)
+
+with open('seafloor_profile_3d.txt', 'w') as f:
+    # First line is number of (x,y) coords
+    f.write(f"{len(x)} {len(y)}\n")
+    
+    # Next two lines specify the axis (tick) values for x, y respectively
+    for xx in x:
+        f.write(f"{xx} ")
+    f.write("\n")
+
+    for yy in y:
+        f.write(f"{yy} ")
+    f.write("\n")
+
+    # All the remaining lines specify an (x, y, z) coordinate
+    for idx, xs in enumerate(x):
+        for idy, ys in enumerate(y):
+            f.write(f"{xs} {ys} {BASE_DEPTH + ys}\n")
diff --git a/tests/Mooring/3D_seafloor/seafloor_profile_3d.txt b/tests/Mooring/3D_seafloor/seafloor_profile_3d.txt
new file mode 100644
index 00000000..ae19865d
--- /dev/null
+++ b/tests/Mooring/3D_seafloor/seafloor_profile_3d.txt
@@ -0,0 +1,503 @@
+100 5
+-50.0 -48.98989898989899 -47.97979797979798 -46.96969696969697 -45.95959595959596 -44.94949494949495 -43.93939393939394 -42.92929292929293 -41.91919191919192 -40.90909090909091 -39.898989898989896 -38.888888888888886 -37.878787878787875 -36.868686868686865 -35.858585858585855 -34.848484848484844 -33.838383838383834 -32.82828282828282 -31.818181818181817 -30.808080808080806 -29.797979797979796 -28.787878787878785 -27.777777777777775 -26.767676767676765 -25.757575757575758 -24.747474747474747 -23.737373737373737 -22.727272727272727 -21.717171717171716 -20.707070707070706 -19.696969696969695 -18.686868686868685 -17.676767676767675 -16.666666666666664 -15.656565656565654 -14.646464646464644 -13.636363636363633 -12.626262626262623 -11.616161616161612 -10.606060606060602 -9.595959595959592 -8.585858585858581 -7.575757575757571 -6.56565656565656 -5.55555555555555 -4.54545454545454 -3.5353535353535293 -2.525252525252519 -1.5151515151515156 -0.5050505050505052 0.5050505050505052 1.5151515151515156 2.525252525252526 3.5353535353535364 4.545454545454547 5.555555555555557 6.5656565656565675 7.575757575757578 8.585858585858588 9.595959595959599 10.606060606060609 11.61616161616162 12.62626262626263 13.63636363636364 14.64646464646465 15.656565656565661 16.66666666666667 17.676767676767682 18.686868686868692 19.696969696969703 20.707070707070713 21.717171717171723 22.727272727272734 23.737373737373744 24.747474747474755 25.757575757575765 26.767676767676775 27.777777777777786 28.787878787878796 29.797979797979806 30.808080808080817 31.818181818181827 32.82828282828284 33.83838383838385 34.84848484848486 35.85858585858587 36.86868686868688 37.87878787878789 38.8888888888889 39.89898989898991 40.90909090909092 41.91919191919193 42.92929292929294 43.93939393939395 44.94949494949496 45.95959595959597 46.96969696969697 47.97979797979798 48.98989898989899 50.0
+-2.0 -1.0 0.0 1.0 2.0
+-50.0 -2.0 -32.0
+-50.0 -1.0 -31.0
+-50.0 0.0 -30.0
+-50.0 1.0 -29.0
+-50.0 2.0 -28.0
+-48.98989898989899 -2.0 -32.0
+-48.98989898989899 -1.0 -31.0
+-48.98989898989899 0.0 -30.0
+-48.98989898989899 1.0 -29.0
+-48.98989898989899 2.0 -28.0
+-47.97979797979798 -2.0 -32.0
+-47.97979797979798 -1.0 -31.0
+-47.97979797979798 0.0 -30.0
+-47.97979797979798 1.0 -29.0
+-47.97979797979798 2.0 -28.0
+-46.96969696969697 -2.0 -32.0
+-46.96969696969697 -1.0 -31.0
+-46.96969696969697 0.0 -30.0
+-46.96969696969697 1.0 -29.0
+-46.96969696969697 2.0 -28.0
+-45.95959595959596 -2.0 -32.0
+-45.95959595959596 -1.0 -31.0
+-45.95959595959596 0.0 -30.0
+-45.95959595959596 1.0 -29.0
+-45.95959595959596 2.0 -28.0
+-44.94949494949495 -2.0 -32.0
+-44.94949494949495 -1.0 -31.0
+-44.94949494949495 0.0 -30.0
+-44.94949494949495 1.0 -29.0
+-44.94949494949495 2.0 -28.0
+-43.93939393939394 -2.0 -32.0
+-43.93939393939394 -1.0 -31.0
+-43.93939393939394 0.0 -30.0
+-43.93939393939394 1.0 -29.0
+-43.93939393939394 2.0 -28.0
+-42.92929292929293 -2.0 -32.0
+-42.92929292929293 -1.0 -31.0
+-42.92929292929293 0.0 -30.0
+-42.92929292929293 1.0 -29.0
+-42.92929292929293 2.0 -28.0
+-41.91919191919192 -2.0 -32.0
+-41.91919191919192 -1.0 -31.0
+-41.91919191919192 0.0 -30.0
+-41.91919191919192 1.0 -29.0
+-41.91919191919192 2.0 -28.0
+-40.90909090909091 -2.0 -32.0
+-40.90909090909091 -1.0 -31.0
+-40.90909090909091 0.0 -30.0
+-40.90909090909091 1.0 -29.0
+-40.90909090909091 2.0 -28.0
+-39.898989898989896 -2.0 -32.0
+-39.898989898989896 -1.0 -31.0
+-39.898989898989896 0.0 -30.0
+-39.898989898989896 1.0 -29.0
+-39.898989898989896 2.0 -28.0
+-38.888888888888886 -2.0 -32.0
+-38.888888888888886 -1.0 -31.0
+-38.888888888888886 0.0 -30.0
+-38.888888888888886 1.0 -29.0
+-38.888888888888886 2.0 -28.0
+-37.878787878787875 -2.0 -32.0
+-37.878787878787875 -1.0 -31.0
+-37.878787878787875 0.0 -30.0
+-37.878787878787875 1.0 -29.0
+-37.878787878787875 2.0 -28.0
+-36.868686868686865 -2.0 -32.0
+-36.868686868686865 -1.0 -31.0
+-36.868686868686865 0.0 -30.0
+-36.868686868686865 1.0 -29.0
+-36.868686868686865 2.0 -28.0
+-35.858585858585855 -2.0 -32.0
+-35.858585858585855 -1.0 -31.0
+-35.858585858585855 0.0 -30.0
+-35.858585858585855 1.0 -29.0
+-35.858585858585855 2.0 -28.0
+-34.848484848484844 -2.0 -32.0
+-34.848484848484844 -1.0 -31.0
+-34.848484848484844 0.0 -30.0
+-34.848484848484844 1.0 -29.0
+-34.848484848484844 2.0 -28.0
+-33.838383838383834 -2.0 -32.0
+-33.838383838383834 -1.0 -31.0
+-33.838383838383834 0.0 -30.0
+-33.838383838383834 1.0 -29.0
+-33.838383838383834 2.0 -28.0
+-32.82828282828282 -2.0 -32.0
+-32.82828282828282 -1.0 -31.0
+-32.82828282828282 0.0 -30.0
+-32.82828282828282 1.0 -29.0
+-32.82828282828282 2.0 -28.0
+-31.818181818181817 -2.0 -32.0
+-31.818181818181817 -1.0 -31.0
+-31.818181818181817 0.0 -30.0
+-31.818181818181817 1.0 -29.0
+-31.818181818181817 2.0 -28.0
+-30.808080808080806 -2.0 -32.0
+-30.808080808080806 -1.0 -31.0
+-30.808080808080806 0.0 -30.0
+-30.808080808080806 1.0 -29.0
+-30.808080808080806 2.0 -28.0
+-29.797979797979796 -2.0 -32.0
+-29.797979797979796 -1.0 -31.0
+-29.797979797979796 0.0 -30.0
+-29.797979797979796 1.0 -29.0
+-29.797979797979796 2.0 -28.0
+-28.787878787878785 -2.0 -32.0
+-28.787878787878785 -1.0 -31.0
+-28.787878787878785 0.0 -30.0
+-28.787878787878785 1.0 -29.0
+-28.787878787878785 2.0 -28.0
+-27.777777777777775 -2.0 -32.0
+-27.777777777777775 -1.0 -31.0
+-27.777777777777775 0.0 -30.0
+-27.777777777777775 1.0 -29.0
+-27.777777777777775 2.0 -28.0
+-26.767676767676765 -2.0 -32.0
+-26.767676767676765 -1.0 -31.0
+-26.767676767676765 0.0 -30.0
+-26.767676767676765 1.0 -29.0
+-26.767676767676765 2.0 -28.0
+-25.757575757575758 -2.0 -32.0
+-25.757575757575758 -1.0 -31.0
+-25.757575757575758 0.0 -30.0
+-25.757575757575758 1.0 -29.0
+-25.757575757575758 2.0 -28.0
+-24.747474747474747 -2.0 -32.0
+-24.747474747474747 -1.0 -31.0
+-24.747474747474747 0.0 -30.0
+-24.747474747474747 1.0 -29.0
+-24.747474747474747 2.0 -28.0
+-23.737373737373737 -2.0 -32.0
+-23.737373737373737 -1.0 -31.0
+-23.737373737373737 0.0 -30.0
+-23.737373737373737 1.0 -29.0
+-23.737373737373737 2.0 -28.0
+-22.727272727272727 -2.0 -32.0
+-22.727272727272727 -1.0 -31.0
+-22.727272727272727 0.0 -30.0
+-22.727272727272727 1.0 -29.0
+-22.727272727272727 2.0 -28.0
+-21.717171717171716 -2.0 -32.0
+-21.717171717171716 -1.0 -31.0
+-21.717171717171716 0.0 -30.0
+-21.717171717171716 1.0 -29.0
+-21.717171717171716 2.0 -28.0
+-20.707070707070706 -2.0 -32.0
+-20.707070707070706 -1.0 -31.0
+-20.707070707070706 0.0 -30.0
+-20.707070707070706 1.0 -29.0
+-20.707070707070706 2.0 -28.0
+-19.696969696969695 -2.0 -32.0
+-19.696969696969695 -1.0 -31.0
+-19.696969696969695 0.0 -30.0
+-19.696969696969695 1.0 -29.0
+-19.696969696969695 2.0 -28.0
+-18.686868686868685 -2.0 -32.0
+-18.686868686868685 -1.0 -31.0
+-18.686868686868685 0.0 -30.0
+-18.686868686868685 1.0 -29.0
+-18.686868686868685 2.0 -28.0
+-17.676767676767675 -2.0 -32.0
+-17.676767676767675 -1.0 -31.0
+-17.676767676767675 0.0 -30.0
+-17.676767676767675 1.0 -29.0
+-17.676767676767675 2.0 -28.0
+-16.666666666666664 -2.0 -32.0
+-16.666666666666664 -1.0 -31.0
+-16.666666666666664 0.0 -30.0
+-16.666666666666664 1.0 -29.0
+-16.666666666666664 2.0 -28.0
+-15.656565656565654 -2.0 -32.0
+-15.656565656565654 -1.0 -31.0
+-15.656565656565654 0.0 -30.0
+-15.656565656565654 1.0 -29.0
+-15.656565656565654 2.0 -28.0
+-14.646464646464644 -2.0 -32.0
+-14.646464646464644 -1.0 -31.0
+-14.646464646464644 0.0 -30.0
+-14.646464646464644 1.0 -29.0
+-14.646464646464644 2.0 -28.0
+-13.636363636363633 -2.0 -32.0
+-13.636363636363633 -1.0 -31.0
+-13.636363636363633 0.0 -30.0
+-13.636363636363633 1.0 -29.0
+-13.636363636363633 2.0 -28.0
+-12.626262626262623 -2.0 -32.0
+-12.626262626262623 -1.0 -31.0
+-12.626262626262623 0.0 -30.0
+-12.626262626262623 1.0 -29.0
+-12.626262626262623 2.0 -28.0
+-11.616161616161612 -2.0 -32.0
+-11.616161616161612 -1.0 -31.0
+-11.616161616161612 0.0 -30.0
+-11.616161616161612 1.0 -29.0
+-11.616161616161612 2.0 -28.0
+-10.606060606060602 -2.0 -32.0
+-10.606060606060602 -1.0 -31.0
+-10.606060606060602 0.0 -30.0
+-10.606060606060602 1.0 -29.0
+-10.606060606060602 2.0 -28.0
+-9.595959595959592 -2.0 -32.0
+-9.595959595959592 -1.0 -31.0
+-9.595959595959592 0.0 -30.0
+-9.595959595959592 1.0 -29.0
+-9.595959595959592 2.0 -28.0
+-8.585858585858581 -2.0 -32.0
+-8.585858585858581 -1.0 -31.0
+-8.585858585858581 0.0 -30.0
+-8.585858585858581 1.0 -29.0
+-8.585858585858581 2.0 -28.0
+-7.575757575757571 -2.0 -32.0
+-7.575757575757571 -1.0 -31.0
+-7.575757575757571 0.0 -30.0
+-7.575757575757571 1.0 -29.0
+-7.575757575757571 2.0 -28.0
+-6.56565656565656 -2.0 -32.0
+-6.56565656565656 -1.0 -31.0
+-6.56565656565656 0.0 -30.0
+-6.56565656565656 1.0 -29.0
+-6.56565656565656 2.0 -28.0
+-5.55555555555555 -2.0 -32.0
+-5.55555555555555 -1.0 -31.0
+-5.55555555555555 0.0 -30.0
+-5.55555555555555 1.0 -29.0
+-5.55555555555555 2.0 -28.0
+-4.54545454545454 -2.0 -32.0
+-4.54545454545454 -1.0 -31.0
+-4.54545454545454 0.0 -30.0
+-4.54545454545454 1.0 -29.0
+-4.54545454545454 2.0 -28.0
+-3.5353535353535293 -2.0 -32.0
+-3.5353535353535293 -1.0 -31.0
+-3.5353535353535293 0.0 -30.0
+-3.5353535353535293 1.0 -29.0
+-3.5353535353535293 2.0 -28.0
+-2.525252525252519 -2.0 -32.0
+-2.525252525252519 -1.0 -31.0
+-2.525252525252519 0.0 -30.0
+-2.525252525252519 1.0 -29.0
+-2.525252525252519 2.0 -28.0
+-1.5151515151515156 -2.0 -32.0
+-1.5151515151515156 -1.0 -31.0
+-1.5151515151515156 0.0 -30.0
+-1.5151515151515156 1.0 -29.0
+-1.5151515151515156 2.0 -28.0
+-0.5050505050505052 -2.0 -32.0
+-0.5050505050505052 -1.0 -31.0
+-0.5050505050505052 0.0 -30.0
+-0.5050505050505052 1.0 -29.0
+-0.5050505050505052 2.0 -28.0
+0.5050505050505052 -2.0 -32.0
+0.5050505050505052 -1.0 -31.0
+0.5050505050505052 0.0 -30.0
+0.5050505050505052 1.0 -29.0
+0.5050505050505052 2.0 -28.0
+1.5151515151515156 -2.0 -32.0
+1.5151515151515156 -1.0 -31.0
+1.5151515151515156 0.0 -30.0
+1.5151515151515156 1.0 -29.0
+1.5151515151515156 2.0 -28.0
+2.525252525252526 -2.0 -32.0
+2.525252525252526 -1.0 -31.0
+2.525252525252526 0.0 -30.0
+2.525252525252526 1.0 -29.0
+2.525252525252526 2.0 -28.0
+3.5353535353535364 -2.0 -32.0
+3.5353535353535364 -1.0 -31.0
+3.5353535353535364 0.0 -30.0
+3.5353535353535364 1.0 -29.0
+3.5353535353535364 2.0 -28.0
+4.545454545454547 -2.0 -32.0
+4.545454545454547 -1.0 -31.0
+4.545454545454547 0.0 -30.0
+4.545454545454547 1.0 -29.0
+4.545454545454547 2.0 -28.0
+5.555555555555557 -2.0 -32.0
+5.555555555555557 -1.0 -31.0
+5.555555555555557 0.0 -30.0
+5.555555555555557 1.0 -29.0
+5.555555555555557 2.0 -28.0
+6.5656565656565675 -2.0 -32.0
+6.5656565656565675 -1.0 -31.0
+6.5656565656565675 0.0 -30.0
+6.5656565656565675 1.0 -29.0
+6.5656565656565675 2.0 -28.0
+7.575757575757578 -2.0 -32.0
+7.575757575757578 -1.0 -31.0
+7.575757575757578 0.0 -30.0
+7.575757575757578 1.0 -29.0
+7.575757575757578 2.0 -28.0
+8.585858585858588 -2.0 -32.0
+8.585858585858588 -1.0 -31.0
+8.585858585858588 0.0 -30.0
+8.585858585858588 1.0 -29.0
+8.585858585858588 2.0 -28.0
+9.595959595959599 -2.0 -32.0
+9.595959595959599 -1.0 -31.0
+9.595959595959599 0.0 -30.0
+9.595959595959599 1.0 -29.0
+9.595959595959599 2.0 -28.0
+10.606060606060609 -2.0 -32.0
+10.606060606060609 -1.0 -31.0
+10.606060606060609 0.0 -30.0
+10.606060606060609 1.0 -29.0
+10.606060606060609 2.0 -28.0
+11.61616161616162 -2.0 -32.0
+11.61616161616162 -1.0 -31.0
+11.61616161616162 0.0 -30.0
+11.61616161616162 1.0 -29.0
+11.61616161616162 2.0 -28.0
+12.62626262626263 -2.0 -32.0
+12.62626262626263 -1.0 -31.0
+12.62626262626263 0.0 -30.0
+12.62626262626263 1.0 -29.0
+12.62626262626263 2.0 -28.0
+13.63636363636364 -2.0 -32.0
+13.63636363636364 -1.0 -31.0
+13.63636363636364 0.0 -30.0
+13.63636363636364 1.0 -29.0
+13.63636363636364 2.0 -28.0
+14.64646464646465 -2.0 -32.0
+14.64646464646465 -1.0 -31.0
+14.64646464646465 0.0 -30.0
+14.64646464646465 1.0 -29.0
+14.64646464646465 2.0 -28.0
+15.656565656565661 -2.0 -32.0
+15.656565656565661 -1.0 -31.0
+15.656565656565661 0.0 -30.0
+15.656565656565661 1.0 -29.0
+15.656565656565661 2.0 -28.0
+16.66666666666667 -2.0 -32.0
+16.66666666666667 -1.0 -31.0
+16.66666666666667 0.0 -30.0
+16.66666666666667 1.0 -29.0
+16.66666666666667 2.0 -28.0
+17.676767676767682 -2.0 -32.0
+17.676767676767682 -1.0 -31.0
+17.676767676767682 0.0 -30.0
+17.676767676767682 1.0 -29.0
+17.676767676767682 2.0 -28.0
+18.686868686868692 -2.0 -32.0
+18.686868686868692 -1.0 -31.0
+18.686868686868692 0.0 -30.0
+18.686868686868692 1.0 -29.0
+18.686868686868692 2.0 -28.0
+19.696969696969703 -2.0 -32.0
+19.696969696969703 -1.0 -31.0
+19.696969696969703 0.0 -30.0
+19.696969696969703 1.0 -29.0
+19.696969696969703 2.0 -28.0
+20.707070707070713 -2.0 -32.0
+20.707070707070713 -1.0 -31.0
+20.707070707070713 0.0 -30.0
+20.707070707070713 1.0 -29.0
+20.707070707070713 2.0 -28.0
+21.717171717171723 -2.0 -32.0
+21.717171717171723 -1.0 -31.0
+21.717171717171723 0.0 -30.0
+21.717171717171723 1.0 -29.0
+21.717171717171723 2.0 -28.0
+22.727272727272734 -2.0 -32.0
+22.727272727272734 -1.0 -31.0
+22.727272727272734 0.0 -30.0
+22.727272727272734 1.0 -29.0
+22.727272727272734 2.0 -28.0
+23.737373737373744 -2.0 -32.0
+23.737373737373744 -1.0 -31.0
+23.737373737373744 0.0 -30.0
+23.737373737373744 1.0 -29.0
+23.737373737373744 2.0 -28.0
+24.747474747474755 -2.0 -32.0
+24.747474747474755 -1.0 -31.0
+24.747474747474755 0.0 -30.0
+24.747474747474755 1.0 -29.0
+24.747474747474755 2.0 -28.0
+25.757575757575765 -2.0 -32.0
+25.757575757575765 -1.0 -31.0
+25.757575757575765 0.0 -30.0
+25.757575757575765 1.0 -29.0
+25.757575757575765 2.0 -28.0
+26.767676767676775 -2.0 -32.0
+26.767676767676775 -1.0 -31.0
+26.767676767676775 0.0 -30.0
+26.767676767676775 1.0 -29.0
+26.767676767676775 2.0 -28.0
+27.777777777777786 -2.0 -32.0
+27.777777777777786 -1.0 -31.0
+27.777777777777786 0.0 -30.0
+27.777777777777786 1.0 -29.0
+27.777777777777786 2.0 -28.0
+28.787878787878796 -2.0 -32.0
+28.787878787878796 -1.0 -31.0
+28.787878787878796 0.0 -30.0
+28.787878787878796 1.0 -29.0
+28.787878787878796 2.0 -28.0
+29.797979797979806 -2.0 -32.0
+29.797979797979806 -1.0 -31.0
+29.797979797979806 0.0 -30.0
+29.797979797979806 1.0 -29.0
+29.797979797979806 2.0 -28.0
+30.808080808080817 -2.0 -32.0
+30.808080808080817 -1.0 -31.0
+30.808080808080817 0.0 -30.0
+30.808080808080817 1.0 -29.0
+30.808080808080817 2.0 -28.0
+31.818181818181827 -2.0 -32.0
+31.818181818181827 -1.0 -31.0
+31.818181818181827 0.0 -30.0
+31.818181818181827 1.0 -29.0
+31.818181818181827 2.0 -28.0
+32.82828282828284 -2.0 -32.0
+32.82828282828284 -1.0 -31.0
+32.82828282828284 0.0 -30.0
+32.82828282828284 1.0 -29.0
+32.82828282828284 2.0 -28.0
+33.83838383838385 -2.0 -32.0
+33.83838383838385 -1.0 -31.0
+33.83838383838385 0.0 -30.0
+33.83838383838385 1.0 -29.0
+33.83838383838385 2.0 -28.0
+34.84848484848486 -2.0 -32.0
+34.84848484848486 -1.0 -31.0
+34.84848484848486 0.0 -30.0
+34.84848484848486 1.0 -29.0
+34.84848484848486 2.0 -28.0
+35.85858585858587 -2.0 -32.0
+35.85858585858587 -1.0 -31.0
+35.85858585858587 0.0 -30.0
+35.85858585858587 1.0 -29.0
+35.85858585858587 2.0 -28.0
+36.86868686868688 -2.0 -32.0
+36.86868686868688 -1.0 -31.0
+36.86868686868688 0.0 -30.0
+36.86868686868688 1.0 -29.0
+36.86868686868688 2.0 -28.0
+37.87878787878789 -2.0 -32.0
+37.87878787878789 -1.0 -31.0
+37.87878787878789 0.0 -30.0
+37.87878787878789 1.0 -29.0
+37.87878787878789 2.0 -28.0
+38.8888888888889 -2.0 -32.0
+38.8888888888889 -1.0 -31.0
+38.8888888888889 0.0 -30.0
+38.8888888888889 1.0 -29.0
+38.8888888888889 2.0 -28.0
+39.89898989898991 -2.0 -32.0
+39.89898989898991 -1.0 -31.0
+39.89898989898991 0.0 -30.0
+39.89898989898991 1.0 -29.0
+39.89898989898991 2.0 -28.0
+40.90909090909092 -2.0 -32.0
+40.90909090909092 -1.0 -31.0
+40.90909090909092 0.0 -30.0
+40.90909090909092 1.0 -29.0
+40.90909090909092 2.0 -28.0
+41.91919191919193 -2.0 -32.0
+41.91919191919193 -1.0 -31.0
+41.91919191919193 0.0 -30.0
+41.91919191919193 1.0 -29.0
+41.91919191919193 2.0 -28.0
+42.92929292929294 -2.0 -32.0
+42.92929292929294 -1.0 -31.0
+42.92929292929294 0.0 -30.0
+42.92929292929294 1.0 -29.0
+42.92929292929294 2.0 -28.0
+43.93939393939395 -2.0 -32.0
+43.93939393939395 -1.0 -31.0
+43.93939393939395 0.0 -30.0
+43.93939393939395 1.0 -29.0
+43.93939393939395 2.0 -28.0
+44.94949494949496 -2.0 -32.0
+44.94949494949496 -1.0 -31.0
+44.94949494949496 0.0 -30.0
+44.94949494949496 1.0 -29.0
+44.94949494949496 2.0 -28.0
+45.95959595959597 -2.0 -32.0
+45.95959595959597 -1.0 -31.0
+45.95959595959597 0.0 -30.0
+45.95959595959597 1.0 -29.0
+45.95959595959597 2.0 -28.0
+46.96969696969697 -2.0 -32.0
+46.96969696969697 -1.0 -31.0
+46.96969696969697 0.0 -30.0
+46.96969696969697 1.0 -29.0
+46.96969696969697 2.0 -28.0
+47.97979797979798 -2.0 -32.0
+47.97979797979798 -1.0 -31.0
+47.97979797979798 0.0 -30.0
+47.97979797979798 1.0 -29.0
+47.97979797979798 2.0 -28.0
+48.98989898989899 -2.0 -32.0
+48.98989898989899 -1.0 -31.0
+48.98989898989899 0.0 -30.0
+48.98989898989899 1.0 -29.0
+48.98989898989899 2.0 -28.0
+50.0 -2.0 -32.0
+50.0 -1.0 -31.0
+50.0 0.0 -30.0
+50.0 1.0 -29.0
+50.0 2.0 -28.0
diff --git a/tests/Mooring/seafloor.txt b/tests/Mooring/seafloor.txt
new file mode 100644
index 00000000..80a45dda
--- /dev/null
+++ b/tests/Mooring/seafloor.txt
@@ -0,0 +1,28 @@
+--------------------- MoorDyn Input File ------------------------------------
+MoorDyn input for a pendulum that will collide with a 3D seafloor. The conection 
+line is almost weightless, i.e. it has the same density than water,and it has 0 damping
+----------------------- LINE TYPES ------------------------------------------
+TypeName   Diam    Mass/m     EA         BA/-zeta    EI         Cd     Ca     CdAx    CaAx
+(name)     (m)     (kg/m)     (N)        (N-s/-)     (N-m^2)    (-)    (-)    (-)     (-)
+main       0.1     7.85398    1.0E9      -0.01       0          0.0    0.0    0.0     0.0
+---------------------- CONNECTION PROPERTIES --------------------------------
+ID    Type      X       Y       Z       Mass   Volume  CdA    Ca
+(#)   (-)       (m)     (m)     (m)     (kg)   (mˆ3)   (m^2)  (-)
+1     Fixed     0       -1       0       0      0       0      0
+2     Free      10      -1       -29    1E1    0       0      0
+---------------------- LINES ----------------------------------------
+ID   LineType   AttachA  AttachB  UnstrLen  NumSegs  LineOutputs
+(#)   (name)     (#)      (#)       (m)       (-)     (-)
+1     main       1        2         32.0      20      p
+---------------------- OPTIONS -----------------------------------------
+2             writeLog      Write a log file
+1E-4          dtM           time step to use in mooring integration (s)
+RK4           tScheme       The time integration Scheme (-)
+1000.0        WtrDnsty      water density (kg/m^3)
+500           WtrDpth       water depth (m)
+Mooring/3D_seafloor/seafloor_profile_3d.txt    SeafloorFile    3D seafloor file path
+1E-3          dtIC          time interval for analyzing convergence during IC gen (s)
+1.0           TmaxIC        max time for ic gen (s)
+1.0           CdScaleIC     factor by which to scale drag coefficients during dynamic relaxation (-)
+0.01          threshIC      threshold for IC convergence (-)
+------------------------- need this line -------------------------------------- 
diff --git a/tests/Mooring/tripod.txt b/tests/Mooring/tripod.txt
new file mode 100644
index 00000000..9ec186f1
--- /dev/null
+++ b/tests/Mooring/tripod.txt
@@ -0,0 +1,68 @@
+--------------------- MoorDyn Input File -------------------------------------------------------
+Trying to simulate a single chain swinging in seawater. Outputs should be p so that we
+can get the position and tension for the line!
+----------------------- LINE TYPES --------------------------------------------------------------
+TypeName   Diam    Mass/m     EA         BA/-zeta    EI         Cd     Ca     CdAx    CaAx
+(name)     (m)     (kg/m)     (N)        (N-s/-)     (N-m^2)    (-)    (-)    (-)     (-)
+chain       13.332E-3  1.1        7.51E6     -0.5        0        0.0    0.0      0.0     1.0
+Anchor-Shaft 0.03582611 7.602860166 211693971.8 -0.5 17783.46734 1.2 1.0 0.08 1.0
+Anchor-Chain 0.029256013 5.2972 35870379.7 -0.5 1918.873134 1.2 1.0 0.08 1.0
+Anchor-Line 0.01 0.195 5002986.301 -0.5 31.26866438 1.2 1.0 0.08 1.0
+Anchor-Bridle 0.019371902 0.336 389814.3373 -0.5 9.142866329 1.2 1.0 0.08 1.0
+nodeFloat 0.82 133.3333333 2218027245.0 -0.5 93212594.98 1.2 1.0 0.455555556 1.0
+nodeTether 0.024778998 3.8 25789847.71 -0.5 989.6834156 1.2 1.0 0.08 1.0
+nodeInstrumentedFloat 0.78 94.88888889 2006912219.0 -0.5 76312837.13 1.2 1.0 0.433333333 1.0
+nodeInstrumentedTether 0.042907387 10.90540541 303650012.4 -0.5 34939.56131 1.2 1.0 0.08 1.0
+Header 0.019371902 0.336 389814.3373 -0.5 9.142866329 1.2 1.0 0.08 1.0
+Longline 0.009 0.15 3117245.311 -0.5 15.78105438 1.2 1.0 0.08 1.0
+LightLongline 0.00906336 0.08 79354.83871 -0.5 0.407410156 1.2 1.0 0.08 1.0
+GridX 0.01 0.153231182 3848451.001 -0.5 24.05281875 1.2 1.0 0.08 0.0
+Tether-Line 0.015875 0.195953283 583823.5294 -0.5 9.195790728 1.2 1.0 0.08 1.0
+Tether-Float 0.25 5.0 10308350895.0 -0.5 40266995.68 1.2 1.0 1.2 1.0
+MooringMidLineFloat 0.29 22.89719626 13870916964.0 -0.5 72909007.29 1.2 1.0 1.2 1.0
+MooringMidLineTether 0.019067037 2.1 15304568.52 -0.5 347.7503106 1.2 1.0 0.08 1.0
+Head-Tether-Float 0.142729929 126.08 3360000000.0 -0.5 4278084.87 1.2 1.0 0.08 1.0
+mPlate 0.170604929 185.95594 4571966373.0 -0.5 8316990.305 1.2 1.0 0.08 1.0
+----------------------- POINTS -------------------------------------------------------------------
+Node      Type      X        Y         Z        M        V         CdA   CA
+(-)       (-)      (m)      (m)       (m)      (kg)     (m^3)     (m^2)  (-)
+1 free -0.8228425527966355 1.4773081554316 -0.17035212342813671 0 0 0 0
+2 free -0.41298218397859154 1.5012137754105057 0.3075003699197342 0 0 0 0
+3 free -0.82037111325625 1.4824722198985547 -1.2840966813934565 0 0 0 0
+4 free -4.146958996323875 -0.5600288725967154 -5.844913811344564 0 0 0 0
+5 free -10.848650830889092 -4.663153393213892 -14.58360635696342 0 0 0 0
+6 fixed -14.24 -6.74 -15.801199187198375 0 0 0 0
+7 free 2.959773801146883 2.7994389802741177 -5.758893180522135 0 0 0 0
+8 free 10.524353845100293 5.445622031958943 -14.355007892432736 0 0 0 0
+9 fixed 14.24 6.74 -15.985999187198374 0 0 0 0
+10 free -3.905979387438681 2.640181666208941 -6.299393757668381 0 0 0 0
+11 free -10.437517955985847 5.090970059015089 -15.751713646406762 0 0 0 0
+12 fixed -14.24 6.74 -15.891065853865044 0 0 0 0
+-------------------------- LINES -----------------------------------------------------------------
+Line     LineType NodeA     NodeB  UnstrLen  NumSegs     Flags/Outputs
+(-)      (-)       (-)       (-)   (m)         (-)          (-)
+1 nodeFloat 1 2 0.63 3 p
+2 nodeTether 3 1 1.11374 5 p
+3 Anchor-Bridle 3 4 6.0 7 p
+4 Anchor-Line 4 5 11.75240030480061 14 p
+5 Anchor-Chain 5 6 4.42 5 p
+6 Anchor-Bridle 3 7 6.0 7 p
+7 Anchor-Line 7 8 11.75240030480061 14 p
+8 Anchor-Chain 8 9 4.42 5 p
+9 Anchor-Bridle 3 10 6.0 7 p
+10 Anchor-Line 10 11 11.75240030480061 14 p
+11 Anchor-Chain 11 12 4.42 5 p
+-------------------------- SOLVER OPTIONS----------------------------------------------------------
+2         writeLog     - Write a log file
+0.000055  dtM          - time step to use in mooring integration
+rk4       tScheme      - Solver
+3.0e6     kb           - bottom stiffness
+3.0e5     cb           - bottom damping
+16.0      WtrDpth      - water depth
+4.0       ICDfac       - factor by which to scale drag coefficients during dynamic relaxation IC gen
+0.0000015 threshIC     - threshold for IC convergence
+0.0       TmaxIC       - threshold for IC convergence
+0.00001   dtIC         - Time lapse between convergence tests (s)
+0         Currents     - Whether or not to pull in currents
+0         WaveKin      - Whether or not to pull in waves
+------------------------- need this line -------------------------------------- 
diff --git a/tests/Mooring/wavekin_4/current_profile_4d.txt b/tests/Mooring/wavekin_4/current_profile_4d.txt
new file mode 100644
index 00000000..edc9dfcf
--- /dev/null
+++ b/tests/Mooring/wavekin_4/current_profile_4d.txt
@@ -0,0 +1,1589 @@
+11 2 36 2
+-50.0 -40.0 -30.0 -20.0 -10.0 0.0 10.0 20.0 30.0 40.0 50.0 
+-2.0 2.0 
+-30.0 -29.0 -28.0 -27.0 -26.0 -25.0 -24.0 -23.0 -22.0 -21.0 -20.0 -19.0 -18.0 -17.0 -16.0 -15.0 -14.0 -13.0 -12.0 -11.0 -10.0 -9.0 -8.0 -7.0 -6.0 -5.0 -4.0 -3.0 -2.0 -1.0 0.0 1.0 2.0 3.0 4.0 5.0 
+0.0 15.0 
+-50.0 -2.0 -30.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -30.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -29.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -29.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -28.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -28.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -27.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -27.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -26.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -26.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -25.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -25.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -24.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -24.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -23.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -23.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -22.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -22.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -21.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -21.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -20.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -20.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -19.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -19.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -18.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -18.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -17.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -17.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -16.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -16.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -15.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -15.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -14.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -14.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -13.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -13.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -12.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -12.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -11.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -11.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -10.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -10.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -9.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -9.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -8.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -8.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -7.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -7.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -6.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -6.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -5.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -5.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -4.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -4.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -3.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -3.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -2.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -2.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 -1.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 -1.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 0.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 0.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 1.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 1.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 2.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 2.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 3.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 3.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 4.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 4.0 15.0 -5 -0.0 -0.0
+-50.0 -2.0 5.0 0.0 -5 -0.0 -0.0
+-50.0 -2.0 5.0 15.0 -5 -0.0 -0.0
+-50.0 2.0 -30.0 0.0 -5 0.0 0.0
+-50.0 2.0 -30.0 15.0 -5 0.0 0.0
+-50.0 2.0 -29.0 0.0 -5 0.0 0.0
+-50.0 2.0 -29.0 15.0 -5 0.0 0.0
+-50.0 2.0 -28.0 0.0 -5 0.0 0.0
+-50.0 2.0 -28.0 15.0 -5 0.0 0.0
+-50.0 2.0 -27.0 0.0 -5 0.0 0.0
+-50.0 2.0 -27.0 15.0 -5 0.0 0.0
+-50.0 2.0 -26.0 0.0 -5 0.0 0.0
+-50.0 2.0 -26.0 15.0 -5 0.0 0.0
+-50.0 2.0 -25.0 0.0 -5 0.0 0.0
+-50.0 2.0 -25.0 15.0 -5 0.0 0.0
+-50.0 2.0 -24.0 0.0 -5 0.0 0.0
+-50.0 2.0 -24.0 15.0 -5 0.0 0.0
+-50.0 2.0 -23.0 0.0 -5 0.0 0.0
+-50.0 2.0 -23.0 15.0 -5 0.0 0.0
+-50.0 2.0 -22.0 0.0 -5 0.0 0.0
+-50.0 2.0 -22.0 15.0 -5 0.0 0.0
+-50.0 2.0 -21.0 0.0 -5 0.0 0.0
+-50.0 2.0 -21.0 15.0 -5 0.0 0.0
+-50.0 2.0 -20.0 0.0 -5 0.0 0.0
+-50.0 2.0 -20.0 15.0 -5 0.0 0.0
+-50.0 2.0 -19.0 0.0 -5 0.0 0.0
+-50.0 2.0 -19.0 15.0 -5 0.0 0.0
+-50.0 2.0 -18.0 0.0 -5 0.0 0.0
+-50.0 2.0 -18.0 15.0 -5 0.0 0.0
+-50.0 2.0 -17.0 0.0 -5 0.0 0.0
+-50.0 2.0 -17.0 15.0 -5 0.0 0.0
+-50.0 2.0 -16.0 0.0 -5 0.0 0.0
+-50.0 2.0 -16.0 15.0 -5 0.0 0.0
+-50.0 2.0 -15.0 0.0 -5 0.0 0.0
+-50.0 2.0 -15.0 15.0 -5 0.0 0.0
+-50.0 2.0 -14.0 0.0 -5 0.0 0.0
+-50.0 2.0 -14.0 15.0 -5 0.0 0.0
+-50.0 2.0 -13.0 0.0 -5 0.0 0.0
+-50.0 2.0 -13.0 15.0 -5 0.0 0.0
+-50.0 2.0 -12.0 0.0 -5 0.0 0.0
+-50.0 2.0 -12.0 15.0 -5 0.0 0.0
+-50.0 2.0 -11.0 0.0 -5 0.0 0.0
+-50.0 2.0 -11.0 15.0 -5 0.0 0.0
+-50.0 2.0 -10.0 0.0 -5 0.0 0.0
+-50.0 2.0 -10.0 15.0 -5 0.0 0.0
+-50.0 2.0 -9.0 0.0 -5 0.0 0.0
+-50.0 2.0 -9.0 15.0 -5 0.0 0.0
+-50.0 2.0 -8.0 0.0 -5 0.0 0.0
+-50.0 2.0 -8.0 15.0 -5 0.0 0.0
+-50.0 2.0 -7.0 0.0 -5 0.0 0.0
+-50.0 2.0 -7.0 15.0 -5 0.0 0.0
+-50.0 2.0 -6.0 0.0 -5 0.0 0.0
+-50.0 2.0 -6.0 15.0 -5 0.0 0.0
+-50.0 2.0 -5.0 0.0 -5 0.0 0.0
+-50.0 2.0 -5.0 15.0 -5 0.0 0.0
+-50.0 2.0 -4.0 0.0 -5 0.0 0.0
+-50.0 2.0 -4.0 15.0 -5 0.0 0.0
+-50.0 2.0 -3.0 0.0 -5 0.0 0.0
+-50.0 2.0 -3.0 15.0 -5 0.0 0.0
+-50.0 2.0 -2.0 0.0 -5 0.0 0.0
+-50.0 2.0 -2.0 15.0 -5 0.0 0.0
+-50.0 2.0 -1.0 0.0 -5 0.0 0.0
+-50.0 2.0 -1.0 15.0 -5 0.0 0.0
+-50.0 2.0 0.0 0.0 -5 0.0 0.0
+-50.0 2.0 0.0 15.0 -5 0.0 0.0
+-50.0 2.0 1.0 0.0 -5 0.0 0.0
+-50.0 2.0 1.0 15.0 -5 0.0 0.0
+-50.0 2.0 2.0 0.0 -5 0.0 0.0
+-50.0 2.0 2.0 15.0 -5 0.0 0.0
+-50.0 2.0 3.0 0.0 -5 0.0 0.0
+-50.0 2.0 3.0 15.0 -5 0.0 0.0
+-50.0 2.0 4.0 0.0 -5 0.0 0.0
+-50.0 2.0 4.0 15.0 -5 0.0 0.0
+-50.0 2.0 5.0 0.0 -5 0.0 0.0
+-50.0 2.0 5.0 15.0 -5 0.0 0.0
+-40.0 -2.0 -30.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -30.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -29.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -29.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -28.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -28.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -27.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -27.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -26.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -26.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -25.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -25.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -24.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -24.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -23.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -23.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -22.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -22.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -21.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -21.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -20.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -20.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -19.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -19.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -18.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -18.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -17.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -17.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -16.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -16.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -15.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -15.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -14.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -14.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -13.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -13.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -12.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -12.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -11.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -11.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -10.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -10.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -9.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -9.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -8.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -8.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -7.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -7.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -6.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -6.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -5.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -5.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -4.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -4.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -3.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -3.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -2.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -2.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 -1.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 -1.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 0.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 0.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 1.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 1.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 2.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 2.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 3.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 3.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 4.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 4.0 15.0 -4 -0.0 -0.0
+-40.0 -2.0 5.0 0.0 -4 -0.0 -0.0
+-40.0 -2.0 5.0 15.0 -4 -0.0 -0.0
+-40.0 2.0 -30.0 0.0 -4 0.0 0.0
+-40.0 2.0 -30.0 15.0 -4 0.0 0.0
+-40.0 2.0 -29.0 0.0 -4 0.0 0.0
+-40.0 2.0 -29.0 15.0 -4 0.0 0.0
+-40.0 2.0 -28.0 0.0 -4 0.0 0.0
+-40.0 2.0 -28.0 15.0 -4 0.0 0.0
+-40.0 2.0 -27.0 0.0 -4 0.0 0.0
+-40.0 2.0 -27.0 15.0 -4 0.0 0.0
+-40.0 2.0 -26.0 0.0 -4 0.0 0.0
+-40.0 2.0 -26.0 15.0 -4 0.0 0.0
+-40.0 2.0 -25.0 0.0 -4 0.0 0.0
+-40.0 2.0 -25.0 15.0 -4 0.0 0.0
+-40.0 2.0 -24.0 0.0 -4 0.0 0.0
+-40.0 2.0 -24.0 15.0 -4 0.0 0.0
+-40.0 2.0 -23.0 0.0 -4 0.0 0.0
+-40.0 2.0 -23.0 15.0 -4 0.0 0.0
+-40.0 2.0 -22.0 0.0 -4 0.0 0.0
+-40.0 2.0 -22.0 15.0 -4 0.0 0.0
+-40.0 2.0 -21.0 0.0 -4 0.0 0.0
+-40.0 2.0 -21.0 15.0 -4 0.0 0.0
+-40.0 2.0 -20.0 0.0 -4 0.0 0.0
+-40.0 2.0 -20.0 15.0 -4 0.0 0.0
+-40.0 2.0 -19.0 0.0 -4 0.0 0.0
+-40.0 2.0 -19.0 15.0 -4 0.0 0.0
+-40.0 2.0 -18.0 0.0 -4 0.0 0.0
+-40.0 2.0 -18.0 15.0 -4 0.0 0.0
+-40.0 2.0 -17.0 0.0 -4 0.0 0.0
+-40.0 2.0 -17.0 15.0 -4 0.0 0.0
+-40.0 2.0 -16.0 0.0 -4 0.0 0.0
+-40.0 2.0 -16.0 15.0 -4 0.0 0.0
+-40.0 2.0 -15.0 0.0 -4 0.0 0.0
+-40.0 2.0 -15.0 15.0 -4 0.0 0.0
+-40.0 2.0 -14.0 0.0 -4 0.0 0.0
+-40.0 2.0 -14.0 15.0 -4 0.0 0.0
+-40.0 2.0 -13.0 0.0 -4 0.0 0.0
+-40.0 2.0 -13.0 15.0 -4 0.0 0.0
+-40.0 2.0 -12.0 0.0 -4 0.0 0.0
+-40.0 2.0 -12.0 15.0 -4 0.0 0.0
+-40.0 2.0 -11.0 0.0 -4 0.0 0.0
+-40.0 2.0 -11.0 15.0 -4 0.0 0.0
+-40.0 2.0 -10.0 0.0 -4 0.0 0.0
+-40.0 2.0 -10.0 15.0 -4 0.0 0.0
+-40.0 2.0 -9.0 0.0 -4 0.0 0.0
+-40.0 2.0 -9.0 15.0 -4 0.0 0.0
+-40.0 2.0 -8.0 0.0 -4 0.0 0.0
+-40.0 2.0 -8.0 15.0 -4 0.0 0.0
+-40.0 2.0 -7.0 0.0 -4 0.0 0.0
+-40.0 2.0 -7.0 15.0 -4 0.0 0.0
+-40.0 2.0 -6.0 0.0 -4 0.0 0.0
+-40.0 2.0 -6.0 15.0 -4 0.0 0.0
+-40.0 2.0 -5.0 0.0 -4 0.0 0.0
+-40.0 2.0 -5.0 15.0 -4 0.0 0.0
+-40.0 2.0 -4.0 0.0 -4 0.0 0.0
+-40.0 2.0 -4.0 15.0 -4 0.0 0.0
+-40.0 2.0 -3.0 0.0 -4 0.0 0.0
+-40.0 2.0 -3.0 15.0 -4 0.0 0.0
+-40.0 2.0 -2.0 0.0 -4 0.0 0.0
+-40.0 2.0 -2.0 15.0 -4 0.0 0.0
+-40.0 2.0 -1.0 0.0 -4 0.0 0.0
+-40.0 2.0 -1.0 15.0 -4 0.0 0.0
+-40.0 2.0 0.0 0.0 -4 0.0 0.0
+-40.0 2.0 0.0 15.0 -4 0.0 0.0
+-40.0 2.0 1.0 0.0 -4 0.0 0.0
+-40.0 2.0 1.0 15.0 -4 0.0 0.0
+-40.0 2.0 2.0 0.0 -4 0.0 0.0
+-40.0 2.0 2.0 15.0 -4 0.0 0.0
+-40.0 2.0 3.0 0.0 -4 0.0 0.0
+-40.0 2.0 3.0 15.0 -4 0.0 0.0
+-40.0 2.0 4.0 0.0 -4 0.0 0.0
+-40.0 2.0 4.0 15.0 -4 0.0 0.0
+-40.0 2.0 5.0 0.0 -4 0.0 0.0
+-40.0 2.0 5.0 15.0 -4 0.0 0.0
+-30.0 -2.0 -30.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -30.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -29.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -29.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -28.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -28.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -27.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -27.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -26.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -26.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -25.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -25.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -24.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -24.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -23.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -23.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -22.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -22.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -21.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -21.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -20.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -20.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -19.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -19.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -18.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -18.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -17.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -17.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -16.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -16.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -15.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -15.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -14.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -14.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -13.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -13.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -12.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -12.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -11.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -11.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -10.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -10.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -9.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -9.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -8.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -8.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -7.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -7.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -6.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -6.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -5.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -5.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -4.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -4.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -3.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -3.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -2.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -2.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 -1.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 -1.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 0.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 0.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 1.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 1.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 2.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 2.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 3.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 3.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 4.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 4.0 15.0 -3 -0.0 -0.0
+-30.0 -2.0 5.0 0.0 -3 -0.0 -0.0
+-30.0 -2.0 5.0 15.0 -3 -0.0 -0.0
+-30.0 2.0 -30.0 0.0 -3 0.0 0.0
+-30.0 2.0 -30.0 15.0 -3 0.0 0.0
+-30.0 2.0 -29.0 0.0 -3 0.0 0.0
+-30.0 2.0 -29.0 15.0 -3 0.0 0.0
+-30.0 2.0 -28.0 0.0 -3 0.0 0.0
+-30.0 2.0 -28.0 15.0 -3 0.0 0.0
+-30.0 2.0 -27.0 0.0 -3 0.0 0.0
+-30.0 2.0 -27.0 15.0 -3 0.0 0.0
+-30.0 2.0 -26.0 0.0 -3 0.0 0.0
+-30.0 2.0 -26.0 15.0 -3 0.0 0.0
+-30.0 2.0 -25.0 0.0 -3 0.0 0.0
+-30.0 2.0 -25.0 15.0 -3 0.0 0.0
+-30.0 2.0 -24.0 0.0 -3 0.0 0.0
+-30.0 2.0 -24.0 15.0 -3 0.0 0.0
+-30.0 2.0 -23.0 0.0 -3 0.0 0.0
+-30.0 2.0 -23.0 15.0 -3 0.0 0.0
+-30.0 2.0 -22.0 0.0 -3 0.0 0.0
+-30.0 2.0 -22.0 15.0 -3 0.0 0.0
+-30.0 2.0 -21.0 0.0 -3 0.0 0.0
+-30.0 2.0 -21.0 15.0 -3 0.0 0.0
+-30.0 2.0 -20.0 0.0 -3 0.0 0.0
+-30.0 2.0 -20.0 15.0 -3 0.0 0.0
+-30.0 2.0 -19.0 0.0 -3 0.0 0.0
+-30.0 2.0 -19.0 15.0 -3 0.0 0.0
+-30.0 2.0 -18.0 0.0 -3 0.0 0.0
+-30.0 2.0 -18.0 15.0 -3 0.0 0.0
+-30.0 2.0 -17.0 0.0 -3 0.0 0.0
+-30.0 2.0 -17.0 15.0 -3 0.0 0.0
+-30.0 2.0 -16.0 0.0 -3 0.0 0.0
+-30.0 2.0 -16.0 15.0 -3 0.0 0.0
+-30.0 2.0 -15.0 0.0 -3 0.0 0.0
+-30.0 2.0 -15.0 15.0 -3 0.0 0.0
+-30.0 2.0 -14.0 0.0 -3 0.0 0.0
+-30.0 2.0 -14.0 15.0 -3 0.0 0.0
+-30.0 2.0 -13.0 0.0 -3 0.0 0.0
+-30.0 2.0 -13.0 15.0 -3 0.0 0.0
+-30.0 2.0 -12.0 0.0 -3 0.0 0.0
+-30.0 2.0 -12.0 15.0 -3 0.0 0.0
+-30.0 2.0 -11.0 0.0 -3 0.0 0.0
+-30.0 2.0 -11.0 15.0 -3 0.0 0.0
+-30.0 2.0 -10.0 0.0 -3 0.0 0.0
+-30.0 2.0 -10.0 15.0 -3 0.0 0.0
+-30.0 2.0 -9.0 0.0 -3 0.0 0.0
+-30.0 2.0 -9.0 15.0 -3 0.0 0.0
+-30.0 2.0 -8.0 0.0 -3 0.0 0.0
+-30.0 2.0 -8.0 15.0 -3 0.0 0.0
+-30.0 2.0 -7.0 0.0 -3 0.0 0.0
+-30.0 2.0 -7.0 15.0 -3 0.0 0.0
+-30.0 2.0 -6.0 0.0 -3 0.0 0.0
+-30.0 2.0 -6.0 15.0 -3 0.0 0.0
+-30.0 2.0 -5.0 0.0 -3 0.0 0.0
+-30.0 2.0 -5.0 15.0 -3 0.0 0.0
+-30.0 2.0 -4.0 0.0 -3 0.0 0.0
+-30.0 2.0 -4.0 15.0 -3 0.0 0.0
+-30.0 2.0 -3.0 0.0 -3 0.0 0.0
+-30.0 2.0 -3.0 15.0 -3 0.0 0.0
+-30.0 2.0 -2.0 0.0 -3 0.0 0.0
+-30.0 2.0 -2.0 15.0 -3 0.0 0.0
+-30.0 2.0 -1.0 0.0 -3 0.0 0.0
+-30.0 2.0 -1.0 15.0 -3 0.0 0.0
+-30.0 2.0 0.0 0.0 -3 0.0 0.0
+-30.0 2.0 0.0 15.0 -3 0.0 0.0
+-30.0 2.0 1.0 0.0 -3 0.0 0.0
+-30.0 2.0 1.0 15.0 -3 0.0 0.0
+-30.0 2.0 2.0 0.0 -3 0.0 0.0
+-30.0 2.0 2.0 15.0 -3 0.0 0.0
+-30.0 2.0 3.0 0.0 -3 0.0 0.0
+-30.0 2.0 3.0 15.0 -3 0.0 0.0
+-30.0 2.0 4.0 0.0 -3 0.0 0.0
+-30.0 2.0 4.0 15.0 -3 0.0 0.0
+-30.0 2.0 5.0 0.0 -3 0.0 0.0
+-30.0 2.0 5.0 15.0 -3 0.0 0.0
+-20.0 -2.0 -30.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -30.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -29.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -29.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -28.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -28.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -27.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -27.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -26.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -26.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -25.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -25.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -24.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -24.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -23.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -23.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -22.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -22.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -21.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -21.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -20.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -20.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -19.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -19.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -18.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -18.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -17.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -17.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -16.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -16.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -15.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -15.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -14.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -14.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -13.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -13.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -12.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -12.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -11.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -11.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -10.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -10.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -9.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -9.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -8.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -8.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -7.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -7.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -6.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -6.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -5.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -5.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -4.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -4.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -3.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -3.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -2.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -2.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 -1.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 -1.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 0.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 0.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 1.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 1.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 2.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 2.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 3.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 3.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 4.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 4.0 15.0 -2 -0.0 -0.0
+-20.0 -2.0 5.0 0.0 -2 -0.0 -0.0
+-20.0 -2.0 5.0 15.0 -2 -0.0 -0.0
+-20.0 2.0 -30.0 0.0 -2 0.0 0.0
+-20.0 2.0 -30.0 15.0 -2 0.0 0.0
+-20.0 2.0 -29.0 0.0 -2 0.0 0.0
+-20.0 2.0 -29.0 15.0 -2 0.0 0.0
+-20.0 2.0 -28.0 0.0 -2 0.0 0.0
+-20.0 2.0 -28.0 15.0 -2 0.0 0.0
+-20.0 2.0 -27.0 0.0 -2 0.0 0.0
+-20.0 2.0 -27.0 15.0 -2 0.0 0.0
+-20.0 2.0 -26.0 0.0 -2 0.0 0.0
+-20.0 2.0 -26.0 15.0 -2 0.0 0.0
+-20.0 2.0 -25.0 0.0 -2 0.0 0.0
+-20.0 2.0 -25.0 15.0 -2 0.0 0.0
+-20.0 2.0 -24.0 0.0 -2 0.0 0.0
+-20.0 2.0 -24.0 15.0 -2 0.0 0.0
+-20.0 2.0 -23.0 0.0 -2 0.0 0.0
+-20.0 2.0 -23.0 15.0 -2 0.0 0.0
+-20.0 2.0 -22.0 0.0 -2 0.0 0.0
+-20.0 2.0 -22.0 15.0 -2 0.0 0.0
+-20.0 2.0 -21.0 0.0 -2 0.0 0.0
+-20.0 2.0 -21.0 15.0 -2 0.0 0.0
+-20.0 2.0 -20.0 0.0 -2 0.0 0.0
+-20.0 2.0 -20.0 15.0 -2 0.0 0.0
+-20.0 2.0 -19.0 0.0 -2 0.0 0.0
+-20.0 2.0 -19.0 15.0 -2 0.0 0.0
+-20.0 2.0 -18.0 0.0 -2 0.0 0.0
+-20.0 2.0 -18.0 15.0 -2 0.0 0.0
+-20.0 2.0 -17.0 0.0 -2 0.0 0.0
+-20.0 2.0 -17.0 15.0 -2 0.0 0.0
+-20.0 2.0 -16.0 0.0 -2 0.0 0.0
+-20.0 2.0 -16.0 15.0 -2 0.0 0.0
+-20.0 2.0 -15.0 0.0 -2 0.0 0.0
+-20.0 2.0 -15.0 15.0 -2 0.0 0.0
+-20.0 2.0 -14.0 0.0 -2 0.0 0.0
+-20.0 2.0 -14.0 15.0 -2 0.0 0.0
+-20.0 2.0 -13.0 0.0 -2 0.0 0.0
+-20.0 2.0 -13.0 15.0 -2 0.0 0.0
+-20.0 2.0 -12.0 0.0 -2 0.0 0.0
+-20.0 2.0 -12.0 15.0 -2 0.0 0.0
+-20.0 2.0 -11.0 0.0 -2 0.0 0.0
+-20.0 2.0 -11.0 15.0 -2 0.0 0.0
+-20.0 2.0 -10.0 0.0 -2 0.0 0.0
+-20.0 2.0 -10.0 15.0 -2 0.0 0.0
+-20.0 2.0 -9.0 0.0 -2 0.0 0.0
+-20.0 2.0 -9.0 15.0 -2 0.0 0.0
+-20.0 2.0 -8.0 0.0 -2 0.0 0.0
+-20.0 2.0 -8.0 15.0 -2 0.0 0.0
+-20.0 2.0 -7.0 0.0 -2 0.0 0.0
+-20.0 2.0 -7.0 15.0 -2 0.0 0.0
+-20.0 2.0 -6.0 0.0 -2 0.0 0.0
+-20.0 2.0 -6.0 15.0 -2 0.0 0.0
+-20.0 2.0 -5.0 0.0 -2 0.0 0.0
+-20.0 2.0 -5.0 15.0 -2 0.0 0.0
+-20.0 2.0 -4.0 0.0 -2 0.0 0.0
+-20.0 2.0 -4.0 15.0 -2 0.0 0.0
+-20.0 2.0 -3.0 0.0 -2 0.0 0.0
+-20.0 2.0 -3.0 15.0 -2 0.0 0.0
+-20.0 2.0 -2.0 0.0 -2 0.0 0.0
+-20.0 2.0 -2.0 15.0 -2 0.0 0.0
+-20.0 2.0 -1.0 0.0 -2 0.0 0.0
+-20.0 2.0 -1.0 15.0 -2 0.0 0.0
+-20.0 2.0 0.0 0.0 -2 0.0 0.0
+-20.0 2.0 0.0 15.0 -2 0.0 0.0
+-20.0 2.0 1.0 0.0 -2 0.0 0.0
+-20.0 2.0 1.0 15.0 -2 0.0 0.0
+-20.0 2.0 2.0 0.0 -2 0.0 0.0
+-20.0 2.0 2.0 15.0 -2 0.0 0.0
+-20.0 2.0 3.0 0.0 -2 0.0 0.0
+-20.0 2.0 3.0 15.0 -2 0.0 0.0
+-20.0 2.0 4.0 0.0 -2 0.0 0.0
+-20.0 2.0 4.0 15.0 -2 0.0 0.0
+-20.0 2.0 5.0 0.0 -2 0.0 0.0
+-20.0 2.0 5.0 15.0 -2 0.0 0.0
+-10.0 -2.0 -30.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -30.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -29.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -29.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -28.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -28.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -27.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -27.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -26.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -26.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -25.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -25.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -24.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -24.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -23.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -23.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -22.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -22.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -21.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -21.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -20.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -20.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -19.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -19.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -18.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -18.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -17.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -17.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -16.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -16.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -15.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -15.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -14.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -14.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -13.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -13.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -12.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -12.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -11.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -11.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -10.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -10.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -9.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -9.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -8.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -8.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -7.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -7.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -6.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -6.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -5.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -5.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -4.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -4.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -3.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -3.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -2.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -2.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 -1.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 -1.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 0.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 0.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 1.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 1.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 2.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 2.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 3.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 3.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 4.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 4.0 15.0 -1 -0.0 -0.0
+-10.0 -2.0 5.0 0.0 -1 -0.0 -0.0
+-10.0 -2.0 5.0 15.0 -1 -0.0 -0.0
+-10.0 2.0 -30.0 0.0 -1 0.0 0.0
+-10.0 2.0 -30.0 15.0 -1 0.0 0.0
+-10.0 2.0 -29.0 0.0 -1 0.0 0.0
+-10.0 2.0 -29.0 15.0 -1 0.0 0.0
+-10.0 2.0 -28.0 0.0 -1 0.0 0.0
+-10.0 2.0 -28.0 15.0 -1 0.0 0.0
+-10.0 2.0 -27.0 0.0 -1 0.0 0.0
+-10.0 2.0 -27.0 15.0 -1 0.0 0.0
+-10.0 2.0 -26.0 0.0 -1 0.0 0.0
+-10.0 2.0 -26.0 15.0 -1 0.0 0.0
+-10.0 2.0 -25.0 0.0 -1 0.0 0.0
+-10.0 2.0 -25.0 15.0 -1 0.0 0.0
+-10.0 2.0 -24.0 0.0 -1 0.0 0.0
+-10.0 2.0 -24.0 15.0 -1 0.0 0.0
+-10.0 2.0 -23.0 0.0 -1 0.0 0.0
+-10.0 2.0 -23.0 15.0 -1 0.0 0.0
+-10.0 2.0 -22.0 0.0 -1 0.0 0.0
+-10.0 2.0 -22.0 15.0 -1 0.0 0.0
+-10.0 2.0 -21.0 0.0 -1 0.0 0.0
+-10.0 2.0 -21.0 15.0 -1 0.0 0.0
+-10.0 2.0 -20.0 0.0 -1 0.0 0.0
+-10.0 2.0 -20.0 15.0 -1 0.0 0.0
+-10.0 2.0 -19.0 0.0 -1 0.0 0.0
+-10.0 2.0 -19.0 15.0 -1 0.0 0.0
+-10.0 2.0 -18.0 0.0 -1 0.0 0.0
+-10.0 2.0 -18.0 15.0 -1 0.0 0.0
+-10.0 2.0 -17.0 0.0 -1 0.0 0.0
+-10.0 2.0 -17.0 15.0 -1 0.0 0.0
+-10.0 2.0 -16.0 0.0 -1 0.0 0.0
+-10.0 2.0 -16.0 15.0 -1 0.0 0.0
+-10.0 2.0 -15.0 0.0 -1 0.0 0.0
+-10.0 2.0 -15.0 15.0 -1 0.0 0.0
+-10.0 2.0 -14.0 0.0 -1 0.0 0.0
+-10.0 2.0 -14.0 15.0 -1 0.0 0.0
+-10.0 2.0 -13.0 0.0 -1 0.0 0.0
+-10.0 2.0 -13.0 15.0 -1 0.0 0.0
+-10.0 2.0 -12.0 0.0 -1 0.0 0.0
+-10.0 2.0 -12.0 15.0 -1 0.0 0.0
+-10.0 2.0 -11.0 0.0 -1 0.0 0.0
+-10.0 2.0 -11.0 15.0 -1 0.0 0.0
+-10.0 2.0 -10.0 0.0 -1 0.0 0.0
+-10.0 2.0 -10.0 15.0 -1 0.0 0.0
+-10.0 2.0 -9.0 0.0 -1 0.0 0.0
+-10.0 2.0 -9.0 15.0 -1 0.0 0.0
+-10.0 2.0 -8.0 0.0 -1 0.0 0.0
+-10.0 2.0 -8.0 15.0 -1 0.0 0.0
+-10.0 2.0 -7.0 0.0 -1 0.0 0.0
+-10.0 2.0 -7.0 15.0 -1 0.0 0.0
+-10.0 2.0 -6.0 0.0 -1 0.0 0.0
+-10.0 2.0 -6.0 15.0 -1 0.0 0.0
+-10.0 2.0 -5.0 0.0 -1 0.0 0.0
+-10.0 2.0 -5.0 15.0 -1 0.0 0.0
+-10.0 2.0 -4.0 0.0 -1 0.0 0.0
+-10.0 2.0 -4.0 15.0 -1 0.0 0.0
+-10.0 2.0 -3.0 0.0 -1 0.0 0.0
+-10.0 2.0 -3.0 15.0 -1 0.0 0.0
+-10.0 2.0 -2.0 0.0 -1 0.0 0.0
+-10.0 2.0 -2.0 15.0 -1 0.0 0.0
+-10.0 2.0 -1.0 0.0 -1 0.0 0.0
+-10.0 2.0 -1.0 15.0 -1 0.0 0.0
+-10.0 2.0 0.0 0.0 -1 0.0 0.0
+-10.0 2.0 0.0 15.0 -1 0.0 0.0
+-10.0 2.0 1.0 0.0 -1 0.0 0.0
+-10.0 2.0 1.0 15.0 -1 0.0 0.0
+-10.0 2.0 2.0 0.0 -1 0.0 0.0
+-10.0 2.0 2.0 15.0 -1 0.0 0.0
+-10.0 2.0 3.0 0.0 -1 0.0 0.0
+-10.0 2.0 3.0 15.0 -1 0.0 0.0
+-10.0 2.0 4.0 0.0 -1 0.0 0.0
+-10.0 2.0 4.0 15.0 -1 0.0 0.0
+-10.0 2.0 5.0 0.0 -1 0.0 0.0
+-10.0 2.0 5.0 15.0 -1 0.0 0.0
+0.0 -2.0 -30.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -30.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -29.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -29.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -28.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -28.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -27.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -27.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -26.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -26.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -25.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -25.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -24.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -24.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -23.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -23.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -22.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -22.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -21.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -21.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -20.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -20.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -19.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -19.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -18.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -18.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -17.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -17.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -16.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -16.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -15.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -15.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -14.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -14.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -13.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -13.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -12.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -12.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -11.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -11.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -10.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -10.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -9.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -9.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -8.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -8.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -7.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -7.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -6.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -6.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -5.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -5.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -4.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -4.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -3.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -3.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -2.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -2.0 15.0 0 -0.0 -0.0
+0.0 -2.0 -1.0 0.0 0 -0.0 -0.0
+0.0 -2.0 -1.0 15.0 0 -0.0 -0.0
+0.0 -2.0 0.0 0.0 0 -0.0 -0.0
+0.0 -2.0 0.0 15.0 0 -0.0 -0.0
+0.0 -2.0 1.0 0.0 0 -0.0 -0.0
+0.0 -2.0 1.0 15.0 0 -0.0 -0.0
+0.0 -2.0 2.0 0.0 0 -0.0 -0.0
+0.0 -2.0 2.0 15.0 0 -0.0 -0.0
+0.0 -2.0 3.0 0.0 0 -0.0 -0.0
+0.0 -2.0 3.0 15.0 0 -0.0 -0.0
+0.0 -2.0 4.0 0.0 0 -0.0 -0.0
+0.0 -2.0 4.0 15.0 0 -0.0 -0.0
+0.0 -2.0 5.0 0.0 0 -0.0 -0.0
+0.0 -2.0 5.0 15.0 0 -0.0 -0.0
+0.0 2.0 -30.0 0.0 0 0.0 0.0
+0.0 2.0 -30.0 15.0 0 0.0 0.0
+0.0 2.0 -29.0 0.0 0 0.0 0.0
+0.0 2.0 -29.0 15.0 0 0.0 0.0
+0.0 2.0 -28.0 0.0 0 0.0 0.0
+0.0 2.0 -28.0 15.0 0 0.0 0.0
+0.0 2.0 -27.0 0.0 0 0.0 0.0
+0.0 2.0 -27.0 15.0 0 0.0 0.0
+0.0 2.0 -26.0 0.0 0 0.0 0.0
+0.0 2.0 -26.0 15.0 0 0.0 0.0
+0.0 2.0 -25.0 0.0 0 0.0 0.0
+0.0 2.0 -25.0 15.0 0 0.0 0.0
+0.0 2.0 -24.0 0.0 0 0.0 0.0
+0.0 2.0 -24.0 15.0 0 0.0 0.0
+0.0 2.0 -23.0 0.0 0 0.0 0.0
+0.0 2.0 -23.0 15.0 0 0.0 0.0
+0.0 2.0 -22.0 0.0 0 0.0 0.0
+0.0 2.0 -22.0 15.0 0 0.0 0.0
+0.0 2.0 -21.0 0.0 0 0.0 0.0
+0.0 2.0 -21.0 15.0 0 0.0 0.0
+0.0 2.0 -20.0 0.0 0 0.0 0.0
+0.0 2.0 -20.0 15.0 0 0.0 0.0
+0.0 2.0 -19.0 0.0 0 0.0 0.0
+0.0 2.0 -19.0 15.0 0 0.0 0.0
+0.0 2.0 -18.0 0.0 0 0.0 0.0
+0.0 2.0 -18.0 15.0 0 0.0 0.0
+0.0 2.0 -17.0 0.0 0 0.0 0.0
+0.0 2.0 -17.0 15.0 0 0.0 0.0
+0.0 2.0 -16.0 0.0 0 0.0 0.0
+0.0 2.0 -16.0 15.0 0 0.0 0.0
+0.0 2.0 -15.0 0.0 0 0.0 0.0
+0.0 2.0 -15.0 15.0 0 0.0 0.0
+0.0 2.0 -14.0 0.0 0 0.0 0.0
+0.0 2.0 -14.0 15.0 0 0.0 0.0
+0.0 2.0 -13.0 0.0 0 0.0 0.0
+0.0 2.0 -13.0 15.0 0 0.0 0.0
+0.0 2.0 -12.0 0.0 0 0.0 0.0
+0.0 2.0 -12.0 15.0 0 0.0 0.0
+0.0 2.0 -11.0 0.0 0 0.0 0.0
+0.0 2.0 -11.0 15.0 0 0.0 0.0
+0.0 2.0 -10.0 0.0 0 0.0 0.0
+0.0 2.0 -10.0 15.0 0 0.0 0.0
+0.0 2.0 -9.0 0.0 0 0.0 0.0
+0.0 2.0 -9.0 15.0 0 0.0 0.0
+0.0 2.0 -8.0 0.0 0 0.0 0.0
+0.0 2.0 -8.0 15.0 0 0.0 0.0
+0.0 2.0 -7.0 0.0 0 0.0 0.0
+0.0 2.0 -7.0 15.0 0 0.0 0.0
+0.0 2.0 -6.0 0.0 0 0.0 0.0
+0.0 2.0 -6.0 15.0 0 0.0 0.0
+0.0 2.0 -5.0 0.0 0 0.0 0.0
+0.0 2.0 -5.0 15.0 0 0.0 0.0
+0.0 2.0 -4.0 0.0 0 0.0 0.0
+0.0 2.0 -4.0 15.0 0 0.0 0.0
+0.0 2.0 -3.0 0.0 0 0.0 0.0
+0.0 2.0 -3.0 15.0 0 0.0 0.0
+0.0 2.0 -2.0 0.0 0 0.0 0.0
+0.0 2.0 -2.0 15.0 0 0.0 0.0
+0.0 2.0 -1.0 0.0 0 0.0 0.0
+0.0 2.0 -1.0 15.0 0 0.0 0.0
+0.0 2.0 0.0 0.0 0 0.0 0.0
+0.0 2.0 0.0 15.0 0 0.0 0.0
+0.0 2.0 1.0 0.0 0 0.0 0.0
+0.0 2.0 1.0 15.0 0 0.0 0.0
+0.0 2.0 2.0 0.0 0 0.0 0.0
+0.0 2.0 2.0 15.0 0 0.0 0.0
+0.0 2.0 3.0 0.0 0 0.0 0.0
+0.0 2.0 3.0 15.0 0 0.0 0.0
+0.0 2.0 4.0 0.0 0 0.0 0.0
+0.0 2.0 4.0 15.0 0 0.0 0.0
+0.0 2.0 5.0 0.0 0 0.0 0.0
+0.0 2.0 5.0 15.0 0 0.0 0.0
+10.0 -2.0 -30.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -30.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -29.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -29.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -28.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -28.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -27.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -27.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -26.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -26.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -25.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -25.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -24.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -24.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -23.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -23.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -22.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -22.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -21.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -21.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -20.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -20.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -19.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -19.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -18.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -18.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -17.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -17.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -16.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -16.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -15.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -15.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -14.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -14.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -13.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -13.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -12.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -12.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -11.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -11.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -10.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -10.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -9.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -9.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -8.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -8.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -7.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -7.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -6.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -6.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -5.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -5.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -4.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -4.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -3.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -3.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -2.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -2.0 15.0 1 -0.0 -0.0
+10.0 -2.0 -1.0 0.0 1 -0.0 -0.0
+10.0 -2.0 -1.0 15.0 1 -0.0 -0.0
+10.0 -2.0 0.0 0.0 1 -0.0 -0.0
+10.0 -2.0 0.0 15.0 1 -0.0 -0.0
+10.0 -2.0 1.0 0.0 1 -0.0 -0.0
+10.0 -2.0 1.0 15.0 1 -0.0 -0.0
+10.0 -2.0 2.0 0.0 1 -0.0 -0.0
+10.0 -2.0 2.0 15.0 1 -0.0 -0.0
+10.0 -2.0 3.0 0.0 1 -0.0 -0.0
+10.0 -2.0 3.0 15.0 1 -0.0 -0.0
+10.0 -2.0 4.0 0.0 1 -0.0 -0.0
+10.0 -2.0 4.0 15.0 1 -0.0 -0.0
+10.0 -2.0 5.0 0.0 1 -0.0 -0.0
+10.0 -2.0 5.0 15.0 1 -0.0 -0.0
+10.0 2.0 -30.0 0.0 1 0.0 0.0
+10.0 2.0 -30.0 15.0 1 0.0 0.0
+10.0 2.0 -29.0 0.0 1 0.0 0.0
+10.0 2.0 -29.0 15.0 1 0.0 0.0
+10.0 2.0 -28.0 0.0 1 0.0 0.0
+10.0 2.0 -28.0 15.0 1 0.0 0.0
+10.0 2.0 -27.0 0.0 1 0.0 0.0
+10.0 2.0 -27.0 15.0 1 0.0 0.0
+10.0 2.0 -26.0 0.0 1 0.0 0.0
+10.0 2.0 -26.0 15.0 1 0.0 0.0
+10.0 2.0 -25.0 0.0 1 0.0 0.0
+10.0 2.0 -25.0 15.0 1 0.0 0.0
+10.0 2.0 -24.0 0.0 1 0.0 0.0
+10.0 2.0 -24.0 15.0 1 0.0 0.0
+10.0 2.0 -23.0 0.0 1 0.0 0.0
+10.0 2.0 -23.0 15.0 1 0.0 0.0
+10.0 2.0 -22.0 0.0 1 0.0 0.0
+10.0 2.0 -22.0 15.0 1 0.0 0.0
+10.0 2.0 -21.0 0.0 1 0.0 0.0
+10.0 2.0 -21.0 15.0 1 0.0 0.0
+10.0 2.0 -20.0 0.0 1 0.0 0.0
+10.0 2.0 -20.0 15.0 1 0.0 0.0
+10.0 2.0 -19.0 0.0 1 0.0 0.0
+10.0 2.0 -19.0 15.0 1 0.0 0.0
+10.0 2.0 -18.0 0.0 1 0.0 0.0
+10.0 2.0 -18.0 15.0 1 0.0 0.0
+10.0 2.0 -17.0 0.0 1 0.0 0.0
+10.0 2.0 -17.0 15.0 1 0.0 0.0
+10.0 2.0 -16.0 0.0 1 0.0 0.0
+10.0 2.0 -16.0 15.0 1 0.0 0.0
+10.0 2.0 -15.0 0.0 1 0.0 0.0
+10.0 2.0 -15.0 15.0 1 0.0 0.0
+10.0 2.0 -14.0 0.0 1 0.0 0.0
+10.0 2.0 -14.0 15.0 1 0.0 0.0
+10.0 2.0 -13.0 0.0 1 0.0 0.0
+10.0 2.0 -13.0 15.0 1 0.0 0.0
+10.0 2.0 -12.0 0.0 1 0.0 0.0
+10.0 2.0 -12.0 15.0 1 0.0 0.0
+10.0 2.0 -11.0 0.0 1 0.0 0.0
+10.0 2.0 -11.0 15.0 1 0.0 0.0
+10.0 2.0 -10.0 0.0 1 0.0 0.0
+10.0 2.0 -10.0 15.0 1 0.0 0.0
+10.0 2.0 -9.0 0.0 1 0.0 0.0
+10.0 2.0 -9.0 15.0 1 0.0 0.0
+10.0 2.0 -8.0 0.0 1 0.0 0.0
+10.0 2.0 -8.0 15.0 1 0.0 0.0
+10.0 2.0 -7.0 0.0 1 0.0 0.0
+10.0 2.0 -7.0 15.0 1 0.0 0.0
+10.0 2.0 -6.0 0.0 1 0.0 0.0
+10.0 2.0 -6.0 15.0 1 0.0 0.0
+10.0 2.0 -5.0 0.0 1 0.0 0.0
+10.0 2.0 -5.0 15.0 1 0.0 0.0
+10.0 2.0 -4.0 0.0 1 0.0 0.0
+10.0 2.0 -4.0 15.0 1 0.0 0.0
+10.0 2.0 -3.0 0.0 1 0.0 0.0
+10.0 2.0 -3.0 15.0 1 0.0 0.0
+10.0 2.0 -2.0 0.0 1 0.0 0.0
+10.0 2.0 -2.0 15.0 1 0.0 0.0
+10.0 2.0 -1.0 0.0 1 0.0 0.0
+10.0 2.0 -1.0 15.0 1 0.0 0.0
+10.0 2.0 0.0 0.0 1 0.0 0.0
+10.0 2.0 0.0 15.0 1 0.0 0.0
+10.0 2.0 1.0 0.0 1 0.0 0.0
+10.0 2.0 1.0 15.0 1 0.0 0.0
+10.0 2.0 2.0 0.0 1 0.0 0.0
+10.0 2.0 2.0 15.0 1 0.0 0.0
+10.0 2.0 3.0 0.0 1 0.0 0.0
+10.0 2.0 3.0 15.0 1 0.0 0.0
+10.0 2.0 4.0 0.0 1 0.0 0.0
+10.0 2.0 4.0 15.0 1 0.0 0.0
+10.0 2.0 5.0 0.0 1 0.0 0.0
+10.0 2.0 5.0 15.0 1 0.0 0.0
+20.0 -2.0 -30.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -30.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -29.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -29.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -28.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -28.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -27.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -27.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -26.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -26.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -25.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -25.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -24.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -24.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -23.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -23.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -22.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -22.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -21.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -21.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -20.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -20.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -19.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -19.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -18.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -18.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -17.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -17.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -16.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -16.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -15.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -15.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -14.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -14.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -13.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -13.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -12.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -12.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -11.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -11.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -10.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -10.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -9.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -9.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -8.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -8.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -7.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -7.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -6.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -6.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -5.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -5.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -4.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -4.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -3.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -3.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -2.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -2.0 15.0 2 -0.0 -0.0
+20.0 -2.0 -1.0 0.0 2 -0.0 -0.0
+20.0 -2.0 -1.0 15.0 2 -0.0 -0.0
+20.0 -2.0 0.0 0.0 2 -0.0 -0.0
+20.0 -2.0 0.0 15.0 2 -0.0 -0.0
+20.0 -2.0 1.0 0.0 2 -0.0 -0.0
+20.0 -2.0 1.0 15.0 2 -0.0 -0.0
+20.0 -2.0 2.0 0.0 2 -0.0 -0.0
+20.0 -2.0 2.0 15.0 2 -0.0 -0.0
+20.0 -2.0 3.0 0.0 2 -0.0 -0.0
+20.0 -2.0 3.0 15.0 2 -0.0 -0.0
+20.0 -2.0 4.0 0.0 2 -0.0 -0.0
+20.0 -2.0 4.0 15.0 2 -0.0 -0.0
+20.0 -2.0 5.0 0.0 2 -0.0 -0.0
+20.0 -2.0 5.0 15.0 2 -0.0 -0.0
+20.0 2.0 -30.0 0.0 2 0.0 0.0
+20.0 2.0 -30.0 15.0 2 0.0 0.0
+20.0 2.0 -29.0 0.0 2 0.0 0.0
+20.0 2.0 -29.0 15.0 2 0.0 0.0
+20.0 2.0 -28.0 0.0 2 0.0 0.0
+20.0 2.0 -28.0 15.0 2 0.0 0.0
+20.0 2.0 -27.0 0.0 2 0.0 0.0
+20.0 2.0 -27.0 15.0 2 0.0 0.0
+20.0 2.0 -26.0 0.0 2 0.0 0.0
+20.0 2.0 -26.0 15.0 2 0.0 0.0
+20.0 2.0 -25.0 0.0 2 0.0 0.0
+20.0 2.0 -25.0 15.0 2 0.0 0.0
+20.0 2.0 -24.0 0.0 2 0.0 0.0
+20.0 2.0 -24.0 15.0 2 0.0 0.0
+20.0 2.0 -23.0 0.0 2 0.0 0.0
+20.0 2.0 -23.0 15.0 2 0.0 0.0
+20.0 2.0 -22.0 0.0 2 0.0 0.0
+20.0 2.0 -22.0 15.0 2 0.0 0.0
+20.0 2.0 -21.0 0.0 2 0.0 0.0
+20.0 2.0 -21.0 15.0 2 0.0 0.0
+20.0 2.0 -20.0 0.0 2 0.0 0.0
+20.0 2.0 -20.0 15.0 2 0.0 0.0
+20.0 2.0 -19.0 0.0 2 0.0 0.0
+20.0 2.0 -19.0 15.0 2 0.0 0.0
+20.0 2.0 -18.0 0.0 2 0.0 0.0
+20.0 2.0 -18.0 15.0 2 0.0 0.0
+20.0 2.0 -17.0 0.0 2 0.0 0.0
+20.0 2.0 -17.0 15.0 2 0.0 0.0
+20.0 2.0 -16.0 0.0 2 0.0 0.0
+20.0 2.0 -16.0 15.0 2 0.0 0.0
+20.0 2.0 -15.0 0.0 2 0.0 0.0
+20.0 2.0 -15.0 15.0 2 0.0 0.0
+20.0 2.0 -14.0 0.0 2 0.0 0.0
+20.0 2.0 -14.0 15.0 2 0.0 0.0
+20.0 2.0 -13.0 0.0 2 0.0 0.0
+20.0 2.0 -13.0 15.0 2 0.0 0.0
+20.0 2.0 -12.0 0.0 2 0.0 0.0
+20.0 2.0 -12.0 15.0 2 0.0 0.0
+20.0 2.0 -11.0 0.0 2 0.0 0.0
+20.0 2.0 -11.0 15.0 2 0.0 0.0
+20.0 2.0 -10.0 0.0 2 0.0 0.0
+20.0 2.0 -10.0 15.0 2 0.0 0.0
+20.0 2.0 -9.0 0.0 2 0.0 0.0
+20.0 2.0 -9.0 15.0 2 0.0 0.0
+20.0 2.0 -8.0 0.0 2 0.0 0.0
+20.0 2.0 -8.0 15.0 2 0.0 0.0
+20.0 2.0 -7.0 0.0 2 0.0 0.0
+20.0 2.0 -7.0 15.0 2 0.0 0.0
+20.0 2.0 -6.0 0.0 2 0.0 0.0
+20.0 2.0 -6.0 15.0 2 0.0 0.0
+20.0 2.0 -5.0 0.0 2 0.0 0.0
+20.0 2.0 -5.0 15.0 2 0.0 0.0
+20.0 2.0 -4.0 0.0 2 0.0 0.0
+20.0 2.0 -4.0 15.0 2 0.0 0.0
+20.0 2.0 -3.0 0.0 2 0.0 0.0
+20.0 2.0 -3.0 15.0 2 0.0 0.0
+20.0 2.0 -2.0 0.0 2 0.0 0.0
+20.0 2.0 -2.0 15.0 2 0.0 0.0
+20.0 2.0 -1.0 0.0 2 0.0 0.0
+20.0 2.0 -1.0 15.0 2 0.0 0.0
+20.0 2.0 0.0 0.0 2 0.0 0.0
+20.0 2.0 0.0 15.0 2 0.0 0.0
+20.0 2.0 1.0 0.0 2 0.0 0.0
+20.0 2.0 1.0 15.0 2 0.0 0.0
+20.0 2.0 2.0 0.0 2 0.0 0.0
+20.0 2.0 2.0 15.0 2 0.0 0.0
+20.0 2.0 3.0 0.0 2 0.0 0.0
+20.0 2.0 3.0 15.0 2 0.0 0.0
+20.0 2.0 4.0 0.0 2 0.0 0.0
+20.0 2.0 4.0 15.0 2 0.0 0.0
+20.0 2.0 5.0 0.0 2 0.0 0.0
+20.0 2.0 5.0 15.0 2 0.0 0.0
+30.0 -2.0 -30.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -30.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -29.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -29.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -28.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -28.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -27.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -27.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -26.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -26.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -25.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -25.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -24.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -24.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -23.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -23.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -22.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -22.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -21.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -21.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -20.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -20.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -19.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -19.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -18.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -18.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -17.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -17.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -16.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -16.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -15.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -15.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -14.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -14.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -13.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -13.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -12.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -12.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -11.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -11.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -10.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -10.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -9.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -9.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -8.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -8.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -7.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -7.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -6.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -6.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -5.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -5.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -4.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -4.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -3.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -3.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -2.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -2.0 15.0 3 -0.0 -0.0
+30.0 -2.0 -1.0 0.0 3 -0.0 -0.0
+30.0 -2.0 -1.0 15.0 3 -0.0 -0.0
+30.0 -2.0 0.0 0.0 3 -0.0 -0.0
+30.0 -2.0 0.0 15.0 3 -0.0 -0.0
+30.0 -2.0 1.0 0.0 3 -0.0 -0.0
+30.0 -2.0 1.0 15.0 3 -0.0 -0.0
+30.0 -2.0 2.0 0.0 3 -0.0 -0.0
+30.0 -2.0 2.0 15.0 3 -0.0 -0.0
+30.0 -2.0 3.0 0.0 3 -0.0 -0.0
+30.0 -2.0 3.0 15.0 3 -0.0 -0.0
+30.0 -2.0 4.0 0.0 3 -0.0 -0.0
+30.0 -2.0 4.0 15.0 3 -0.0 -0.0
+30.0 -2.0 5.0 0.0 3 -0.0 -0.0
+30.0 -2.0 5.0 15.0 3 -0.0 -0.0
+30.0 2.0 -30.0 0.0 3 0.0 0.0
+30.0 2.0 -30.0 15.0 3 0.0 0.0
+30.0 2.0 -29.0 0.0 3 0.0 0.0
+30.0 2.0 -29.0 15.0 3 0.0 0.0
+30.0 2.0 -28.0 0.0 3 0.0 0.0
+30.0 2.0 -28.0 15.0 3 0.0 0.0
+30.0 2.0 -27.0 0.0 3 0.0 0.0
+30.0 2.0 -27.0 15.0 3 0.0 0.0
+30.0 2.0 -26.0 0.0 3 0.0 0.0
+30.0 2.0 -26.0 15.0 3 0.0 0.0
+30.0 2.0 -25.0 0.0 3 0.0 0.0
+30.0 2.0 -25.0 15.0 3 0.0 0.0
+30.0 2.0 -24.0 0.0 3 0.0 0.0
+30.0 2.0 -24.0 15.0 3 0.0 0.0
+30.0 2.0 -23.0 0.0 3 0.0 0.0
+30.0 2.0 -23.0 15.0 3 0.0 0.0
+30.0 2.0 -22.0 0.0 3 0.0 0.0
+30.0 2.0 -22.0 15.0 3 0.0 0.0
+30.0 2.0 -21.0 0.0 3 0.0 0.0
+30.0 2.0 -21.0 15.0 3 0.0 0.0
+30.0 2.0 -20.0 0.0 3 0.0 0.0
+30.0 2.0 -20.0 15.0 3 0.0 0.0
+30.0 2.0 -19.0 0.0 3 0.0 0.0
+30.0 2.0 -19.0 15.0 3 0.0 0.0
+30.0 2.0 -18.0 0.0 3 0.0 0.0
+30.0 2.0 -18.0 15.0 3 0.0 0.0
+30.0 2.0 -17.0 0.0 3 0.0 0.0
+30.0 2.0 -17.0 15.0 3 0.0 0.0
+30.0 2.0 -16.0 0.0 3 0.0 0.0
+30.0 2.0 -16.0 15.0 3 0.0 0.0
+30.0 2.0 -15.0 0.0 3 0.0 0.0
+30.0 2.0 -15.0 15.0 3 0.0 0.0
+30.0 2.0 -14.0 0.0 3 0.0 0.0
+30.0 2.0 -14.0 15.0 3 0.0 0.0
+30.0 2.0 -13.0 0.0 3 0.0 0.0
+30.0 2.0 -13.0 15.0 3 0.0 0.0
+30.0 2.0 -12.0 0.0 3 0.0 0.0
+30.0 2.0 -12.0 15.0 3 0.0 0.0
+30.0 2.0 -11.0 0.0 3 0.0 0.0
+30.0 2.0 -11.0 15.0 3 0.0 0.0
+30.0 2.0 -10.0 0.0 3 0.0 0.0
+30.0 2.0 -10.0 15.0 3 0.0 0.0
+30.0 2.0 -9.0 0.0 3 0.0 0.0
+30.0 2.0 -9.0 15.0 3 0.0 0.0
+30.0 2.0 -8.0 0.0 3 0.0 0.0
+30.0 2.0 -8.0 15.0 3 0.0 0.0
+30.0 2.0 -7.0 0.0 3 0.0 0.0
+30.0 2.0 -7.0 15.0 3 0.0 0.0
+30.0 2.0 -6.0 0.0 3 0.0 0.0
+30.0 2.0 -6.0 15.0 3 0.0 0.0
+30.0 2.0 -5.0 0.0 3 0.0 0.0
+30.0 2.0 -5.0 15.0 3 0.0 0.0
+30.0 2.0 -4.0 0.0 3 0.0 0.0
+30.0 2.0 -4.0 15.0 3 0.0 0.0
+30.0 2.0 -3.0 0.0 3 0.0 0.0
+30.0 2.0 -3.0 15.0 3 0.0 0.0
+30.0 2.0 -2.0 0.0 3 0.0 0.0
+30.0 2.0 -2.0 15.0 3 0.0 0.0
+30.0 2.0 -1.0 0.0 3 0.0 0.0
+30.0 2.0 -1.0 15.0 3 0.0 0.0
+30.0 2.0 0.0 0.0 3 0.0 0.0
+30.0 2.0 0.0 15.0 3 0.0 0.0
+30.0 2.0 1.0 0.0 3 0.0 0.0
+30.0 2.0 1.0 15.0 3 0.0 0.0
+30.0 2.0 2.0 0.0 3 0.0 0.0
+30.0 2.0 2.0 15.0 3 0.0 0.0
+30.0 2.0 3.0 0.0 3 0.0 0.0
+30.0 2.0 3.0 15.0 3 0.0 0.0
+30.0 2.0 4.0 0.0 3 0.0 0.0
+30.0 2.0 4.0 15.0 3 0.0 0.0
+30.0 2.0 5.0 0.0 3 0.0 0.0
+30.0 2.0 5.0 15.0 3 0.0 0.0
+40.0 -2.0 -30.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -30.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -29.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -29.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -28.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -28.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -27.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -27.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -26.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -26.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -25.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -25.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -24.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -24.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -23.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -23.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -22.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -22.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -21.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -21.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -20.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -20.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -19.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -19.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -18.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -18.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -17.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -17.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -16.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -16.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -15.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -15.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -14.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -14.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -13.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -13.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -12.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -12.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -11.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -11.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -10.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -10.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -9.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -9.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -8.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -8.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -7.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -7.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -6.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -6.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -5.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -5.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -4.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -4.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -3.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -3.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -2.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -2.0 15.0 4 -0.0 -0.0
+40.0 -2.0 -1.0 0.0 4 -0.0 -0.0
+40.0 -2.0 -1.0 15.0 4 -0.0 -0.0
+40.0 -2.0 0.0 0.0 4 -0.0 -0.0
+40.0 -2.0 0.0 15.0 4 -0.0 -0.0
+40.0 -2.0 1.0 0.0 4 -0.0 -0.0
+40.0 -2.0 1.0 15.0 4 -0.0 -0.0
+40.0 -2.0 2.0 0.0 4 -0.0 -0.0
+40.0 -2.0 2.0 15.0 4 -0.0 -0.0
+40.0 -2.0 3.0 0.0 4 -0.0 -0.0
+40.0 -2.0 3.0 15.0 4 -0.0 -0.0
+40.0 -2.0 4.0 0.0 4 -0.0 -0.0
+40.0 -2.0 4.0 15.0 4 -0.0 -0.0
+40.0 -2.0 5.0 0.0 4 -0.0 -0.0
+40.0 -2.0 5.0 15.0 4 -0.0 -0.0
+40.0 2.0 -30.0 0.0 4 0.0 0.0
+40.0 2.0 -30.0 15.0 4 0.0 0.0
+40.0 2.0 -29.0 0.0 4 0.0 0.0
+40.0 2.0 -29.0 15.0 4 0.0 0.0
+40.0 2.0 -28.0 0.0 4 0.0 0.0
+40.0 2.0 -28.0 15.0 4 0.0 0.0
+40.0 2.0 -27.0 0.0 4 0.0 0.0
+40.0 2.0 -27.0 15.0 4 0.0 0.0
+40.0 2.0 -26.0 0.0 4 0.0 0.0
+40.0 2.0 -26.0 15.0 4 0.0 0.0
+40.0 2.0 -25.0 0.0 4 0.0 0.0
+40.0 2.0 -25.0 15.0 4 0.0 0.0
+40.0 2.0 -24.0 0.0 4 0.0 0.0
+40.0 2.0 -24.0 15.0 4 0.0 0.0
+40.0 2.0 -23.0 0.0 4 0.0 0.0
+40.0 2.0 -23.0 15.0 4 0.0 0.0
+40.0 2.0 -22.0 0.0 4 0.0 0.0
+40.0 2.0 -22.0 15.0 4 0.0 0.0
+40.0 2.0 -21.0 0.0 4 0.0 0.0
+40.0 2.0 -21.0 15.0 4 0.0 0.0
+40.0 2.0 -20.0 0.0 4 0.0 0.0
+40.0 2.0 -20.0 15.0 4 0.0 0.0
+40.0 2.0 -19.0 0.0 4 0.0 0.0
+40.0 2.0 -19.0 15.0 4 0.0 0.0
+40.0 2.0 -18.0 0.0 4 0.0 0.0
+40.0 2.0 -18.0 15.0 4 0.0 0.0
+40.0 2.0 -17.0 0.0 4 0.0 0.0
+40.0 2.0 -17.0 15.0 4 0.0 0.0
+40.0 2.0 -16.0 0.0 4 0.0 0.0
+40.0 2.0 -16.0 15.0 4 0.0 0.0
+40.0 2.0 -15.0 0.0 4 0.0 0.0
+40.0 2.0 -15.0 15.0 4 0.0 0.0
+40.0 2.0 -14.0 0.0 4 0.0 0.0
+40.0 2.0 -14.0 15.0 4 0.0 0.0
+40.0 2.0 -13.0 0.0 4 0.0 0.0
+40.0 2.0 -13.0 15.0 4 0.0 0.0
+40.0 2.0 -12.0 0.0 4 0.0 0.0
+40.0 2.0 -12.0 15.0 4 0.0 0.0
+40.0 2.0 -11.0 0.0 4 0.0 0.0
+40.0 2.0 -11.0 15.0 4 0.0 0.0
+40.0 2.0 -10.0 0.0 4 0.0 0.0
+40.0 2.0 -10.0 15.0 4 0.0 0.0
+40.0 2.0 -9.0 0.0 4 0.0 0.0
+40.0 2.0 -9.0 15.0 4 0.0 0.0
+40.0 2.0 -8.0 0.0 4 0.0 0.0
+40.0 2.0 -8.0 15.0 4 0.0 0.0
+40.0 2.0 -7.0 0.0 4 0.0 0.0
+40.0 2.0 -7.0 15.0 4 0.0 0.0
+40.0 2.0 -6.0 0.0 4 0.0 0.0
+40.0 2.0 -6.0 15.0 4 0.0 0.0
+40.0 2.0 -5.0 0.0 4 0.0 0.0
+40.0 2.0 -5.0 15.0 4 0.0 0.0
+40.0 2.0 -4.0 0.0 4 0.0 0.0
+40.0 2.0 -4.0 15.0 4 0.0 0.0
+40.0 2.0 -3.0 0.0 4 0.0 0.0
+40.0 2.0 -3.0 15.0 4 0.0 0.0
+40.0 2.0 -2.0 0.0 4 0.0 0.0
+40.0 2.0 -2.0 15.0 4 0.0 0.0
+40.0 2.0 -1.0 0.0 4 0.0 0.0
+40.0 2.0 -1.0 15.0 4 0.0 0.0
+40.0 2.0 0.0 0.0 4 0.0 0.0
+40.0 2.0 0.0 15.0 4 0.0 0.0
+40.0 2.0 1.0 0.0 4 0.0 0.0
+40.0 2.0 1.0 15.0 4 0.0 0.0
+40.0 2.0 2.0 0.0 4 0.0 0.0
+40.0 2.0 2.0 15.0 4 0.0 0.0
+40.0 2.0 3.0 0.0 4 0.0 0.0
+40.0 2.0 3.0 15.0 4 0.0 0.0
+40.0 2.0 4.0 0.0 4 0.0 0.0
+40.0 2.0 4.0 15.0 4 0.0 0.0
+40.0 2.0 5.0 0.0 4 0.0 0.0
+40.0 2.0 5.0 15.0 4 0.0 0.0
+50.0 -2.0 -30.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -30.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -29.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -29.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -28.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -28.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -27.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -27.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -26.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -26.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -25.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -25.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -24.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -24.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -23.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -23.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -22.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -22.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -21.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -21.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -20.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -20.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -19.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -19.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -18.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -18.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -17.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -17.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -16.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -16.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -15.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -15.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -14.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -14.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -13.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -13.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -12.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -12.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -11.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -11.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -10.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -10.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -9.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -9.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -8.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -8.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -7.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -7.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -6.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -6.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -5.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -5.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -4.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -4.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -3.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -3.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -2.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -2.0 15.0 5 -0.0 -0.0
+50.0 -2.0 -1.0 0.0 5 -0.0 -0.0
+50.0 -2.0 -1.0 15.0 5 -0.0 -0.0
+50.0 -2.0 0.0 0.0 5 -0.0 -0.0
+50.0 -2.0 0.0 15.0 5 -0.0 -0.0
+50.0 -2.0 1.0 0.0 5 -0.0 -0.0
+50.0 -2.0 1.0 15.0 5 -0.0 -0.0
+50.0 -2.0 2.0 0.0 5 -0.0 -0.0
+50.0 -2.0 2.0 15.0 5 -0.0 -0.0
+50.0 -2.0 3.0 0.0 5 -0.0 -0.0
+50.0 -2.0 3.0 15.0 5 -0.0 -0.0
+50.0 -2.0 4.0 0.0 5 -0.0 -0.0
+50.0 -2.0 4.0 15.0 5 -0.0 -0.0
+50.0 -2.0 5.0 0.0 5 -0.0 -0.0
+50.0 -2.0 5.0 15.0 5 -0.0 -0.0
+50.0 2.0 -30.0 0.0 5 0.0 0.0
+50.0 2.0 -30.0 15.0 5 0.0 0.0
+50.0 2.0 -29.0 0.0 5 0.0 0.0
+50.0 2.0 -29.0 15.0 5 0.0 0.0
+50.0 2.0 -28.0 0.0 5 0.0 0.0
+50.0 2.0 -28.0 15.0 5 0.0 0.0
+50.0 2.0 -27.0 0.0 5 0.0 0.0
+50.0 2.0 -27.0 15.0 5 0.0 0.0
+50.0 2.0 -26.0 0.0 5 0.0 0.0
+50.0 2.0 -26.0 15.0 5 0.0 0.0
+50.0 2.0 -25.0 0.0 5 0.0 0.0
+50.0 2.0 -25.0 15.0 5 0.0 0.0
+50.0 2.0 -24.0 0.0 5 0.0 0.0
+50.0 2.0 -24.0 15.0 5 0.0 0.0
+50.0 2.0 -23.0 0.0 5 0.0 0.0
+50.0 2.0 -23.0 15.0 5 0.0 0.0
+50.0 2.0 -22.0 0.0 5 0.0 0.0
+50.0 2.0 -22.0 15.0 5 0.0 0.0
+50.0 2.0 -21.0 0.0 5 0.0 0.0
+50.0 2.0 -21.0 15.0 5 0.0 0.0
+50.0 2.0 -20.0 0.0 5 0.0 0.0
+50.0 2.0 -20.0 15.0 5 0.0 0.0
+50.0 2.0 -19.0 0.0 5 0.0 0.0
+50.0 2.0 -19.0 15.0 5 0.0 0.0
+50.0 2.0 -18.0 0.0 5 0.0 0.0
+50.0 2.0 -18.0 15.0 5 0.0 0.0
+50.0 2.0 -17.0 0.0 5 0.0 0.0
+50.0 2.0 -17.0 15.0 5 0.0 0.0
+50.0 2.0 -16.0 0.0 5 0.0 0.0
+50.0 2.0 -16.0 15.0 5 0.0 0.0
+50.0 2.0 -15.0 0.0 5 0.0 0.0
+50.0 2.0 -15.0 15.0 5 0.0 0.0
+50.0 2.0 -14.0 0.0 5 0.0 0.0
+50.0 2.0 -14.0 15.0 5 0.0 0.0
+50.0 2.0 -13.0 0.0 5 0.0 0.0
+50.0 2.0 -13.0 15.0 5 0.0 0.0
+50.0 2.0 -12.0 0.0 5 0.0 0.0
+50.0 2.0 -12.0 15.0 5 0.0 0.0
+50.0 2.0 -11.0 0.0 5 0.0 0.0
+50.0 2.0 -11.0 15.0 5 0.0 0.0
+50.0 2.0 -10.0 0.0 5 0.0 0.0
+50.0 2.0 -10.0 15.0 5 0.0 0.0
+50.0 2.0 -9.0 0.0 5 0.0 0.0
+50.0 2.0 -9.0 15.0 5 0.0 0.0
+50.0 2.0 -8.0 0.0 5 0.0 0.0
+50.0 2.0 -8.0 15.0 5 0.0 0.0
+50.0 2.0 -7.0 0.0 5 0.0 0.0
+50.0 2.0 -7.0 15.0 5 0.0 0.0
+50.0 2.0 -6.0 0.0 5 0.0 0.0
+50.0 2.0 -6.0 15.0 5 0.0 0.0
+50.0 2.0 -5.0 0.0 5 0.0 0.0
+50.0 2.0 -5.0 15.0 5 0.0 0.0
+50.0 2.0 -4.0 0.0 5 0.0 0.0
+50.0 2.0 -4.0 15.0 5 0.0 0.0
+50.0 2.0 -3.0 0.0 5 0.0 0.0
+50.0 2.0 -3.0 15.0 5 0.0 0.0
+50.0 2.0 -2.0 0.0 5 0.0 0.0
+50.0 2.0 -2.0 15.0 5 0.0 0.0
+50.0 2.0 -1.0 0.0 5 0.0 0.0
+50.0 2.0 -1.0 15.0 5 0.0 0.0
+50.0 2.0 0.0 0.0 5 0.0 0.0
+50.0 2.0 0.0 15.0 5 0.0 0.0
+50.0 2.0 1.0 0.0 5 0.0 0.0
+50.0 2.0 1.0 15.0 5 0.0 0.0
+50.0 2.0 2.0 0.0 5 0.0 0.0
+50.0 2.0 2.0 15.0 5 0.0 0.0
+50.0 2.0 3.0 0.0 5 0.0 0.0
+50.0 2.0 3.0 15.0 5 0.0 0.0
+50.0 2.0 4.0 0.0 5 0.0 0.0
+50.0 2.0 4.0 15.0 5 0.0 0.0
+50.0 2.0 5.0 0.0 5 0.0 0.0
+50.0 2.0 5.0 15.0 5 0.0 0.0
diff --git a/tests/Mooring/wavekin_4/hanging_lines.txt b/tests/Mooring/wavekin_4/hanging_lines.txt
new file mode 100644
index 00000000..10527ecd
--- /dev/null
+++ b/tests/Mooring/wavekin_4/hanging_lines.txt
@@ -0,0 +1,56 @@
+--------------------- MoorDyn Input File -------------------------------------------------------
+Trying to simulate a single chain swinging in seawater. Outputs should be p,t so that we
+can get the position and tension for the line!
+----------------------- LINE TYPES --------------------------------------------------------------
+TypeName   Diam    Mass/m     EA         BA/-zeta    EI         Cd     Ca     CdAx    CaAx
+(name)     (m)     (kg/m)     (N)        (N-s/-)     (N-m^2)    (-)    (-)    (-)     (-)
+chain       13.332E-3  1.1        7.51E6     -0.5        0        0.0    0.0      0.0     1.0
+----------------------- POINTS -------------------------------------------------------------------
+Node      Type      X        Y         Z        M        V         CdA   CA
+(-)       (-)      (m)      (m)       (m)      (kg)     (m^3)     (m^2)  (-)
+1 fixed -50 0.0 -16.6 0 0 0 0
+2 free -50 0.0 -26.6 0 0 0 0
+3 fixed -40 0.0 -16.6 0 0 0 0
+4 free -40 0.0 -26.6 0 0 0 0
+5 fixed -30 0.0 -16.6 0 0 0 0
+6 free -30 0.0 -26.6 0 0 0 0
+7 fixed -20 0.0 -16.6 0 0 0 0
+8 free -20 0.0 -26.6 0 0 0 0
+9 fixed -10 0.0 -16.6 0 0 0 0
+10 free -10 0.0 -26.6 0 0 0 0
+11 fixed 0 0.0 -16.6 0 0 0 0
+12 free 0 0.0 -26.6 0 0 0 0
+13 fixed 10 0.0 -16.6 0 0 0 0
+14 free 10 0.0 -26.6 0 0 0 0
+15 fixed 20 0.0 -16.6 0 0 0 0
+16 free 20 0.0 -26.6 0 0 0 0
+17 fixed 30 0.0 -16.6 0 0 0 0
+18 free 30 0.0 -26.6 0 0 0 0
+19 fixed 40 0.0 -16.6 0 0 0 0
+20 free 40 0.0 -26.6 0 0 0 0
+-------------------------- LINES -----------------------------------------------------------------
+Line     LineType NodeA     NodeB  UnstrLen  NumSegs     Flags/Outputs
+(-)      (-)       (-)       (-)   (m)         (-)          (-)
+1 chain 1 2 10 10 p,t
+2 chain 3 4 10 10 p,t
+3 chain 5 6 10 10 p,t
+4 chain 7 8 10 10 p,t
+5 chain 9 10 10 10 p,t
+6 chain 11 12 10 10 p,t
+7 chain 13 14 10 10 p,t
+8 chain 15 16 10 10 p,t
+9 chain 17 18 10 10 p,t
+10 chain 19 20 10 10 p,t
+-------------------------- SOLVER OPTIONS----------------------------------------------------------
+2         writeLog     - Write a log file
+0.00050   dtM          - time step to use in mooring integration
+3.0e6     kb           - bottom stiffness
+3.0e5     cb           - bottom damping
+4000      WtrDpth      - water depth
+4.0       ICDfac       - factor by which to scale drag coefficients during dynamic relaxation IC gen
+0.0000015 threshIC     - threshold for IC convergence
+0.0       TmaxIC       - threshold for IC convergence
+0.00001   dtIC         - Time lapse between convergence tests (s)
+5         Currents     - 4D current profile
+0         WaveKin      - No waves
+------------------------- need this line -------------------------------------- 
diff --git a/tests/Mooring/wavekin_4/make_4d_currents.py b/tests/Mooring/wavekin_4/make_4d_currents.py
new file mode 100644
index 00000000..67812986
--- /dev/null
+++ b/tests/Mooring/wavekin_4/make_4d_currents.py
@@ -0,0 +1,57 @@
+import numpy as np
+
+T = 15.0
+A = 1.0
+UX = 1.25
+x = np.linspace(-50.0, 50.0, num=11)
+y = np.linspace(-2.0, 2.0, num=2)
+z = np.linspace(-30.0, 5.0, num=36)
+# t = np.linspace(0.0, 15.0, num=101)
+t = np.linspace(0.0, 15.0, num=2)
+zeta = A * np.sin(2.0 * np.pi * t / T)
+
+# Define a current that increases in X dimension, pointing along the positive x-axis
+# Doesn't vary current in any other dimension.
+xx, yy, zz, tt = np.meshgrid(x,y,z,t)
+ux = 0.1 * xx
+uy = 0.0 * yy
+uz = 0.0 * zz
+
+with open('water_grid.txt', 'w') as f:
+    f.write('--------------------- MoorDyn Waves grid File ----------------------------------\n')
+    f.write('List of grid points, in 3 blocks (x, y, z)\n')
+    f.write('Each block starts with a 2 (i.e. list of coords), and then th coordinates (m)\n')
+    for l in (x, y, z):
+        txt = '1\n'
+        for p in l:
+            txt = txt + str(p) + ' '
+        f.write(txt.strip() + '\n')
+
+with open('current_profile_4d.txt', 'w') as f:
+    # First line is number of (x,y,z,t) coords
+    f.write(f"{len(x)} {len(y)} {len(z)} {len(t)}\n")
+    
+    # Next four lines specify the axis (tick) values for x, y, z, t respectively
+    for xx in x:
+        f.write(f"{xx} ")
+    f.write("\n")
+
+    for yy in y:
+        f.write(f"{yy} ")
+    f.write("\n")
+
+    for zz in z:
+        f.write(f"{zz} ")
+    f.write("\n")
+
+    for tt in t:
+        f.write(f"{tt} ")
+    f.write("\n")
+
+    # All the remaining lines specify an (x, y, z, t) coordinate followed by
+    # a (ux, uy, uz) current vector at that coordinate
+    for idx, xs in enumerate(x):
+        for idy, ys in enumerate(y):
+            for idz, zs in enumerate(z):
+                for idt, ts in enumerate(t):
+                    f.write(f"{xs} {ys} {zs} {ts} {ux[idy][idx][idz][idt]:.6g} {uy[idy][idx][idz][idt]} {uy[idy][idx][idz][idt]}\n")
diff --git a/tests/Mooring/wavekin_4/water_grid.txt b/tests/Mooring/wavekin_4/water_grid.txt
new file mode 100644
index 00000000..763313d8
--- /dev/null
+++ b/tests/Mooring/wavekin_4/water_grid.txt
@@ -0,0 +1,9 @@
+--------------------- MoorDyn Waves grid File ----------------------------------
+List of grid points, in 3 blocks (x, y, z)
+Each block starts with a 2 (i.e. list of coords), and then th coordinates (m)
+1
+-50.0 -40.0 -30.0 -20.0 -10.0 0.0 10.0 20.0 30.0 40.0 50.0
+1
+-2.0 2.0
+1
+-30.0 -29.0 -28.0 -27.0 -26.0 -25.0 -24.0 -23.0 -22.0 -21.0 -20.0 -19.0 -18.0 -17.0 -16.0 -15.0 -14.0 -13.0 -12.0 -11.0 -10.0 -9.0 -8.0 -7.0 -6.0 -5.0 -4.0 -3.0 -2.0 -1.0 0.0 1.0 2.0 3.0 4.0 5.0
diff --git a/tests/pendulum.cpp b/tests/pendulum.cpp
index a49fae05..2cc08fdd 100644
--- a/tests/pendulum.cpp
+++ b/tests/pendulum.cpp
@@ -112,6 +112,7 @@ pendulum()
 	std::vector<double> t_peaks = { 0.0 };
 	double d_peak = 0.0;
 	while (t < 5.0 * T) {
+		cout << t << " / " << 5.0 * T << '\n';
 		err = MoorDyn_Step(system, NULL, NULL, NULL, &t, &dt);
 		if (err != MOORDYN_SUCCESS) {
 			cerr << "Failure during the mooring step: " << err << endl;
diff --git a/tests/seafloor.cpp b/tests/seafloor.cpp
new file mode 100644
index 00000000..7d548536
--- /dev/null
+++ b/tests/seafloor.cpp
@@ -0,0 +1,151 @@
+/** @file seafloor.cpp
+ * Pendulum swining into the seafloor to validate 3D seafloor
+ */
+
+// Visual studio still uses this
+#define _USE_MATH_DEFINES
+
+#include "MoorDyn.h"
+#include "MoorDyn2.h"
+#include "Seafloor.h"
+#include <iostream>
+#include <iomanip>
+#include <cmath>
+#include <vector>
+
+#define TOL 1.0e-1
+
+bool
+compare(double v1, double v2, double tol)
+{
+	return fabs(v1 - v2) <= tol;
+}
+
+#define CHECK_VALUE(name, v1, v2, tol, t)                                      \
+	if (!compare(v1, v2, tol)) {                                               \
+		cerr << setprecision(8) << "Checking " << name                         \
+		     << " failed at t = " << t << " s. " << v1 << " was expected"      \
+		     << " but " << v2 << " was computed" << endl;                      \
+		MoorDyn_Close(system);                                                 \
+		return false;                                                          \
+	}
+
+using namespace std;
+
+/** @brief Pendulum motion into the seafloor
+ * @return true if the test worked, false otherwise
+ */
+bool
+pendulum()
+{
+	MoorDyn system = MoorDyn_Create("Mooring/seafloor.txt");
+	if (!system) {
+		cerr << "Failure Creating the Mooring system" << endl;
+		return false;
+	}
+
+	unsigned int n_dof;
+	if (MoorDyn_NCoupledDOF(system, &n_dof) != MOORDYN_SUCCESS) {
+		MoorDyn_Close(system);
+		return false;
+	}
+	if (n_dof != 0) {
+		cerr << "0 DOFs were expected, but " << n_dof << "were reported"
+		     << endl;
+		MoorDyn_Close(system);
+		return false;
+	}
+
+	int err;
+	double pos[3];
+	const auto conn = MoorDyn_GetConnection(system, 2);
+	err = MoorDyn_GetConnectPos(conn, pos);
+	if (err != MOORDYN_SUCCESS) {
+		cerr << "Failure getting the mass position: " << err << endl;
+		return false;
+	}
+	const double x0 = pos[0];
+	const double z0 = pos[2];
+	const double l0 = sqrt(x0 * x0 + z0 * z0);
+	cout << l0 << endl;
+
+	err = MoorDyn_Init(system, NULL, NULL);
+	if (err != MOORDYN_SUCCESS) {
+		cerr << "Failure during the mooring initialization: " << err << endl;
+		return false;
+	}
+
+	MoorDynSeafloor seafloor = MoorDyn_GetSeafloor(system);
+	if (!seafloor) {
+		cerr << "Could not get seafloor instance" << endl;
+		return false;
+	}
+
+	double avgDepth, minDepth;
+	err = MoorDyn_GetMinDepth(seafloor, &minDepth);
+	if (err != MOORDYN_SUCCESS) {
+		cerr << "couldn't get min depth" << endl;
+		return false;
+	}
+
+	err = MoorDyn_GetAverageDepth(seafloor, &avgDepth);
+	if (err != MOORDYN_SUCCESS) {
+		cerr << "couldn't get min depth" << endl;
+		return false;
+	}
+	
+	cout << "Average depth is " << avgDepth << ", Minimum depth is " << minDepth << endl;
+
+	const double w = sqrt(9.81 / l0);
+	const double T = 2.0 * M_PI / w;
+	double dt = T / 20.0;
+	double t = 0.0;
+	std::vector<double> x_peaks = { x0 };
+	std::vector<double> t_peaks = { 0.0 };
+	double d_peak = 0.0;
+	while (t < 1.0 * T) {
+		cout << t << " / " << 1.0 * T << '\n';
+		err = MoorDyn_Step(system, NULL, NULL, NULL, &t, &dt);
+		if (err != MOORDYN_SUCCESS) {
+			cerr << "Failure during the mooring step: " << err << endl;
+			return false;
+		}
+		err = MoorDyn_GetConnectPos(conn, pos);
+		if (err != MOORDYN_SUCCESS) {
+			cerr << "Failure getting the mass position: " << err << endl;
+			return false;
+		}
+		const double x = pos[0];
+		const double z = pos[2];
+		const double l = sqrt(x * x + z * z);
+		// CHECK_VALUE("L", l0, l, 0.01 * l0, t);
+		const double d = fabs(x - x_peaks.back());
+		if (d > d_peak)
+			d_peak = d;
+		else {
+			x_peaks.push_back(x);
+			t_peaks.push_back(t);
+			d_peak = 0.0;
+		}
+	}
+
+	err = MoorDyn_Close(system);
+	if (err != MOORDYN_SUCCESS) {
+		cerr << "Failure closing Moordyn: " << err << endl;
+		return false;
+	}
+
+	return true;
+}
+
+/** @brief Runs all the test
+ * @return 0 if the tests have ran just fine. The index of the failing test
+ * otherwise
+ */
+int
+main(int, char**)
+{
+	if (!pendulum())
+		return 1;
+	return 0;
+}
diff --git a/tests/time_schemes.cpp b/tests/time_schemes.cpp
index 141671f4..f32e70a7 100644
--- a/tests/time_schemes.cpp
+++ b/tests/time_schemes.cpp
@@ -79,7 +79,7 @@ static std::vector<std::string> dts({ "1.5E-4",
                                       "2.8E-4",
                                       "2.8E-4",
                                       "2.8E-4",
-                                      "2.8E-4" });
+                                      "2.8E-5" });
 
 using namespace std;
 
diff --git a/tests/tripod.cpp b/tests/tripod.cpp
new file mode 100644
index 00000000..5653b0b8
--- /dev/null
+++ b/tests/tripod.cpp
@@ -0,0 +1,50 @@
+#include "MoorDyn2.h"
+#include <stdexcept>
+#include <iostream>
+
+
+int main(int, char**) {
+    // Path for input file:
+    MoorDyn system = MoorDyn_Create("./Mooring/tripod.txt");
+
+    if (!system)
+    {
+        std::cerr << "Failure Creating the mooring system\n";
+        return 1;
+    }
+
+    int err;
+
+    err = MoorDyn_Init(system, NULL, NULL);
+    if (err != MOORDYN_SUCCESS)
+    {
+        std::cerr << "Failure during the mooring initialization: " << err << "\n";
+        MoorDyn_Close(system);
+        return 3;
+    }
+
+    double dt = 0.01; // Set time increment
+    const unsigned int nts = 15 / dt; // Number of time steps
+
+    for (unsigned int i = 0; i < nts; i++) {
+        // This deals with the type mismatch between nts (int) and time (double):
+	    // May need to be cautious of rounding errors:
+        double t = i * dt;
+        std::cout << "Time: " << t << '\n';
+
+	    // At each time step, advance the system:
+        err = MoorDyn_Step(system, NULL, NULL, NULL, &t, &dt);
+        if (err != MOORDYN_SUCCESS) {
+            std::cerr << "Failure during the mooring step " << i << ": "
+                << err << "\n";
+            MoorDyn_Close(system);
+            return 4;
+        }
+    }
+
+    std::cout << "Closing down system.\n";
+
+    // Close the system and deallocate memory:
+    err = MoorDyn_Close(system);
+    return 0;
+}
\ No newline at end of file
diff --git a/tests/wavekin_4.cpp b/tests/wavekin_4.cpp
new file mode 100644
index 00000000..370e77c1
--- /dev/null
+++ b/tests/wavekin_4.cpp
@@ -0,0 +1,57 @@
+#include "MoorDyn2.h"
+#include <stdexcept>
+#include <iostream>
+
+/** @brief Check that we can initialize a mooring system with Beta_n
+* and Beta_t defined in the input file and run a step.
+* @return true if the test passed
+*/
+bool
+test_4d_currents()
+{
+	MoorDyn system = MoorDyn_Create("Mooring/wavekin_4/hanging_lines.txt");
+	if (!system) {
+		std::cerr << "Failure Creating the Mooring system" << std::endl;
+		return false;
+	}
+
+	int err;
+	err = MoorDyn_Init(system, NULL, NULL);
+	if (err != MOORDYN_SUCCESS) {
+		MoorDyn_Close(system);
+		std::cerr << "Failure during the mooring initialization: " << err << std::endl;
+		return false;
+	}
+
+	// Integrate in time
+	const double t_max = 15.0;
+	double t = 0.0, dt = 0.1;
+	while (t < t_max) {
+		err = MoorDyn_Step(system, NULL, NULL, NULL, &t, &dt);
+		if (err != MOORDYN_SUCCESS) {
+			MoorDyn_Close(system);
+			std::cerr << "Failure during the mooring step: " << err << std::endl;
+			return false;
+		}
+	}
+
+	err = MoorDyn_Close(system);
+	if (err != MOORDYN_SUCCESS) {
+		std::cerr << "Failure closing Moordyn: " << err << std::endl;
+		return false;
+	}
+
+	return true;
+}
+
+/** @brief Run all tests
+* @return 0 if the tests ran fine. Index of failing test otherwise.
+*/
+int
+main(int, char**)
+{
+	if (!test_4d_currents())
+		return 1;
+
+	return 0;
+}