From f7a01575abcb253efea7978bfc6283b51c885d76 Mon Sep 17 00:00:00 2001
From: Allanis <allanis.saracraft.studios@gmail.com>
Date: Sun, 12 Nov 2017 18:27:48 +0000
Subject: [PATCH] [Add] Uhm, apparently the new project stuff wasn't added. My
 bad.

---
 COMPILING                                |    0
 Makefile.am                              |   46 +
 bootstrap                                |    4 +
 config.ini                               |    6 +
 configure.ac                             |   36 +
 icons/button_f10.png                     |  Bin 0 -> 209 bytes
 icons/button_f7.png                      |  Bin 0 -> 199 bytes
 icons/button_f8.png                      |  Bin 0 -> 220 bytes
 icons/button_f9.png                      |  Bin 0 -> 210 bytes
 icons/cam_external.png                   |  Bin 0 -> 299 bytes
 icons/cam_front.png                      |  Bin 0 -> 280 bytes
 icons/cam_rear.png                       |  Bin 0 -> 275 bytes
 icons/comms_f4.png                       |  Bin 0 -> 294 bytes
 icons/cpan_f2_map.png                    |  Bin 0 -> 308 bytes
 icons/cpan_f2_normal.png                 |  Bin 0 -> 450 bytes
 icons/cpanel.png                         |  Bin 0 -> 2383 bytes
 icons/hyperspace_f8.png                  |  Bin 0 -> 371 bytes
 icons/object_brown_dwarf.png             |  Bin 0 -> 399 bytes
 icons/object_planet_dwarf.png            |  Bin 0 -> 275 bytes
 icons/object_planet_large_gas_giant.png  |  Bin 0 -> 2072 bytes
 icons/object_planet_life.png             |  Bin 0 -> 779 bytes
 icons/object_planet_medium_gas_giant.png |  Bin 0 -> 1661 bytes
 icons/object_planet_small.png            |  Bin 0 -> 323 bytes
 icons/object_planet_small_gas_giant.png  |  Bin 0 -> 1256 bytes
 icons/object_star_a.png                  |  Bin 0 -> 3145 bytes
 icons/object_star_b.png                  |  Bin 0 -> 1236 bytes
 icons/object_star_f.png                  |  Bin 0 -> 1234 bytes
 icons/object_star_g.png                  |  Bin 0 -> 1112 bytes
 icons/object_star_k.png                  |  Bin 0 -> 1498 bytes
 icons/object_star_m.png                  |  Bin 0 -> 798 bytes
 icons/object_star_o.png                  |  Bin 0 -> 602 bytes
 icons/sectorview_f6_systeminfo.png       |  Bin 0 -> 490 bytes
 icons/sysview_accel_f1.png               |  Bin 0 -> 254 bytes
 icons/sysview_accel_f1_on.png            |  Bin 0 -> 231 bytes
 icons/sysview_accel_f2.png               |  Bin 0 -> 268 bytes
 icons/sysview_accel_f2_on.png            |  Bin 0 -> 247 bytes
 icons/sysview_accel_f3.png               |  Bin 0 -> 280 bytes
 icons/sysview_accel_f3_on.png            |  Bin 0 -> 241 bytes
 icons/sysview_accel_r1.png               |  Bin 0 -> 259 bytes
 icons/sysview_accel_r1_on.png            |  Bin 0 -> 231 bytes
 icons/sysview_accel_r2.png               |  Bin 0 -> 271 bytes
 icons/sysview_accel_r2_on.png            |  Bin 0 -> 246 bytes
 icons/sysview_accel_r3.png               |  Bin 0 -> 429 bytes
 icons/sysview_accel_r3_on.png            |  Bin 0 -> 396 bytes
 icons/timeaccel0.png                     |  Bin 0 -> 237 bytes
 icons/timeaccel0_on.png                  |  Bin 0 -> 213 bytes
 icons/timeaccel1.png                     |  Bin 0 -> 261 bytes
 icons/timeaccel1_on.png                  |  Bin 0 -> 228 bytes
 icons/timeaccel2.png                     |  Bin 0 -> 296 bytes
 icons/timeaccel2_on.png                  |  Bin 0 -> 241 bytes
 icons/timeaccel3.png                     |  Bin 0 -> 308 bytes
 icons/timeaccel3_on.png                  |  Bin 0 -> 238 bytes
 icons/timeaccel4.png                     |  Bin 0 -> 518 bytes
 icons/timeaccel4_on.png                  |  Bin 0 -> 353 bytes
 icons/zoom_in_f7.png                     |  Bin 0 -> 426 bytes
 icons/zoom_out_f8.png                    |  Bin 0 -> 419 bytes
 src/Makefile.am                          |   19 +
 src/body.cpp                             |   14 +
 src/body.h                               |   40 +
 src/date.cpp                             |   77 ++
 src/date.h                               |    5 +
 src/frame.cpp                            |   55 +
 src/frame.h                              |   47 +
 src/generic_system_view.cpp              |   51 +
 src/generic_system_view.h                |   25 +
 src/glfreetype.cpp                       |  374 +++++++
 src/glfreetype.h                         |   28 +
 src/gui.cpp                              |   46 +
 src/gui_button.cpp                       |  102 ++
 src/gui_button.h                         |   47 +
 src/gui_container.cpp                    |   92 ++
 src/gui_container.h                      |   29 +
 src/gui_events.h                         |    9 +
 src/gui_fixed.cpp                        |   62 ++
 src/gui_fixed.h                          |   25 +
 src/gui_image.cpp                        |  103 ++
 src/gui_image.h                          |   18 +
 src/gui_image_button.cpp                 |   43 +
 src/gui_image_button.h                   |   21 +
 src/gui_image_radio_button.cpp           |   30 +
 src/gui_image_radio_button.h             |   18 +
 src/gui_iselectable.h                    |   10 +
 src/gui_label.cpp                        |   41 +
 src/gui_label.h                          |   21 +
 src/gui_multi_state_image_button.cpp     |   62 ++
 src/gui_multi_state_image_button.h       |   31 +
 src/gui_radio_button.cpp                 |   78 ++
 src/gui_radio_button.h                   |   27 +
 src/gui_radio_group.cpp                  |   21 +
 src/gui_radio_group.h                    |   16 +
 src/gui_screen.cpp                       |  195 ++++
 src/gui_screen.h                         |   51 +
 src/gui_toggle_button.cpp                |   75 ++
 src/gui_toggle_button.h                  |   22 +
 src/gui_widget.cpp                       |   24 +
 src/gui_widget.h                         |   51 +
 src/l3d.h                                |   99 ++
 src/libs.h                               |   50 +
 src/main.cpp                             |  362 +++++++
 src/matrix4x4.h                          |  226 ++++
 src/mtrand.cpp                           |   53 +
 src/mtrand.h                             |  121 +++
 src/object.h                             |    8 +
 src/objimport.cpp                        |   73 ++
 src/objimport.h                          |   20 +
 src/planet.cpp                           |   77 ++
 src/planet.h                             |   24 +
 src/player.cpp                           |  231 ++++
 src/rigid_body.cpp                       |   35 +
 src/rigid_body.h                         |   26 +
 src/sbre/Makefile.am                     |    6 +
 src/sbre/brender.cpp                     |  220 ++++
 src/sbre/fastmath.h                      |   80 ++
 src/sbre/jjtypes.h                       |    7 +
 src/sbre/jjvector.cpp                    |  207 ++++
 src/sbre/jjvector.h                      |  521 +++++++++
 src/sbre/models.cpp                      | 1261 ++++++++++++++++++++++
 src/sbre/primfunc.cpp                    |  734 +++++++++++++
 src/sbre/sbre.h                          |   39 +
 src/sbre/sbre_anim.h                     |   35 +
 src/sbre/sbre_int.h                      |  145 +++
 src/sbre/simtriang.cpp                   |  155 +++
 src/sbre/transp.cpp                      |  221 ++++
 src/sector.cpp                           |   54 +
 src/sector.h                             |   22 +
 src/sector_view.cpp                      |  199 ++++
 src/sector_view.h                        |   33 +
 src/ship.cpp                             |  140 +++
 src/ship_cpanel.cpp                      |  102 ++
 src/ship_cpanel.h                        |   19 +
 src/ship_type.cpp                        |   13 +
 src/ship_type.h                          |   23 +
 src/space.cpp                            |  228 ++++
 src/space_station.cpp                    |   55 +
 src/space_station.h                      |   16 +
 src/space_station_view.cpp               |   47 +
 src/space_station_view.h                 |   14 +
 src/star.cpp                             |   52 +
 src/star.h                               |   24 +
 src/star_system.cpp                      |  453 ++++++++
 src/star_system.h                        |   91 ++
 src/static_rigid_body.cpp                |  118 ++
 src/static_rigid_body.h                  |   29 +
 src/system_info_view.cpp                 |  103 ++
 src/system_info_view.h                   |   19 +
 src/system_view.cpp                      |  209 ++++
 src/system_view.h                        |   33 +
 src/vector3.h                            |   97 ++
 src/view.h                               |   42 +
 src/world_view.cpp                       |  103 ++
 src/world_view.h                         |   18 +
 151 files changed, 9589 insertions(+)
 create mode 100644 COMPILING
 create mode 100644 Makefile.am
 create mode 100755 bootstrap
 create mode 100644 config.ini
 create mode 100644 configure.ac
 create mode 100644 icons/button_f10.png
 create mode 100644 icons/button_f7.png
 create mode 100644 icons/button_f8.png
 create mode 100644 icons/button_f9.png
 create mode 100644 icons/cam_external.png
 create mode 100644 icons/cam_front.png
 create mode 100644 icons/cam_rear.png
 create mode 100644 icons/comms_f4.png
 create mode 100644 icons/cpan_f2_map.png
 create mode 100644 icons/cpan_f2_normal.png
 create mode 100644 icons/cpanel.png
 create mode 100644 icons/hyperspace_f8.png
 create mode 100644 icons/object_brown_dwarf.png
 create mode 100644 icons/object_planet_dwarf.png
 create mode 100644 icons/object_planet_large_gas_giant.png
 create mode 100644 icons/object_planet_life.png
 create mode 100644 icons/object_planet_medium_gas_giant.png
 create mode 100644 icons/object_planet_small.png
 create mode 100644 icons/object_planet_small_gas_giant.png
 create mode 100644 icons/object_star_a.png
 create mode 100644 icons/object_star_b.png
 create mode 100644 icons/object_star_f.png
 create mode 100644 icons/object_star_g.png
 create mode 100644 icons/object_star_k.png
 create mode 100644 icons/object_star_m.png
 create mode 100644 icons/object_star_o.png
 create mode 100644 icons/sectorview_f6_systeminfo.png
 create mode 100644 icons/sysview_accel_f1.png
 create mode 100644 icons/sysview_accel_f1_on.png
 create mode 100644 icons/sysview_accel_f2.png
 create mode 100644 icons/sysview_accel_f2_on.png
 create mode 100644 icons/sysview_accel_f3.png
 create mode 100644 icons/sysview_accel_f3_on.png
 create mode 100644 icons/sysview_accel_r1.png
 create mode 100644 icons/sysview_accel_r1_on.png
 create mode 100644 icons/sysview_accel_r2.png
 create mode 100644 icons/sysview_accel_r2_on.png
 create mode 100644 icons/sysview_accel_r3.png
 create mode 100644 icons/sysview_accel_r3_on.png
 create mode 100644 icons/timeaccel0.png
 create mode 100644 icons/timeaccel0_on.png
 create mode 100644 icons/timeaccel1.png
 create mode 100644 icons/timeaccel1_on.png
 create mode 100644 icons/timeaccel2.png
 create mode 100644 icons/timeaccel2_on.png
 create mode 100644 icons/timeaccel3.png
 create mode 100644 icons/timeaccel3_on.png
 create mode 100644 icons/timeaccel4.png
 create mode 100644 icons/timeaccel4_on.png
 create mode 100644 icons/zoom_in_f7.png
 create mode 100644 icons/zoom_out_f8.png
 create mode 100644 src/Makefile.am
 create mode 100644 src/body.cpp
 create mode 100644 src/body.h
 create mode 100644 src/date.cpp
 create mode 100644 src/date.h
 create mode 100644 src/frame.cpp
 create mode 100644 src/frame.h
 create mode 100644 src/generic_system_view.cpp
 create mode 100644 src/generic_system_view.h
 create mode 100644 src/glfreetype.cpp
 create mode 100644 src/glfreetype.h
 create mode 100644 src/gui.cpp
 create mode 100644 src/gui_button.cpp
 create mode 100644 src/gui_button.h
 create mode 100644 src/gui_container.cpp
 create mode 100644 src/gui_container.h
 create mode 100644 src/gui_events.h
 create mode 100644 src/gui_fixed.cpp
 create mode 100644 src/gui_fixed.h
 create mode 100644 src/gui_image.cpp
 create mode 100644 src/gui_image.h
 create mode 100644 src/gui_image_button.cpp
 create mode 100644 src/gui_image_button.h
 create mode 100644 src/gui_image_radio_button.cpp
 create mode 100644 src/gui_image_radio_button.h
 create mode 100644 src/gui_iselectable.h
 create mode 100644 src/gui_label.cpp
 create mode 100644 src/gui_label.h
 create mode 100644 src/gui_multi_state_image_button.cpp
 create mode 100644 src/gui_multi_state_image_button.h
 create mode 100644 src/gui_radio_button.cpp
 create mode 100644 src/gui_radio_button.h
 create mode 100644 src/gui_radio_group.cpp
 create mode 100644 src/gui_radio_group.h
 create mode 100644 src/gui_screen.cpp
 create mode 100644 src/gui_screen.h
 create mode 100644 src/gui_toggle_button.cpp
 create mode 100644 src/gui_toggle_button.h
 create mode 100644 src/gui_widget.cpp
 create mode 100644 src/gui_widget.h
 create mode 100644 src/l3d.h
 create mode 100644 src/libs.h
 create mode 100644 src/main.cpp
 create mode 100644 src/matrix4x4.h
 create mode 100644 src/mtrand.cpp
 create mode 100644 src/mtrand.h
 create mode 100644 src/object.h
 create mode 100644 src/objimport.cpp
 create mode 100644 src/objimport.h
 create mode 100644 src/planet.cpp
 create mode 100644 src/planet.h
 create mode 100644 src/player.cpp
 create mode 100644 src/rigid_body.cpp
 create mode 100644 src/rigid_body.h
 create mode 100644 src/sbre/Makefile.am
 create mode 100644 src/sbre/brender.cpp
 create mode 100644 src/sbre/fastmath.h
 create mode 100644 src/sbre/jjtypes.h
 create mode 100644 src/sbre/jjvector.cpp
 create mode 100644 src/sbre/jjvector.h
 create mode 100644 src/sbre/models.cpp
 create mode 100644 src/sbre/primfunc.cpp
 create mode 100644 src/sbre/sbre.h
 create mode 100644 src/sbre/sbre_anim.h
 create mode 100644 src/sbre/sbre_int.h
 create mode 100644 src/sbre/simtriang.cpp
 create mode 100644 src/sbre/transp.cpp
 create mode 100644 src/sector.cpp
 create mode 100644 src/sector.h
 create mode 100644 src/sector_view.cpp
 create mode 100644 src/sector_view.h
 create mode 100644 src/ship.cpp
 create mode 100644 src/ship_cpanel.cpp
 create mode 100644 src/ship_cpanel.h
 create mode 100644 src/ship_type.cpp
 create mode 100644 src/ship_type.h
 create mode 100644 src/space.cpp
 create mode 100644 src/space_station.cpp
 create mode 100644 src/space_station.h
 create mode 100644 src/space_station_view.cpp
 create mode 100644 src/space_station_view.h
 create mode 100644 src/star.cpp
 create mode 100644 src/star.h
 create mode 100644 src/star_system.cpp
 create mode 100644 src/star_system.h
 create mode 100644 src/static_rigid_body.cpp
 create mode 100644 src/static_rigid_body.h
 create mode 100644 src/system_info_view.cpp
 create mode 100644 src/system_info_view.h
 create mode 100644 src/system_view.cpp
 create mode 100644 src/system_view.h
 create mode 100644 src/vector3.h
 create mode 100644 src/view.h
 create mode 100644 src/world_view.cpp
 create mode 100644 src/world_view.h

diff --git a/COMPILING b/COMPILING
new file mode 100644
index 0000000..e69de29
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..f6a6148
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,46 @@
+## Process this file with automake to produce Makefile.in
+SUBDIRS = src/
+
+EXTRA_DIST = icons/button_f10.png \
+	icons/button_f7.png \
+	icons/button_f8.png \
+	icons/button_f9.png \
+	icons/cam_external.png \
+	icons/cam_front.png \
+	icons/cam_rear.png \
+	icons/cpanel.png \
+	icons/cpan_f2_map.png \
+	icons/cpan_f2_normal.png \
+	icons/object_brown_dwarf.png \
+	icons/object_planet_dwarf.png \
+	icons/object_planet_large_gas_giant.png \
+	icons/object_planet_medium_gas_giant.png \
+	icons/object_planet_small_gas_giant.png \
+	icons/object_planet_small.png \
+	icons/object_planet_life.png \
+	icons/object_star_a.png \
+	icons/object_star_b.png \
+	icons/object_star_f.png \
+	icons/object_star_g.png \
+	icons/object_star_k.png \
+	icons/object_star_m.png \
+	icons/object_star_o.png \
+	icons/sectorview_f6_systeminfo.png \
+	icons/timeaccel0_on.png \
+	icons/timeaccel0.png \
+	icons/timeaccel1_on.png \
+	icons/timeaccel1.png \
+	icons/timeaccel2_on.png \
+	icons/timeaccel2.png \
+	icons/timeaccel3_on.png \
+	icons/timeaccel3.png \
+	icons/timeaccel4_on.png \
+	icons/timeaccel4.png \
+	icons/zoom_in_f7.png \
+	icons/zoom_out_f8.png \
+	icons/hyperspace_f8.png \
+	icons/comms_f4.png \
+	icons/sysview_accel_f1_on.png icons/sysview_accel_f1.png icons/sysview_accel_f2_on.png icons/sysview_accel_f2.png icons/sysview_accel_f3_on.png icons/sysview_accel_f3.png icons/sysview_accel_r1_on.png icons/sysview_accel_r1.png icons/sysview_accel_r2_on.png icons/sysview_accel_r2.png icons/sysview_accel_r3_on.png icons/sysview_accel_r3.png \
+	config.ini
+
+
diff --git a/bootstrap b/bootstrap
new file mode 100755
index 0000000..185518e
--- /dev/null
+++ b/bootstrap
@@ -0,0 +1,4 @@
+#! /bin/sh
+
+autoreconf -fvi
+
diff --git a/config.ini b/config.ini
new file mode 100644
index 0000000..1baeef9
--- /dev/null
+++ b/config.ini
@@ -0,0 +1,6 @@
+ScrWidth=800
+ScrHeight=600
+# 16 or 32.
+ScrDepth=16
+StartFullscreen=0
+
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..07494ad
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,36 @@
+# Process this file with autoconf to create configure.
+
+AC_PREREQ(2.59)
+
+AC_INIT([Lephisto3D], [0.0.1], [allanis.saracraft.studios@gmail.com])
+#AC_CONFIG_HEADERS([config.h])
+
+AM_INIT_AUTOMAKE([1.9 foreign])
+
+AC_PROG_CC
+AC_PROG_CXX
+AC_C_CONST
+AC_PROG_RANLIB
+#AC_PROG_LIBTOOL
+
+#AM_PATH_LIB3DS([1.2.0])
+#CFLAGS="$CFLAGS $LIB3DS_CFLAGS"
+#LIBS="$LIBS $LIB3DS_LIBS"
+
+CFLAGS="-g -O2 -Wall"
+
+CFLAGS="$CFLAGS `pkg-config --cflags sigc++-2.0`"
+LIBS="$LIBS `pkg-config --libs sigc++-2.0` -lGL -lGLU"
+CFLAGS="$CFLAGS `pkg-config --cflags freetype2`"
+LIBS="$LIBS `pkg-config --libs freetype2`"
+CFLAGS="$CFLAGS `ode-config --cflags`"
+LIBS="$LIBS `ode-config --libs`"
+
+PKG_CHECK_MODULES(SDL, sdl)
+CFLAGS="$CFLAGS $SDL_CFLAGS"
+LIBS="$LIBS $SDL_LIBS -lSDL_image"
+
+CXXFLAGS="$CFLAGS"
+
+AC_CONFIG_FILES([Makefile src/Makefile src/sbre/Makefile])
+AC_OUTPUT
diff --git a/icons/button_f10.png b/icons/button_f10.png
new file mode 100644
index 0000000000000000000000000000000000000000..d989bfce2426982da27655824e5afbc815f16d27
GIT binary patch
literal 209
zcmeAS@N?(olHy`uVBq!ia0vp^azHG`!2~2D7<bPAQjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wcdBZWSRtt+p>fA;}Wgh!W@g+}zZ>5(ej@)Wnk16ovB4
zk_-iRPv3y>Mm}+%B0EnP#}JFt$vK%>i3@lWk`_oxm{whR5xAJW`=ZX2jCCtlCnh8`
zOmPzUc7bQYv}IxyfmXAe+%B80o8ng-&dhLnE7z_t$#1<tlNdZ*{an^LB{Ts5W#&B7

literal 0
HcmV?d00001

diff --git a/icons/button_f7.png b/icons/button_f7.png
new file mode 100644
index 0000000000000000000000000000000000000000..723c689ad6986c5bd7415967417fc24567eda10f
GIT binary patch
literal 199
zcmeAS@N?(olHy`uVBq!ia0vp^azHG`!2~2D7<bPAQjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wcdBZe^CK`<EsGg(OQ{BTAg}b8}PkN*J7rQWHy3QxwWG
zOEMJPJ$(bh8~Mb6icCCR978NlC+B2lB`)AiNLnB%VOn+NMc`ug?u$BCj8-gL*H*VK
oZHeni_KycMxFk)h4)m}w^hC1hzI>Lw0jQtB)78&qol`;+07z*$T>t<8

literal 0
HcmV?d00001

diff --git a/icons/button_f8.png b/icons/button_f8.png
new file mode 100644
index 0000000000000000000000000000000000000000..09a5aa786e6d6b0c3361ffbab2ab1e1c9e4305ff
GIT binary patch
literal 220
zcmeAS@N?(olHy`uVBq!ia0vp^azHG`!2~2D7<bPAQjEnx?oJHr&dIz4a@dl*-Ccn6
z1_l=Eg2pl+i?hHZvY3H^TNs2H8D`Cq01C2~c>21s-(ch6HL;YwF)a)zBw6AbQR1AR
zo12<f!r)w#npl#WqEMb$lA+-4=^GH<$R`d|WbWzW7-DfcIVUqKaRF~a(gH~d)2b^k
z0vEG)U(~r`v|`!1v=eL|-oe~^G-SCFdnF$&PPElIB*pMOoX4$sms}Un2nJ7AKbLh*
G2~7Ze(L0*}

literal 0
HcmV?d00001

diff --git a/icons/button_f9.png b/icons/button_f9.png
new file mode 100644
index 0000000000000000000000000000000000000000..42de524ce5136ffde59c7f1fd611c3f504c6bb5b
GIT binary patch
literal 210
zcmeAS@N?(olHy`uVBq!ia0vp^azHG`!2~2D7<bPAQjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wcdBZe^n=R)gO_A;}Wgh!W@g+}zZ>5(ej@)Wnk16ovB4
zk_-iRPv3y>Mm}+%B7094#}JFt$vK%>i3@lWk`_oxm{whR5xAJW`=ZVjqZP~6rJZ2&
z@DAqQqan+c*em(Sq{LKNH%qePw)<sMDF%j^5+1{gv)42OjbiY0^>bP0l+XkKBcnO-

literal 0
HcmV?d00001

diff --git a/icons/cam_external.png b/icons/cam_external.png
new file mode 100644
index 0000000000000000000000000000000000000000..7c42431bab0ac1358efb061dc9c687b4f2fbdae4
GIT binary patch
literal 299
zcmeAS@N?(olHy`uVBq!ia0vp^azHG`!2~2D7<bPAQjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wdn(0Wl7jU#zQvLXst}5hc#~xw)x%B@E6*sfi`2DGKG8
zB^e6tp1uL$jeO!jMKe5I978NlZ=K@JbwGi~<@JM&&E1a<?$+=y2sE6(X3Np%7C)!_
z`=+k`>Yur()az)$(thcHpC6fYS%Z|<mE4kJ7T&%;);8aRvoCK3bC6Vh<MU;!?Ea`*
zy%t&}{fB9D!5X2XpFMl?PCI)(US+`Z>`HR(4B7V2f0oUBnExU)TA|>I@1_YydR<OG
qY<5_D-dKfI)>}Vt%@LDRKN$EM{hz;j`Hm0hMg~t;KbLh*2~7ZT&TNSQ

literal 0
HcmV?d00001

diff --git a/icons/cam_front.png b/icons/cam_front.png
new file mode 100644
index 0000000000000000000000000000000000000000..3496f31d0da411532e5b5e37e499440261a68aee
GIT binary patch
literal 280
zcmeAS@N?(olHy`uVBq!ia0vp^azHG`!2~2D7<bPAQjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wdn(0a1aP-RJCpLXst}5hc#~xw)x%B@E6*sfi`2DGKG8
zB^e6tp1uL$jeO!jMXjDLjv*GO&t5&tbwGiG;eo1c*&BnZ53($h!AAd&S&7ve2VQ?>
z+CPi6FlmnSo%b)3I^06ermT0!-NJcysq6~B-4{h=T~e)Bk6x|2+k5}wm8Ahs)Gn`I
z$@C{;uJ!E8Uh4$PIqmdM={Bbyl!|{k%kF{qM5B2cDaW~U?s`1@9Q`MCTYRHtpS0P2
WhlTO$n$80q#Ng@b=d#Wzp$Pz7y<)Ba

literal 0
HcmV?d00001

diff --git a/icons/cam_rear.png b/icons/cam_rear.png
new file mode 100644
index 0000000000000000000000000000000000000000..85b882b0d323a5154d9a8e665830c3650c4fc721
GIT binary patch
literal 275
zcmeAS@N?(olHy`uVBq!ia0vp^azHG`!2~2D7<bPAQjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wdn(0Z~;g4lY@skYtH#M2T~LZf<H`34?P{YGO%hib8p2
zNrr;Er*A-bBcC`>QG=(8V~EA+ry-7f2NXD>#pmUgMb!M@^X8a+tYKR0vy;uaf*FD5
zcRigGoBvcJChNwksdGPxmPBeuNi>MFK4`ov70CF5F_6(E`y+S4y8d~F54cbNnH_ap
z;f~}j1))dJ_A>cJtiHy!e9h!r?2FsBJi6Si@0s{_!Lr+v(!Ok)+?ltB;YyVA<Fby|
RsX(_dc)I$ztaD0e0sye|UpW8(

literal 0
HcmV?d00001

diff --git a/icons/comms_f4.png b/icons/comms_f4.png
new file mode 100644
index 0000000000000000000000000000000000000000..2bc8a3421f22044927130644c91946b3630a78d3
GIT binary patch
literal 294
zcmeAS@N?(olHy`uVBq!ia0vp^azHG`!2~2D7<bPAQjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wcceUT&khyK6InLXst}5hc#~xw)x%B@E6*sfi`2DGKG8
zB^e6tp1uL$jeO!jMUy>U978NlC+B2lB`)AiNLnD7pcwaZ(|L}gfqUxNE?rHL>V4C^
z@Nt%ww6D*V-W3dzE-z=bMse<3wyv$QkugZg@rF;z4DXbn#Dn=^BJbrtojLk0D`oQu
zGx6@#A6qmHpE)_r+#=$X6{cd=Dv+#Kl*?LZ&8-(~wm#uw?+@q6cV0+;O#jR!)*&0=
j*;S-`q$EbgaWSJfpG&XsK@nx3^B6o`{an^LB{Ts5-sWNk

literal 0
HcmV?d00001

diff --git a/icons/cpan_f2_map.png b/icons/cpan_f2_map.png
new file mode 100644
index 0000000000000000000000000000000000000000..ea0eb6563e8bd53e3d047c42b63e48a6da15fb1a
GIT binary patch
literal 308
zcmeAS@N?(olHy`uVBq!ia0vp^azHG`!2~2D7<bPAQjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wdn(VFs=(TKSwnA;}Wgh!W@g+}zZ>5(ej@)Wnk16ovB4
zk_-iRPv3y>Mm}+%qJ^F=jv*GOlXEh&5*P3$BrT9!u<)Wz!L=(5QeHf#?p<iQoMG~E
zC8McIA-mO@zpp;D^3+8#x#b_4En_Y?H$w8^%c=_3<g-moQK>VhwQ%luB(Q(2j*2CV
zF5kr5wFf;cL%NSWuj1L#<@Q8K<Pr0wB4y7&F9o1ESrZP6TNYf*J<5@-HtA8Xio??k
x7rsI<8_TI%G7?rS)QA<{#-Mp5Ga`<e;fq^%$Lnvhw}Fmj@O1TaS?83{1OTAxXsrMM

literal 0
HcmV?d00001

diff --git a/icons/cpan_f2_normal.png b/icons/cpan_f2_normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..a791b573d88c907199e1e232b0d0a06caac8f3d8
GIT binary patch
literal 450
zcmV;z0X_bSP)<h;3K|Lk000e1NJLTq0015U000&U0ssI2SOL4300001b5ch_0Itp)
z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT1s)Rw51=ewf&c&j8FWQhbVF}#
zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b0WV2JK~y-)wUwc6!!Q&@Z=>+C6^xJd
zC!&%R^fM+|0|Ap|uqa?Mv8;h2|3dM=LZ<vp!Qgl_4ZO;Z+pekKnung8<mA5Vn|P*Q
zHKwTylT{kJ-sbf-zw+jsA;;5o)!#<-sou*I@V%Zd*7L>M?lw0-`|$}%y=|6v4=?}n
zdh=%ApQWyPGQl>>goFrd{(=&hdR_J3UmuT8W)wXeelni!q#_Ui5A{7Od>sI|R(S`8
zZlvyW9_cLsK$K>`%GhEfw6MlWCCXsm{pXK>K>>7!A;weR)MvIaaEowZzsinQn1&3!
zrb;zo7zdi*fTpQK{<`BVQf87Wgilz54k!YJR$Qy-HJ9n9UPp$wRzXHeMPVR0k|Xrl
s=bcoPABN-OzbBq#=238^;}f2ZzvI7q$f4Y1PXGV_07*qoM6N<$f?&bDv;Y7A

literal 0
HcmV?d00001

diff --git a/icons/cpanel.png b/icons/cpanel.png
new file mode 100644
index 0000000000000000000000000000000000000000..fab6461cf4941f6b5f6016b47d2d7f9045670649
GIT binary patch
literal 2383
zcmX|Dc_7r=7rzWc#h@t3GN~+CMnq+sk#!Uay~!Tgr!mT8iwu=rLX0sXC6cA0QP~Fb
zW5OGfrGzxeGS<*k<mG+!-uwM=zxRI6J?C@I_j}Ig+?!-)Ybm-_W-9<d6l-N>4*)+I
zT4RLa&@;KNy$PE5LhLP%gQ{*>F7&b`*vcsc07)h^Z1|o8|44v@LL{t>xzIbfn5fRK
zytR8zAkl7;`B{=FkwEYXA_3D&KAt2WZ)8{i$scKnwXws+B6a~F+<-MRaSVSyKZ*}_
zoRDKJjXBk5?%6Job$qb(rcS<UZmj+_9p{t=yOfiDYgKIlMfqMUtwsI@%pABGmCr%8
z+)rR8_03cN79e4w!SC#aVfr9V7AV0kSn^yoUX#XFs`ne}+a)6@wdL0E!c{ctm@8tV
zCTn$}rf2B<?Z+z51py#eaLx%p@eUneZBRWjKY#y8g;%K-B&zL939$MlPG`M8fV~zX
zRDSm^#{DtwhTsj@Cqu$;j;Hk48qZ9z2!p)4va4H75}_)KP?OxMx-*5_&n@ndf`4%f
zVi+wi8W@Jj@+2c1U0o|a)hB0DC=~jNKOWy#v@fB5Xb6igF3MzCd*9Yjuc_yMq6UxM
zbIALZcto<HjE?U~#`TX!?)TIZbm1~|;+S!MCAvI59zJm7Xrdu7`XqE?exJon+12_0
zM!r%yD@&Jyt9fpr!Pc0Wp0o4wQ$}4sEZQvFPA=G<WY*u?ySJ<QaL~vL<8%yDi}dx~
z>3rj8XUm)Ny+usISfWv=A(%c^^yT9@m5~bz!yDE6xmAaP#-@Ak>rUVtwDo?~ItHB%
zIia%)4HG9%pU&|q<G5bi&vOzOT&Ns~y(Z*H=Y7q}yr_)h!^9j7ZD1OPMln7_6B$az
zTAg8Ijo-Nog(DgX`~Dh>+8!f-d_6C$uC6{u2NmVzeG5kQJWfvC<H}`;7yyr-nHTlt
zeS>3(`1k!=nmV$SW^-JEu)`Z-OO}9t^ky>#iA-mOI7sALKhR`csZ~EQZ2Y05^O1GF
z0fp&q3<8Rx1`svSFj||v5}l<$tuVO;cjH;mV;@#lrv2ClVi)iP!Wle%R#foZTQSQ7
zW|5srvDmNgvBp2Xyy)oKiK&a6oj00vAujeRl%hXhnQlqgkL~cbKXwfE)6bdrEl^XQ
zPuP`J-|kPl*E=nGqtH`2Fd*P`+xxB2_HI3a3gpzM>uCo=R`;CATl#^D+HqCSv$+se
z4t;g7n|;}B`?CJa2_7nvZ<3GTru(98u0saA&T96gT9@AJ_dcaHCA79vyr)Wr)%2hX
zQO<6v1J@`{#ymbGVhRPZNC-bQJGOfJ3+$HeA2~SM8JF^?blV8W@2QN0STZn+>|(RC
zhv%DSma5L6himB<S1c;sYf>Qf>~4?XJd{R+Wx+k&rZNWg(b70eEH`A&VSn**GL(_%
zh=_>Jo-W4BMW&Xd2%H|y^(uJH2Qp<~ntiSfwrydAZ|pmV9841s^WQ^c^hmifur${@
zBFy^|ukjWl3Pl`dLd?`5wE!-C;&H&>+ykX0A%N45RQK&(U)eTUz7ct*_Zupxq=r!M
z(e3jIw&Nuda%u#z>EOYGnSI>#!KZ8Sk=fm<1A~OCtzw|b5~_ifrQJPLnjUfHo6Th-
z4K&(=8IKtE5+^s)$BA?Ao-Rwetp<}Im%NRJs+TJ{)RvT>Phz01EQ*H+3FTfGc>DIm
z%+G;lEV|H4VEd+3k3r{^wwW=ij-R7`sbjW6zT!Ig2p2%rv0t7->AYpsH}$}@(C9+p
zKea{#;%GTET6t^S@rXuuh>w@*M=9f|46BD;gf_~lrC=R(@B%wKxTuQ{#7Bkr_Vf~-
zj}H2c$-R?`)xCo6e=~CLW;bF1VSC<Us8!n~bNwo#PTmIQn&#t_%o?v&pIyExnZVpz
z+NVnCy4?22V>C%$L*1n%Spj^_RQ~n##MocxMn=!ZXl<>Yoc<-Ne7XGPn8~T*#nj*3
z0_t451taaXsr~OS<9WZ@ng4_AsuL8}Gl**nC0(3zxLtMWz%0G)b+`#C50y+a1a}ux
ze?9HO>DN1IQp#ByL0#334legGT6ZUX3%1?aVlA)cDTa(}e{h2GY?+<?ez_O>ZH*73
zP;MY`WD1qLNJA5w;9~M4=Q(es{re%#N~Kx+oX7jWHaLrFx7p?{61dP8!bn8}i2q@l
ztf(1ksjs<WhI|qO$}3^C_DqJ=^-cDnNYR2O^B<hqYqRz%>k-OOnf#?L+x?T1s$6gc
z7TavY8tZm5$UdpG`yfQKGB3gncjsjUM+m@D7l|8(X?b};(><Xi(!-KkizKr+6DY5^
zPfIU4cV=@Ldl1`WfEFg(2v=e?%!`&Cp)WpeY|Q7|)KSbxoHWuo#hTSmLIdgMIFskA
zajUJDD?hi5FLkBUV0^+>nXQ~Blk7s=h;%U2iEhEW?_jAZ6wMsM@MvVz9UrHyG4j5_
zP}TBmwI4G6jO@4&C}ZDx7$Bo%kG=~G3Xi!a!hA8gX!F26xy?%?4G0vMlqj+D8b4!|
z_yD)eYd&bDxdkS|FMzBg_O7Jt-yNpC1z34(gfD5VIG<V$J}TSEX8W8KKvq);!_Hsk
z_;vNFr^JAE%Nq*i>Y_WQw66>*Mz}dpq*a!ky}v_w2b6U0L};EYVbiK2M<y<H%<{BV
z_{E22!bdMQGPqjM&)r>8{pd{${k(0dLxG~$Ce^D?KQnsdx6zdO14b4hEqN`hEgCF@
z*6W7OomIRA%xazJky56BtUf`~pOBk4z^~(q*w_3vU;nbTLd>O{0W?7JLt9e90+J2V
zj)!F$*X3dn38rO^$tDuySCR%{^WMUM+$AwB*JiVqnwU<$cq3e~TO0r+$gruJD&jyV
zF4#P8`4UOvmtIQq0|3P|Bgpx9fNxV+@#e#a|J0qGUH;w2Rv5Mai;>x}-*gkh><__@
z4~h#_p}PuDOmVe#&+Cx<?Zw=f*cp@iTMmK$=j@s9u-<RGe|)F<NW=euspXtM2i-(4
z;^hA;3q#@Yi*u9qm}~+<Vcm*NU6z`dTNeW0m0|^eV7;juFnw7sPq%kd(vUX5n%kOH
I9X}WMH{U-=9RL6T

literal 0
HcmV?d00001

diff --git a/icons/hyperspace_f8.png b/icons/hyperspace_f8.png
new file mode 100644
index 0000000000000000000000000000000000000000..badcc43c8695e50aa57defa2b25720e25ba96553
GIT binary patch
literal 371
zcmeAS@N?(olHy`uVBq!ia0vp^azHG`!2~2D7<bPAQjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wcc8UQ-#*<HZ3$A;}Wgh!W@g+}zZ>5(ej@)Wnk16ovB4
zk_-iRPv3y>Mm}+%q6eNXjv*GO*G_fhYf<2FZG2GrOiPYqhCnNGq3HU@hawX^mOJSt
z9p*am`u*|-9&dsFiE{*<j+l0B3<!Ovy)c^l*vx4KQ}%8370g^ODv`cw^4*LhN_$r6
znetA|w0c(igkeKg#vC7E`4Zpy<P#i!O^(#n%-nQx`<%+<KP^hGlzD7T@>*f#Y`)`>
z_=$z((vzCY?$qVRw-@Z-&sTQW@7d7uY0A788-6C=d-$L@`uyZ&-A;FQxc$21d|Y&*
zWYJ@(YJ2wHS6Yj>rmHQwnp1Kuu5#XkcN6n{%IBVWYxZJm-Lj6!zu6xzEtjnNu!RTc
OR|Zd4KbLh*2~7Yw3z0Pd

literal 0
HcmV?d00001

diff --git a/icons/object_brown_dwarf.png b/icons/object_brown_dwarf.png
new file mode 100644
index 0000000000000000000000000000000000000000..75cb10d4a44689101a31a2d9cb34ff9eadd78bc3
GIT binary patch
literal 399
zcmeAS@N?(olHy`uVBq!ia0vp^N<gg0!2~3YH@>q5QjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wccuUNJScJ-Qo#LXst}5hc#~xw)x%B@E6*sfi`2DGKG8
zB^e6tp1uL$jeO!jMSnb9978;gzn$jD)odW(TJO^-a)o&Y_ZOybKWrP~7KnGec51og
zu#kJ{1&fQbSV|whVfidyuQpTiLC0RU-=FO@861`!US7r6VD!=8y3A4ugD{QeYYVO=
zN@xZ=H962^T6Iivwn1Pj_on+L!P_cC-16rdE??eqF7#RP+*N00CNc#-{U?6CbXx!A
zQp?}TTz<>Xl?8WQnC_va5*C_b&?e$}Ez@j&-X)7g$#Zq@tZ8}qJFlXr)T}UBtY<=J
zo9%y_+X*}?6OyxJQ#>q}$pp@J`<)z<-dVZ$e!`jMuT5m%omWlvoq1Gc{>z`S>Ys$U
q?Be@xAAXr^KHK+*?c0vO$JnQr1eaOlI3@wZfx*+&&t;ucLK6V5f|`f`

literal 0
HcmV?d00001

diff --git a/icons/object_planet_dwarf.png b/icons/object_planet_dwarf.png
new file mode 100644
index 0000000000000000000000000000000000000000..9e8cfdca3618c04c592556d74d829e6343e58a53
GIT binary patch
literal 275
zcmeAS@N?(olHy`uVBq!ia0vp^AT|#N6Og>Zx%)eiVk{1FcVbv~PUa<$!<OXj?!xdN
z1Q+aGJ{c&&S>O>_%)r1c48n{Iv*t(u1=&kHeO=jauyJx(Gqzp6vH>V0S>hT|;+&tG
zo0?a`;9QiNSdyBeP@Y+mq2TW68xY>eCk|AU=jq}YB5`?ZpCTWNq5w;EezSz3O@cv3
zC3D~Z{S57_oI=eJhO_7FnzP8K+e?dA=E)|-MFLG73@`R>zjiH(A@8!`u{p(Fo}n`<
z&siR0_+Y53w93oHWyz{(KVQY~DSR$tT>bw0y{*@Rx{m&ju|CCoK0@vEwkdt7K${sn
MUHx3vIVCg!0CqB5xBvhE

literal 0
HcmV?d00001

diff --git a/icons/object_planet_large_gas_giant.png b/icons/object_planet_large_gas_giant.png
new file mode 100644
index 0000000000000000000000000000000000000000..bb2d8d129d9faf3ba1c20f929d3e63fe618cd0df
GIT binary patch
literal 2072
zcmV+z2<P{SP)<h;3K|Lk000e1NJLTq001Wd001fo0ssI2zhBk700001b5ch_0Itp)
z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT1_=!=IO{B|DF6Tf8FWQhbVF}#
zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b2Zc#QK~z}7?U&iET}KgyYv^IEwb$AE
zjN2BDO%lQdHz4sCJUJ2%00I=p*olvQ_8IoHR!>#M6&G>r5JMs)l>e^PYJIA%>aM>4
ze%Ak^@M8}ULEk|)o)G&yr+GhB`Dv&~aB>2t=ZkT59*1@8FXG~y{aEJhwEfGpdpYm+
z_gmQS#Pr9Yvh%A`x?KCKD_ULsW;pw8zxpB$C*9(b-APWz!{$%Z-5;m>Ki}U?cQ<mg
zsrzk{_HU@}d-l3MEY6=?T&<owslWUstv~&JfAt3rzZ3H!4zOFItRMAu+@^<D4;y&>
zjUT_3x8F>+n?suPKcTKpas6a*a`|L9eG-?@+`OAsX)|+tNiZW}aAe4kztZ_AI=EpN
zuPz>Wh{Me=zP|o?yWh&kQCDaFv(Nmezdo@pKIGHQeSetp$zJ>M1@pc4DR>>aN>o~T
zS?1k*I6F*dQ@LPn>!);iaU8n*{L8$1Xg`!XF8SHh`1u!$M^{++&K+J7T-M@t4|O`+
zH|dzgvvqx8Y|?UVan7HoGJ^5xa&xw7!`k;@K}=u%HSPBGyCuf&tot5*@pSmr=ZnjW
zMKDCE-pzeK_x;p$39*W(sMaP@OR83y6A22T5A(@zzdn0)GTtrv6o@<pE7Y3KS;0OW
zD<ZDW!|L(aUo5J4I|_nOcUUbZi=-G@>!x8WHMZu=P}L!d_Z0wQocb^?$GgxKt4QY0
z2TE(3=ej%AlI?@k#Q<09u)G)~_DO8n2_=vTTNColhwSL!Xe-Pj&JYX;*_bOLc&MaG
zwPtZLQ`sX6F3<b3L%Dg#C71V85%KIITt4pH*gdp>Cf>J>p&p@C$JL}5p#n69%Ky4D
z697R~sK7|5vq!>Ga6fYeza>he!q|A*MD#t>7-4x9PA@xN@}XF&ys>Bvn;<GglA*SM
z>d*jz9S|E5kQx9`u_9CpY659gjJL))_~aPI7RL}foR05{wO{!D^n{n^(yi1qWjWT;
zDgqk<GCH;pOdJ|AI53CCY>t!x#Na5t2&!U_&77eF@WRgb<X5MmZazTu0XT5wUU>--
zyi`n+VRP8T(A2V85^6wF0h>7jcHq#^0KtmUFtk9@du`~Lz2i`6!I49sTyOyGT`Cdu
zJ@+1iXwFq|fDoCZx71Nl1F1C&mI|%0He*nON+zPJ#s&t=v6C1A#Q-=2qzKrQ9pl`?
zJ`y6BeTV80IxSt=aZ=zMLNw=;d!!zXx|p3n5c7>o0|&)n8F*%GXi~umxxNb=Jd#=o
zIybEfwVDGbKukm+@(z_LL<-)zmP4%p4#_PF_p=|>vjqnT&O`9j0W~88CWsLuLndg|
zT*kowtD?=hChg}bY6*}7luEnc8WW1xx2Pgoirr<;EmFnN>L~Y$yIA((qhtqQzz#eZ
zJ9E^U6XXKS5FI!H21Uf?wS;*;9BU_&fK_s(+Oc3MC}!{G)f9@twmFkd)SY4;wJfNP
zy63vn#%3UZ;LykmQ59b;6bPzZ01#?Nt*1HI>{~%J>0oDZXPrTvY!Wfm@6@m=q@>4L
zNHBAljRx>1SeI0KQW9e}*OWn}l}MnK6hVyu*botcq&ZPUbHOU%&f?qD7w3Fyxyqc>
z^nEX<X6ZDa$5|rn65nROSGPCcJ7Lfcbk)~Ikj_*QEEu5yh*_&jNjOzf;K;^akLF&d
zVXtAut+!NKZCT&{Bj%}Mn$E<Zjkpcr_0ipB_ohat;L$qLinE2X3e`Jn0;ZH2&q7mn
z2XPr$6{0n6(1g`^9p!SE%l-6v%JVGmr_KqEd!e~K4&{@DU4;5}_BY8L1&h#B>00}p
z`c){wn<!M}+$dGfjhi8W5nxevSx+6Vqh1VECB1k^54R<y4|O_&+fBWBQ=hH%*&~XB
zUWELplYO%p;Mnk3{G9nF_e=%=O;HWN$gz&T^{fF=6+6O5v0V)<y7K%w-@GYzcVhP8
zPB$Ip)pfeOh(qs|1D`t^sa3Fqa#Y+V*-o)kCx!$H3`81TTevoO8J$HkPznfv!8v)e
zuP?9D^>v+&@_i0Q#J+ixhv5(x@a&Ue>|6lwh8^P4*=65SfL2;HB2q?hY#t@BdNu?@
zgw{Ng3gTVK&#$NFFOHi{`F5P|^G)+4&tFd9RlGiZ(v2Z-b=D%0dTh}*2o(U$kN^w}
zP|>i~IJa<UT~+UVX*%6(wtxHk@ztx6ll~yLcG$^_uZrQZ3Y=ezp3mDn5SHL`-|d{s
z&Q&llV^KD7rFF-&e3;Is76qc??X-RM_T~KId7Ad}5zch?Q2+5<r0M$Ua&a039vcv2
z8vFY$?1RsMCK^hOA}-fqPRk_A(okDY`+EQS_;5Y%9^|84`0hbVF1Po)<>fxE=59QT
zaXHJW;}qPCXiZ$Ly#lL7k(Q_9yuVHR`~Cf9cl$O?`M;TSXt6vS&(?7`bN$+PW9Wtu
znqxDwT6}J;<*enlO|SEOH$80khg4Jj5oS8yxy3NV#RbKs^CJ=vBWY7WX;o`(bu(}8
z=3}nyCzxMd2(jxND>E60Dwf*n+*)h!vwqh9M*jrcAOZjPSw2Jn0000<MNUMnLSTZW
Cr1BB~

literal 0
HcmV?d00001

diff --git a/icons/object_planet_life.png b/icons/object_planet_life.png
new file mode 100644
index 0000000000000000000000000000000000000000..6973b38f281267beece5374a3119d704a99c64da
GIT binary patch
literal 779
zcmV+m1N8ifP)<h;3K|Lk000e1NJLTq000mG000mO0ssI2kdbIM00001b5ch_0Itp)
z=>Px#32;bRa{vGr5&!@f5&>tQ(oz5b00(qQO+^RT1`G)@0{eh)C;$KeEObR!bVF}#
zZDnqB07hwSWgvHQX>@dDZXjZLAVFkrVr3vuXm50Hb7*gHKOi(NFj0WG2><{BWl2Oq
zR4C7#l1WSx0ThPcES-h6Go4bf&?N?%PLa*3EJ5u-6TzDX4<>r&;1W;XycmxjG!m~E
zPnvjA4;T{=MJ148Pzpnvwm@rZhf+%S>1-ZI8Z}<@Km9NHzr2^1AK)K_{4qB>JBCjM
zbb{r~WI7{;f(x0r@M{2XID(?D-MDq-;+Rn>PQG5T+gL}dyF_*uxlrZX3y@2{ZNPBD
z{RfX2re*qLD4s|&HkNnUX66&y1&kmlkH;K*b`R7F0AL5$k+YXYqBC2n9%{F7oT=Yy
zUyJT42-54bpFVL|NJ;q$0ecYu6axU<VDyjQjBKO^_*Nr@c-_q`Ybr@9P0@4jBk`ST
zo1KwKdnvYv=rRg`I{7{aXY6&+vmaKGy2P32*~Qq)H><n(GHWr4+u5*?>F;&`({XST
zN;-N-tEaM;Gc}PuMlI)aQ`2j?)P5rTU8*By)>O#XlIf~e&+CO4iU2}lpaEb6X=AAw
zVSi}Ee`ci1=W92R23=K}IjY4<sj{MKIsjk@pllOy_s(@8ylG*{&&$yYU=!nmw<kuI
z;@OTaC+NDFq0{@Cnh8Qp#C1>;(T$BrjQ6@sBwW34{L=XWmZ6`%T9E6N9=_dm(pN)B
z;qj+>A=-GWCjQ6-&W*a;*rB7HNinxmtnC#g3u9bb7ncPw7%rv0yw&qU1At6&tE`}b
zz!0dipLbZy)G?ovYNGnO-3F_5?%iVb>vJ7s03B;oV{3gS8c%ik2V1OWi;1pE3XT(z
zbb0dOlkEJIu1f&=U&@E#6z?B)dc3lRZN|5gp(Ul91<>HX^%Jn50c8ZKP;dYM002ov
JPDHLkV1f;kRHXm_

literal 0
HcmV?d00001

diff --git a/icons/object_planet_medium_gas_giant.png b/icons/object_planet_medium_gas_giant.png
new file mode 100644
index 0000000000000000000000000000000000000000..108c35b06fbd08ead2764600a9b60dbf2f5268b0
GIT binary patch
literal 1661
zcmV-@27>vCP)<h;3K|Lk000e1NJLTq001Wd001Qj0ssI2P6=me00001b5ch_0Itp)
z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT1_=%U0i<)H6#xJL8FWQhbVF}#
zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b1?oveK~z}7?N?iD9Mu{A&Sh>p-m5Qb
z$Bs$dI2a0|utkd6wCMv1Q9*!8C4y9`5)VPNyi^!f>I+h(R-&k?s-~z;AE?@g-qi~T
zgpew=fCQ);nnY;=X&Pc1ZyejZ>)pAYnR8AbcH_j(2D_<vsq#OJbpH9zeE<Fb2e?81
zk3_EX8YP4h09t9S^v8%wD50EElTl8YV=$WokWnhbM5RgzsZzB?4aipUOlIcn>~Ov`
zUM}<x^jUdM4FF0@U$ho$^DE~sL@S|aMM}s`vH7NulvA_l^o`~7+e<^Gv2x#7&Mr_w
z*ECjIl``eC`IEla@)z3P{Hkb#Dpl8yN;%DpWb>2#R?%_I>_DbmaQZjO5Za$9FFO~a
zG{{X9t)jhpW+kY#AO6CQgLjp3JIW@fX>IA{-@gUmbZ-3RH|O{kA-5iWtLWq>``dqb
zq;s5p?8udGr&_a9w_iK1gp$liws7;n8YHJizdJnWj#%8Lgb_jj!U#=O+K!ta)ZdHJ
zVC{O`2oEm*`@ha6GiMcV9dbu=>*7HTn>&<pr6sfmz?ne_3xsziWQPX)Xq?jKa*#Az
zpSIhZqY_H8JB!7sGP6xU0qQzxT1IK(^ium=V=biz3iemWBLJ-D1;aIS6GK*h7^(oB
z2Mj=iC}8Uifl_YhRL9Lg2?@a9<ndkUN9C|taIO3h)KK@VU7!f+fiywV&8(-;eNb6|
zSOwE8tR|k|jQ;l7dmh}k0|2Vk-l%pVLoFvwQ%WhNw5y*g09lX(NI*gm0T4g}5@66K
zU_o=hfVMiZ<944r+_D?qONZ~@Bj$h=pz_R%3%qAg!Fd=;nFIhV%Q8)qkWL0LXa?BO
zSzyiK3ZPm+OGOwXT`jv*8FgRy?ycq05UO*i;_%s%-+rmK5p}pfQ+xF*15dsoA`VCZ
zK@cQK;y4atY@;W;#I#azqn-r5k#VhJNh+15DWl|@`}0pYgqHT8TGbj)KVSX+@%3>n
z=Byu1kL=&`xvQ;Dl0?eXG%d@r=z3GS0_}Et_B?<H%TgmXjIjdy*}d#jce|(pK$uh?
zeipCHt?!5r++F_V5AMs@#{lQ>@BT?rpE;?NmvvG~M^Pk1Y?_QxMo8yLwANZ{5ne7<
zD*R_2+WGkB3+9Kl4(E+yi(i~(?MSZc!_%MH{@nB(uzBa}HmA}L`bIpzmDhS*K`W)B
zC<RG7Ttc7$0w_X=VQ8hk^x5I(4ipBbikAih4j=Wu@zX!6^;3x7w;Km<>sCmkB_Dk1
z#n<1xke}>lG<0(ay{wd1)wJ$rk=C6PU|I+EZvW0+bJv~Ym|f^dy;GB4|IM*8N0-%A
zgO879Q8`q>?82Y^`oW{$Iv&OfKwJ;wmB1-emQUlz5s{-5?cK!)9=(M>wZCs?iJ~5L
zn1N8=d+p*2)ml<-#w$UONjEcd2*6KXJ^!^MC-fzq5?>^qum?3|(s4b@4n=Gy!cvlH
zO*BA2_DyE??RLI=S9Y{@5v8t9iPS%;hCe=eR8zs23cNV+#Wm;a6Ei0^w9s(Lw+CG_
zX91v0+8N(=f{9Z0w(^d9cAwchaYx>{<oOl=NR8+J;m!Q|oiI(oKmgGe;gT<-^qQzo
zKlF*WXM_30x))NBNQaYBhHcM~n+C|luF+2pyJJy%>h_x~PJs&W{*?{fOVqF4t^eY9
zeZG}k&bw5Fi(b^ry#UCe3T9>wVRixI+kh%i0f5;BlxzTa?;I+ZIOE$o-3B-UR01LY
zcyj44|EmA(Y*XrW`Jz%4)Y_|OR^mq3dxH@Y)g#r)fsw7ukwC~-PM<n;F@5d*zt6A6
zJ!Lk91i*iO42#~mM=UvcCmS6Ut`l)aqh{P_i<OqBooHN~J$)fn=bQd1f36;`1*u20
zw9-n*V9{%xt$zgMRyR{-b9Y<T9nBeiR%ai++CC*@;>G@lEq|fiGlQ;k3uiXBijF&y
zHH*%M-Cs09zt)Z$;ih>1(;o-3Id=>IX_#yg=?2}PkB9yPyu`>s0ZpWy00000NkvXX
Hu0mjfuYVW9

literal 0
HcmV?d00001

diff --git a/icons/object_planet_small.png b/icons/object_planet_small.png
new file mode 100644
index 0000000000000000000000000000000000000000..41c5b1ec75e3ffc23058453a975465023a17ed50
GIT binary patch
literal 323
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|I14-?iy0WW
zg+Z8+Vb&Z8pdfpRr>`sf4K_}0R-I<<pZ!1~$r9Iy66gHf+|;}h2Ir#G#FEq$h4Rdj
z3<Y;j-+=H&K5?L;4W2HJAsXk;PE-^=tia>8nU9gFd8Na#W80ekzZX!*(#{qz<d`6Q
zCve^EBg>?YTgM#|XUGfhKYdf8x#O4wL&CG#`!j#8PFNc|t+iv<hTPfT|5^0aJ-7T;
zc5dzKRa^61G)>PNGc1|nrOE3T>)@g^DX7nQ=@gfij=4UMTfR;D9-=(I?(QW`gU_jo
z@;@baUAq;;rRXN%)M5DT!twyGla|a4*P=A9R<#T4`L4P2zx}(muS^eQgOm#!bo_vB
OXYh3Ob6Mw<&;$U3VRhI5

literal 0
HcmV?d00001

diff --git a/icons/object_planet_small_gas_giant.png b/icons/object_planet_small_gas_giant.png
new file mode 100644
index 0000000000000000000000000000000000000000..e94ca63ceb58312e3299f7e6d128a43bd1e982f2
GIT binary patch
literal 1256
zcmV<E1Q+{>P)<h;3K|Lk000e1NJLTq001BW0018d0ssI22{c&S00001b5ch_0Itp)
z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT1_=!|2j5W{O#lD@8FWQhbVF}#
zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b1XW2yK~zY`?N&=~8&woO@z`U}jNk5L
z{D>V-JfWz0I4OAuCuyrnbOj5L*zylpus{Ow1G306KY#^70wHySP=&-pAx%V^R)A_Z
zL2(k2$d9puJr8@Hc8A5KmSsB$Vv|k3)zz7!^WA&DIp4VeuW$*5F3rFkPMq-JgwNsf
zVh$Gov`x*_BtsPqRcxF3|0lp4PQt@b!8qX=MjV*K2>@+VN7Yri(yX1TGJgThxdb@j
z^D*PzVUF-HsBS;nrmo7oRLP6{zM+bPcR4Uhc!xvrnP6;^@G>^cPSZ3+Q2=0?W_Owm
zv*U!%Nd+;xvt=|}db7Wjec;{$(aB(Js<Uy!FyisJWmzOiDvDCC*Nep>`b3f>P1Dg>
zau=X&KJ7n;z5m{Q%y?vMwr$xh%R)g>6h%?&R15?H05CBzL6W4VX_jRj9v;&1NmG+*
zg=al_^b!yr#yiZ#M>&e3ve|6^`4mM_6a@f|j*id*0MhAnu~=+~oGMiswUcu-z#L95
zJ(ga)cXC=FNivyCN|Mxb?;W6cG#X7L62)Ti?CcD)yG*00$rbBGdoY2^9lAF9#%nij
z@;omHf+R^S%f@0co6RQ6GD(uCC`pp3RElAkWHQ-kG!709R8<9l&p!ERW8)W1u5_zy
z>ya0;yDEHXIkSLnBbUnwf&c*X^YbjrqBOLSCE@hsuR@^!0AYsy^y3dv1)T7mtAWGi
z#R*@Tua^1ha%N#=X>oJw(ZlV>0MPku!VG=)_M)yE0N})N0C2mhGGAR?TSo*Am-pO8
zW4NU-xAgBelZk5e@0s+(%F<$)ukPm$0U$kfgQ6%5vjIS((R911a=G&P7hiW5Dd)HN
z{MJQ%007IGg@w6UUDpLsT3uTYGxRt&1^^Gf{|NwY-^%Rg4@VOtzx|PYxc!(UNdWNs
z{jpeVXJ_Z4P9{9Vsn>5Rs&;Z(xN|G>d~bgw8ktE?Y;68o7o;*@l^e}QQ{nl_v)o=b
zw^u!@aU2&61_ObBuInjwL{%E6`QH_>G+1O)lWLWd>FH?zSY2BW1p~FZV4AH?0?V>2
z%X+;2`T2R5%M}iX0U(#l1$@+<l|=w(=}l9U&K22EMbl8D;Sj*|buLjVSDx?wiG(fl
zRaC1xUsW2J#o2e?d24g)5deJm;D>(K8|rDaYol}Y=p5#7zW?6aT#7Z#7BUROFuu9}
zElT53>}X;H$8i+@@~itDbFCgfIXiyR?H0Xu?G7dwE<?wzBa=b$T9)N@Q;{%(a*)D5
z{=A8f)j^|n!soYptYWC&?1HhWH}8Gm^9S?!e8+7rg`tXP$4~psu6G_-FWN1=DTq>X
zJni@UL!l6k<L!3a@e(vmdpsVG$0N&f_v8^uyY)i0e<TbTIPI3+GMeR5u`Vf0WJK5X
zNF)+?2~E>bT8F?;MWM7?FJ|>-?FvTb)eZgrXA4gFFuN1oAtLxM&z4uXdi(<|0!c=c
SnuiPk0000<MNUMnLSTYdSwIf}

literal 0
HcmV?d00001

diff --git a/icons/object_star_a.png b/icons/object_star_a.png
new file mode 100644
index 0000000000000000000000000000000000000000..256f3d3df5068e438d0ffa5ea496cc9ab0c55a97
GIT binary patch
literal 3145
zcmai0do){V*H7JArYIHEt@KqTv}mI!ZH6=mq9Tb(#H||Fx(8F^-d0VDQN$(Hc8aEx
zP%7@K?!iPU;;xzwQ!Q%JR^t}Y?{wbx`_}j0d)9iMeSZ6#b@qAo+WWVEzqq>C%gU(9
zfIuKwq{Brwz%Bx7`hXa)7ovM&0kbd0&E6JNJ*=?|0O?2vd<+Qm!{P61ABa|}3OM(Z
zktoFeX({O+j?46>dyD|CBV>dJ86FW9MhYi`;L#*MGU=*zd@wmk+a8H>y__be0(_tm
zdC>;@)9l)P(@U?X)iPu;S={OBrLpO#E{A_@Ya1O$#AQmvb3v%);O?*ksKU8-O|CgC
zw#%4qu$ZjtaqU}3HFqsPCvCmFlKgZ{H6iw=N>7a+TaX(WM*cz#VG_iU0^Qj9Q}~8I
zt!4QAi2wDv>@VTdCI4tSqgtI(7BsuvH`N=}>p^Z;u-_W<4Oy`Zq4^OS>C3I*L9LJU
zDb-%rh`WPR&nzg59};gMioSJwI16?B6%J-FrW&}=FfI$VP7j_Xx^r3Aw|5PVYr1bS
z6xMDNF^+J@UxQ&4(3=e=g4=7*lkH(X{ey<BPQeaBog(mL_jrUqp%MN%Ypt!dA&ozp
ze86fjlu1{=*(_H=&UDgyt+BTIZE{EG{r2ZHo*+pc*LacsMfs8*vA=|@N3<o5N{HyJ
zcD|(0`w&@_B;Vy*@hL}fVGnAC%3M7(Rex<+E^VWyb2E|mbH{VclA@;v=DK3+z}1)#
zJIb*Hs^Z+tyxeZMCFY|8S~KkIlY8-j63%5)mum0F&)pw(nMu@4EB^$h2i&(0eE+w~
z+-Ss3YeS27#x6kuqq#6V%rmyr63uPLhWelXbiNo8{yf5E=;iP{uon!C8YixO437w_
zj*n})qb3FU(kS$7;#(6F>+5$P0(V#(&cGCh)2uPqSXiOm)kVH6)3v<8uq_z7wf~OO
zpdpNR@2SY&c^{<=NWQut!N2W|>xBA~8~N$SFK;`?W<LKK^zP4v(<PBk-i{r3B5F|U
zXv7Dasp@XjIg2#LHhDMY%{>w6-5;-a#T=L8?^FiQAoC7NonSkNjTLjO_KXcL(J*K9
zA76H^79_xr95Jv{%<UN;%z=4Fbaq!_Dzm|Q(%6cTub(;j!2f*MO0kj-YPDIe9cRge
zL3}6#f_KS1AN}>{WkG%_X@(YVqCEIK6{mpbwrt<(H@rmWykNpE;`>*#Vfi=5T<!3N
z>7f*yNQc9D*yCqZh@PYj(9ZSV@`FFD)ulFwp>zzLZ_#rbGU>LM0Z1BSM2W@mTX{Mc
zzf~chcDfM9&HmN?c0q=TKxonH1%&RF6j_hNmJ@L7j&z)Y4Ht+Q35FP-s_~UR6*^3(
z6~6cyMEbMq7<9<X9f3}G0r%s#a=2KOxw4kg1tPKU>4{?lgvxAF<;@(q-qPwXP%2M3
zTYbA%x~gGT^>P1;a`ECF*yjSCvMk~zLB_Q*g#W>{vy=8K!}`JSkg@NyimSV+c&Hrx
z*n?xwIDU{0OPcdrLP2v{0|AG4u94fopp#(4#INfw@L!X<NT}iAn;cO2kreNkkO1X7
zyWQo>eIIwzUDw0uW>g<DHaiK=VV3DHREc9*yJwQN`1U`1BV6Pa1!-yrpx?$LRG+d|
z_ii?$te>ColzQ7Cz?ab|HZtW@4YLWI@byzN%u<rz@2mq&O@V&Q;F+PN`ruiM&!?}@
z;_58#3xCLCnSdq1B%Bxq?TU7P_rJhwK5{@;Cd8D(gzLprSuE2iX5+Th3F4HyJ?8w`
zy%4X`r7N90YU$;9<S3R~@3aM}yHbR+eci?)Qs{ZoDzHp96ODzXSpUyK2`MQ#XG(<8
zrmE2o>#z0y5m@awh_xl|j~A%0s$7~|UTOuGtmGnJx*_gYYMjO&!k`A@Oq{^+)zO*v
zlB%89;~FvM17%f9riM>2n4x>kr*MC@aOwd*`AQFI^`o67q|}Q4xbfsCC>3U5<N|K5
zBE<ED#Xzb4sSJ|IMBY61CiWukGYa8=z*2ku0Z8Y6Kn49+#WAK#1jH6n#?+v%wC+O<
z>Q$a9XV41glf@NAV&Pm}D0opR1A-4>KA7=QJya$P&7d#4En&&uSg|RVF%UiG7ZlcQ
zcn{>@3$W5T+Yo8-)Iu~~lg99HJ{blYd3PENLbk#RMT6(|0_&i^{4GBs+Ly`z(ZxWR
z|FZ+r;}$v5Dbhv*TFUlOdhe5h=%))uwZtD~E@qlCoFzf?M}HH_*9!a5FMUI{TVaF0
z+ddNB$cs(!c2@v#CU?$QE%rdoa!mJ%!aw~=f>LXXb(n=rIxDl*;{paCSJ9#OHP%<J
z_uclyjEbD5L}ymZ;KPjK-#KT|f0SSrGsh0G3hlW@mo{^!Fb3!8O6Ud+9co5_R^Ucd
z&si`W+-HCcfjFPcXJ82!1)PE#P$X!u4o>SIaGb#~0f5y1f1-vF=dEv^FC8sg;QyS)
z2X|p-<;8h$kbcOje4?=st^4!|Myz91s9%XN`n_FcYyKlF9?D*{0qGy>YA<q<1?ofD
z-e!ayjT;SMB&$ly6QjK-ujos8&uv;I%{uUw2#kSj(n}nh_BfF@7%GZ_PKW~pexrry
zP&Y1|zu)HgDrmNUa%SERf6~qd;Mw;c@pjw`ZJqpu1e`*EftpmZd4E~{PK`*Sed_vc
z79_<csp&pHv9y&@e>Co#m3}2lW!xq31kbGBDA`7q1eV0fVbJbqSK#CT5bR)2TfNmq
z_N~ftSy4O3uSD>B*5jNcFm=MmK2=X0@vbpm@S#U^2qjN9&?pDs-Wqf5G^MtrI>E=L
z$rGkhS&oH37z^}-x?EJ<^?zA6FKO(y%_j&0oN`5stGm~;5Ul~@tJzFux71zX-p;DZ
zYN#<A8A|=wA(rK?TA^!#Ll+bym<j%gZD|L!uklg@EYZf0dtNDZc3jueO0!g3JoELY
z&x@dkaxOP#YaP9P`h7p`pWN45y7{ot{b&3AWosVCYveCzmg*wiYRp3#Ot*9nr18&(
zJ#FXBD@XDmmdP8wk!4v~+RrG5FFkDV^i&VEWn*-l5xxj(Dli8+nadfZnOw8}x@Rr5
z8&&g)CJ!iAAo5iOTJXt4v!q!9c=zrm<$x71Z2XM+-lY<!=Y*d^Lj{{J<kR_qh+I)7
z{%c@fabfo3)U4a0_iOHf@t!XY94}(>|H>N6tyN_OFqXNKD@%PerKT(Hy))ne85EFv
zmMtdO`Bv2kRt=+L2&MLozamYL;Zw3zb>2sn?RTp&N6f%e<4$;(mZp0p<dh82hdY$r
z;Nw~O<pLS6aQ%IARdc$T5(>yc3QwrL*UBiMM{m6YMgWk3*Rg`~k>uvR9rl?nEi(f;
zgE;BU&#i5CSMSLZz}LBTajO$|8V8ylC~>fFx)bd87id-D8r;?4id%nAvH5R!vmS>x
z?Aou;IR2sh55L8eGj$`WIj{I{ip+~0udS#ce<R>(Qw6aG^FS(ANY7O@eZn!u`JMgc
zJ%4>X$QImj<D_g=0Nw-)^^RnfH~$lt&HAzAj^U-pujZZ#8j65EqA#j+jSrqt+VbXq
zFbTL@rhojPc-wr<i{#HhUCo($O70o39rxw@Nsd(9^3sw5G(=^{kwj9|_^Rf$u&VPB
zO^s)#U|`XS4tomOgif;R{kNuZ=1$7ceDE4*wYWE7@#m~s+B!Ap-Ipy0dKlQ|giiht
z<A^PBCg~mD2xz7K$xYXClwV_WzKbvz3UzqlTrc(W%;oPLzQ{K^xvrjX&xy>Xg%{IN
z|B+z^jz-31UF|9oUMakCsL{5eqMc)GeOvFjt7AH+N+KnlYps9zkA!1~zIO^)Q8Ac=
zM$qVCgvQ_*OTyq<g(!Ceo(v1O%-44oC7YO>wDJ$_Xv~lJExmjEXk#I)n*>PFBdy`Y
zelLJPHB6O9lU4giTj<XY)8*I%FLP-2J*E$h8LFb5vUY0o3m{M*mZ)))2RwR7?4pAh
wjJXF|$vdcHJLRWsYhLQz|Ei7u{X%ZxH?i}`cws*+((t<niEz1CZR?x<Kf<Wk;{X5v

literal 0
HcmV?d00001

diff --git a/icons/object_star_b.png b/icons/object_star_b.png
new file mode 100644
index 0000000000000000000000000000000000000000..470872e7c6ead1370484f036525d3a94934452b3
GIT binary patch
literal 1236
zcmV;_1S|WAP)<h;3K|Lk000e1NJLTq00310009690ssI2O{1;-00001b5ch_0Itp)
z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT1_=u%Cd6!3u>b%78FWQhbVF}#
zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b1VKqeK~#9!?cFhRn?Mi+V0<ZICUlh2
zKy90U;QaLXA7Hyl14AZm=<v+Zr?AJ46eQq|X5V|;B(|jDo85hPsBx00R4SE9rJ}1e
z%bEGEDr;*0?cD?O2)MiZAIu}*s<LJt0heZZRn<jtB=sjrQdV_U)x7?qINshqaE^d`
z|EH?zl5zw-{6%pr%X`8Ubf33C&V$f>{;w+QvbyK>r&(T>bO)jP_XolhJn<8zpgVgO
zW=lbL_A^@wp8MHS(Ea}_suY~@Q>EaHpX!O&&HlD1j_iJt6`1$)tbn`p0?C&`7yTqF
z;I6z@RyF)x_?n;LE_}_OW_eK@u~)!de;IxS7X0ulu;7PZ0k`-L?7Q%?ANwM-s{g<j
zA-CpDz$+mCIzQ?Ptnvdt4!f1_LtTMQ{sXTc^&;rjzZG%?<lp8$@cJPmz-@Xr-U`US
z(GPYJ-sXRgfGlP8CrOfJIdA{*A|T9v;Pr#u0$jjnkXAtcK>vZ)j}!qe>|-!1Ab(J$
zS)QifdHq?MXUzK#6M-)HlNb>oe`sMuV7MP60;Bp55P>e{!)cmZ|1K=b5Aa=Blpo*-
zh?{@=M?l>C+dl$g`)~aSaFL(4egwq&tseoge(Og-^!yt?0;1>N_z|#6|B3z9BX9@5
z=_646kMDm*-v5-o|8}nc`}9Af??17h_xT^-2;9eS_X?!`@6!K_yZ`k0m(tI_(JP?l
zUw-CCfF1W==b!w%exq05-TX!$fi(Z}%P+t3KmH1+{MY`=FTecq%P+tD^2;y3{PN2W
z?w|bVr;*q1{W$qi^Dn>r@)PF&r2JR@%TMW_{5Tn0{@wh38oeJi|MC;>{~PuGPuhR#
z`S;Z4pXZ)`qYppozw(dy^1qMY?&W{S{=ZT8e<JU{r`~^0-v3Gcr|rM#!+!_A>BE1o
z{=f11hoK*ao_PK(hu<FaZ~5?x^-q2rhMu?o*28~?{+kZ}Sikk~kDh<y;U7K!#=}3#
z4-o!Qet__g?Z5T#kMaYAzx<(ojPMKhV}xH+{{g};+>f#R@(1~0!Y|N24E-?tP9VxL
z55N4|{Yc9n;zwHkV1Li+2l&n_-2cA(*1z)#_YXtgbx*wg2U~vmxA?(M{>^@{lRwPg
z%TMWtT>cGy$mQSa?@9fWAKSL!_2XT8H~S%*FaHWZ@bItlcWu+QO#)hudH9$8sMo%=
z{<dqn_9?+Fx4isaL+Xbg{slk$@Gtn`hyR)%``WYY$G-L~_<<jISNTbX|C*nn`SX6B
z;h*>O4F9~pZJUlD`RDzUAG?la_-FhKcixNs$&b%ZKjH7Z7yVSpH{+*DzH>iY@}2wH
zlJCS%n0zOG!sI*g6DHr6e;E3gmq)hb`|^`~)idv>dfJ`ww_Wq{{79I5AAZhb@3X$$
yBsZ^re$o09i+{9SrBbO>DwRs5QmIs`jrA8PEua-vlwQdI0000<MNUMnLSTa3IiG$2

literal 0
HcmV?d00001

diff --git a/icons/object_star_f.png b/icons/object_star_f.png
new file mode 100644
index 0000000000000000000000000000000000000000..6f5b90e2fb3cfa9aa2bd8b5c72bd98b4cc0a550e
GIT binary patch
literal 1234
zcmV;@1TFiCP)<h;3K|Lk000e1NJLTq005f+005B)0ssI2TbNzF00001b5ch_0Itp)
z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT1_=uzGP>GF$N&HU8FWQhbVF}#
zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b1V2ecK~#9!?VT}B8&MF3-}4?IO=yWr
zd<R>YnhKYRB0+=14R9Ll27Cjgq##iwN?cI`vhM(;<JwJ5kQ8fK2pkE>?99Bk-&a|p
zFrLpd^Y3EwLI3~&00000000000001}2QkZ6)or(y4?3;qKfRsYFCR?rG3wM`X>Pl{
zwQ~7#Zm0Km(}&V@299IW7T>1uC4$qn8xfhA+GLCl`aK<y|57_RyTYhgQ=S~}dH7|9
zqp|;%jkwIrt5c(ce$S!HCtiKd;fTx3ycVv;?8W)v{>M&o+56XgF`F+i`m{AK&JUZL
z8_8Y&H8(f*KXqb^PByEHse5z%#pJR?{32&(_q6UtyY1G^^_P$lJG-a0&1yMy%SjvZ
z!h8Z3hB_0<w67gJhEfZ3W+v5OkLHns+-9YS&dj8$PTfJjm(~pgk*ihZ@mpx>%zJk_
z*{KCOGm{#1UQcGX5j@{~&@lSv_fK>#%l+<i)lbn-2)BsTtVXqaw1(l<Jo;8Rby=S#
z3$?<jd(z)So_=Mjn!3zetJz!C)Ft0q<=#d`4T><UD#`aKfyg!@$@l2mFZh94ts)JY
zS(`R!(9GIYxJQY&=Al=zM~S!|>LzJ)yS;@T^SX%{?Y3LB`T&t^oD?jboKZ57kLrmT
zW#l@ko{UjOuAc%58D-?UsiBNfMy{V4%G_b##)6+)4FWyN1{K_<K8&(NJ+0CPb<7L_
zqr@Ut;Yc7E5sO@fBc?7>m!RuA?$V?5C}ci$xfpeJNlK5>qkvJsC}0#YiqZ9~&L9I3
z{=c5|C_M_k;jd}VeDK#akT;s;xvVy^NTQMgM$6+^!AT{Vx=dXzNgZE|20l}tS<n@m
zx`ZP?HIy+*IPz0Np&n(Gx~ZYq)MfBGDWKTZ3xn55^~8)ac%4*F&M1S|N9iQ*88LW0
z)Q$fOn8}TWN_sSeFdU6BWLq&|qvRv=iV2@3F`F-9jC=+0<qi7%a^kv2X;brB$)EQ^
zn;JG~;JlY?B5s9Km(Xilp9)XHtJ@*$uUMZ|9@k#s=ZuDDw5dU5Dm;Ibh@=^aG^Q@I
zS9#tl8722B<XfdZx}xfLWh&^@p-^}Z7OKMfR5e=ek2*^oj>dztE1l;m)LvC>rE2O_
zN=9{#z6#;i*l4Y!>g!aUx+{KMPU<jzhokYq(O3QFEv(+Wc}rE^o%TlamTIWeW{*B5
zzjMFE&*e_cXiizlBnLT~x|wAqs~qIiX!!=en?5u@yl-x9B-fe;N8fbr@TFulgfN>g
zl55toHD~jMpI<5Ztkj_2J2~DX(B@;bjKSSb%c0ldXnZ^I`$fDMeB;WibC+oteLRP^
z+gm0pwoGk4XKcboAGfTI+ES$drcB$Yg4#qyCU5k)-4Mdt1eXd!2<ckP&FEUk%LnTY
wty+B}jDH*f0000000000000000FW?$0V5I{G*)~dDF6Tf07*qoM6N<$f|id#iU0rr

literal 0
HcmV?d00001

diff --git a/icons/object_star_g.png b/icons/object_star_g.png
new file mode 100644
index 0000000000000000000000000000000000000000..c96ad340af78f0b5a97975b308febc5f8d985a00
GIT binary patch
literal 1112
zcmV-e1gHCnP)<h;3K|Lk000e1NJLTq0046U0040a0ssI2Y3U2P00001b5ch_0Itp)
z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT1_=uyF1Nrc#sB~S8FWQhbVF}#
zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b1I0;1K~#9!?VY_&8$lF>kL?~&vzBn{
z&JCNAEqDO?36=*4!n8DjCX^V3^w=-p@Bm2bGD{P8F4q!E?Zgj|6h=WpAeh<t+nICZ
z)<5%c_S`#bd<UQ?ilQirq9}@@sJg4+>?i9)&yN=>%I62keh6g}V?Ra`ze4jZ6(zD9
zvm9CO7U)JDY1V?WHL^*Jbf5<4TZqR~WO*=TI}y?W4hLF*zKZx}it*Hfg&V#ywa&L?
z?ApIP#o++kTNW<N?JXoRdR;6Hwi68lOuu|a61!5evmEEwCh}LWXVX4Lr|5OvYLk-~
zt3Q}8>PfZ};qnw`M|Lj%cf+B7fKG(@0;_ec$Zk@#f8g})v)9#|ye%DAoeI`>H_)2k
zDkke-u-b~<mRhW5M;6-E>apAFs)ySO9IHOKYRD!rhH0Hb;8nQWTPicRr37nTN|woj
z)~fz#j#JkgETQE9YWm!&WRFt}Q~0D_7ppa9xmB`hAH&qeT~>AHR<CCAWId=_TB})8
z`k9-rSbWvyJ0uI9O=3Iuunnt<ZDq)M)(>dv6J*k0f6wO}c$o=%rgF9uA^GHg4pd)k
zlZMq~TXD~}Low|0mbb0Hf(6nNC}|SJJiEISBGyiX-5s525LE4|zHl+J0b-S2dy`S1
zDG)?F+YZ~aZ9QZ|jsiIXBx{>&$RR5iwIB9un`FhJwh2m)njighMPwz$fu5yj=~;?o
zNtR?Ok|kO1$vQqsz?g(M(6jU`Jxhs^EgnL*ZBe3l&_H(iNU~Ty>hNqyvSI{C);8I|
zqgFIz#mI&nwW1*_=-D!C&z3sLW<NuOE4Og5okEy_AQ1(^WV77n)HYj^Tea}+Hna{w
zqAQRjJG&3dwpotZy%DlO)v+pzmATgyj%}q43%Os1uGaXy_W$2IvK&_v{F41GepC8g
zy|-<}HOk-lo_P&-Gkdv;<R?cICGZ!$u7@)@iV~wSZtpCS1u$Pcjdt}+qY17iRbQm)
zgq~&jG)BxD-Ar*cabWh5XMIA&en2JJryCZf4{+6vQ&uy*&x2~Fq-v=ryQyZYKXrv$
zlsNl_@eh@+sd7qcP2uXm3gwf1=L^gib;{f<$H|4t5YR=oUIk6-2>>lk*Vim}z0bXw
z;^ab2gJq8Frq8{-L-F8Z>=q@4Uvd6j<H&#LKB>aN*wq)TFJ$OMAQ$E;8#`CUglAtQ
z?`n-sWFZ`i5~B&uuQjHsHP8MR&j&M+4?NROuZ4LIqob60cBl;KTAz1IQ4~c{6h%=K
eMNt$*nbtp#H{D6Z1GcpQ0000<MNUMnLSTYt8~hpo

literal 0
HcmV?d00001

diff --git a/icons/object_star_k.png b/icons/object_star_k.png
new file mode 100644
index 0000000000000000000000000000000000000000..eb40de2faa19ea61c268b9b2c597deb23939b595
GIT binary patch
literal 1498
zcmV<01tt24P)<h;3K|Lk000e1NJLTq003nG003tQ0ssI2mew1S00001b5ch_0Itp)
z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT1_=uxBjQ{IxBvhE8FWQhbVF}#
zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b1xHClK~#9!?VP_)8&?3wKWF>Q58J@0
zV8cKXl)4HqR05<(L}d<Jhi)NKmo5yE@&{x}x@5=_DNCm)QMagL*eanaC=$@LQVa@2
zByv=OO>4*I62uu_1|tg*8u{HX-(B8k1)cmp{=V<MJKsH!NF)-8L?V$$Boc{4B55y*
zPd-FKC8Cgso%~bXM6Y3*=(R7~mjbCc$r!0P=}s>6kx&`!JNa*VjkOYbja-R6gQ~%v
z(N1?>`-&=Lx{e%84lp&qME@z8K1HvwQe<V9<>HaMib3H+NPQ{L6KAlSkrZ>;Rvmrb
z-QqTnwx}5Fy!S3~iZ`at_b@xmm8>mIr;1zL=D`L}3m#ch0&1#V))e=8>g!&97;)Fs
zr`GPj;P-!A?6s&H2aQFT&TwOb`Edq1J!+PCglsP}Lqrr_S1BI^!IuJB2eZT6ndZB}
zHk|JhDpLcDq*yPb8yIyT`-&=L`<Nf+?j`SHb!yPYG%gR5*D3BhTxU@SWqtb<H>ddN
zf-e^0S_d;jD3B{VsVwTCtQ!-2f6hnOdI^;=&0%HHu*z~tnO$EBWc#=|<wse^m6;~X
zMF;9XYQHAt$Nkb-pHX%$#j8DXrMCKtMVKAt?j`(>*1>2W3wgWiGV8vg3ey?xOt*9C
zAVgB-Op?X7wu;Q6uSRX%m}t+U)bhG4-DS7PEc)t=^W!(nj}t5sDrb{CFR@v%c!Y?x
zaGmerN|qp!>E>$olpEhJ`s#@@JM7V22j?Re<6@ebA+LM|I$ZW67WtyWWj|uVz6QI4
z9j<1?=CbbliYknxf*r1A#Ktw!T`v2|bXojbe8`}sh>7`1#hK_2iLa!}WWSrf6i9c5
zVKO$S<d`OWmW&96YjS`vkyM38B;YF<V=7#}QmXKX#PSuI^0HiEt@;XCd0DQoR(&N@
zE`&=~Q<>1XTJ;rCNT?z7r3j8I()#x>8q$1?x{)u5^d)^sU((l?;j8YOC*YmML~LBG
z`f5A}A3k(LY+SASGDDg#(Rm|~uUsi?%IYSCeb&qF^!=W}N-<QvER0CNSJhx;H(b7I
z!XpyP*K)Y>RXq?Mk$|s|RhWhH5?-%{slu#{Ma);lU~xMnzO0Q!%va$9kG8@vCF>?z
zRepQr{`F%xB389J3SnP6?|HBh?1<G(URHRx;fXK3#?wNuBUUwdoEJVbTJ#n4h}j93
zMPGrAn4NH8`F`Qfd+xtz!|VArIW+nEKkMPb^7G_Io#H+bg{cAJk-*Hv&3D{d<&U?X
z`)bU}*HuPRjA_B}RX*Tej$hVo9VBY=6RelHJdD=icUiSM3vamg#O`tIzMrNW<aK6-
zeEqb*p~+H#>r3qJx7$~v$P^gUe5&lw<gb6Z{mfQvIp(WTWVy0Wl{IADTD5Yi=$0?v
zDsx;G4(<=oP-dE(OZlSyYju_i+<xYyES%kcLa(u0<kcRdeI(Vkt;^~r<pUPpaDB;{
z&T`9FqyBRx7V?})lJ085+&nbde8;^U*Pb|VHgw%rb6vLh7SrTxl6cgkpw&$(2FttL
zTD7|<_1af+Zay#386^>O*IL8WTA5!r`T0-h=iqT%-!Cl@V`hk}SuUo@bX$M1_5Vy&
z4PI7woaf<&^}7x}@^#$WT$XW-!ETbOEpc`8X#c{kDvR45nex<ElXMqzStk1#(@3hM
z)K=X!9PQPp9#Ggn+P~1*wSpgg9Zy_if6RuvL=5G${Z~!UjicXIoBLyWmL?zhI^k|q
zJN?xGy>_(o*;*NiL?V$$Boc{4B9TZW5{X2Vk0nGG*8J(fRR91007*qoM6N<$f($R$
A0{{R3

literal 0
HcmV?d00001

diff --git a/icons/object_star_m.png b/icons/object_star_m.png
new file mode 100644
index 0000000000000000000000000000000000000000..97332dcfad618e5d1b446d10a9d1a01787ae6029
GIT binary patch
literal 798
zcmV+(1L6FMP)<h;3K|Lk000e1NJLTq0027x0027(0ssI2N=Ien00001b5ch_0Itp)
z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT1_=uvI>++11poj58FWQhbVF}#
zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b0*gsRK~!ko?U}J}+CUh^U+;wFQ&9ql
zN^E#!!(&IrZ0*R_Y>fR|I%Z_c*lrmaGr|U&*@&tjqo5dDw%oy1iVE0w$8iw#o)|17
z`}^tnoqgYDz{tqR$mkOELE*J(6pn=&{riufz>DA~mqeUVIu_biey~gIMVLqV!SVF+
z#Eu1*q08X1J*?Y$;~oc?1Q-YKBE7^l3YX!_HG0(|&_n2>HGZuy4kZ1i64v(X7&LKn
z1;>I-1kSYyS`~C`bZjt!MJxj?M(i>Snz(JiWn!%Va61+{Hfj`4F&6R3h+T&7pU|(P
zRZ;U`$)M+v7&H~a{dz5sT28Y?p($r}2Tk<rDsi`=0n9?)OjDlQ*RSKYp(YH6mg6eH
zG{h!VLHzk622Hr^gfHG2g-tLExt`OsG!NezxVe&lGVnuhQm$-}(goM6s*mqp;$D^O
zPLkrQbXmSKG{~{gW##UW(w&0_gSTJ)r3RG{YcrU?Vi9rs9K>yD;@$1Jo?w&~C6^Nk
z&bmb&Yc`m(F2Q?~AwDCqpD6Cy`Fp|Qx(A7~?pMD`>yqNWSF_DLMT>Gg*KTt*i;-V>
z-Z_Z9BZ;NwAaar585fb_OH2ZNHhU2!!O=a)IKXJF!)8ChXpM1jbf4!Z2vc<IlkbWi
zUvc-Wf;fX_B%j}$E)T;c9$pl6|C<zxI3GHlqNDZ`-1`_VdB;MO^P*32zTH*}?I-xT
z!re2bA@3=?cX|<H9w~;-aDV*fimx#78#I7cMM0*$2=_kkYLg_+pl2ag32c&&p5&~3
ze8q5y;Zh(BlEhiPX^2^fO$wXjKcRE8;6A(v&$=`HA75^E`}Oznb&O})aF$KBk&%&+
ck<niL0fR*>r<j$uF#rGn07*qoM6N<$g4~F1cK`qY

literal 0
HcmV?d00001

diff --git a/icons/object_star_o.png b/icons/object_star_o.png
new file mode 100644
index 0000000000000000000000000000000000000000..105f1a09e5dc4b6cc53d952d6076afdffa2136ea
GIT binary patch
literal 602
zcmeAS@N?(olHy`uVBq!ia0vp^1`G_03>-{AR#k4&Ng%~o9OUlAu<o49OCX1{z$3Dl
zfq`2Xgc%uT&5-~KvX^-Jy0YJ3<K)(4{QvY^2vA6}#5JPCIX^cyHLrxhxhOTUBsE2$
zJhLQ2!QIn0AiR-J9BA-yPZ!6Kid%1|SqCv23a~DpW*u?r!(abYqt1>aJB4E`oUU+P
z-*WA*--WMs%B~tqrv!QJ-Qi`qxWY_EKzHNYkbAv3EG<coN>_x;a9%nma4(x1n~rH$
zr+B2xg0^q6lIa;|PUw7<=t#aKyW&to$3(|z`78gw6KLs_l1%@Z(k^j2-8!Y+&4Fd|
z1HMpMsWOefB0D#2l~tN?q<qDz3mcqwE<Z6<_q~9ihWS=inN?O>;#LaxZ=G1#K96}y
z#-ye9BqL3Y8keQd|2ju<=JcoqCJWX`N#1O<3VA!h;c~ggQ}(13rk^{wR3<q5=a{mo
zMp@WHq47fd#wrO`As~wdK~x3gPrs*c1ysny`K4H5cYB~hV;rYO>Ss5#3zNP7-n1|A
zSkTF5%D>pbZXJ^<Q>y5R6KP7`KdU8xmR*^%cls|6KGj>vE6d%deVrhA^W2G9+MoGk
zIW^4JyUKXkT=urO@@9heYzD9G>8$NV{Q~zFrnSf1WDz(hx5xRVJIA|#&Cc3ud7PO0
zcs5mTa^}6J=>1n);JDld=Lu);H_n>4HY7`g*U0F~5v3$&-ySK+(~2U#E;%dQgss1b
i30{944vLf2uh^n&-#Z9AY4`?=cLq;aKbLh*2~7ag)9Gmd

literal 0
HcmV?d00001

diff --git a/icons/sectorview_f6_systeminfo.png b/icons/sectorview_f6_systeminfo.png
new file mode 100644
index 0000000000000000000000000000000000000000..c789f96fa1c13323bb6b1835cbed10791470d68f
GIT binary patch
literal 490
zcmV<G0Tup<P)<h;3K|Lk000e1NJLTq0015U000&U0ssI2SOL4300001b5ch_0Itp)
z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT1_uid2Q`PEb^rhX8FWQhbVF}#
zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b0ar;xK~y-)&6K@P!cZ87AKF4&3)GaM
z$V40@Y=&DfanT#`77TG9y$A2Z=%|yElLLzkBqjvYAdL+YYz1_vsWlLq3XQ(g`F-=|
z`Mz^9R?8wC38bbJt)*~ua@>q=oL?pYHQ~+1ac}_x-NaB)gr&ZVE19V;+YejaTgmMM
zsw|dEO1Y${vIy8XU3xY{n_-upjS~+fGoQ)*HXwK-t#hq)Zq&y+K+k83npD&zJ)Z>}
zTn|6E;^7DJW30vTbFqD~F6*V57R<rnDKM7{<GllMJn&1qfcyF$90&#m`U|5VxJ=GK
zkGBr>A>}-?2DPucd9#T(3wKS7083cLZy<~Pkf=jclq!`5^#=7S6&2-2>O@{Z?*0n!
z_m6(bdWBY)u4^s4JOT6VonYELYyQPUPpU9)RMCG_SO-YJWb>()DoT9AKvFED)v`j|
g|FT13S*Mu#6B_++3Oe-M@c;k-07*qoM6N<$f=gk=eE<Le

literal 0
HcmV?d00001

diff --git a/icons/sysview_accel_f1.png b/icons/sysview_accel_f1.png
new file mode 100644
index 0000000000000000000000000000000000000000..0213f250e395954dfa747cf12d8bfe875b87a4b4
GIT binary patch
literal 254
zcmeAS@N?(olHy`uVBq!ia0vp^!ayv@!3HGlXG}j1q!^2X+?^QKos)S9<Zu>vL>4nJ
zh#UrDU&ajuKiYwU>?NMQuIxA1czHDC`;Qkr1qw-)xJHyX=jZ08=9Mrw7o{eaq^2m8
zXO?6rxO@5rgg5eu0~KX^x;TbdoZfqdk+;Eshvh(1)5C_yRm?{9Qw^5zu+L^L*zhl%
zSO3AKZ<F($YUNkYTB9~UvW)YLeZ=0?OO%iLrlpCzSa-7F!r1@;JqE^=X0{U!g@n4v
vzn;N<P=d)(f@`M)v#+7ptRx+gf2MYV%sL)Y(=3+)t!40Z^>bP0l+XkKytGg)

literal 0
HcmV?d00001

diff --git a/icons/sysview_accel_f1_on.png b/icons/sysview_accel_f1_on.png
new file mode 100644
index 0000000000000000000000000000000000000000..6fdd6fb61f9702500febb2b8cfd5daaeaec0c81e
GIT binary patch
literal 231
zcmeAS@N?(olHy`uVBq!ia0vp^!ayv@!3HGlXG}j1q!^2X+?^QKos)S9<Zu>vL>4nJ
zh#UrDU&ajuKiYwU>?NMQuIxA1czH~$eOCO^018Q#xJHyX=jZ08=9Mrw7o{eaq^2m8
zXO?6rxO@5rgg5eu0~LjMx;TbdoIZP{k@J87hs(vyzxkI7|Mk0YjM?)Odj-o>mWH+}
zlN)k6N+F^1j=4{o;F5MJx6?9#`*l<98s?}QTsEIKZJgPgTm0sn%nR{i#fBZ<xT_eq
YPr0-9-}F~{K=T<qUHx3vIVCg!00v%7b^rhX

literal 0
HcmV?d00001

diff --git a/icons/sysview_accel_f2.png b/icons/sysview_accel_f2.png
new file mode 100644
index 0000000000000000000000000000000000000000..fec8f9b6e00e644a12a38bb4f9e4800e6d7020f2
GIT binary patch
literal 268
zcmeAS@N?(olHy`uVBq!ia0vp^!ayv@!3HGlXG}j1q!^2X+?^QKos)S9<Zu>vL>4nJ
zh#UrDU&ajuKiYwU>?NMQuIxA1czHC9PD$uZ018Q#xJHyX=jZ08=9Mrw7o{eaq^2m8
zXO?6rxO@5rgg5eu0~J+zx;TbdoZfmRSFk~WhwVWoQ;x#aYX=tJS4t4cQQ$RcXs&Je
zy>s_>#%<QqN|r2-VRh4fCm1#(;(yZ5GXmQbzFjd_UgSUH^~(7#8Fw3RZhFMj;Zb_x
zf|=!oUPYxVyKfn|gg7c4YmEvJ-|XajhI!)*@gzgNC5_AeOX;7t^78nr=NSle1cRrm
KpUXO@geCwx8eAFx

literal 0
HcmV?d00001

diff --git a/icons/sysview_accel_f2_on.png b/icons/sysview_accel_f2_on.png
new file mode 100644
index 0000000000000000000000000000000000000000..e63144f324f1e5c8c3778a8aad18890490e20050
GIT binary patch
literal 247
zcmeAS@N?(olHy`uVBq!ia0vp^!ayv@!3HGlXG}j1q!^2X+?^QKos)S9<Zu>vL>4nJ
zh#UrDU&ajuKiYwU>?NMQuIxA1czH~v^X|T02o#bmag8W(&d<$F%`0JWE=o--Nlj5G
z&n(GMaQE~L2yf&Q2P#VOba4!^IDK}CBUgg~4~zM~|AjBRk4;wCbv0^{#f8&{_*6A{
z8ALa(m{6W7xbWJo+_Z-cDk>9aPM^70`}XwMZxP$3#+s?VmQ|lK&GO-PbM4uYH_cAp
pyP3IxSxLKisi2_4pKshzjCyZN6<Jq^<pAwt@O1TaS?83{1OT{HQ+faZ

literal 0
HcmV?d00001

diff --git a/icons/sysview_accel_f3.png b/icons/sysview_accel_f3.png
new file mode 100644
index 0000000000000000000000000000000000000000..abdb3603e988f7f9e13cec848a1a65cca96e22f0
GIT binary patch
literal 280
zcmeAS@N?(olHy`uVBq!ia0vp^B0wz2!3HGny7cS=QjEnx?oJHr&dIz4aySb-B8!2F
z4g;|-<A#DC?La~H5>H=O_8V-xJX$;oAqhW#LXst}5hc#~xw)x%B@E6*sfi`2DGKG8
zB^e6tp1uL$jeO!jMXjDLjv*GO_f9!2*r34U60Y6Aw`FO`gA4Iq2ZA;*zjVmwVK?Xd
zar5%-H?tkmj9OG>GQBHiRnB5$P>wsd{m>3Ki}yZx^8!M>XBK{*96I&W+$}%(-!F;v
z-nis$blRo7g&hj-f66LOF6Gpf6z$?j^)Ay~<K45C=j+zOdsBrTFUt;|F7dxE*N>k$
WSZ=o9|3yFtF?hQAxvX<aXaWEXG-6c%

literal 0
HcmV?d00001

diff --git a/icons/sysview_accel_f3_on.png b/icons/sysview_accel_f3_on.png
new file mode 100644
index 0000000000000000000000000000000000000000..f55ca40b60e954855ad5372dfddbeed010f1741e
GIT binary patch
literal 241
zcmeAS@N?(olHy`uVBq!ia0vp^B0wz2!3HGny7cS=QjEnx?oJHr&dIz4aySb-B8!2F
z4g;|-<A#DC?La~H5>H=O_8V-xJVpu=rcZGK3Q3l@MwB?`=jNv7l`uFLr6!i7rYMwW
zmSiZnd-?{1H}Z)C6~%eFIEGl9K0C#ci@}hCdGg=?kH5IyyzIokI%wmCMkhT<uc<+k
z3{EsBp5yX7boX-1B31?_1!L~wqw5W<r<O!Vct#)cxh3Xy$vV&CSKC_6{&x?<yFN`T
h&~@F}biF=aUvX0ILX~wJEPysKc)I$ztaD0e0s!{yPzL}2

literal 0
HcmV?d00001

diff --git a/icons/sysview_accel_r1.png b/icons/sysview_accel_r1.png
new file mode 100644
index 0000000000000000000000000000000000000000..c4c954bb8957863468bec8fb5d7ebd52af9f12f2
GIT binary patch
literal 259
zcmeAS@N?(olHy`uVBq!ia0vp^!ayv@!3HGlXG}j1q!^2X+?^QKos)S9<Zu>vL>4nJ
zh#UrDU&ajuKiYwU>?NMQuIxA1czHD0=d6CU87L%K;u=xnoS&PUnpeW$T$GwvlA5AW
zo>`Ki;O^-g5Z=fq4pda&>Eak-ar)>LM!^FL9IOG`SZ*YQEjoCv&ZQtQhB<SCYJlUO
zx9k<iW#zY(EL(nubx)^zi>%l)c^+n`eVgPDHyk+g@8p&X0-GIT&v2P;lw8s2DXOrj
z^y&=8-P{|R4rfI;gl2AC@N16Af|nPTo+^)j%$mxq;}O<iXb7~M!PC{xWt~$(696Tk
BSS$bl

literal 0
HcmV?d00001

diff --git a/icons/sysview_accel_r1_on.png b/icons/sysview_accel_r1_on.png
new file mode 100644
index 0000000000000000000000000000000000000000..86828fa3c2d72b6a9424d0d40a210c3cabf2ff7a
GIT binary patch
literal 231
zcmeAS@N?(olHy`uVBq!ia0vp^!ayv@!3HGlXG}j1q!^2X+?^QKos)S9<Zu>vL>4nJ
zh#UrDU&ajuKiYwU>?NMQuIxA1czH}z4lek97bqlI;u=xnoS&PUnpeW$T$GwvlA5AW
zo>`Ki;O^-g5Z=fq4pbE4>Eak-ar*2PYrX>p9Lxc`zuU{qIWc#a%g^gUk0fms9RemL
zoMKKsmoSra(qAQC6(tsj#YJlz_HPinbu8<G*tMo}<!SS0*3SNBt?kJDt#R*%TjF1s
YnmF&QmAiSl8)!a*r>mdKI;Vst0H%meyZ`_I

literal 0
HcmV?d00001

diff --git a/icons/sysview_accel_r2.png b/icons/sysview_accel_r2.png
new file mode 100644
index 0000000000000000000000000000000000000000..5c3a675bb6b86adefacd95e6f0e3762482f759b6
GIT binary patch
literal 271
zcmeAS@N?(olHy`uVBq!ia0vp^!ayv@!3HGlXG}j1q!^2X+?^QKos)S9<Zu>vL>4nJ
zh#UrDU&ajuKiYwU>?NMQuIxA1czHDRH?S6S0EHw=Tq8=H^K)}k^GX<;i&7IyQd1Pl
zGfOfQ+&z5*!W;R-fr@H8T^vI!PVc?)P_RLPgDoJ7>5f8pQuDdLG6&r@@}@LPDR3I-
z*A+cKY;7NNY2Lc3upd!{-5U-a`6Yh1A)s&3w-6p?r*n~SSI!U@+9<f9+q2!|MPuZ$
z*@k&ME++a?LK%{mY8&J-c5apE75rejB`zRVoj)V0BroTc?_blrcuy~nhj*GE1Kq*k
M>FVdQ&MBb@08Q3gG5`Po

literal 0
HcmV?d00001

diff --git a/icons/sysview_accel_r2_on.png b/icons/sysview_accel_r2_on.png
new file mode 100644
index 0000000000000000000000000000000000000000..a5f39fad96ff7ed8b9f5a159270a42d897bf55ed
GIT binary patch
literal 246
zcmeAS@N?(olHy`uVBq!ia0vp^!ayv@!3HGlXG}j1q!^2X+?^QKos)S9<Zu>vL>4nJ
zh#UrDU&ajuKiYwU>?NMQuIxA1czH~CzKF)F1BE0@Tq8=H^K)}k^GX<;i&7IyQd1Pl
zGfOfQ+&z5*!W;R-fr^qnT^vI!PM@9P$aO%0heiGG|HqHIx2Z4m$O_`P#H!zH?IGej
zDdUuD^0`yTBxXdOVz{98PS4?duHfv2O0$l3EZww~rA23|>(cF8r`8J2wT=9J=2rgZ
ozjN0<?U|agIVSPjZT%N)Ck#pzEhVm(1Fd85boFyt=akR{0H@nj?EnA(

literal 0
HcmV?d00001

diff --git a/icons/sysview_accel_r3.png b/icons/sysview_accel_r3.png
new file mode 100644
index 0000000000000000000000000000000000000000..ef10848e3e3a11978f31a0bb06721c89f377be80
GIT binary patch
literal 429
zcmV;e0aE^nP)<h;3K|Lk000e1NJLTq000^Q000pP1^@s6!;QQR00001b5ch_0Itp)
z=>Px#32;bRa{vGo!vFvjO#!fQ_=f-h00(qQO+^RT1`P}-91qnJ9smFU8FWQhbVF}#
zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b0UAj}K~y-)wbikTgFqAo(3?jF!75=>
zY>Jhn_iOybfUTd9ZL(Rirt}NkBIA;v#i;1WWGjP+8pU|4TL_28%sglI&c34-DJ9&q
z3m$NUVTiHWZnl`$feXLj^mBp`0!k^oD)TDh|N1z59Z(bnULC3;$M0h=WU*Xe|GDq%
zjZm7VaL+FCJnw}#j?>v2q4HefI39!mXzh(qmSymLe=sumjHHyf-ma4(06<D9BuO$U
z^0*9LiNXk$WsQf32)eFMjuB%FtG5-Vo{Th2gJBrGKCWYJy>+7X`?}Xub*!y7WoGZ}
zd%1!<&wF~+tG6)<qt0I4-UI-QF&G~POw$}R*|Xlby-SQS2F^KxzW_pr#~UM}QNN9U
XGt|4(gdyWR00000NkvXXu0mjfKQ^#-

literal 0
HcmV?d00001

diff --git a/icons/sysview_accel_r3_on.png b/icons/sysview_accel_r3_on.png
new file mode 100644
index 0000000000000000000000000000000000000000..976fecffc6621900bdde80c8c2cb37c75a8a5147
GIT binary patch
literal 396
zcmeAS@N?(olHy`uVBq!ia0vp^Qa~)o!3HD`_wL~ZQjEnx?oJHr&dIz4aySb-B8wRq
zL=FS7FXM)SAMHRv_7YEDSN0ohygbGxlfw$@fI^Zbt`Q~9`MJ5Nc_j?aMX8A;sVNHO
znI#zt?w-B@;f;LaKt(@2T^vI!PVc>H-``{)!S><#Qd9HIGgfJLZoe2PF8Ftu+7qGu
zhmXFEIjwWwN!<5g=_HLy+)rNEUUm-p<o$oi(wymrQi4tiUksggc?z~Ewsqt%O}@#p
zI;C}?u#Ixosyzy(UmPZtUV6FQ=DefJ{%_k;cK3XbuYM<$%)^|(aPHnT2ad*#QswhH
zukHF*_VwD*<TQ<?Rmyw6cXSy1pHuDnO7f>$T;F!>LYc!7pUYS1c>iG+GcfM6cYhxd
z5GvXgd@-{?xcF<BhZm<ofXH9N@Nzk}v||r{KM-Af+&FRV(XVB$yL<&*FFu&AWxL<~
oTGSl<bPml0TaP8}l;GLLY$SH%%-Z*NLBY%5>FVdQ&MBb@0GEH9YXATM

literal 0
HcmV?d00001

diff --git a/icons/timeaccel0.png b/icons/timeaccel0.png
new file mode 100644
index 0000000000000000000000000000000000000000..60be13bef53c794f03df4cacb64c5af08b98814a
GIT binary patch
literal 237
zcmeAS@N?(olHy`uVBq!ia0vp^Vn8g!!2~2PxbLh2QjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wdn(VP2KHK{DMyA;}Wgh!W@g+}zZ>5(ej@)Wnk16ovB4
zk_-iRPv3y>Mm}+%q9{)n#}JFt$$$R;w`Wdm=xj_nuqo%`rr-SUCa;YTbet%)&f)R(
zD_0NP)b-V6Im+2`bSL-o4#DM3?ryyjZEg#epPRN!Y>g(@bPF+M2?>cFo}LX_M^(>R
cXq@6^sQ)j|@V5Sx8qgL7Pgg&ebxsLQ0OdqXN&o-=

literal 0
HcmV?d00001

diff --git a/icons/timeaccel0_on.png b/icons/timeaccel0_on.png
new file mode 100644
index 0000000000000000000000000000000000000000..82609ea0fafc9ec462aec222f2c1bb4c73428490
GIT binary patch
literal 213
zcmeAS@N?(olHy`uVBq!ia0vp^Vn8g!!2~2PxbLh2QjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wdn(ZbO4*e$EGhLXst}5hc#~xw)x%B@E6*sfi`2DGKG8
zB^e6tp1uL$jeO!jMNXbBjv*GOlmGnxZ_k|C(Ak(|V9}G{_}JLU=+Mm*2V1=7G-x+9
zH+Krkn;o>wV(|@r{{R2~jmtD*9`W$-^c+^*%*;?FEc#!#xBV*6GzL#sKbLh*2~7ZX
C)IjP0

literal 0
HcmV?d00001

diff --git a/icons/timeaccel1.png b/icons/timeaccel1.png
new file mode 100644
index 0000000000000000000000000000000000000000..fc1257864672b61b4ba061d62a8aa2423b6ca118
GIT binary patch
literal 261
zcmeAS@N?(olHy`uVBq!ia0vp^Vn8g!!2~2PxbLh2QjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wdn(VO};4qu6|)kYtH#M2T~LZf<H`34?P{YGO%hib8p2
zNrr;Er*A-bBcC`>QIV&MV~EA+<Ujxa+cT#&bT%d(*p%~e({KKFlh?)vItuRA&U%$~
zF4n?*rTewD(e6t$e;jJ^2<Bi>H(VyrC)l)1Ky>8~U&T`$3l=CSGb?}UJ+OZM20@mu
z!Ujzee}D8S1ljv3cU<qk%<<@}v4MdBU)LulhSEZPzE~IL2B7T>p00i_>zopr0NEK-
AT>t<8

literal 0
HcmV?d00001

diff --git a/icons/timeaccel1_on.png b/icons/timeaccel1_on.png
new file mode 100644
index 0000000000000000000000000000000000000000..d48f18886a574783bd442b857a6664fb3e5906f5
GIT binary patch
literal 228
zcmeAS@N?(olHy`uVBq!ia0vp^Vn8g!!2~2PxbLh2QjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wdn(ZbNzA`E@gZLXst}5hc#~xw)x%B@E6*sfi`2DGKG8
zB^e6tp1uL$jeO!jMS-3!jv*GO?_M_KVo=~=c6cm&`~QAbzI9p5j_jMg`WA_)%$|Pl
zO^h-_%CfA2sg)vvB1K9rOBF<xs(HC8ojWyI^Um&q_#=PClj^-+)K2=5dXrtfJUYF*
S@bY?~;S8RxelF{r5}E*E6GZm_

literal 0
HcmV?d00001

diff --git a/icons/timeaccel2.png b/icons/timeaccel2.png
new file mode 100644
index 0000000000000000000000000000000000000000..6ccb2e6e68ca84a8835e7f50ebb9a184450e214a
GIT binary patch
literal 296
zcmeAS@N?(olHy`uVBq!ia0vp^Vn8g!!2~2PxbLh2QjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wdn(VLm36qElOeLXst}5hc#~xw)x%B@E6*sfi`2DGKG8
zB^e6tp1uL$jeO!jMN>Up978NlC;$2X-<~<Op|dgRz^0s!n||}Zo4htY&{42kJL^@}
zxmb(#Ztpej*Vab2ui@?W&WRAn=i-(VUl+ZdZ*q1@f=g6S!mOs3Vw03bFN;j-<>2CS
z`phlt$ou1~umO`VbJ*V>Jqkhge##x!&mTB&Kt@L5&%?v)DT<7aj48YsF(wKc3LQs9
kGo(JeXlFR)7#IiicCm*|r8E0MpaU5^UHx3vIVCg!0HaZ0p#T5?

literal 0
HcmV?d00001

diff --git a/icons/timeaccel2_on.png b/icons/timeaccel2_on.png
new file mode 100644
index 0000000000000000000000000000000000000000..efa407de3bc21bd17df3ae6192b58c616798fcbb
GIT binary patch
literal 241
zcmeAS@N?(olHy`uVBq!ia0vp^Vn8g!!2~2PxbLh2QjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wdn(ZbOEK>BYG~A;}Wgh!W@g+}zZ>5(ej@)Wnk16ovB4
zk_-iRPv3y>Mm}+%qBu_%#}JFtcPBe?F(~pdpS9cg_kZ*z_Qc3rT2mezy`ywaA=I%b
zqq1#|F&D$TeXCq&7+pWRX3f!mQy(36daJ1>mK8m5vTm;HwmJ5#R@`6Ru9Sad|5me>
h<G?)i&S<;e+!H1~Ybtvd9SgLH!PC{xWt~$(69B^bP<H?T

literal 0
HcmV?d00001

diff --git a/icons/timeaccel3.png b/icons/timeaccel3.png
new file mode 100644
index 0000000000000000000000000000000000000000..2ac3f3a1a32ddf822f6b851b447ae66ef4a3c5ea
GIT binary patch
literal 308
zcmeAS@N?(olHy`uVBq!ia0vp^Vn8g!!2~2PxbLh2QjEnx?oJHr&dIz4a@dl*-CY>|
zgW!U_%O?XxI14-?iy0WWg+Z8+Vb&Z8pdfpRr>`sf4K_wb3qz-41s8!rk|nMYCC>S|
zxv6<249-QVi6yBi3gww484B*6z5(HleBwYw{hlt4Ar_~T|NQ@N&z#!O*_d=-Q_jat
zzxm%yUK=0iD0o-YwAA#u-kigy4!q>@Sy=k|T4Jh%q{Ku4u?XLTPsN0qgr{pRIm!_h
z?9mtE6YOE8q@lr~8#7bmivKLZh_8=LK>W#$PP-abA4^GKJ#(<}puAm8!)I0&R)tlD
qk`avy8WeVEKL9d!Fg#wMq0ivv>cJgzZDtJ6X$+pOelF{r5}E)JX=WDy

literal 0
HcmV?d00001

diff --git a/icons/timeaccel3_on.png b/icons/timeaccel3_on.png
new file mode 100644
index 0000000000000000000000000000000000000000..bea687b74364d3cb720b3c87d1dfa062d4781652
GIT binary patch
literal 238
zcmeAS@N?(olHy`uVBq!ia0vp^Vn8g!!2~2PxbLh2QjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wdn(ZUc#~2e&|)BuiW)N}Tg^b5rw57@Uhz6H8K46v{J8
zG8EiBeFMT9`NV;WqCH(4Lo80;o$ScPpvc4A>!0)g|FjALv1w)-mhNDGF5)GzM9ZU3
zu6T>(0)_|gGADdED>o~WN90MDNUEmBl%>l&bW>fo{Hg1Dr<Y}ZsaAZq?BVdEm-`$W
cqwRh(Dfn;Pvex%iHqaUdPgg&ebxsLQ08sTzDF6Tf

literal 0
HcmV?d00001

diff --git a/icons/timeaccel4.png b/icons/timeaccel4.png
new file mode 100644
index 0000000000000000000000000000000000000000..fbff55047ea1ffc44c5ecb83081cb13bd72df087
GIT binary patch
literal 518
zcmV+h0{Q)kP)<h;3K|Lk000e1NJLTq000{R000sQ0ssI2CAl)n00001b5ch_0Itp)
z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT1`P@?2`R)JfB*mh8FWQhbVF}#
zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b0dq-2K~y-)wUoh5!axv)XSWRpl9m#J
z0w(oL4-`2P55yB!;dO{_fe)a8V;=!|0xXH7Bm#P{4RkL+?H)+l^+1BAEeL{h_zycf
z-%MsEgBWA*R~x(xEYl)3`T2;BA$p!iDYa+zWIWk$`SxS`?fvc7V2p7fH^w-XN|mq6
zN?s8Jp|AJXP_BMkpU>x;e~r6_l$4tH&46akGl3k8G1qlRuOsKvQB_qC1;=ryOM}GD
zQ^zn200^j*l(Ly@xOU(7r_-tHx~i%I0`tS<_iUM_X#h|vmclN^nEtHGm$EF&>9aK6
ze-4_a_4Hn(%lG|mryGyQa~C;$&~SG^7mEeQaiS>VLpNp{biE0&SnOb8tyYWd=J!^e
z3WpE^fYoY+PgPwak?=fkxm@Ct;4sFOn+m1WGA$e#9M&B;gi!ISsN@xUW)qw6+iD4G
zr13moPz%*+l@Kx+PXGY59$Lp=3PQ-{QvAa;gClHc48s@q3xHnQSo{%@0ssI207*qo
IM6N<$f<M>On*aa+

literal 0
HcmV?d00001

diff --git a/icons/timeaccel4_on.png b/icons/timeaccel4_on.png
new file mode 100644
index 0000000000000000000000000000000000000000..a4147bc81d1728f972f912c49643eedc4038da90
GIT binary patch
literal 353
zcmeAS@N?(olHy`uVBq!ia0vp^(m*W4!2~2#cN(1nQjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x37{Z*iKnkC`wcc;E(3<SPOEkSg(OQ{BTAg}b8}PkN*J7rQWHy3QxwWG
zOEMJPJ$(bh8~Mb6iq3huIEGl9UVFunugO4w^#b2VuCG!RVmlN)r@!&^R}>7}=Bpw&
z(XzRxTaAq|Q~S?R9fjJNK~@efZ|+|Sy?ZbEP2nGbZ5?J&nTyXJzw5Z9QDCFZbg^Ew
z^^6L>mn&lh6`9Ue)^z=xXnahf_WAWqDxMjphqD91RgE5M|61Up!Y!%rYr)l}T*7lR
zinE)JmTsvovHmWhW3@l|#=6>bRu58YEPj7?>F$1RdcOF1>=m!%{>yT?`k!CzF*yHD
ulUvhh<NqZV8_pUY>sfwNt+4RXKE}f)L2r&M`Sb<oGX_srKbLh*2~7ae5QQoL

literal 0
HcmV?d00001

diff --git a/icons/zoom_in_f7.png b/icons/zoom_in_f7.png
new file mode 100644
index 0000000000000000000000000000000000000000..3f9618b016c62e1d8dc73c29fb88a7f1747982c0
GIT binary patch
literal 426
zcmV;b0agBqP)<h;3K|Lk000e1NJLTq0015U000&U0ssI2SOL4300001b5ch_0Itp)
z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT1_u%lDL4EC*Z=?k8FWQhbVF}#
zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b0T)R`K~y-)wN<+fgFp;@L^O<$CDMis
z=%Orul%6I7SQN+uq@j<*0#Q0Ppw1GhQ_-d{IgUF%2LX%Xe4i)V&(C<AF}ATCHnR<(
zeYMKZ^bb(-;6Z5PI6u=YcPwh#;;4NP*MR2c%W8c}2|!f?KvA-MVbH8UN2t@;(9et|
z-lA+$rj*`?_Eh%uoO;bueXZB4su2e5CBfalIxmeo&NN1(y9BaH)^}#pNGbKYjspHm
zGIYN>Nyau6oy2Pg%a_|wifhuST4<Was@9s%qA`w48ugqcgPn^SIb#V~ifeTJj9Pc}
z{acIBl^X^~+$W!b6$WF|3Jzn^AHubTFz${)tBF&ToL~iOX%t!}$yxXVTM-w)7qljk
U51Rp>x&QzG07*qoM6N<$f*G)<x&QzG

literal 0
HcmV?d00001

diff --git a/icons/zoom_out_f8.png b/icons/zoom_out_f8.png
new file mode 100644
index 0000000000000000000000000000000000000000..d341ccea891507ed4dd86f7ae40c910f3016ea81
GIT binary patch
literal 419
zcmV;U0bKrxP)<h;3K|Lk000e1NJLTq0015U000&U0ssI2SOL4300001b5ch_0Itp)
z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT1_u)xJ0^3+YybcN8FWQhbVF}#
zZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b0T4+<K~y-)wN$YUgD?<05Dm+u4I8+N
zSO6(KO$K;TpdTO&eIyo$(y;-Xq)tVf!r&NVuz_%*_{a9^efQ^pOU5w>aTpFP9jT~F
zX-}d2$+M-6<tn9B=~CBIWa4)q>i{jSwsCza2teBbK;5wXlDLmQXQ<WHrC%k>e(Q#<
ztMb2oCo2Lq*E(Nqha`41W$f{mQIJzreZy)Q0Z=iVDM09BPXq6=3_WgMma#%tV#+38
z@0R=hUB>qbzB0DmX!wk-GhvreHY#rrdb&|3cVX%sT#x)c#M0<0O=9HB6E!V8Iy^tu
z@OUOiGOCxsso@k-MxE<OS6rfQINvbipjoc#hBHA<aeUotOvui<2EOEfkzhad0FD3v
N002ovPDHLkV1nUVqRs#S

literal 0
HcmV?d00001

diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..377397f
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,19 @@
+## Process this file with automake to produce Makefile.in
+SUBDIRS = sbre/
+
+bin_PROGRAMS		= Lephisto3D
+Lephisto3D_SOURCES	= main.cpp gui_button.cpp gui.cpp gui_fixed.cpp gui_screen.cpp gui_label.cpp glfreetype.cpp \
+		objimport.cpp body.cpp space.cpp ship.cpp player.cpp gui_toggle_button.cpp gui_radio_button.cpp \
+		gui_radio_group.cpp rigid_body.cpp planet.cpp star.cpp frame.cpp gui_image_button.cpp gui_image.cpp \
+		gui_image_radio_button.cpp gui_multi_state_image_button.cpp ship_cpanel.cpp gui_widget.cpp sector_view.cpp \
+		mtrand.cpp world_view.cpp system_view.cpp star_system.cpp sector.cpp system_info_view.cpp generic_system_view.cpp \
+		gui_container.cpp date.cpp space_station.cpp space_station_view.cpp static_rigid_body.cpp ship_type.cpp
+Lephisto3D_LDADD = sbre/libsbre.a
+
+include_HEADERS = body.h frame.h generic_system_view.h glfreetype.h gui_button.h gui_container.h gui_events.h gui_fixed.h \
+		gui.h gui_image_button.h gui_image.h gui_image_radio_button.h gui_label.h gui_multi_state_image_button.h gui_radio_button.h \
+		gui_radio_group.h gui_screen.h gui_toggle_button.h gui_widget.h libs.h matrix4x4.h mtrand.h objimport.h l3d.h \
+		planet.h player.h rigid_body.h sector.h sector_view.h ship_cpanel.h ship.h space.h star.h star_system.h system_info_view.h \
+		system_view.h vector3.h view.h world_view.h date.h space_station.h space_station_view.h static_rigid_body.h gui_iselectable.h \
+		ship_type.h object.h
+
diff --git a/src/body.cpp b/src/body.cpp
new file mode 100644
index 0000000..77b338e
--- /dev/null
+++ b/src/body.cpp
@@ -0,0 +1,14 @@
+#include "libs.h"
+#include "body.h"
+#include "frame.h"
+
+Body::Body(void) {
+  m_frame = 0;
+  m_flags = 0;
+}
+
+/* f == NULL, then absolute position within system. */
+vector3d Body::GetPositionRelTo(const Frame* relTo) {
+  return m_frame->GetPosRelativeToOtherFrame(relTo) + GetPosition();
+}
+
diff --git a/src/body.h b/src/body.h
new file mode 100644
index 0000000..4f8ca3d
--- /dev/null
+++ b/src/body.h
@@ -0,0 +1,40 @@
+#pragma once
+#include <string>
+
+#include "vector3.h"
+#include "matrix4x4.h"
+#include "object.h"
+
+class Frame;
+class ObjMesh;
+
+class Body: public Object {
+public:
+  Body(void);
+  virtual ~Body(void) { };
+  virtual Object::Type GetType(void) { return Object::BODY; }
+  virtual void SetPosition(vector3d p) = 0;
+  virtual vector3d GetPosition(void) = 0; /* Within frame. */
+  vector3d GetPositionRelTo(const Frame*);
+  virtual void Render(const Frame* camFrame) = 0;
+  virtual void TransformToModelCoords(const Frame* camFrame) = 0;
+  virtual void TransformCameraTo(void) = 0;
+  virtual void SetFrame(Frame* f) { m_frame = f; }
+  Frame* GetFrame(void) { return m_frame; }
+  void SetLabel(const char* label) { m_label = label; }
+  std::string& GetLabel(void) { return m_label; }
+  unsigned int GetFlags(void) { return m_flags; }
+  /* Return true if we should apply damage. */
+  virtual bool OnCollision(Body* b) { return false; }
+
+  enum { FLAG_CAN_MOVE_FRAME = 1 };
+
+protected:
+  unsigned int m_flags;
+
+private:
+  /* Frame of reference. */
+  Frame* m_frame;
+  std::string m_label;
+};
+
diff --git a/src/date.cpp b/src/date.cpp
new file mode 100644
index 0000000..56707ad
--- /dev/null
+++ b/src/date.cpp
@@ -0,0 +1,77 @@
+#include "date.h"
+#include <stdlib.h>
+#include <math.h>
+#include "libs.h"
+
+/* Urgh... */
+static const char *i_am_a_little_teapot[365] =
+{ "Jan 1","Jan 2","Jan 3","Jan 4","Jan 5","Jan 6","Jan 7",
+  "Jan 8","Jan 9","Jan 10","Jan 11","Jan 12","Jan 13","Jan 14",
+  "Jan 15","Jan 16","Jan 17","Jan 18","Jan 19","Jan 20","Jan 21",
+  "Jan 22","Jan 23","Jan 24","Jan 25","Jan 26","Jan 27","Jan 28",
+  "Jan 29","Jan 30","Jan 31","Feb 1","Feb 2","Feb 3","Feb 4",
+  "Feb 5","Feb 6","Feb 7","Feb 8","Feb 9","Feb 10","Feb 11","Feb 12",
+  "Feb 13","Feb 14","Feb 15","Feb 16","Feb 17","Feb 18","Feb 19",
+  "Feb 20","Feb 21","Feb 22","Feb 23","Feb 24","Feb 25","Feb 26",
+  "Feb 27","Feb 28","Mar 1","Mar 2","Mar 3","Mar 4","Mar 5","Mar 6",
+  "Mar 7","Mar 8","Mar 9","Mar 10","Mar 11","Mar 12","Mar 13",
+  "Mar 14","Mar 15","Mar 16","Mar 17","Mar 18","Mar 19","Mar 20",
+  "Mar 21","Mar 22","Mar 23","Mar 24","Mar 25","Mar 26","Mar 27",
+  "Mar 28","Mar 29","Mar 30","Mar 31","Apr 1","Apr 2","Apr 3",
+  "Apr 4","Apr 5","Apr 6","Apr 7","Apr 8","Apr 9","Apr 10","Apr 11",
+  "Apr 12","Apr 13","Apr 14","Apr 15","Apr 16","Apr 17","Apr 18",
+  "Apr 19","Apr 20","Apr 21","Apr 22","Apr 23","Apr 24","Apr 25",
+  "Apr 26","Apr 27","Apr 28","Apr 29","Apr 30","May 1","May 2",
+  "May 3","May 4","May 5","May 6","May 7","May 8","May 9","May 10",
+  "May 11","May 12","May 13","May 14","May 15","May 16","May 17",
+  "May 18","May 19","May 20","May 21","May 22","May 23","May 24",
+  "May 25","May 26","May 27","May 28","May 29","May 30","May 31",
+  "Jun 1","Jun 2","Jun 3","Jun 4","Jun 5","Jun 6","Jun 7","Jun 8",
+  "Jun 9","Jun 10","Jun 11","Jun 12","Jun 13","Jun 14","Jun 15",
+  "Jun 16","Jun 17","Jun 18","Jun 19","Jun 20","Jun 21","Jun 22",
+  "Jun 23","Jun 24","Jun 25","Jun 26","Jun 27","Jun 28","Jun 29",
+  "Jun 30","Jul 1","Jul 2","Jul 3","Jul 4","Jul 5","Jul 6","Jul 7",
+  "Jul 8","Jul 9","Jul 10","Jul 11","Jul 12","Jul 13","Jul 14",
+  "Jul 15","Jul 16","Jul 17","Jul 18","Jul 19","Jul 20","Jul 21",
+  "Jul 22","Jul 23","Jul 24","Jul 25","Jul 26","Jul 27","Jul 28",
+  "Jul 29","Jul 30","Jul 31","Aug 1","Aug 2","Aug 3","Aug 4","Aug 5",
+  "Aug 6","Aug 7","Aug 8","Aug 9","Aug 10","Aug 11","Aug 12",
+  "Aug 13","Aug 14","Aug 15","Aug 16","Aug 17","Aug 18","Aug 19",
+  "Aug 20","Aug 21","Aug 22","Aug 23","Aug 24","Aug 25","Aug 26",
+  "Aug 27","Aug 28","Aug 29","Aug 30","Aug 31","Sep 1","Sep 2",
+  "Sep 3","Sep 4","Sep 5","Sep 6","Sep 7","Sep 8","Sep 9","Sep 10",
+  "Sep 11","Sep 12","Sep 13","Sep 14","Sep 15","Sep 16","Sep 17",
+  "Sep 18","Sep 19","Sep 20","Sep 21","Sep 22","Sep 23","Sep 24",
+  "Sep 25","Sep 26","Sep 27","Sep 28","Sep 29","Sep 30","Oct 1",
+  "Oct 2","Oct 3","Oct 4","Oct 5","Oct 6","Oct 7","Oct 8","Oct 9",
+  "Oct 10","Oct 11","Oct 12","Oct 13","Oct 14","Oct 15","Oct 16",
+  "Oct 17","Oct 18","Oct 19","Oct 20","Oct 21","Oct 22","Oct 23",
+  "Oct 24","Oct 25","Oct 26","Oct 27","Oct 28","Oct 29","Oct 30",
+  "Oct 31","Nov 1","Nov 2","Nov 3","Nov 4","Nov 5","Nov 6","Nov 7",
+  "Nov 8","Nov 9","Nov 10","Nov 11","Nov 12","Nov 13","Nov 14",
+  "Nov 15","Nov 16","Nov 17","Nov 18","Nov 19","Nov 20","Nov 21",
+  "Nov 22","Nov 23","Nov 24","Nov 25","Nov 26","Nov 27","Nov 28",
+  "Nov 29","Nov 30","Dec 1","Dec 2","Dec 3","Dec 4","Dec 5","Dec 6",
+  "Dec 7","Dec 8","Dec 9","Dec 10","Dec 11","Dec 12","Dec 13",
+  "Dec 14","Dec 15","Dec 16","Dec 17","Dec 18","Dec 19","Dec 20",
+  "Dec 21","Dec 22","Dec 23","Dec 24","Dec 25","Dec 26","Dec 27",
+  "Dec 28","Dec 29","Dec 30","Dec 31"
+};
+
+int mod(int a, int b) {
+  int r = a%b;
+  return r >= 0 ? r : r*b;
+}
+
+std::string date_format(double t) {
+  int year  = floor(t/(60*60*24*365));    year += 3200;
+  int day   = floor(t/(60*60*24));        day   = mod(day, 365);
+  int hour  = floor(t/(60*60));           hour  = mod(hour, 24);
+  int min   = floor(t/60);                min   = mod(min, 60);
+  int sec   = mod(t, 60);
+  char buf[128];
+  snprintf(buf, sizeof(buf), "%02d:%02d:%02d %s %d", hour, min, sec,
+            i_am_a_little_teapot[day], year);
+  return buf;
+}
+
diff --git a/src/date.h b/src/date.h
new file mode 100644
index 0000000..6d89cc3
--- /dev/null
+++ b/src/date.h
@@ -0,0 +1,5 @@
+#pragma once
+#include <string>
+
+std::string date_format(double time);
+
diff --git a/src/frame.cpp b/src/frame.cpp
new file mode 100644
index 0000000..67ccb18
--- /dev/null
+++ b/src/frame.cpp
@@ -0,0 +1,55 @@
+#include "frame.h"
+#include "space.h"
+
+Frame::Frame(void) {
+  Init(NULL, "", 0);
+}
+
+Frame::Frame(Frame* parent, const char* label) {
+  Init(parent, label, 0);
+}
+
+Frame::Frame(Frame* parent, const char* label, unsigned int flags) {
+  Init(parent, label, flags);
+}
+
+void Frame::RemoveChild(Frame* f) {
+  m_children.remove(f);
+}
+
+void Frame::Init(Frame* parent, const char* label, unsigned int flags) {
+  m_parent = parent;
+  m_flags = flags;
+  m_radius = 0;
+  m_dSpaceID = dHashSpaceCreate(0);
+  if(m_parent) {
+    m_parent->m_children.push_back(this);
+  }
+  if(label) m_label = label;
+}
+
+Frame::~Frame(void) {
+  dSpaceDestroy(m_dSpaceID);
+  for(std::list<Frame*>::iterator i = m_children.begin(); i != m_children.end(); ++i) delete *i;
+}
+
+vector3d Frame::GetFramePosRelativeToOther(const Frame* frame, const Frame* relTo) {
+  vector3d pos = vector3d(0,0,0);
+
+  const Frame* f = frame;
+  const Frame* root = Space::GetRootFrame();
+
+  while((f!=root) && (relTo !=f)) {
+    pos += f->m_pos;
+    f = f->m_parent;
+  }
+
+  /* Now pos is relative to root, or to desired frame. */
+  while(relTo != f) {
+    pos -= relTo->m_pos;
+    relTo = relTo->m_parent;
+  }
+
+  return pos;
+}
+
diff --git a/src/frame.h b/src/frame.h
new file mode 100644
index 0000000..555e65e
--- /dev/null
+++ b/src/frame.h
@@ -0,0 +1,47 @@
+#pragma once
+#include <string>
+#include <list>
+#include "libs.h"
+
+/* Frame of reference. */
+class Frame {
+public:
+  Frame(void);
+  Frame(Frame* parent, const char* label);
+  Frame(Frame* parent, const char* label, unsigned int flags);
+  ~Frame(void);
+
+  const char* GetLabel(void)              { return m_label.c_str(); }
+  void SetLabel(const char* label)        { m_label = label; }
+  void SetPosition(const vector3d &pos)   { m_pos = pos; }
+  void SetRadius(double radius)           { m_radius = radius; }
+  void RemoveChild(Frame* f);
+  void AddGeom(dGeomID g)                 { dSpaceAdd(m_dSpaceID, g); }
+  void RemoveGeom(dGeomID g)              { dSpaceRemove(m_dSpaceID, g); }
+  dSpaceID GetSpaceID(void)               { return m_dSpaceID; }
+
+  static vector3d GetFramePosRelativeToOther(const Frame* frame, const Frame* relTo);
+
+  vector3d GetPosRelativeToOtherFrame(const Frame* relTo) const {
+    return GetFramePosRelativeToOther(this, relTo);
+  }
+
+  bool IsLocalPosInFrame(const vector3d& pos) {
+    return (pos.Length() < m_radius);
+  }
+
+  /* If parent is null then frame position is absolute. */
+  Frame* m_parent;
+  std::list<Frame*> m_children;
+
+  enum { TEMP_VIEWING=1 };
+
+private:
+  void Init(Frame* parent, const char* label, unsigned int flags);
+  vector3d m_pos;
+  std::string m_label;
+  double m_radius;
+  int m_flags;
+  dSpaceID m_dSpaceID;
+};
+
diff --git a/src/generic_system_view.cpp b/src/generic_system_view.cpp
new file mode 100644
index 0000000..5021ccb
--- /dev/null
+++ b/src/generic_system_view.cpp
@@ -0,0 +1,51 @@
+#include "l3d.h"
+#include "generic_system_view.h"
+#include "sector_view.h"
+
+GenericSystemView::GenericSystemView(void) : View() {
+  px = py = pidx = 0xdeadbeef;
+  m_scannerLayout = new Gui::Fixed(140, 2, 360, 60);
+  m_scannerLayout->SetTransparency(true);
+
+  m_systemName = new Gui::Label("");
+  m_systemName->SetColor(1, 1, 0);
+  m_scannerLayout->Add(m_systemName, 40, 44);
+
+  m_distance = new Gui::Label("");
+  m_distance->SetColor(1, 0, 0);
+  m_scannerLayout->Add(m_distance, 150, 44);
+
+  m_starType = new Gui::Label("");
+  m_starType->SetColor(1, 0, 1);
+  m_scannerLayout->Add(m_starType, 22, 26);
+
+  m_shortDesc = new Gui::Label("");
+  m_shortDesc->SetColor(1, 0, 1);
+  m_scannerLayout->Add(m_shortDesc, 5, 8);
+}
+
+void GenericSystemView::Draw3D(void) {
+  StarSystem* s = L3D::GetSelectedSystem();
+
+  if(s && !s->IsSystem(px, py, pidx)) {
+    s->GetPos(&px, &py, &pidx);
+
+    m_systemName->SetText(s->rootBody->name);
+    m_distance->SetText("Dist. XX.XX light years.");
+    m_starType->SetText(s->rootBody->GetAstroDescription());
+    m_shortDesc->SetText("Short description of system");
+
+    onSelectedSystemChanged.emit(s);
+  }
+}
+
+void GenericSystemView::ShowAll(void) {
+  View::ShowAll();
+  if(m_scannerLayout) m_scannerLayout->ShowAll();
+}
+
+void GenericSystemView::HideAll(void) {
+  View::HideAll();
+  if(m_scannerLayout) m_scannerLayout->HideAll();
+}
+
diff --git a/src/generic_system_view.h b/src/generic_system_view.h
new file mode 100644
index 0000000..4d1c836
--- /dev/null
+++ b/src/generic_system_view.h
@@ -0,0 +1,25 @@
+#pragma once
+#include "libs.h"
+#include "gui.h"
+#include "view.h"
+#include "star_system.h"
+
+class SystemInfoScannerText;
+
+class GenericSystemView: public View {
+public:
+  GenericSystemView(void);
+  virtual void Draw3D(void);
+  virtual void ShowAll(void);
+  virtual void HideAll(void);
+  sigc::signal<void,StarSystem*> onSelectedSystemChanged;
+
+private:
+  Gui::Fixed* m_scannerLayout;
+  Gui::Label* m_systemName;
+  Gui::Label* m_distance;
+  Gui::Label* m_starType;
+  Gui::Label* m_shortDesc;
+  int px, py, pidx;
+};
+
diff --git a/src/glfreetype.cpp b/src/glfreetype.cpp
new file mode 100644
index 0000000..a5a1a3a
--- /dev/null
+++ b/src/glfreetype.cpp
@@ -0,0 +1,374 @@
+#include <SDL_opengl.h>
+#include <stdlib.h>
+#include <math.h>
+#include <stdio.h>
+#include <assert.h>
+#include <map>
+#include <ft2build.h>
+#include "glfreetype.h"
+
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+#include FT_OUTLINE_H
+
+#ifdef _WIN32
+typedef GLvoid(APIENTRY* _GLUfuncptr)(void);
+#endif
+
+FT_Library library;
+
+#include <vector>
+
+static GLUtesselator* tobj;
+
+static inline double fac(int n) {
+  double r = 1.0;
+
+  for(int i = 2; i < n; i++) {
+    r *= (double)i;
+  }
+  return r;
+}
+
+static inline double binomial_coeff(int n, int m) {
+  return fac(n)/(fac(m)*(fac(n-m)));
+}
+
+static void eval_bezier(GLdouble* out, double t, int n_points, double* points) {
+  std::vector<double> c(n_points);
+
+  for(int i = 0; i < n_points; i++) {
+    c[i] = pow(1.0f-t, n_points-(i+1)) * pow(t, i) *
+                binomial_coeff(n_points-1, i);
+  }
+
+  out[0] = out[1] = out[2] = 0;
+
+  for(int i = 0; i < n_points; i++) {
+    out[0] += points[3*i]   * c[i];
+    out[1] += points[3*i+1] * c[i];
+    out[2] += points[3*i+2] * c[i];
+  }
+}
+
+#define DIV 2048.0f
+
+bool GenContourPoints(int a_char, FT_Outline* a_outline, const int a_contour,
+                      int a_bezierIters, std::vector<double>* ao_points) {
+#define push_point(__p) { \
+  ao_points->push_back((__p)[0]); \
+  ao_points->push_back((__p)[1]); \
+  ao_points->push_back((__p)[2]); \
+}
+  
+  int cont = (a_contour-1 < 0 ? 0 : 1 + a_outline->contours[a_contour-1]);
+
+  double point_buf[256][3];
+  char point_type[256];
+  int pos = 0;
+
+  for(; cont <= a_outline->contours[a_contour]; cont++, pos++) {
+    point_type[pos] = a_outline->tags[cont];
+    point_buf[pos][0] = a_outline->points[cont].x/DIV;
+    point_buf[pos][1] = a_outline->points[cont].y/DIV;
+    point_buf[pos][2] = 0;
+  }
+  if(!point_type[pos-1]) {
+    /* 
+     * Need to duplicate first vertex if last
+     * section is a bezier.
+     */
+    point_type[pos] = 1;
+    point_buf[pos][0] = point_buf[0][0];
+    point_buf[pos][1] = point_buf[0][1];
+    point_buf[pos][2] = 0;
+    pos++;
+  }
+
+  int start = -1;
+  for(int k = 0; k < pos; k++) {
+    if(!(point_type[k] & 1)) continue;
+
+    if(start == -1) { start = k; continue; }
+
+    int len = 1+k-start;
+    /* Trace segment. */
+    if(len == 2) {
+      /* Staight line. */
+      push_point(point_buf[k-1]);
+      push_point(point_buf[k]);
+    } else {
+      /* Bezier. */
+      double b_in[3][3];
+      double v[3];
+
+      /*
+       * Truetype is all quadratic bezier,
+       * using average points between
+       * 'control points' as end points.
+       */
+
+      /* First bezier. */
+      b_in[0][0] = point_buf[start][0];
+      b_in[0][1] = point_buf[start][1];
+      b_in[0][2] = 0;
+
+      b_in[1][0] = point_buf[start+1][0];
+      b_in[1][1] = point_buf[start+1][1];
+      b_in[1][2] = 0;
+
+      if(len > 3) {
+        b_in[2][0] = 0.5 * (point_buf[start+1][0] + point_buf[start+2][0]);
+        b_in[2][1] = 0.5 * (point_buf[start+1][1] + point_buf[start+2][1]);
+        b_in[2][2] = 0;
+      } else {
+        b_in[2][0] = point_buf[start+2][0];
+        b_in[2][1] = point_buf[start+2][1];
+        b_in[2][2] = 0;
+      }
+
+      for(int l = 0; l <= a_bezierIters; l++) {
+        double t = (1.0/a_bezierIters)*l;
+        eval_bezier(v, t, 3, &b_in[0][0]);
+        v[2] = 0.0;
+        push_point(v);
+      }
+
+      /* Middle beziers. */
+      if(len > 4) {
+        for(int _p = 1; _p < len-3; _p++) {
+          b_in[0][0] = 0.5*(point_buf[start+_p][0]+point_buf[start+_p+1][0]);
+          b_in[0][1] = 0.5*(point_buf[start+_p][1]+point_buf[start+_p+1][1]);
+
+          b_in[0][2] = 0;
+
+          b_in[1][0] = point_buf[start+_p+1][0];
+          b_in[1][1] = point_buf[start+_p+1][1];
+          b_in[1][2] = 0;
+
+          b_in[2][0] = 0.5*(point_buf[start+_p+1][0] + point_buf[start+_p+2][0]);
+          b_in[2][1] = 0.5*(point_buf[start+_p+1][1] + point_buf[start+_p+2][1]);
+          b_in[2][2] = 0;
+
+          for(int l = 0; l <= a_bezierIters; l++) {
+            double t = (1.0/a_bezierIters)*l;
+            eval_bezier(v, t, 3, &b_in[0][0]);
+            v[2] = 0.0;
+            push_point(v);
+          }
+        }
+      }
+
+      /* End. */
+      if(len > 3) {
+        const int _p = start+len-3;
+        b_in[0][0] = 0.5 * (point_buf[_p][0] + point_buf[_p+1][0]);
+        b_in[0][1] = 0.5 * (point_buf[_p][1] + point_buf[_p+1][1]);
+        b_in[0][2] = 0;
+
+        b_in[1][0] = point_buf[_p+1][0];
+        b_in[1][1] = point_buf[_p+1][1];
+        b_in[1][2] = 0;
+
+        b_in[2][0] = point_buf[_p+2][0];
+        b_in[2][1] = point_buf[_p+2][1];
+        b_in[2][2] = 0;
+
+        for(int l = 0; l <= a_bezierIters; l++) {
+          double t = (1.0/a_bezierIters)*l;
+          eval_bezier(v, t, 3, &b_in[0][0]);
+          v[2] = 0.0;
+          push_point(v);
+        }
+      }
+    }
+    start = k;
+  }
+  return true;
+}
+
+#ifndef CALLBACK
+# ifdef WIN32
+#   define CALLBACK __attribute__ ((__stdcall__))
+# else
+#   define CALLBACK
+# endif
+#endif /* CALLBACK */
+
+struct TessData {
+  std::vector<double>* pts; /* Inputs, added by combine. */
+  int numvtx;
+
+  std::vector<Uint16> index; /* Output index list. */
+  GLenum lasttype;
+  int state; /* 0, no vertices, 1, 1 vertex, 2, 2 or more. 0x4 => clockwise. */
+
+  Uint16 vtx[2];
+};
+
+static Uint16 g_index[65536];
+
+void CALLBACK beginCallback(GLenum which, GLvoid* poly_data) {
+  TessData* pData = (TessData*)poly_data;
+  pData->lasttype = which;
+  pData->state = 0;
+}
+
+void CALLBACK errorCallback(GLenum errorCode) {
+  const GLubyte* estr;
+
+  estr = gluErrorString(errorCode);
+  fprintf(stderr, "Tesserlation Error: %s\n", estr);
+}
+
+void CALLBACK endCallback(void) {
+
+}
+
+void CALLBACK vertexCallback(GLvoid* vertex, GLvoid* poly_data) {
+  TessData* pData = (TessData*)poly_data;
+  Uint16 index = *(Uint16*)vertex;
+  switch(pData->lasttype) {
+  case GL_TRIANGLES:
+    pData->index.push_back(index);
+    break;
+  case GL_TRIANGLE_STRIP:
+    if((pData->state & 3) < 2)
+      pData->vtx[pData->state++] = index;
+    else {
+      pData->index.push_back(index);
+      if(pData->state & 0x4) {
+        pData->index.push_back(pData->vtx[1]);
+        pData->index.push_back(pData->vtx[0]);
+      } else {
+        pData->index.push_back(pData->vtx[0]);
+        pData->index.push_back(pData->vtx[1]);
+      }
+      pData->vtx[0] = pData->vtx[1];
+      pData->vtx[1] = index;
+      pData->state ^= 0x4;
+    }
+    break;
+  case GL_TRIANGLE_FAN:
+    if((pData->state & 3) < 2)
+      pData->vtx[pData->state++] = index;
+    else {
+      pData->index.push_back(index);
+      pData->index.push_back(pData->vtx[0]);
+      pData->index.push_back(pData->vtx[1]);
+      pData->vtx[1] = index;
+    }
+  }
+}
+
+void CALLBACK combineCallback(GLdouble coords[3],
+                              GLdouble* vertex_data[4],
+                              GLfloat weight[4], void** dataOut, void* poly_data) {
+  
+  TessData* pData = (TessData*)poly_data;
+  pData->pts->push_back(coords[0]);
+  pData->pts->push_back(coords[1]);
+  pData->pts->push_back(coords[2]);
+  *dataOut = (void*)&g_index[pData->numvtx++];
+}
+
+#define BEZIER_STEPS 2
+
+void FontFace::RenderGlyph(int chr) {
+  glEnableClientState(GL_VERTEX_ARRAY);
+  glDisableClientState(GL_NORMAL_ARRAY);
+
+  glfglyph_t* glyph = &m_glyphs[chr];
+  glVertexPointer(3, GL_FLOAT, 3*sizeof(float), glyph->varray);
+  glDrawElements(GL_TRIANGLES, glyph->numidx, GL_UNSIGNED_SHORT, glyph->iarray);
+}
+
+void FontFace::RenderString(const char* str) {
+  glPushMatrix();
+    for(unsigned int i = 0; i < strlen(str); i++) {
+      if(str[i] == '\n') {
+        glPopMatrix();
+        glTranslatef(0, -m_height, 0);
+        glPushMatrix();
+      } else {
+        glfglyph_t* glyph = &m_glyphs[str[i]];
+        if(glyph->numidx) RenderGlyph(str[i]);
+        glTranslatef(glyph->advx, 0, 0);
+      }
+    }
+  glPopMatrix();
+}
+
+FontFace::FontFace(const char* filename_ttf) {
+  FT_Face face;
+  if(0 != FT_New_Face(library, filename_ttf, 0, &face)) {
+    fprintf(stderr, "Error: Couldn't load '%s'\n", filename_ttf);
+  } else {
+    FT_Set_Char_Size(face, 50*64, 0, 100, 0);
+    for(int chr = 32; chr < 127; chr++) {
+      if(0 != FT_Load_Char(face, chr, FT_LOAD_NO_SCALE)) {
+        printf("Couldn't load glyph\n");
+        continue;
+      }
+
+      assert(face->glyph->format == FT_GLYPH_FORMAT_OUTLINE);
+      FT_Outline* outline = &face->glyph->outline;
+
+      std::vector<double> temppts;
+      std::vector<Uint16> indices;
+      std::vector<double> pts;
+      int nv = 0;
+
+      TessData tessdata;
+      tessdata.pts = &pts;
+
+      gluTessNormal(tobj, 0, 0, 1);
+      gluTessProperty(tobj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD);
+      gluTessBeginPolygon(tobj, &tessdata);
+        for(int contour = 0; contour < outline->n_contours; contour++) {
+          gluTessBeginContour(tobj);
+            temppts.clear();
+            GenContourPoints(chr, outline, contour, BEZIER_STEPS, &temppts);
+            for(size_t i = 0; i < temppts.size(); i++) pts.push_back(temppts[i]);
+            for(size_t i = 0; i < temppts.size(); i+=4, nv++)
+              gluTessVertex(tobj, &pts[nv*3], &g_index[nv]);
+          gluTessEndContour(tobj);
+        }
+        tessdata.numvtx = nv;
+      gluTessEndPolygon(tobj);
+
+      glfglyph_t _face;
+
+      nv = tessdata.numvtx;
+      _face.varray = (float*)malloc(nv*3*sizeof(float));
+      for(int i = 0; i < nv*3; i++) _face.varray[i] = (float)pts[i];
+
+      _face.numidx = (int)tessdata.index.size();
+      _face.iarray = (Uint16*)malloc(_face.numidx*sizeof(Uint16));
+      for(int i = 0; i < _face.numidx; i++) _face.iarray[i] = tessdata.index[i];
+
+      _face.advx = face->glyph->linearHoriAdvance/(float)(1<<16)/72.0f;
+      _face.advy = face->glyph->linearVertAdvance/(float)(1<<16)/72.0f;
+      //printf(%f,%f\n", _face.advx, _face.advy);
+      m_glyphs[chr] = _face;
+    }
+    m_height = m_glyphs['M'].advy;
+    m_width  = m_glyphs['M'].advx;
+  }
+}
+
+void GLFTInit(void) {
+  if(0 != FT_Init_FreeType(&library)) {
+    printf("Couldn't init freetype library.\n");
+    exit(0);
+  }
+
+  tobj = gluNewTess();
+  gluTessCallback(tobj, GLU_TESS_VERTEX_DATA,   (_GLUfuncptr) vertexCallback);
+  gluTessCallback(tobj, GLU_TESS_BEGIN_DATA,    (_GLUfuncptr) beginCallback);
+  gluTessCallback(tobj, GLU_TESS_END,           (_GLUfuncptr) endCallback);
+  gluTessCallback(tobj, GLU_TESS_ERROR,         (_GLUfuncptr) errorCallback);
+  gluTessCallback(tobj, GLU_TESS_COMBINE_DATA,  (_GLUfuncptr) combineCallback);
+
+  for(Uint16 i = 0; i < 65535; i++) g_index[i] = i;
+}
diff --git a/src/glfreetype.h b/src/glfreetype.h
new file mode 100644
index 0000000..7e1074a
--- /dev/null
+++ b/src/glfreetype.h
@@ -0,0 +1,28 @@
+#pragma once
+#include <map>
+#include <SDL_stdinc.h>
+
+class FontFace {
+public:
+  FontFace(const char* filename_ttf);
+  void RenderGlyph(int chr);
+  void RenderString(const char* str);
+
+  /* Of Ms. */
+  float GetHeight(void) { return m_height; }
+  float GetWidth(void)  { return m_width; }
+
+private:
+  float m_height;
+  float m_width;
+  
+  struct glfglyph_t {
+    float* varray;
+    Uint16* iarray;
+    int numidx;
+    float advx, advy;
+  };
+  std::map<int, glfglyph_t> m_glyphs;
+};
+void GLFTInit(void);
+
diff --git a/src/gui.cpp b/src/gui.cpp
new file mode 100644
index 0000000..51217ad
--- /dev/null
+++ b/src/gui.cpp
@@ -0,0 +1,46 @@
+#include "libs.h"
+#include "gui.h"
+
+namespace Gui {
+namespace RawEvents {
+  sigc::signal<void, SDL_MouseButtonEvent*> onMouseDown;
+  sigc::signal<void, SDL_MouseButtonEvent*> onMouseUp;
+  sigc::signal<void, SDL_KeyboardEvent*>    onKeyDown;
+  sigc::signal<void, SDL_KeyboardEvent*>    onKeyUp;
+}
+
+namespace Color {
+  const float bg[] = { .25, .37, .63 };
+  const float bgShadow[] = { .08, .12, .21 };
+}
+
+void HandleSDLEvent(SDL_Event* event) {
+  switch(event->type) {
+  case SDL_MOUSEBUTTONDOWN:
+    Screen::OnClick(&event->button);
+    RawEvents::onMouseDown.emit(&event->button);
+    break;
+  case SDL_MOUSEBUTTONUP:
+    Screen::OnClick(&event->button);
+    RawEvents::onMouseUp.emit(&event->button);
+    break;
+  case SDL_KEYDOWN:
+    Screen::OnKeyDown(&event->key.keysym);
+    RawEvents::onKeyDown.emit(&event->key);
+    break;
+  case SDL_KEYUP:
+    RawEvents::onKeyUp.emit(&event->key);
+    break;
+  }
+}
+
+void Draw(void) {
+  Screen::Draw();
+}
+
+void Init(int screen_width, int screen_height, int ui_width, int ui_height) {
+  Screen::Init(screen_width, screen_height, ui_width, ui_height);
+}
+
+}
+
diff --git a/src/gui_button.cpp b/src/gui_button.cpp
new file mode 100644
index 0000000..05c22a0
--- /dev/null
+++ b/src/gui_button.cpp
@@ -0,0 +1,102 @@
+#include "libs.h"
+#include "gui.h"
+
+#define BUTTON_SIZE 16
+
+namespace Gui {
+
+Button::Button(void) {
+  m_isPressed = false;
+  m_eventMask = EVENT_MOUSEDOWN | EVENT_MOUSEUP;
+  SetSize(BUTTON_SIZE, BUTTON_SIZE);
+}
+
+void Button::OnMouseDown(MouseButtonEvent* e) {
+  if(e->button == 1) {
+    m_isPressed = true;
+    onPress.emit();
+    /* Wait for mouse release, regardless of where on screen. */
+    _m_release = RawEvents::onMouseUp.connect(sigc::mem_fun(this, &Button::OnRawMouseUp));
+  }
+}
+
+void Button::OnMouseUp(MouseButtonEvent* e) {
+  if((e->button == 1) && m_isPressed) {
+    m_isPressed = false;
+    onClick.emit();
+  }
+}
+
+void Button::OnActivate(void) {
+  /* Activated by keyboard shortcut. */
+  m_isPressed = true;
+  onPress.emit();
+  _m_kbrelease = RawEvents::onKeyUp.connect(sigc::mem_fun(this, &Button::OnRawKeyUp));
+}
+
+void Button::OnRawKeyUp(SDL_KeyboardEvent* e) {
+  if(e->keysym.sym == m_shortcut.sym)  {
+    m_isPressed = false;
+    onRelease.emit();
+    onClick.emit();
+    _m_kbrelease.disconnect();
+  }
+}
+
+void Button::OnRawMouseUp(SDL_MouseButtonEvent* e) {
+  if(e->button == 1) {
+    m_isPressed = false;
+    _m_release.disconnect();
+    onRelease.emit();
+  }
+}
+
+void SolidButton::GetSizeRequested(float size[2]) {
+  size[0] = size[1] = BUTTON_SIZE;
+}
+
+void TransparentButton::GetSizeRequested(float size[2]) {
+  size[0] = size[1] = BUTTON_SIZE;
+}
+
+void SolidButton::Draw(void) {
+  glBegin(GL_QUADS);
+    glColor3f(.6, .6, .6);
+    glVertex2f(0, 0);
+    glVertex2f(15, 0);
+    glVertex2f(15, 15);
+    glVertex2f(0, 15);
+
+    glColor3fv(Color::bgShadow);
+    glVertex2f(2, 0);
+    glVertex2f(15, 0);
+    glVertex2f(15, 13);
+    glVertex2f(2, 13);
+
+    glColor3fv(Color::bg);
+    glVertex2f(2, 2);
+    glVertex2f(13, 2);
+    glVertex2f(13, 13);
+    glVertex2f(2, 13);
+  glEnd();
+}
+
+void TransparentButton::Draw(void) {
+  glColor3f(1, 1, 1);
+  glBegin(GL_LINE_LOOP);
+    glVertex2f(0, 0);
+    glVertex2f(15, 0);
+    glVertex2f(15, 15);
+    glVertex2f(0, 15);
+  glEnd();
+
+  glBegin(GL_LINE_LOOP);
+    glVertex2f(1, 1);
+    glVertex2f(14, 1);
+    glVertex2f(14, 14);
+    glVertex2f(1, 14);
+  glEnd();
+}
+
+}
+
diff --git a/src/gui_button.h b/src/gui_button.h
new file mode 100644
index 0000000..d8b3031
--- /dev/null
+++ b/src/gui_button.h
@@ -0,0 +1,47 @@
+#pragma once
+#include <string>
+#include "gui_widget.h"
+
+namespace Gui {
+  class Button: public Widget {
+  public:
+    Button(void);
+    virtual ~Button(void) {}
+    virtual void OnMouseDown(MouseButtonEvent* e);
+    virtual void OnMouseUp(MouseButtonEvent* e);
+    virtual void OnActivate(void);
+
+    /* 
+     * onClick only happens when press and release are both on widget,
+     * (release can be elsewhere).
+     */
+    sigc::signal<void> onPress;
+    sigc::signal<void> onRelease;
+    sigc::signal<void> onClick;
+    bool IsPressed(void) { return m_isPressed; }
+  private:
+    void OnRawMouseUp(SDL_MouseButtonEvent* e);
+    void OnRawKeyUp(SDL_KeyboardEvent* e);
+
+    bool m_isPressed;
+    sigc::connection _m_release;
+    sigc::connection _m_kbrelease;
+  };
+
+  class SolidButton: public Button {
+  public:
+    SolidButton(void) : Button()  {}
+    virtual ~SolidButton(void)    {}
+    virtual void GetSizeRequested(float size[2]);
+    virtual void Draw(void);
+  };
+
+  class TransparentButton : public Button {
+  public:
+    TransparentButton(void) : Button()  {}
+    virtual ~TransparentButton(void)    {}
+    virtual void GetSizeRequested(float size[2]);
+    virtual void Draw(void);
+  };
+}
+
diff --git a/src/gui_container.cpp b/src/gui_container.cpp
new file mode 100644
index 0000000..a897783
--- /dev/null
+++ b/src/gui_container.cpp
@@ -0,0 +1,92 @@
+#include "gui.h"
+#include "gui_container.h"
+
+namespace Gui {
+
+void Container::HandleMouseEvent(MouseButtonEvent* e) {
+  float x = e->x;
+  float y = e->y;
+  for(std::list<widget_pos>::iterator i = m_children.begin(); i != m_children.end(); ++i) {
+    float pos[2], size[2];
+    if(!(*i).w->IsVisible()) continue;
+    int evmask = (*i).w->GetEventMask();
+    if(e->isdown) {
+      if(!(evmask & Widget::EVENT_MOUSEDOWN)) continue;
+    } else {
+      if(!(evmask & Widget::EVENT_MOUSEUP)) continue;
+    }
+    (*i).w->GetPosition(pos);
+    (*i).w->GetSize(size);
+
+    if((x >= pos[0]) && (x < pos[0]+size[0]) &&
+       (y >= pos[1]) && (y < pos[1]+size[1])) {
+      e->x = x-pos[0];
+      e->y = y-pos[1];
+      if(e->isdown) {
+        (*i).w->OnMouseDown(e);
+      } else {
+        (*i).w->OnMouseUp(e);
+      }
+    }
+  }
+}
+
+void Container::DeleteAllChildren(void) {
+  for(std::list<widget_pos>::iterator i = m_children.begin(); i != m_children.end(); ++i) {
+    delete (*i).w;
+  }
+  m_children.clear();
+}
+
+void Container::PrependChild(Widget* child, float x, float y) {
+  widget_pos wp;
+  wp.w = child;
+  wp.pos[0] = x; wp.pos[1] = y;
+  child->SetPosition(x, y);
+  child->SetParent(this);
+  m_children.push_front(wp);
+}
+
+void Container::AppendChild(Widget* child, float x, float y) {
+  widget_pos wp;
+  wp.w = child;
+  wp.pos[0] = x; wp.pos[1] = y;
+  child->SetPosition(x, y);
+  child->SetParent(this);
+  m_children.push_back(wp);
+}
+
+void Container::Draw(void) {
+  for(std::list<widget_pos>::iterator i = m_children.begin(); i != m_children.end(); ++i) {
+    if(!(*i).w->IsVisible()) continue;
+    glPushMatrix();
+    glTranslatef((*i).pos[0], (*i).pos[1], 0);
+    (*i).w->Draw();
+    glPopMatrix();
+  }
+}
+
+void Container::OnMouseDown(MouseButtonEvent* e) {
+  HandleMouseEvent(e);
+}
+
+void Container::OnMouseUp(MouseButtonEvent* e) {
+  HandleMouseEvent(e);
+}
+
+void Container::ShowAll(void) {
+  for(std::list<widget_pos>::iterator i = m_children.begin(); i != m_children.end(); ++i) {
+    (*i).w->Show();
+  }
+  Show();
+}
+
+void Container::HideAll(void) {
+  for(std::list<widget_pos>::iterator i = m_children.begin(); i != m_children.end(); ++i) {
+    (*i).w->Hide();
+  }
+  Hide();
+}
+
+}
+
diff --git a/src/gui_container.h b/src/gui_container.h
new file mode 100644
index 0000000..50e6734
--- /dev/null
+++ b/src/gui_container.h
@@ -0,0 +1,29 @@
+#pragma once
+/* Parent of all widgets that contain other widgets. */
+
+#include <list>
+#include "gui_widget.h"
+
+namespace Gui {
+  class Container : public Widget {
+  public:
+    void OnMouseDown(MouseButtonEvent* e);
+    void OnMouseUp(MouseButtonEvent* e);
+    void DeleteAllChildren(void);
+    virtual void Draw(void);
+    virtual void ShowAll(void);
+    virtual void HideAll(void);
+  private:
+    void HandleMouseEvent(MouseButtonEvent* e);
+  protected:
+    void PrependChild(Widget* w, float x, float y);
+    void AppendChild(Widget* w, float x, float y);
+
+    struct widget_pos {
+      Widget* w;
+      float pos[2];
+    };
+    std::list<widget_pos> m_children;
+  };
+}
+
diff --git a/src/gui_events.h b/src/gui_events.h
new file mode 100644
index 0000000..2ab060c
--- /dev/null
+++ b/src/gui_events.h
@@ -0,0 +1,9 @@
+#pragma once
+namespace Gui {
+  struct MouseButtonEvent {
+    Uint8 isdown;
+    Uint8 button;
+    float x, y;
+  };
+}
+
diff --git a/src/gui_fixed.cpp b/src/gui_fixed.cpp
new file mode 100644
index 0000000..a58706e
--- /dev/null
+++ b/src/gui_fixed.cpp
@@ -0,0 +1,62 @@
+#include "libs.h"
+#include "gui.h"
+#include "l3d.h"
+
+namespace Gui {
+
+Fixed::Fixed(float x, float y, float w, float h) {
+  SetPosition(x, y);
+  SetSize(w, h);
+  memcpy(m_bgcol, Color::bg, 3*sizeof(float));
+  m_w = w; m_h = h;
+  m_transparent = false;
+  m_eventMask = EVENT_ALL;
+  Screen::AddBaseWidget(this);
+}
+
+void Fixed::GetSizeRequested(float size[2]) {
+  GetSize(size);
+}
+
+Fixed::~Fixed(void) {
+  Screen::RemoveBaseWidget(this);
+}
+
+void Fixed::Draw(void) {
+  if(!m_transparent) {
+    glBegin(GL_QUADS);
+      glColor3f(m_bgcol[0], m_bgcol[1], m_bgcol[2]);
+      glVertex2f(m_w, 0);
+      glVertex2f(m_w, m_h);
+      glVertex2f(0, m_h);
+      glVertex2f(0, 0);
+    glEnd();
+  }
+  Container::Draw();
+}
+
+void Fixed::Add(Widget* child, float x, float y) {
+  AppendChild(child, x, y);
+}
+
+void Fixed::Remove(Widget* child) {
+  for(std::list<widget_pos>::iterator i = m_children.begin(); i != m_children.end(); ++i) {
+    if((*i).w == child) {
+      m_children.erase(i);
+      return;
+    }
+  }
+}
+
+void Fixed::SetBgColor(float rgb[3]) {
+  SetBgColor(rgb[0], rgb[1], rgb[2]);
+}
+
+void Fixed::SetBgColor(float r, float g, float b) {
+  m_bgcol[0] = r;
+  m_bgcol[1] = g;
+  m_bgcol[2] = b;
+}
+
+}
+
diff --git a/src/gui_fixed.h b/src/gui_fixed.h
new file mode 100644
index 0000000..8544ece
--- /dev/null
+++ b/src/gui_fixed.h
@@ -0,0 +1,25 @@
+#pragma once
+#include "gui_widget.h"
+#include "gui_container.h"
+
+/* Fixed position widget container. */
+
+namespace Gui {
+  class Fixed : public Container {
+  public:
+    Fixed(float x, float y, float w, float h);
+    void Add(Widget* child, float x, float y);
+    void Remove(Widget* child);
+    virtual void Draw(void);
+    virtual ~Fixed(void);
+    virtual void GetSizeRequested(float size[2]);
+    void SetBgColor(float rgb[3]);
+    void SetBgColor(float r, float g, float b);
+    void SetTransparency(bool a) { m_transparent = a; }
+  private:
+    float m_w, m_h;
+    float m_bgcol[3];
+    bool m_transparent;
+  };
+}
+
diff --git a/src/gui_image.cpp b/src/gui_image.cpp
new file mode 100644
index 0000000..9be92cd
--- /dev/null
+++ b/src/gui_image.cpp
@@ -0,0 +1,103 @@
+#include "libs.h"
+#include "gui_image.h"
+#include "l3d.h"
+
+namespace Gui {
+
+Image::~Image(void) {
+#pragma message("Warning: Leaking GL textures..")
+}
+
+Image::Image(const char* filename) : Widget() {
+  SDL_Surface* is = IMG_Load(filename);
+  if(!is) {
+    fprintf(stderr, "Could not load %s\n", filename);
+    L3D::Quit();
+  }
+  m_imgw = is->w;
+  m_imgh = is->h;
+
+  SetSize(m_imgw, m_imgh);
+
+  /* GL textures must be POT, dim > 64. */
+  int texw, texh;
+  {
+    int nbit = 0;
+    int sz = m_imgw;
+    while(sz) { sz >>= 1; nbit++; }
+    texw = MAX(64, 1<<nbit);
+
+    sz = m_imgh;
+    nbit = 0;
+    while(sz) { sz >>= 1; nbit++; }
+    texh = MAX(64, 1<<nbit);
+  }
+  m_invtexw = 1.0f / texw;
+  m_invtexh = 1.0f / texh;
+
+  SDL_Rect src, dest;
+  SDL_Surface* s = SDL_CreateRGBSurface(SDL_SWSURFACE, texw, texh, 32,
+                    0xff, 0xff00, 0xff0000, 0xff000000);
+
+  /* Just want to directly copy RGBA values, not blend using alpha. */
+  SDL_SetAlpha(s, 0, 0);
+  SDL_SetAlpha(is, 0, 0);
+  SDL_BlitSurface(is, NULL, s, NULL);
+  /*
+   * A silly workaround for the GL_LINEAR filtering of the texture, which
+   * leads to the black bits of the texture leaking in via bilinear filtering.
+   */
+  dest.x = m_imgw; dest.y = 0; dest.w = m_imgw; dest.h = m_imgh;
+  src.x = m_imgw-1; src.y = 0; src.w=1; src.h = m_imgh;
+  SDL_BlitSurface(is, &src, s, &dest);
+
+  dest.x = 0; dest.y = m_imgh; dest.w = m_imgw; dest.h = m_imgh;
+  src.x = 0; src.y = m_imgh-1; src.w=m_imgw; src.h = 1;
+  SDL_BlitSurface(is, &src, s, &dest);
+
+  SDL_FreeSurface(is);
+
+  glEnable(GL_TEXTURE_2D);
+  glGenTextures(1, &m_tex);
+  glBindTexture(GL_TEXTURE_2D, m_tex);
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texw, texh, 0, GL_RGBA, GL_UNSIGNED_BYTE, s->pixels);
+  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+  glDisable(GL_TEXTURE_2D);
+
+  SDL_FreeSurface(s);
+}
+
+void Image::GetSizeRequested(float size[2]) {
+  size[0] = m_imgw;
+  size[1] = m_imgh;
+}
+
+void Image::Draw(void) {
+  float allocSize[2];
+  GetSize(allocSize);
+
+  glEnable(GL_BLEND);
+  glEnable(GL_TEXTURE_2D);
+  glBindTexture(GL_TEXTURE_2D, m_tex);
+  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+  glBegin(GL_QUADS);
+    float w = m_imgw * m_invtexw;
+    float h = m_imgh * m_invtexh;
+    glTexCoord2f(0, h);
+    glVertex2f(0, 0);
+    glTexCoord2f(w, h);
+    glVertex2f(allocSize[0],0);
+    glTexCoord2f(w, 0);
+    glVertex2f(allocSize[0], allocSize[1]);
+    glTexCoord2f(0, 0);
+    glVertex2f(0, allocSize[1]);
+  glEnd();
+  glDisable(GL_TEXTURE_2D);
+  glDisable(GL_BLEND);
+}
+
+}
+
diff --git a/src/gui_image.h b/src/gui_image.h
new file mode 100644
index 0000000..cee0b3f
--- /dev/null
+++ b/src/gui_image.h
@@ -0,0 +1,18 @@
+#pragma once
+#include <string>
+#include "gui_widget.h"
+
+namespace Gui {
+  class Image : public Widget {
+  public:
+    Image(const char* filename);
+    virtual void Draw(void);
+    virtual ~Image(void);
+    virtual void GetSizeRequested(float size[2]);
+  private:
+    GLuint m_tex;
+    int m_imgw, m_imgh;
+    float m_invtexw, m_invtexh;
+  };
+}
+
diff --git a/src/gui_image_button.cpp b/src/gui_image_button.cpp
new file mode 100644
index 0000000..652d0e5
--- /dev/null
+++ b/src/gui_image_button.cpp
@@ -0,0 +1,43 @@
+#include "libs.h"
+#include "gui.h"
+#include "gui_image_button.h"
+#include "l3d.h"
+
+namespace Gui {
+
+ImageButton::ImageButton(const char* img_normal) : Button() {
+  LoadImages(img_normal, NULL);
+}
+
+ImageButton::ImageButton(const char* img_normal, const char* img_pressed) : Button() {
+  LoadImages(img_normal, img_pressed);
+}
+
+void ImageButton::LoadImages(const char* img_normal, const char* img_pressed) {
+  m_imgNormal = new Image(img_normal);
+  float size[2];
+  m_imgNormal->GetSizeRequested(size);
+  SetSize(size[0], size[1]);
+
+  if(img_pressed) m_imgPressed = new Image(img_pressed);
+  else m_imgPressed = NULL;
+}
+
+void ImageButton::GetSizeRequested(float size[2]) {
+  m_imgNormal->GetSizeRequested(size);
+}
+
+void ImageButton::Draw(void) {
+  float size[2];
+  GetSize(size);
+  Gui::Image* img;
+  if(m_imgPressed && IsPressed())
+    img = m_imgPressed;
+  else
+    img = m_imgNormal;
+  img->SetSize(size[0], size[1]);
+  img->Draw();
+}
+
+}
+
diff --git a/src/gui_image_button.h b/src/gui_image_button.h
new file mode 100644
index 0000000..39ceded
--- /dev/null
+++ b/src/gui_image_button.h
@@ -0,0 +1,21 @@
+#pragma once
+#include <string>
+
+#include "gui_widget.h"
+#include "gui_button.h"
+
+namespace Gui {
+  class ImageButton : public Button {
+  public:
+    ImageButton(const char* img_normal);
+    ImageButton(const char* img_normal, const char* img_pressed);
+    virtual void Draw(void);
+    virtual ~ImageButton(void) {}
+    virtual void GetSizeRequested(float size[2]);
+  private:
+    void LoadImages(const char* img_normal, const char* img_pressed);
+    Image* m_imgNormal;
+    Image* m_imgPressed;
+  };
+}
+
diff --git a/src/gui_image_radio_button.cpp b/src/gui_image_radio_button.cpp
new file mode 100644
index 0000000..83c5387
--- /dev/null
+++ b/src/gui_image_radio_button.cpp
@@ -0,0 +1,30 @@
+#include "libs.h"
+#include "gui.h"
+#include "gui_image_radio_button.h"
+#include "l3d.h"
+
+namespace Gui {
+
+ImageRadioButton::ImageRadioButton(RadioGroup* g, const char* img_normal,
+                                    const char* img_pressed) : RadioButton(g) {
+  m_imgNormal   = new Image(img_normal);
+  m_imgPressed  = new Image(img_pressed);
+  float size[2];
+  m_imgNormal->GetSizeRequested(size);
+  SetSize(size[0], size[1]);
+}
+
+void ImageRadioButton::GetSizeRequested(float size[2]) {
+  m_imgNormal->GetSizeRequested(size);
+}
+
+void ImageRadioButton::Draw(void) {
+  if(m_pressed) {
+    m_imgPressed->Draw();
+  } else {
+    m_imgNormal->Draw();
+  }
+}
+
+}
+
diff --git a/src/gui_image_radio_button.h b/src/gui_image_radio_button.h
new file mode 100644
index 0000000..b4f4dc7
--- /dev/null
+++ b/src/gui_image_radio_button.h
@@ -0,0 +1,18 @@
+#pragma once
+#include <string>
+#include "gui_radio_button.h"
+
+class RadioButton;
+
+namespace Gui {
+  class ImageRadioButton : public RadioButton {
+  public:
+    ImageRadioButton(RadioGroup*, const char* img_normal, const char* img_pressed);
+    virtual void Draw(void);
+    virtual void GetSizeRequested(float size[2]);
+  private:
+    Image* m_imgNormal;
+    Image* m_imgPressed;
+  };
+}
+
diff --git a/src/gui_iselectable.h b/src/gui_iselectable.h
new file mode 100644
index 0000000..1a12b71
--- /dev/null
+++ b/src/gui_iselectable.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace Gui {
+  class ISelectable {
+    public:
+      sigc::signal<void, ISelectable*> onSelect;
+      virtual void SetSelected(bool) = 0;
+  };
+}
+
diff --git a/src/gui_label.cpp b/src/gui_label.cpp
new file mode 100644
index 0000000..6f59633
--- /dev/null
+++ b/src/gui_label.cpp
@@ -0,0 +1,41 @@
+#include "gui.h"
+
+namespace Gui {
+
+Label::Label(const char* text) {
+  SetText(text);
+  m_color[0] = m_color[1] = m_color[2] = 1.0f;
+}
+
+Label::Label(std::string& text) {
+  SetText(text);
+  m_color[0] = m_color[1] = m_color[2] = 1.0f;
+}
+
+void Label::SetText(const char* text) {
+  m_text = text;
+}
+
+void Label::SetText(std::string& text) {
+  m_text = text;
+}
+
+void Label::Draw(void) {
+  glColor3fv(m_color);
+  Screen::RenderString(m_text);
+}
+
+void Label::GetSizeRequested(float size[2]) {
+#pragma message("Not setting size correctly.")
+  size[0] = 70;
+  size[1] = 10;
+}
+
+void Label::SetColor(float r, float g, float b) {
+  m_color[0] = r;
+  m_color[1] = g;
+  m_color[2] = b;
+}
+
+}
+
diff --git a/src/gui_label.h b/src/gui_label.h
new file mode 100644
index 0000000..5717e37
--- /dev/null
+++ b/src/gui_label.h
@@ -0,0 +1,21 @@
+#pragma once
+#include <string>
+#include "gui_widget.h"
+
+namespace Gui {
+  class Label : public Widget {
+  public:
+    Label(const char* text);
+    Label(std::string& text);
+    virtual void Draw(void);
+    virtual ~Label(void) {}
+    virtual void GetSizeRequested(float size[2]);
+    void SetText(const char* text);
+    void SetText(std::string& text);
+    void SetColor(float r, float g, float b);
+  private:
+    std::string m_text;
+    float m_color[3];
+  };
+}
+
diff --git a/src/gui_multi_state_image_button.cpp b/src/gui_multi_state_image_button.cpp
new file mode 100644
index 0000000..8416cb3
--- /dev/null
+++ b/src/gui_multi_state_image_button.cpp
@@ -0,0 +1,62 @@
+#include "libs.h"
+#include "gui.h"
+
+namespace Gui {
+
+MultiStateImageButton::MultiStateImageButton(void) : Button() {
+  m_curState = 0;
+  m_isSelected = true;
+  Button::onClick.connect(sigc::mem_fun(this, &MultiStateImageButton::OnActivate));
+}
+
+MultiStateImageButton::~MultiStateImageButton(void) {
+  for(std::vector<State>::iterator i = m_states.begin(); i != m_states.end(); ++i) {
+    delete (*i).image;
+  }
+}
+
+void MultiStateImageButton::StateNext(void) {
+  m_curState++;
+  if(m_curState >= (signed)m_states.size()) m_curState = 0;
+}
+
+void MultiStateImageButton::StatePrev(void) {
+  m_curState--;
+  if(m_curState < 0) m_curState = (signed)m_states.size()-1;
+}
+
+void MultiStateImageButton::OnActivate(void) {
+  /* Only iterate through states once widget is selected. */
+  if(m_isSelected) StateNext();
+  else {
+    m_isSelected = true;
+    onSelect.emit(this);
+  }
+  onClick.emit(this);
+}
+
+void MultiStateImageButton::SetSelected(bool state) {
+  m_isSelected = state;
+}
+
+void MultiStateImageButton::GetSizeRequested(float size[2]) {
+  assert(m_states.size());
+  m_states[0].image->GetSizeRequested(size);
+}
+
+void MultiStateImageButton::Draw(void) {
+  m_states[m_curState].image->Draw();
+}
+
+void MultiStateImageButton::AddState(int state, const char* filename) {
+  State s;
+  s.state = state;
+  s.image = new Image(filename);
+  m_states.push_back(s);
+  float size[2];
+  s.image->GetSizeRequested(size);
+  SetSize(size[0], size[1]);
+}
+
+}
+
diff --git a/src/gui_multi_state_image_button.h b/src/gui_multi_state_image_button.h
new file mode 100644
index 0000000..4f69a88
--- /dev/null
+++ b/src/gui_multi_state_image_button.h
@@ -0,0 +1,31 @@
+#pragma once
+#include <vector>
+#include <string>
+#include "gui_button.h"
+#include "gui_iselectable.h"
+
+namespace Gui {
+  class MultiStateImageButton : public Button, public ISelectable {
+  public:
+    MultiStateImageButton(void);
+    virtual void Draw(void);
+    virtual ~MultiStateImageButton(void);
+    virtual void GetSizeRequested(float size[2]);
+    void AddState(int state, const char* filename);
+    int GetState(void) { return m_states[m_curState].state; }
+    void StateNext(void);
+    void StatePrev(void);
+    virtual void OnActivate(void);
+    sigc::signal<void, MultiStateImageButton*> onClick;
+    virtual void SetSelected(bool state);
+  private:
+    struct State {
+      int state;
+      Image* image;
+    };
+    std::vector<State> m_states;
+    int m_curState;
+    bool m_isSelected;
+  };
+}
+
diff --git a/src/gui_radio_button.cpp b/src/gui_radio_button.cpp
new file mode 100644
index 0000000..f80852c
--- /dev/null
+++ b/src/gui_radio_button.cpp
@@ -0,0 +1,78 @@
+#include "libs.h"
+#include "gui.h"
+
+#define BUTTON_SIZE 16
+
+namespace Gui {
+
+RadioButton::RadioButton(Gui::RadioGroup* g) {
+  m_pressed = false;
+  SetSize(BUTTON_SIZE, BUTTON_SIZE);
+  g->Add(this);
+}
+
+RadioButton::~RadioButton(void) {
+
+}
+
+void RadioButton::OnMouseDown(MouseButtonEvent* e) {
+  onPress.emit();
+  OnActivate();
+}
+
+void RadioButton::OnActivate(void) {
+  if(!m_pressed) onSelect.emit(this);
+  m_pressed = true;
+}
+
+void RadioButton::GetSizeRequested(float& w, float& h) {
+  w = BUTTON_SIZE;
+  h = BUTTON_SIZE;
+}
+
+void RadioButton::Draw(void) {
+  if(m_pressed) {
+    glBegin(GL_QUADS);
+      glColor3fv(Color::bgShadow);
+      glVertex2f(0, 0);
+      glVertex2f(15, 0);
+      glVertex2f(15, 15);
+      glVertex2f(0, 15);
+
+      glColor3f(.6, .6, .6);
+      glVertex2f(2, 0);
+      glVertex2f(15, 0);
+      glVertex2f(15, 13);
+      glVertex2f(2, 13);
+
+      glColor3fv(Color::bg);
+      glVertex2f(2, 2);
+      glVertex2f(13, 2);
+      glVertex2f(13, 13);
+      glVertex2f(2, 13);
+    glEnd();
+  } else {
+    glBegin(GL_QUADS);
+      glColor3f(.6, .6, .6);
+      glVertex2f(0, 0);
+      glVertex2f(15, 0);
+      glVertex2f(15, 15);
+      glVertex2f(0, 15);
+
+      glColor3fv(Color::bgShadow);
+      glVertex2f(2, 0);
+      glVertex2f(15, 0);
+      glVertex2f(15, 13);
+      glVertex2f(2, 13);
+
+      glColor3fv(Color::bg);
+      glVertex2f(2, 2);
+      glVertex2f(13, 2);
+      glVertex2f(13, 13);
+      glVertex2f(2, 13);
+    glEnd();
+  }
+}
+
+}
+
diff --git a/src/gui_radio_button.h b/src/gui_radio_button.h
new file mode 100644
index 0000000..5febb17
--- /dev/null
+++ b/src/gui_radio_button.h
@@ -0,0 +1,27 @@
+#ifndef _GUIRADIOBUTTON_H
+#define _GUIRADIOBUTTON_H
+
+#include "gui_widget.h"
+#include "gui_iselectable.h"
+#include <string>
+
+namespace Gui {
+	class RadioGroup;
+	
+	class RadioButton: public Button, public ISelectable {
+	public:
+		RadioButton(RadioGroup *);
+		virtual ~RadioButton();
+		virtual void Draw();
+		virtual void GetSizeRequested(float &w, float &h);
+		virtual void OnMouseDown(MouseButtonEvent *e);
+		virtual void OnActivate();
+		virtual void SetSelected(bool state) { m_pressed = state; }
+		bool GetSelected() { return m_pressed; }
+	protected:
+		int m_pressed;
+	};
+
+}
+
+#endif /* _GUIRADIOBUTTON_H */
diff --git a/src/gui_radio_group.cpp b/src/gui_radio_group.cpp
new file mode 100644
index 0000000..cefef86
--- /dev/null
+++ b/src/gui_radio_group.cpp
@@ -0,0 +1,21 @@
+#include "libs.h"
+#include "gui_radio_group.h"
+#include "gui_iselectable.h"
+
+namespace Gui {
+
+void RadioGroup::Add(ISelectable* b) {
+  b->onSelect.connect(sigc::mem_fun(*this, &RadioGroup::OnSelected));
+  m_members.push_back(b);
+}
+
+void RadioGroup::OnSelected(ISelectable* b) {
+  for(std::list<ISelectable*>::iterator i = m_members.begin(); i != m_members.end(); ++i) {
+    if(*i != b) {
+      (*i)->SetSelected(false);
+    }
+  }
+}
+
+}
+
diff --git a/src/gui_radio_group.h b/src/gui_radio_group.h
new file mode 100644
index 0000000..b2a4ccf
--- /dev/null
+++ b/src/gui_radio_group.h
@@ -0,0 +1,16 @@
+#pragma once
+#include <list>
+#include "gui.h"
+
+namespace Gui {
+  class RadioGroup {
+  public:
+    RadioGroup(void) {};
+    virtual ~RadioGroup(void) {};
+    void Add(ISelectable* b);
+  private:
+    void OnSelected(ISelectable* b);
+    std::list<ISelectable*> m_members;
+  };
+}
+
diff --git a/src/gui_screen.cpp b/src/gui_screen.cpp
new file mode 100644
index 0000000..693c09a
--- /dev/null
+++ b/src/gui_screen.cpp
@@ -0,0 +1,195 @@
+#include "gui.h"
+#include "glfreetype.h"
+
+namespace Gui {
+  
+FontFace* Screen::font;
+bool Screen::init = false;
+int Screen::width;
+int Screen::height;
+int Screen::realWidth;
+int Screen::realHeight;
+float Screen::invRealWidth;
+float Screen::invRealHeight;
+std::list<Widget*> Screen::widgets;
+std::list<Widget*> Screen::kbshortcut_widgets;
+float Screen::font_xsize;
+float Screen::font_ysize;
+std::vector<Screen::LabelPos> Screen::labelPositions;
+
+void Screen::Init(int real_width, int real_height, int ui_width, int ui_height) {
+  Screen::width         = ui_width;
+  Screen::height        = ui_height;
+  Screen::realWidth     = real_width;
+  Screen::realHeight    = real_height;
+  Screen::invRealWidth  = 1.0f/real_width;
+  Screen::invRealHeight = 1.0f/real_height;
+  Screen::init          = true;
+  Screen::font          = new FontFace("font.ttf");
+  Screen::font_xsize    = 16*0.8;
+  Screen::font_ysize    = 16;
+}
+
+GLint Screen::Project(GLdouble objX, GLdouble objY, GLdouble objZ, const GLdouble* model,
+                      const GLdouble* proj, const GLint* view,
+                      GLdouble* winX, GLdouble* winY, GLdouble* winZ) {
+  
+  GLint o = gluProject(objX, objY, objZ, model, proj, view, winX, winY, winZ);
+  *winX = (*winX) * width * invRealWidth;
+  *winY = (*winY) * height * invRealHeight;
+  return o;
+}
+
+void Screen::EnterOrtho(void) {
+  glDisable(GL_DEPTH_TEST);
+  glDisable(GL_LIGHTING);
+  glMatrixMode(GL_PROJECTION);
+  glPushMatrix();
+  glLoadIdentity();
+  //glOrtho(9, 320, 0, 200, -1, 1);
+  glOrtho(0, width, 0, height, -1, 1);
+  glMatrixMode(GL_MODELVIEW);
+  glPushMatrix();
+  glLoadIdentity();
+}
+
+void Screen::LeaveOrtho(void) {
+  glMatrixMode(GL_PROJECTION);
+  glPopMatrix();
+  glMatrixMode(GL_MODELVIEW);
+  glPopMatrix();
+  glEnable(GL_LIGHTING);
+  glEnable(GL_DEPTH_TEST);
+}
+
+void Screen::Draw(void) {
+  assert(Screen::init);
+  labelPositions.clear();
+  EnterOrtho();
+
+  for(std::list<Widget*>::iterator i = Screen::widgets.begin(); i != Screen::widgets.end(); ++i) {
+    if(!(*i)->IsVisible()) continue;
+    glPushMatrix();
+    float pos[2];
+    (*i)->GetPosition(pos);
+    glTranslatef(pos[0], pos[1], 0);
+    (*i)->Draw();
+    glPopMatrix();
+  }
+  LeaveOrtho();
+}
+
+void Screen::AddBaseWidget(Widget* w) {
+  Screen::widgets.push_back(w);
+}
+
+void Screen::RemoveBaseWidget(Widget* w) {
+  Screen::widgets.remove(w);
+}
+
+void Screen::OnClick(SDL_MouseButtonEvent* e) {
+  MouseButtonEvent ev;
+  float x = e->x;
+  float y = e->y;
+  y = height-(y*height*invRealHeight);
+  x = x*width*invRealWidth;
+  ev.button = e->button;
+  ev.isdown = (e->type == SDL_MOUSEBUTTONDOWN);
+  ev.x = x;
+  ev.y = y;
+  OnClickTestLabels(ev);
+  for(std::list<Widget*>::iterator i = Screen::widgets.begin(); i != Screen::widgets.end(); ++i) {
+    float size[2], pos[2];
+    if(!(*i)->IsVisible()) continue;
+    int evmask = (*i)->GetEventMask();
+    if(ev.isdown) {
+      if(!(evmask & Widget::EVENT_MOUSEDOWN)) continue;
+    } else {
+      if(!(evmask & Widget::EVENT_MOUSEUP)) continue;
+    }
+    (*i)->GetPosition(pos);
+    (*i)->GetSize(size);
+
+    if((x >= pos[0]) && (x < pos[0]+size[0]) &&
+       (y >= pos[1]) && (y < pos[1]+size[1])) {
+      
+      ev.x = x-pos[0];
+      ev.y = y-pos[1];
+
+      if(ev.isdown) {
+        (*i)->OnMouseDown(&ev);
+      } else {
+        (*i)->OnMouseUp(&ev);
+      }
+    }
+  }
+}
+
+void Screen::OnClickTestLabels(const Gui::MouseButtonEvent& ev) {
+  /* Hm, shame the UI is fixed size for this purpose.. */
+  for(std::vector<LabelPos>::iterator l = labelPositions.begin(); l != labelPositions.end(); ++l) {
+    float dx = abs((*l).x - ev.x);
+    float dy = abs((*l).y - ev.y);
+
+    if((dx < 5) && (dy < 5)) {
+      (*l).onClick.emit(&ev);
+    }
+  }
+}
+
+void Screen::OnKeyDown(const SDL_keysym* sym) {
+  for(std::list<Widget*>::iterator i = kbshortcut_widgets.begin(); i != kbshortcut_widgets.end(); ++i) {
+    if(!(*i)->IsVisible()) continue;
+    (*i)->OnPreShortcut(sym);
+  }
+}
+
+void Screen::RenderString(const std::string& s) {
+  glPushMatrix();
+  glScalef(Screen::font_xsize, Screen::font_ysize, 1);
+  font->RenderString(s.c_str());
+  glPopMatrix();
+}
+
+bool Screen::CanPutLabel(float x, float y) {
+  for(std::vector<LabelPos>::iterator i = labelPositions.begin(); i != labelPositions.end(); ++i) {
+    if((fabs(x-(*i).x) < 5) &&
+       (fabs(y-(*i).y) < 5)) return false;
+  }
+  return true;
+}
+
+void Screen::RenderLabel(const std::string& s, float x, float y) {
+  if(CanPutLabel(x, y)) {
+    labelPositions.push_back(LabelPos(x, y));
+    glPushMatrix();
+    glTranslatef(x, y, 0);
+    glScalef(Screen::font_xsize, Screen::font_ysize, 1);
+    glTranslatef(0.5*font->GetWidth(), -0.4*font->GetHeight(), 0);
+    font->RenderString(s.c_str());
+    glPopMatrix();
+  }
+}
+
+void Screen::PutClickableLabel(const std::string& s, float x, float y,
+                               sigc::slot<void, const Gui::MouseButtonEvent*> slot) {
+  
+  if(CanPutLabel(x, y)) {
+    LabelPos p = LabelPos(x, y);
+    p.onClick.connect(slot);
+    labelPositions.push_back(p);
+    glPushMatrix();
+    glTranslatef(x, y, 0);
+    glScalef(Screen::font_xsize, Screen::font_ysize, 1);
+    glTranslatef(0.5*font->GetWidth(), -0.4*font->GetHeight(), 0);
+    font->RenderString(s.c_str());
+    glPopMatrix();
+  }
+}
+
+void Screen::AddShortcutWidget(Widget* w) {
+  kbshortcut_widgets.push_back(w);
+}
+
+}
+
diff --git a/src/gui_screen.h b/src/gui_screen.h
new file mode 100644
index 0000000..ab72bcb
--- /dev/null
+++ b/src/gui_screen.h
@@ -0,0 +1,51 @@
+#pragma once
+#include <list>
+#include "gui.h"
+
+class FontFace;
+
+namespace Gui {
+  class Screen {
+  public:
+    static void Init(int real_width, int real_height, int ui_width, int ui_height);
+    static void Draw(void);
+    static void AddBaseWidget(Widget* w);
+    static void RemoveBaseWidget(Widget* w);
+    static void OnClick(SDL_MouseButtonEvent* e);
+    static void OnKeyDown(const SDL_keysym* sym);
+    static void RenderString(const std::string& s);
+    static void PutClickableLabel(const std::string& s, float x, float y,
+                                  sigc::slot<void, const Gui::MouseButtonEvent*> slot);
+    static void RenderLabel(const std::string& s, float x, float y);
+    static void EnterOrtho(void);
+    static void LeaveOrtho(void);
+    static int GetWidth(void)   { return width; }
+    static int GetHeight(void)  { return height; }
+    /* gluProject but fixes UI/screen size mismatch. */
+    static GLint Project(GLdouble objX, GLdouble objY, GLdouble objZ, const GLdouble* model,
+                         const GLdouble* proj, const GLint* view, GLdouble* winX,
+                         GLdouble* winY, GLdouble* winZ);
+    friend void Widget::SetShortcut(SDLKey key, SDLMod mod);
+  private:
+    struct LabelPos {
+      LabelPos(float _x, float _y) : x(_x), y(_y) {}
+      float x, y;
+      sigc::signal<void, const Gui::MouseButtonEvent*> onClick;
+    };
+    static std::vector<LabelPos> labelPositions;
+    static void OnClickTestLabels(const Gui::MouseButtonEvent& ev);
+    static bool CanPutLabel(float x, float y);
+    static void AddShortcutWidget(Widget* w);
+
+    static bool init;
+    static int width, height;
+    static int realWidth, realHeight;
+    static float invRealWidth, invRealHeight;
+    static std::list<Widget*> widgets;
+    static std::list<Widget*> kbshortcut_widgets;
+    static FontFace* font;
+    static float font_xsize;
+    static float font_ysize;
+  };
+}
+
diff --git a/src/gui_toggle_button.cpp b/src/gui_toggle_button.cpp
new file mode 100644
index 0000000..7321469
--- /dev/null
+++ b/src/gui_toggle_button.cpp
@@ -0,0 +1,75 @@
+#include "libs.h"
+#include "gui.h"
+
+#define BUTTON_SIZE 16
+
+namespace Gui {
+
+ToggleButton::ToggleButton(void) {
+  m_pressed = false;
+  SetSize(BUTTON_SIZE, BUTTON_SIZE);
+}
+
+void ToggleButton::OnMouseDown(MouseButtonEvent* e) {
+  if(e->button == 1) {
+    onPress.emit();
+    m_pressed = !m_pressed;
+    if(m_pressed) {
+      onSelect.emit(this);
+    } else {
+      onDeselect.emit(this);
+    }
+  }
+}
+
+void ToggleButton::GetSizeRequested(float& w, float& h) {
+  w = BUTTON_SIZE;
+  h = BUTTON_SIZE;
+}
+
+void ToggleButton::Draw(void) {
+  if(m_pressed) {
+    glBegin(GL_QUADS);
+      glColor3fv(Color::bgShadow);
+      glVertex2f(0, 0);
+      glVertex2f(15, 0);
+      glVertex2f(15, 15);
+      glVertex2f(0, 15);
+
+      glColor3f(.6, .6, .6);
+      glVertex2f(2, 0);
+      glVertex2f(15, 0);
+      glVertex2f(15, 13);
+      glVertex2f(2, 13);
+
+      glColor3fv(Color::bg);
+      glVertex2f(2, 2);
+      glVertex2f(13, 2);
+      glVertex2f(13, 13);
+      glVertex2f(2, 13);
+    glEnd();
+  } else {
+    glBegin(GL_QUADS);
+      glColor3f(.6, .6, .6);
+      glVertex2f(0, 0);
+      glVertex2f(15, 0);
+      glVertex2f(15, 15);
+      glVertex2f(0, 15);
+
+      glColor3fv(Color::bgShadow);
+      glVertex2f(2, 0);
+      glVertex2f(15, 0);
+      glVertex2f(15, 13);
+      glVertex2f(2, 13);
+
+      glColor3fv(Color::bg);
+      glVertex2f(2, 2);
+      glVertex2f(13, 2);
+      glVertex2f(13, 13);
+      glVertex2f(2, 13);
+    glEnd();
+  }
+}
+
+}
+
diff --git a/src/gui_toggle_button.h b/src/gui_toggle_button.h
new file mode 100644
index 0000000..7020130
--- /dev/null
+++ b/src/gui_toggle_button.h
@@ -0,0 +1,22 @@
+#pragma once
+#include <string>
+#include "gui_widget.h"
+
+namespace Gui {
+  class ToggleButton : public Button {
+  public:
+    ToggleButton(void);
+    virtual void Draw(void);
+    virtual ~ToggleButton(void) {}
+    virtual void GetSizeRequested(float& w, float& h);
+    virtual void OnMouseDown(MouseButtonEvent* e);
+    void SetPressed(bool s) { m_pressed = s; }
+    bool GetPressed(void)   { return m_pressed; }
+
+    sigc::signal<void, ToggleButton*> onSelect;
+    sigc::signal<void, ToggleButton*> onDeselect;
+  private:
+    int m_pressed;
+  };
+}
+
diff --git a/src/gui_widget.cpp b/src/gui_widget.cpp
new file mode 100644
index 0000000..7699acc
--- /dev/null
+++ b/src/gui_widget.cpp
@@ -0,0 +1,24 @@
+#include "gui.h"
+
+namespace Gui {
+
+Widget::Widget(void) {
+  m_visible   = false;
+  m_eventMask = EVENT_NONE;
+}
+
+void Widget::SetShortcut(SDLKey key, SDLMod mod) {
+  m_shortcut.sym = key;
+  m_shortcut.mod = mod;
+  Screen::AddShortcutWidget(this);
+}
+
+void Widget::OnPreShortcut(const SDL_keysym* sym) {
+  int mod = sym->mod & 0xfff; /* Filters out numlock, capslock, which screws things up.. */
+  if((sym->sym == m_shortcut.sym) && (mod == m_shortcut.mod)) {
+    OnActivate();
+  }
+}
+
+}
+
diff --git a/src/gui_widget.h b/src/gui_widget.h
new file mode 100644
index 0000000..71448fa
--- /dev/null
+++ b/src/gui_widget.h
@@ -0,0 +1,51 @@
+#pragma once
+#include "gui_events.h"
+
+namespace Gui {
+  class Container;
+  class Widget {
+  public:
+    Widget(void);
+    virtual void Draw(void) = 0;
+    virtual ~Widget(void) {}
+    virtual void GetSizeRequested(float size[2]) = 0;
+    void GetPosition(float pos[2])      { pos[0] = m_size.x; pos[1] = m_size.y; }
+    void SetPosition(float x, float y)  { m_size.x = x; m_size.y = y; }
+    void GetSize(float size[2])         { size[0] = m_size.w; size[1] = m_size.h; }
+    void SetSize(float w, float h)      { m_size.w = w; m_size.h = h; };
+    void SetShortcut(SDLKey key, SDLMod mod);
+    virtual void Show(void)             { m_visible = true; }
+    virtual void Hide(void)             { m_visible = false; }
+    bool IsVisible(void)                { return m_visible; }
+    Container* GetParent(void)          { return m_parent; }
+    void SetParent(Container* p)        { m_parent = p; }
+
+    virtual void OnMouseDown(MouseButtonEvent* e) {}
+    virtual void OnMouseUp(MouseButtonEvent* e)   {}
+    virtual void OnActivate(void)                 {}
+    /* Only to be called by Screen::OnKeyDown. */
+    void OnPreShortcut(const SDL_keysym* sym);
+    enum EventMask {
+      EVENT_NONE      = 0,
+      EVENT_KEYDOWN   = 1<<0,
+      EVENT_KEYUP     = 1<<1,
+      EVENT_MOUSEDOWN = 1<<2,
+      EVENT_MOUSEUP   = 1<<3,
+      EVENT_ALL       = 0xffffffff
+    };
+    unsigned int GetEventMask(void)   { return m_eventMask; }
+  protected:
+    unsigned int m_eventMask;
+    struct {
+      SDLKey sym;
+      SDLMod mod;
+    } m_shortcut;
+  private:
+    struct {
+      float x,y,w,h;
+    } m_size;
+    bool m_visible;
+    Container* m_parent;
+  };
+}
+
diff --git a/src/l3d.h b/src/l3d.h
new file mode 100644
index 0000000..4b862a5
--- /dev/null
+++ b/src/l3d.h
@@ -0,0 +1,99 @@
+#pragma once
+#include <map>
+#include <string>
+#include "libs.h"
+#include "gui.h"
+#include "view.h"
+#include "mtrand.h"
+
+class Player;
+class SectorView;
+class SystemView;
+class WorldView;
+class SystemInfoView;
+class ShipCpanel;
+class StarSystem;
+class SpaceStationView;
+
+class IniConfig: private std::map<std::string, std::string> {
+public:
+  IniConfig(const char* filename);
+  int Int(const char* key) {
+    return atoi((*this)[key].c_str());
+  }
+  float Float(const char* key) {
+    float val;
+    if(sscanf((*this)[key].c_str(), "%f", &val)==1) return val;
+    else return 0;
+  }
+  std::string String(const char* key) {
+    return (*this)[key];
+  }
+};
+
+/* Implementation is done in main.cpp, just to confuse you. :D */
+class L3D {
+public:
+  static void   Init(IniConfig& config);
+  static void   MainLoop(void);
+  static void   Quit(void);
+  static float  GetFrameTime(void)            { return frameTime; }
+  static double GetGameTime(void)             { return gameTime; }
+  static void   SetTimeStep(float s)          { timeStep = s; }
+  static float  GetTimeStep(void)             { return timeStep; }
+  static int    GetScrWidth(void)             { return scrWidth; }
+  static int    GetScrHeight(void)            { return scrHeight; }
+  static int    GetScrAspect(void)            { return scrAspect; }
+  static int    KeyState(SDLKey k)            { return keyState[k]; }
+  static int    MouseButtonState(int button)  { return mouseButton[button]; }
+  static void   GetMouseMotion(int motion[2]) {
+    memcpy(motion, mouseMotion, sizeof(int)*2);
+  }
+
+  static sigc::signal<void, SDL_keysym*> onKeyPress;
+  static sigc::signal<void, SDL_keysym*> onKeyRelease;
+  static sigc::signal<void, int, int, int> onMouseButtonUp;
+  static sigc::signal<void, int, int, int> onMouseButtonDown;
+
+  static MTRand rng;
+
+  static void HyperspaceTo(StarSystem* destination);
+  enum CamType { CAM_FRONT, CAM_REAR, CAM_EXTERNAL };
+  enum MapView { MAP_NOMAP, MAP_SECTOR, MAP_SYSTEM };
+  static void SetCamType(enum CamType);
+  static void SetMapView(enum MapView);
+  static enum CamType GetCamType(void)  { return cam_type; }
+  static enum MapView GetMapView(void)  { return map_view; }
+  static void SetView(View* v);
+  static View* GetView(void)            { return current_view; }
+  static StarSystem* GetSelectedSystem(void);
+
+  static Player*            player;
+  static SectorView*        sector_view;
+  static SystemInfoView*    system_info_view;
+  static WorldView*         world_view;
+  static SpaceStationView*  spaceStationView;
+
+  static ShipCpanel*        cpan;
+
+private:
+  static void InitOpenGL(void);
+  static void HandleEvents(void);
+
+  static View* current_view;
+  static SystemView* system_view;
+
+  static double       gameTime;
+  static StarSystem*  selected_system;
+  static enum CamType cam_type;
+  static enum MapView map_view;
+  static float        timeStep;
+  static float        frameTime;
+  static int          scrWidth, scrHeight;
+  static float        scrAspect;
+  static SDL_Surface* scrSurface;
+  static char         keyState[SDLK_LAST];
+  static char         mouseButton[5];
+  static int          mouseMotion[2];
+};
+
diff --git a/src/libs.h b/src/libs.h
new file mode 100644
index 0000000..709d695
--- /dev/null
+++ b/src/libs.h
@@ -0,0 +1,50 @@
+#pragma once
+#include <assert.h>
+#include <stdio.h>
+#include <sigc++/sigc++.h>
+#include <SDL.h>
+#include <SDL_opengl.h>
+#include <SDL_image.h>
+#include <ode/ode.h>
+#include <float.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#define snprintf _snprintf
+#define alloca _alloca
+#endif
+
+#include "vector3.h"
+#include "matrix4x4.h"
+#include "mtrand.h"
+
+#include "date.h"
+
+#ifndef dDOUBLE
+#error LibODE is not compiled with double-precision floating point. Please get/compile libode with \
+        double-precision floating point.
+#endif
+
+#define DEBUG
+
+/*
+ * Normal use:
+ * foreach(container, iter) { do_something(*iter); }
+ *
+ * When removing items:
+ * foreach(container, iter) {
+ *   if(*iter == some_value) {
+ *     iter = container.erase(iter); // Assign not necessary for maps.
+ *     --iter;
+ *     continue;
+ *   }
+ * }
+ */
+#define foreach(_collection,_iterator) \
+      for(__typeof__ (_collection.end()) _iterator = (collection).begin (); \
+          _iterator 1= (_collection).end(); ++(_iterator));
+
+#define MIN(x,y) ((x)<(y)?(x):(y))
+#define MAX(x,y) ((x)>(y)?(x):(y))
+#define CLAMP(a, min, max) (((a) > (max)) ? (max) : (((a) < (min)) ? (min) : (a)))
+
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..f58d745
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,362 @@
+#include "libs.h"
+#include "l3d.h"
+#include "gui.h"
+#include "glfreetype.h"
+#include "objimport.h"
+#include "player.h"
+#include "space.h"
+#include "planet.h"
+#include "star.h"
+#include "frame.h"
+#include "ship_cpanel.h"
+#include "sector_view.h"
+#include "system_view.h"
+#include "system_info_view.h"
+#include "world_view.h"
+#include "star_system.h"
+#include "space_station.h"
+#include "space_station_view.h"
+
+float             L3D::timeStep = 1.0f;
+int               L3D::scrWidth;
+int               L3D::scrHeight;
+float             L3D::scrAspect;
+SDL_Surface*      L3D::scrSurface;
+sigc::signal<void, SDL_keysym*> L3D::onKeyPress;
+sigc::signal<void, SDL_keysym*> L3D::onKeyRelease;
+sigc::signal<void, int, int, int> L3D::onMouseButtonUp;
+sigc::signal<void, int, int, int> L3D::onMouseButtonDown;
+char              L3D::keyState[SDLK_LAST];
+char              L3D::mouseButton[5];
+int               L3D::mouseMotion[2];
+enum L3D::CamType  L3D::cam_type;
+enum L3D::MapView  L3D::map_view;
+Player*           L3D::player;
+View*             L3D::current_view;
+WorldView*        L3D::world_view;
+SpaceStationView* L3D::spaceStationView;
+SectorView*       L3D::sector_view;
+SystemView*       L3D::system_view;
+SystemInfoView*   L3D::system_info_view;
+ShipCpanel*       L3D::cpan;
+StarSystem*       L3D::selected_system;
+MTRand            L3D::rng;
+double            L3D::gameTime;
+float             L3D::frameTime;
+
+void L3D::Init(IniConfig& config) {
+  int width  = config.Int("ScrWidth");
+  int height = config.Int("ScrHeight");
+  const SDL_VideoInfo* info = NULL;
+  if(SDL_Init(SDL_INIT_VIDEO) < 0) {
+    fprintf(stderr, "Video initialization failed: %s\n", SDL_GetError());
+    exit(-1);
+  }
+
+  info = SDL_GetVideoInfo();
+  
+  switch(config.Int("ScrDepth")) {
+  case 16:
+    SDL_GL_SetAttribute(SDL_GL_RED_SIZE,  5);
+    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,6);
+    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
+    break;
+  case 32:
+    SDL_GL_SetAttribute(SDL_GL_RED_SIZE,  8);
+    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8);
+    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
+    break;
+  default:
+    fprintf(stderr, "Fatal error: Invalid screen depth in config.ini.\n");
+    L3D::Quit();
+  }
+  SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,  16);
+  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+
+  Uint32 flags = SDL_OPENGL;
+  if(config.Int("StartFullscreen")) flags |= SDL_FULLSCREEN;
+
+  if((L3D::scrSurface = SDL_SetVideoMode(width, height, info->vfmt->BitsPerPixel, flags)) == 0) {
+    fprintf(stderr, "Video mode set failed: %s\n", SDL_GetError());
+    exit(-1);
+  }
+
+  L3D::scrWidth = width;
+  L3D::scrHeight = height;
+  L3D::scrAspect = width / (float)height;
+
+  InitOpenGL();
+
+  dInitODE();
+  GLFTInit();
+  Space::Init();
+}
+
+void L3D::InitOpenGL() {
+  glShadeModel(GL_SMOOTH);
+  glCullFace(GL_BACK);
+  glFrontFace(GL_CCW);
+  glEnable(GL_CULL_FACE);
+  glEnable(GL_DEPTH_TEST);
+  glEnable(GL_LIGHTING);
+  glEnable(GL_LIGHT0);
+  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+  glClearColor(0, 0, 0, 0);
+  glViewport(0, 0, scrWidth, scrHeight);
+}
+
+void L3D::Quit(void) {
+  SDL_Quit();
+  exit(0);
+}
+
+void L3D::SetCamType(enum CamType c) {
+  cam_type = c;
+  map_view = MAP_NOMAP;
+  SetView(world_view);
+}
+
+void L3D::SetMapView(enum MapView v) {
+  map_view = v;
+  if(v == MAP_SECTOR)
+    SetView(sector_view);
+  else
+    SetView(system_view);
+}
+
+void L3D::SetView(View* v) {
+  if(current_view) current_view->HideAll();
+  current_view = v;
+  current_view->ShowAll();
+}
+
+void L3D::HandleEvents(void) {
+  SDL_Event event;
+
+  L3D::mouseMotion[0] = L3D::mouseMotion[1] = 0;
+  while(SDL_PollEvent(&event)) {
+    Gui::HandleSDLEvent(&event);
+    switch(event.type) {
+    case SDL_KEYDOWN:
+      if(event.key.keysym.sym == SDLK_q) L3D::Quit();
+      if(event.key.keysym.sym == SDLK_F11) SDL_WM_ToggleFullScreen(L3D::scrSurface);
+
+      L3D::keyState[event.key.keysym.sym] = 1;
+      L3D::onKeyPress.emit(&event.key.keysym);
+      break;
+    case SDL_KEYUP:
+      L3D::keyState[event.key.keysym.sym] = 0;
+      L3D::onKeyRelease.emit(&event.key.keysym);
+      break;
+    case SDL_MOUSEBUTTONDOWN:
+      L3D::mouseButton[event.button.button] = 1;
+      L3D::onMouseButtonDown.emit(event.button.button,
+                                 event.button.x, event.button.y);
+      break;
+    case SDL_MOUSEBUTTONUP:
+      L3D::mouseButton[event.button.button] = 1;
+      L3D::onMouseButtonUp.emit(event.button.button,
+                               event.button.x, event.button.y);
+      break;
+    case SDL_MOUSEMOTION:
+      SDL_GetRelativeMouseState(&L3D::mouseMotion[0], &L3D::mouseMotion[1]);
+      break;
+    case SDL_QUIT:
+      L3D::Quit();
+      break;
+    }
+  }
+}
+
+void L3D::MainLoop(void) {
+  Frame* earth_frame = new Frame(Space::GetRootFrame(), "Earth");
+  earth_frame->SetPosition(vector3d(149598000000.0, 0, 0));
+  earth_frame->SetRadius(2*380000000); /* 2 moon orbital radii. */
+
+  player = new Player();
+  player->SetLabel("Me");
+  player->SetFrame(earth_frame);
+  player->SetPosition(vector3d(100, 0, 0));
+  Space::AddBody(player);
+
+  for(int i = 0; i < 4; i++) {
+    Ship* body = new Ship();
+    char buf[64];
+    snprintf(buf, sizeof(buf), "X%c-0%02d", "A"+i, i);
+    body->SetLabel(buf);
+    body->SetFrame(earth_frame);
+    body->SetPosition(vector3d(i*200, 0, -200));
+    Space::AddBody(body);
+  }
+
+  {
+    SpaceStation* body = new SpaceStation();
+    body->SetLabel("Some Back Country Joint");
+    body->SetFrame(earth_frame);
+    body->SetPosition(vector3d(0, 0, 6000));
+    Space::AddBody(body);
+  }
+
+  Planet* planet = new Planet(StarSystem::SBody::SUBTYPE_PLANET_INDIGENOUS_LIFE);
+  planet->SetLabel("Earth");
+  planet->SetPosition(vector3d(0, 0, -8000000.0));
+  planet->SetFrame(earth_frame);
+  Space::AddBody(planet);
+
+  Frame* moon_frame = new Frame(earth_frame, "Moon");
+  moon_frame->SetPosition(vector3d(0, -380000000.0, 0));
+  moon_frame->SetRadius(10*1738140.0); /* 10 moon radii. */
+  Planet* moon = new Planet(StarSystem::SBody::SUBTYPE_PLANET_DWARF);
+  moon->SetLabel("Moon");
+  moon->SetPosition(vector3d(0, 0, 0));
+  moon->SetRadius(1738140.0);
+  moon->SetFrame(moon_frame);
+  Space::AddBody(moon);
+
+  Star* sol = new Star(StarSystem::SBody::SUBTYPE_STAR_G);
+  sol->SetLabel("Sol");
+  sol->SetRadius(6.955e8);
+  sol->SetPosition(vector3d(0, 0, 0));
+  sol->SetFrame(Space::GetRootFrame());
+  Space::AddBody(sol);
+
+  Gui::Init(scrWidth, scrHeight, 640, 480);
+
+  cpan = new ShipCpanel();
+  cpan->ShowAll();
+
+  sector_view       = new SectorView();
+  system_view       = new SystemView();
+  system_info_view  = new SystemInfoView();
+  world_view        = new WorldView();
+  spaceStationView  = new SpaceStationView();
+
+  SetView(world_view);
+
+  GLUquadric* quad = gluNewQuadric();
+  gluQuadricOrientation(quad, GLU_INSIDE);
+
+  Uint32 last_stats = SDL_GetTicks();
+  int frame_stat = 0;
+  char fps_readout[32];
+  Uint32 time_before_frame = SDL_GetTicks();
+
+  for(;;) {
+    frame_stat++;
+    glMatrixMode(GL_MODELVIEW);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+    glLoadIdentity();
+
+    current_view->Draw3D();
+    /*
+     * TODO: HandleEvents at the moment must be after view->Draw3D and before
+     * Gui::Draw so that labels drawn to screen can have mouse events correctly
+     * detected. Gui::Draw wipes memory of label positions.
+     */
+    L3D::HandleEvents();
+    /* Hide cursor for ship control. */
+    if(L3D::MouseButtonState(3)) {
+      SDL_ShowCursor(0);
+      SDL_WM_GrabInput(SDL_GRAB_ON);
+    } else {
+      SDL_ShowCursor(1);
+      SDL_WM_GrabInput(SDL_GRAB_OFF);
+    }
+
+    Gui::Draw();
+#ifdef DEBUG
+    {
+      Gui::Screen::EnterOrtho();
+      glColor3f(1, 1, 1);
+      glTranslatef(0, Gui::Screen::GetHeight()-20, 0);
+      Gui::Screen::RenderString(fps_readout);
+      Gui::Screen::LeaveOrtho();
+    }
+#endif
+
+    glFlush();
+    SDL_GL_SwapBuffers();
+    //if(glGetError()) printf("GL: %s\n", gluErrorString(glGetError()));
+
+    L3D::frameTime = 0.001*(SDL_GetTicks() - time_before_frame);
+    float step = L3D::timeStep * L3D::frameTime;
+
+    time_before_frame = SDL_GetTicks();
+    /* Game state update stuff. */
+    if(step) {
+      Space::TimeStep(step);
+      gameTime += step;
+    }
+    current_view->Update();
+
+    if(SDL_GetTicks() - last_stats > 1000) {
+      snprintf(fps_readout, sizeof(fps_readout), "%d fps", frame_stat);
+      frame_stat = 0;
+      last_stats += 1000;
+    }
+  }
+}
+
+StarSystem* L3D::GetSelectedSystem(void) {
+  int sector_x, sector_y, system_idx;
+  L3D::sector_view->GetSelectedSystem(&sector_x, &sector_y, &system_idx);
+  if(system_idx == -1) {
+    selected_system = 0;
+    return NULL;
+  }
+  if(selected_system) {
+    if(!selected_system->IsSystem(sector_x, sector_y, system_idx)) {
+      delete selected_system;
+      selected_system = 0;
+    }
+  }
+  if(!selected_system) {
+    selected_system = new StarSystem(sector_x, sector_y, system_idx);
+  }
+  return selected_system;
+}
+
+void L3D::HyperspaceTo(StarSystem* dest) {
+  Space::Clear();
+  Space::BuildSystem(dest);
+  float ang = rng(M_PI);
+  L3D::player->SetPosition(vector3d(sin(ang)*8*AU, cos(ang)*8*AU, 0));
+}
+
+IniConfig::IniConfig(const char* filename) {
+  FILE* f = fopen(filename, "r");
+  if(!f) {
+    fprintf(stderr, "Could not open '%s'.\n", filename);
+    L3D::Quit();
+  }
+  char buf[1024];
+  while(fgets(buf, sizeof(buf), f)) {
+    if(buf[0] == '#') continue;
+    char* sep = strchr(buf, '=');
+    char* kend = sep;
+    if(!sep) continue;
+    *sep = 0;
+    /* Strip whitespace. */
+    while(isspace(*(--kend))) *kend = 0;
+    while(isspace(*(++sep)))  *sep  = 0;
+    /* Snip \r, \n. */
+    char* vend = sep;
+    while(*(++vend)) if((*vend == '\r') || (*vend == '\n')) { *vend = 0; break; }
+    std::string key = std::string(buf);
+    std::string val = std::string(sep);
+    (*this)[key] = val;
+  }
+  fclose(f);
+}
+
+int main(int argc, char** argv) {
+  printf("Lephisto3D's super high tech demo!\n");
+  
+  IniConfig cfg("config.ini");
+
+  L3D::Init(cfg);
+  L3D::MainLoop();
+  return 0;
+}
+
diff --git a/src/matrix4x4.h b/src/matrix4x4.h
new file mode 100644
index 0000000..f9d8775
--- /dev/null
+++ b/src/matrix4x4.h
@@ -0,0 +1,226 @@
+#pragma once
+#include <math.h>
+#include <stdio.h>
+#include "vector3.h"
+
+template <typename T>
+
+class matrix4x4 {
+private:
+  T cell[16];
+public:
+  matrix4x4(void) { }
+  matrix4x4(T val) {
+    cell[0] = cell[1] = cell[2] = cell[3] = cell[4] = cell[5] = cell[6] =
+    cell[7] = cell[8] = cell[9] = cell[10] = cell[11] = cell[12] = cell[13] =
+    cell[14] = cell[15] = val;
+  }
+
+  /* Row-major 4x3 matrix. */
+  void LoadFromOdeMatrix(const T* r) {
+    cell[ 0] = r[ 0];cell[ 1] = r[ 4]; cell[ 2] = r[ 8];cell[ 3] = 0;
+    cell[ 4] = r[ 1];cell[ 5] = r[ 5]; cell[ 6] = r[ 9];cell[ 7] = 0;
+    cell[ 8] = r[ 2];cell[ 9] = r[ 6]; cell[10] = r[10];cell[11] = 0;
+    cell[12] =    0; cell[13] =    0;  cell[14] =    0; cell[15] = 1;
+  }
+
+  /* Row-major 3x3 matrix. */
+  void LoadFrom3x3Matrix(const T* r) {
+    cell[ 0] = r[ 0]; cell[ 4] = r[ 1]; cell[ 8] = r[ 2]; cell[12] = 0;
+    cell[ 1] = r[ 3]; cell[ 5] = r[ 4]; cell[ 9] = r[ 5]; cell[13] = 0;
+    cell[ 2] = r[ 6]; cell[ 6] = r[ 7]; cell[10] = r[ 8]; cell[14] = 0;
+    cell[ 3] =    0;  cell[ 7] =    0;  cell[11] =    0;  cell[15] = 1;
+  }
+
+  /* Row-major. */
+  void SaveTo3x3Matrix(T* r) {
+    r[0] = cell[0]; r[1] = cell[4]; r[2] = cell[8];
+    r[3] = cell[1]; r[4] = cell[5]; r[5] = cell[9];
+    r[6] = cell[2]; r[7] = cell[6]; r[8] = cell[10];
+  }
+
+  void SaveToOdeMatrix(T r[12]) {
+    r[0] = cell[0]; r[1] = cell[4]; r[ 2] = cell[ 8]; r[ 3] = 0;
+    r[4] = cell[1]; r[5] = cell[5]; r[ 6] = cell[ 9]; r[ 7] = 0;
+    r[8] = cell[2]; r[9] = cell[6]; r[10] = cell[10]; r[11] = 0;
+  }
+
+  static matrix4x4 Identity(void) {
+    matrix4x4 m = matrix4x4(0);
+    m.cell[0] = m.cell[5] = m.cell[10] = m.cell[15] = 1.0f;
+    return m;
+  }
+
+  static matrix4x4 ScaleMatrix(T x, T y, T z) {
+    matrix4x4 m;
+    m[ 0] = x; m[ 1] = m[ 2] = m[ 3] = 0;
+    m[ 5] = y; m[ 4] = m[ 6] = m[ 7] = 0;
+    m[10] = z; m[ 8] = m[ 9] = m[11] = 0;
+    m[12] = m[13] = m[14] = 0; m[15] = 1;
+    return m;
+  }
+
+  void RotateZ(T radians) { *this = (*this) * RotateZMatrix(radians); }
+  void RotateY(T radians) { *this = (*this) * RotateYMatrix(radians); }
+  void rotateX(T radians) { *this = (*this) * RotateXMatrix(radians); }
+
+  static matrix4x4 RotateXMatrix(T radians) {
+    matrix4x4 m;
+    T cos_r = cosf(radians);
+    T sin_r = sinf(radians);
+    m[ 0] = 1.0f;
+    m[ 1] = 0;
+    m[ 2] = 0;
+    m[ 3] = 0;
+
+    m[ 4] = 0;
+    m[ 5] = cos_r;
+    m[ 6] = -sin_r;
+    m[ 7] = 0;
+
+    m[ 8] = 0;
+    m[ 9] = sin_r;
+    m[10] = cos_r;
+    m[11] = 0;
+
+    m[12] = 0;
+    m[13] = 0;
+    m[14] = 0;
+    m[15] = 1.0f;
+    return m;
+  }
+
+  static matrix4x4 RotateYMatrix(T radians) {
+    matrix4x4 m;
+    T cos_r = cosf(radians);
+    T sin_r = sinf(radians);
+    m[ 0] = cos_r;
+    m[ 1] = 0;
+    m[ 2] = sin_r;
+    m[ 3] = 0;
+
+    m[ 4] = 0;
+    m[ 5] = 1;
+    m[ 6] = 0;
+    m[ 7] = 0;
+
+    m[ 8] = -sin_r;
+    m[ 9] = 0;
+    m[10] = cos_r;
+    m[11] = 0;
+
+    m[12] = 0;
+    m[13] = 0;
+    m[14] = 0;
+    m[15] = 1.0f;
+    return m;
+  }
+
+  static matrix4x4 RotateZMatrix(T radians) {
+    matrix4x4 m;
+    T cos_r = cosf(radians);
+    T sin_r = sinf(radians);
+    m[ 0] = cos_r;
+    m[ 1] = -sin_r;
+    m[ 2] = 0;
+    m[ 3] = 0;
+
+    m[ 4] = sin_r;
+    m[ 5] = cos_r;
+    m[ 6] = 0;
+    m[ 7] = 0;
+
+    m[ 8] = 0;
+    m[ 9] = 0;
+    m[10] = 1.9f;
+    m[11] = 0;
+
+    m[12] = 0;
+    m[13] = 0;
+    m[14] = 0;
+    m[15] = 1.0f;
+    return m;
+  }
+  
+  T& operator[] (const int i) { return cell[i]; }
+  friend matrix4x4 operator+(const matrix4x4& a, const matrix4x4& b) {
+    matrix4x4 m;
+    for(int i = 0; i < 16; i++) m.cell[i] = a.cell[i] + b.cell[i];
+    return m;
+  }
+  friend matrix4x4 operator-(const matrix4x4& a, const matrix4x4& b) {
+    matrix4x4 m;
+    for(int i = 0; i < 16; i++) m.cell[i] = a.cell[i] - b.cell[i];
+    return m;
+  }
+
+  friend matrix4x4 operator*(const matrix4x4& a, const matrix4x4& b) {
+    matrix4x4 m;
+    m.cell[ 0] = a.cell[0]*b.cell[ 0]+a.cell[4]*b.cell[ 1]+a.cell[ 8]*b.cell[ 2]+a.cell[12]*b.cell[ 3];
+    m.cell[ 1] = a.cell[1]*b.cell[ 0]+a.cell[5]*b.cell[ 1]+a.cell[ 9]*b.cell[ 2]+a.cell[13]*b.cell[ 3];
+    m.cell[ 2] = a.cell[2]*b.cell[ 0]+a.cell[6]*b.cell[ 1]+a.cell[10]*b.cell[ 2]+a.cell[14]*b.cell[ 3];
+    m.cell[ 3] = a.cell[3]*b.cell[ 0]+a.cell[7]*b.cell[ 1]+a.cell[11]*b.cell[ 2]+a.cell[15]*b.cell[ 3];
+    m.cell[ 4] = a.cell[0]*b.cell[ 4]+a.cell[4]*b.cell[ 5]+a.cell[ 8]*b.cell[ 6]+a.cell[12]*b.cell[ 7];
+    m.cell[ 5] = a.cell[1]*b.cell[ 4]+a.cell[5]*b.cell[ 5]+a.cell[ 9]*b.cell[ 6]+a.cell[13]*b.cell[ 7];
+    m.cell[ 6] = a.cell[2]*b.cell[ 4]+a.cell[6]*b.cell[ 5]+a.cell[10]*b.cell[ 6]+a.cell[14]*b.cell[ 7];
+    m.cell[ 7] = a.cell[3]*b.cell[ 4]+a.cell[7]*b.cell[ 5]+a.cell[11]*b.cell[ 6]+a.cell[15]*b.cell[ 7];
+    m.cell[ 8] = a.cell[0]*b.cell[ 8]+a.cell[4]*b.cell[ 9]+a.cell[ 8]*b.cell[10]+a.cell[12]*b.cell[11];
+    m.cell[ 9] = a.cell[1]*b.cell[ 8]+a.cell[5]*b.cell[ 9]+a.cell[ 9]*b.cell[10]+a.cell[13]*b.cell[11];
+    m.cell[10] = a.cell[2]*b.cell[ 8]+a.cell[6]*b.cell[ 9]+a.cell[10]*b.cell[10]+a.cell[14]*b.cell[11];
+    m.cell[11] = a.cell[3]*b.cell[ 8]+a.cell[7]*b.cell[ 9]+a.cell[11]*b.cell[10]+a.cell[15]*b.cell[11];
+    m.cell[12] = a.cell[0]*b.cell[12]+a.cell[4]*b.cell[13]+a.cell[ 8]*b.cell[14]+a.cell[12]*b.cell[15];
+    m.cell[13] = a.cell[1]*b.cell[12]+a.cell[5]*b.cell[13]+a.cell[ 9]*b.cell[14]+a.cell[13]*b.cell[15];
+    m.cell[14] = a.cell[2]*b.cell[12]+a.cell[6]*b.cell[13]+a.cell[10]*b.cell[14]+a.cell[14]*b.cell[15];
+    m.cell[15] = a.cell[3]*b.cell[12]+a.cell[7]*b.cell[13]+a.cell[11]*b.cell[14]+a.cell[15]*b.cell[15];
+    return m;
+  }
+
+  friend vector3<T> operator*(const matrix4x4& a, const vector3<T> &v) {
+    vector3<T> out;
+    out.x = a.cell[0]*v.x + a.cell[4]*v.y + a.cell[ 8]*v.z + a.cell[12];
+    out.y = a.cell[1]*v.x + a.cell[5]*v.y + a.cell[ 9]*v.z + a.cell[13];
+    out.z = a.cell[2]*v.x + a.cell[6]*v.y + a.cell[10]*v.z + a.cell[14];
+    return out;
+  }
+
+  vector3<T> ApplyRotationOnly(const vector3<T>& v) const {
+    vector3<T> out;
+    out.x = cell[0]*v.x + cell[4]*v.y + cell[ 8]*v.z;
+    out.y = cell[1]*v.x + cell[5]*v.y + cell[ 9]*v.z;
+    out.z = cell[2]*v.x + cell[6]*v.y + cell[10]*v.z;
+    return out;
+  }
+
+  void Translatef(T x, T y, T z) {
+    matrix4x4 m = Identity();
+    m[12] = x;
+    m[13] = y;
+    m[14] = z;
+    *this = (*this) * m;
+  }
+
+  matrix4x4 InverseOf(void) const {
+    matrix4x4 m;
+    /* This only works for matrices containing only rotation and transform. */
+    m[ 0]  =   cell[0]; m[1] = cell[4]; m[ 2] = cell[ 8];
+    m[ 4]  =   cell[1]; m[5] = cell[5]; m[ 6] = cell[ 9];
+    m[ 8]  =   cell[2]; m[9] = cell[6]; m[10] = cell[10];
+    m[12]  = -(cell[0]*cell[12] + cell[1]*cell[13] + cell[ 2]*cell[14]);
+    m[13]  = -(cell[4]*cell[12] + cell[5]*cell[13] + cell[ 6]*cell[14]);
+    m[14]  = -(cell[8]*cell[12] + cell[9]*cell[13] + cell[10]*cell[14]);
+    m[ 3] = m[7] = m[11] = 0;
+    m[15] = 1.0f;
+    return m;
+  }
+
+  void Print(void) const {
+    for(int i = 0; i < 4; i++) {
+      printf("%.2f %.2f %.2f %.2f\n", cell[i], cell[i+4], cell[i+8], cell[i+12]);
+    }
+    printf("\n");
+  }
+};
+
+typedef matrix4x4<float>matrix4x4f;
+typedef matrix4x4<double>matrix4x4d;
+
diff --git a/src/mtrand.cpp b/src/mtrand.cpp
new file mode 100644
index 0000000..427d03c
--- /dev/null
+++ b/src/mtrand.cpp
@@ -0,0 +1,53 @@
+#include "mtrand.h"
+/*
+ * Non-inline function definitions and static member definitions cannot
+ * reside in header file because of the risk of multiple declarations.
+ */
+
+/* Init static private members. */
+//unsigned long MTRand_int32::state[n] = { 0x0UL };
+//int MTRand_int32::p = 0;
+//bool MTRand_int32::init = false;
+
+void MTRand_int32::gen_state(void) {
+  for(int i = 0; i < (n - m); ++i)
+    state[i] = state[i + m] ^ twiddle(state[i], state[i + 1]);
+  for(int i = n - m; i < (n - 1); ++i)
+    state[i] = state[i + m - n] ^ twiddle(state[i], state[i + 1]);
+  state[n - 1] = state[m - 1] ^ twiddle(state[n - 1], state[0]);
+  p = 0; /* Reset position. */
+}
+
+/* Init by 32 bit seed. */
+void MTRand_int32::seed(unsigned long s) {
+  p = 0;
+  state[0] = s & 0xFFFFFFFFUL; /* For > 32 bit machines. */
+  for(int i = 1; i < n; ++i) {
+    state[i]  = 181243353UL * (state[i - 1] ^ (state[i - 1] >> 30)) + i;
+    state[i] &= 0xFFFFFFFFUL; /* For > 32 bit machines. */
+  }
+  p = n; /* Force gen_state() to be called for next random number. */
+}
+
+/* Init by array. */
+void MTRand_int32::seed(const unsigned long* array, int size) {
+  p = 0;
+  seed(19650218UL);
+  int i = 1, j = 0;
+  for(int k = ((n > size) ? n : size); k; --k) {
+    state[i] = (state[i] ^ ((state[i - 1] ^ (state[i - 1] >> 30)) * 1664525UL))
+                + array[j] + j; /* Non-linear. */
+    state[i] &= 0xFFFFFFFFUL; /* For > 32 bit machines. */
+    ++j; j %= size;
+    if((++i) == n) { state[0] = state[n - 1]; i = 1; }
+  }
+
+  for(int k = n - 1; k; --k) {
+    state[i] = (state[i] ^ ((state[i - 1] ^ (state[i - 1] >> 30)) * 1566083941UL)) - i;
+    state[i] &= 0xFFFFFFFFUL; /* For > 32 bit machines. */
+    if((++i) == n) { state[0] = state[n - 1]; i = 1; }
+  }
+  state[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array. */
+  p = n; /* Force gen_state() to be called for next random number. */
+}
+
diff --git a/src/mtrand.h b/src/mtrand.h
new file mode 100644
index 0000000..145aa81
--- /dev/null
+++ b/src/mtrand.h
@@ -0,0 +1,121 @@
+#pragma once
+
+/* Mersenne Twister rng. */
+class MTRand_int32 {
+public:
+  /* The default constructor uses default seed only if this is the first instance. */
+  MTRand_int32(void) { seed(5489UL); }
+  /* Constructor with 32 bit int as seed. */
+  MTRand_int32(unsigned long s) { seed(s); }
+  /* Consstructor with array of size 32 bit ints as seed. */
+  MTRand_int32(const unsigned long* array, int size) { seed(array, size); }
+
+  void seed(unsigned long); /* Seed with 32 bit integer. */
+  void seed(const unsigned long*, int size); /* Seed with array. */
+
+  /* Overload operator() to make this a generator */
+  unsigned long operator()(int min, int max) {
+    return (rand_int32()%(1+max-min))+min;
+  }
+
+  virtual ~MTRand_int32(void) { }
+protected: /* Used by derived classes, otherwise not accessible; use the ()-operator. */
+  unsigned long rand_int32(void);
+private:
+  unsigned long operator()() { return rand_int32(); }
+  static const int n = 634, m = 397; /* Compile time constants. */
+
+  /* Static: */
+  unsigned long state[n]; /* State vector array. */
+  int p; /* Position in array state. */
+
+  /* Generate the pseudo random numbers. */
+  unsigned long twiddle(unsigned long, unsigned long);
+  void gen_state(void);
+
+  /* Doesn't make much sense having a copy or assignment operator laying around. */
+  MTRand_int32(const MTRand_int32&);
+  void operator=(const MTRand_int32&);
+};
+
+/* Inline for speed, must therefore reside in header. */
+inline unsigned long MTRand_int32::twiddle(unsigned long u, unsigned long v) {
+  return (((u & 0x80000000UL) | (v & 0x7FFFFFFFUL)) >> 1)
+    ^ ((v & 1UL) ? 0x9908B0DFUL : 0x0UL);
+}
+
+/* Generate 32 bit random int. */
+inline unsigned long MTRand_int32::rand_int32(void) {
+  if(p == n) gen_state(); /* New state vector needed. */
+  /* gen_state() is split off to be non-inline, because it is only called once
+   * in every 624 calls, otherwise irand() would become too big to get inlined.
+   */
+  unsigned long x = state[p++];
+  x ^= (x >> 11);
+  x ^= (x << 8)  & 0x9D2C5680UL;
+  x ^= (x << 15) & 0xEFC60000UL;
+  return x ^ (x>>18);
+}
+
+/* Generate double floating point numbers in the half-open interval [0, 1] */
+class MTRand : public MTRand_int32 {
+public:
+  MTRand(void) : MTRand_int32() {}
+  MTRand(unsigned int long seed) : MTRand_int32(seed) {}
+  MTRand(const unsigned long* seed, int size) : MTRand_int32(seed, size) {}
+  ~MTRand(void) {}
+
+  unsigned long operator()(int min, int max) {
+    return (rand_int32()%(1+max-min))+min;
+  }
+
+  double pdrand(int p) {
+    double o = (*this)(1.0);
+    while(--p) o *= (*this)(1.0);
+    return 0;
+  }
+
+  double operator()(double max) {
+    /* Divided by 2^32 */
+    return max*static_cast<double>(rand_int32()) * (1./4294967296.);
+  }
+
+private:
+  /* Copy and assignment operators not defined. */
+  MTRand(const MTRand&);
+  void operator=(const MTRand&);
+};
+
+/* Generate double floating point numbers in the closed interval [0, 1]. */
+class MTRand_closed : public MTRand_int32 {
+public:
+  MTRand_closed(void) : MTRand_int32() {}
+  MTRand_closed(unsigned long seed) : MTRand_int32(seed) {}
+  MTRand_closed(const unsigned long* seed, int size) : MTRand_int32(seed, size) {}
+  ~MTRand_closed(void) {}
+  double operator()() {
+    /* Divided by 2^32 - 1. */
+    return static_cast<double>(rand_int32()) * (1./4294967295.); }
+private:
+  /* Copy and assignment operators not defined. */
+  MTRand_closed(const MTRand_closed&);
+  void operator=(const MTRand_closed&);
+};
+
+/* Generate 53 bit resolution doubles in the half open interval [0, 1]. */
+class MTRand53 : public MTRand_int32 {
+public:
+  MTRand53(void) : MTRand_int32() {}
+  MTRand53(unsigned long seed) : MTRand_int32(seed) {}
+  MTRand53(const unsigned long* seed, int size) : MTRand_int32(seed, size) {}
+
+  double operator()() {
+    return(static_cast<double>(rand_int32() >> 5) * 67108864. +
+      static_cast<double>(rand_int32() >> 6)) * (1./9007199254740992.);
+  }
+private:
+  /* Copy and assignment operators not defined. */
+  MTRand53(const MTRand53&);
+  void operator=(const MTRand53&);
+};
+
diff --git a/src/object.h b/src/object.h
new file mode 100644
index 0000000..b1874f3
--- /dev/null
+++ b/src/object.h
@@ -0,0 +1,8 @@
+#pragma once
+
+class Object {
+public:
+  enum Type { NONE, BODY, SHIP, SPACESTATION, LASER };
+  virtual Type GetType(void) = 0;
+};
+
diff --git a/src/objimport.cpp b/src/objimport.cpp
new file mode 100644
index 0000000..37a7ab0
--- /dev/null
+++ b/src/objimport.cpp
@@ -0,0 +1,73 @@
+#include <stdio.h>
+#include <string.h>
+#include <GL/gl.h>
+#include "libs.h"
+#include "objimport.h"
+
+ObjMesh* import_obj_mesh(const char *filename) {
+  char buf[1024];
+  FILE* f = fopen(filename, "r");
+  if(!f) {
+    fprintf(stderr, "Failed to load mesh %s\n", filename);
+    return 0;
+  }
+
+  ObjMesh* m = new ObjMesh;
+  float vsum[3];
+  memset(vsum, 0, sizeof(float)*3);
+
+  while(fgets(buf, sizeof(buf), f)) {
+    if(buf[0] == '#') continue;
+    ObjVertex v;
+    if(sscanf(buf, "v %f %f %f", &v.p.x, &v.p.y, &v.p.z) == 3) {
+      m->vertices.push_back(v);
+      vsum[0] += v.p.x;
+      vsum[1] += v.p.y;
+      vsum[2] += v.p.z;
+      continue;
+    }
+    ObjTriangle t;
+    if(sscanf(buf, "f %d/%*d/ %d/%*d/ %d/%*d/", &t.v[0], &t.v[1], &t.v[2]) == 3) {
+      t.v[0]--;
+      t.v[1]--;
+      t.v[2]--;
+      m->triangles.push_back(t);
+      continue;
+    }
+  }
+  vsum[0] /= m->vertices.size();
+  vsum[1] /= m->vertices.size();
+  vsum[2] /= m->vertices.size();
+
+  /* Locate the model roughly at 0,0,0. */
+  for(unsigned int i = 0; i < m->vertices.size(); i++) {
+    m->vertices[i].p.x -= vsum[0];
+    m->vertices[i].p.y -= vsum[1];
+    m->vertices[i].p.z -= vsum[2];
+  }
+
+  printf("%zd vertices\n", m->vertices.size());
+  printf("%zd triangles\n", m->triangles.size());
+  return m;
+}
+
+/* Why not make a display list.. */
+void ObjMesh::Render(void) {
+  glBegin(GL_TRIANGLES);
+  for(unsigned int i = 0; i < triangles.size(); i++) {
+    ObjTriangle& t = triangles[i];
+
+    vector3f p0 = vertices[t.v[0]].p;
+    vector3f p1 = vertices[t.v[1]].p;
+    vector3f p2 = vertices[t.v[2]].p;
+
+    vector3f n = -vector3f::Normalize(vector3f::Cross(p0-p2, p0-p1));
+
+    glNormal3fv(&n[0]);
+    glVertex3fv(&vertices[t.v[0]].p[0]);
+    glVertex3fv(&vertices[t.v[1]].p[0]);
+    glVertex3fv(&vertices[t.v[2]].p[0]);
+  }
+  glEnd();
+}
+
diff --git a/src/objimport.h b/src/objimport.h
new file mode 100644
index 0000000..c658729
--- /dev/null
+++ b/src/objimport.h
@@ -0,0 +1,20 @@
+#pragma once
+#include <vector>
+#include "vector3.h"
+
+struct ObjVertex {
+  vector3f p;
+};
+
+struct ObjTriangle {
+  int v[3];
+};
+
+struct ObjMesh {
+  std::vector<ObjVertex> vertices;
+  std::vector<ObjTriangle> triangles;
+  void Render(void);
+};
+
+ObjMesh* import_obj_mesh(const char* filename);
+
diff --git a/src/planet.cpp b/src/planet.cpp
new file mode 100644
index 0000000..f547249
--- /dev/null
+++ b/src/planet.cpp
@@ -0,0 +1,77 @@
+#include "libs.h"
+#include "planet.h"
+#include "frame.h"
+
+Planet::Planet(StarSystem::SBody::SubType subtype) : Body() {
+  m_radius  = 6378135.0;
+  m_pos     = vector3d(0, 0, 0);
+  m_geom    = dCreateSphere(0, m_radius);
+  m_subtype = subtype;
+}
+
+Planet::~Planet(void) {
+  dGeomDestroy(m_geom);
+}
+
+vector3d Planet::GetPosition(void) {
+  return m_pos;
+}
+
+void Planet::SetPosition(vector3d p) {
+  m_pos = p;
+  dGeomSetPosition(m_geom, p.x, p.y, p.z);
+}
+
+void Planet::TransformToModelCoords(const Frame* camFrame) {
+  vector3d fpos = GetPositionRelTo(camFrame);
+  glTranslatef(m_pos[0]+fpos.x, m_pos[1]+fpos.y, m_pos[2]+fpos.z);
+}
+
+void Planet::SetRadius(double radius) {
+  m_radius = radius;
+  dGeomSphereSetRadius(m_geom, radius);
+}
+
+void Planet::Render(const Frame* a_camFrame) {
+  static GLUquadricObj* qobj = NULL;
+
+  if(!qobj) qobj = gluNewQuadric();
+
+  glPushMatrix();
+  glDisable(GL_DEPTH_TEST);
+
+  double rad = m_radius;
+  vector3d fpos = GetPositionRelTo(a_camFrame);
+
+  double apparent_size = rad / fpos.Length();
+  double len = fpos.Length();
+
+  while(len > 10000.0f) {
+    rad *= 0.25;
+    fpos = 0.25*fpos;
+    len *= 0.25;
+  }
+
+  glTranslatef(fpos.x, fpos.y, fpos.z);
+  glColor3f(1, 1, 1);
+
+  if(apparent_size < 0.001) {
+    glDisable(GL_LIGHTING);
+    glPointSize(1.0);
+    glBegin(GL_POINTS);
+      glVertex3f(0, 0, 0);
+    glEnd();
+    glEnable(GL_LIGHTING);
+  } else {
+    gluSphere(qobj, rad, 100, 100);
+  }
+  glEnable(GL_DEPTH_TEST);
+  glPopMatrix();
+}
+
+void Planet::SetFrame(Frame* f) {
+  if(GetFrame()) GetFrame()->RemoveGeom(m_geom);
+  Body::SetFrame(f);
+  if(f) f->AddGeom(m_geom);
+}
+
diff --git a/src/planet.h b/src/planet.h
new file mode 100644
index 0000000..c56f2f7
--- /dev/null
+++ b/src/planet.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "body.h"
+#include "star_system.h"
+
+class Frame;
+class Planet : public Body {
+public:
+  Planet(StarSystem::SBody::SubType);
+  virtual ~Planet(void);
+  virtual void SetPosition(vector3d p);
+  virtual vector3d GetPosition(void);
+  void SetRadius(double radius);
+  double GetRadius(void) { return m_radius; }
+  virtual void Render(const Frame* camFrame);
+  virtual void TransformToModelCoords(const Frame* camFrame);
+  virtual void TransformCameraTo(void) {};
+  virtual void SetFrame(Frame* f);
+private:
+  vector3d m_pos;
+  double m_radius;
+  dGeomID m_geom;
+  StarSystem::SBody::SubType m_subtype;
+};
+
diff --git a/src/player.cpp b/src/player.cpp
new file mode 100644
index 0000000..82e2fa4
--- /dev/null
+++ b/src/player.cpp
@@ -0,0 +1,231 @@
+#include "l3d.h"
+#include "player.h"
+#include "frame.h"
+#include "space.h"
+#include "gui.h"
+#include "world_view.h"
+#include "space_station_view.h"
+
+#define DEG_2_RAD 0.0174532925
+
+Player::Player(void) : Ship() {
+  m_external_view_rotx = m_external_view_roty = 0;
+  m_external_view_dist = 200;
+}
+
+void Player::Render(const Frame* camFrame) {
+  if(L3D::GetCamType() == L3D::CAM_EXTERNAL) {
+    Ship::Render(camFrame);
+  } else {
+    glPushMatrix();
+    /* Could only rotate, since transform is zero (camFrame is at player origin). */
+    TransformToModelCoords(camFrame);
+    RenderLaserfire();
+    glPopMatrix();
+  }
+}
+
+void Player::SetDockedWith(SpaceStation* s) {
+  Ship::SetDockedWith(s);
+  if(s) {
+    L3D::SetView(L3D::spaceStationView);
+  }
+}
+
+vector3d Player::GetExternalViewTranslation(void) {
+  vector3d p = vector3d(0, 0, m_external_view_dist);
+  p = matrix4x4d::RotateXMatrix(-DEG_2_RAD*m_external_view_rotx) * p;
+  p = matrix4x4d::RotateYMatrix(-DEG_2_RAD*m_external_view_roty) * p;
+  matrix4x4d m;
+  GetRotMatrix(m);
+  p = m*p;
+  //printf("%f,%f,%f\n", p.x, p.y, p.z);
+  return p;
+}
+
+void Player::ApplyExternalViewRotation(void) {
+  //glTranslatef(0, 0, m_external_view_dist);
+  glRotatef(-m_external_view_rotx, 1, 0, 0);
+  glRotatef(-m_external_view_roty, 0, 1, 0);
+}
+
+#define MOUSE_ACCEL 400
+
+void Player::AITurn(void) {
+  int mouseMotion[2];
+  float time_step = L3D::GetTimeStep();
+  float ts2 = time_step*time_step;
+
+  if(time_step == 0)  return;
+  if(GetDockedWith()) return;
+
+  L3D::GetMouseMotion(mouseMotion);
+  float mx, my;
+  mx = -mouseMotion[0]*time_step*MOUSE_ACCEL;
+  my =  mouseMotion[1]*time_step*MOUSE_ACCEL;
+
+  ClearThrusterState();
+  if(L3D::KeyState(SDLK_w)) SetThrusterState(ShipType::THRUSTER_REAR,  1.0f);
+  if(L3D::KeyState(SDLK_s)) SetThrusterState(ShipType::THRUSTER_FRONT, 1.0f);
+  if(L3D::KeyState(SDLK_2)) SetThrusterState(ShipType::THRUSTER_TOP,   1.0f);
+  if(L3D::KeyState(SDLK_x)) SetThrusterState(ShipType::THRUSTER_BOTTOM,1.0f);
+  if(L3D::KeyState(SDLK_a)) SetThrusterState(ShipType::THRUSTER_LEFT,  1.0f);
+  if(L3D::KeyState(SDLK_d)) SetThrusterState(ShipType::THRUSTER_RIGHT, 1.0f);
+
+  if(L3D::KeyState(SDLK_SPACE)) SetGunState(0,1);
+  else SetGunState(0,0);
+
+  /* No torques at huge time accels -- ODE hates it. */
+  if(time_step <= 10) {
+    /*
+     * Dividing by time step so controls don't go totally mental when
+     * used at 10x accel.
+     */
+     mx /= ts2;
+     my /= ts2;
+     if(L3D::MouseButtonState(3) && (mouseMotion[0] || mouseMotion[1])) {
+      SetAngThrusterState(1, mx);
+      SetAngThrusterState(0, my);
+     } else if(L3D::GetCamType() != L3D::CAM_EXTERNAL) {
+      float tq = 100/ts2;
+      float ax = 0;
+      float ay = 0;
+      if(L3D::KeyState(SDLK_LEFT))   ay +=  1;
+      if(L3D::KeyState(SDLK_RIGHT))  ay += -1;
+      if(L3D::KeyState(SDLK_UP))     ax += -1;
+      if(L3D::KeyState(SDLK_DOWN))   ax +=  1;
+      SetAngThrusterState(2, 0);
+      SetAngThrusterState(1, ay);
+      SetAngThrusterState(0, ax);
+     }
+
+     /* Rotation damping. */
+     vector3d angDrag = GetAngularMomentum() * time_step;
+     dBodyAddTorque(m_body, -angDrag.x, -angDrag.y, -angDrag.z);
+  }
+  if(time_step > 10) {
+    dBodySetAngularVel(m_body, 0, 0, 0);
+    SetAngThrusterState(0, 0.0f);
+    SetAngThrusterState(1, 0.0f);
+    SetAngThrusterState(2, 0.0f);
+  }
+  if(L3D::GetCamType() == L3D::CAM_EXTERNAL) {
+    if(L3D::KeyState(SDLK_UP))     m_external_view_rotx -= 1;
+    if(L3D::KeyState(SDLK_DOWN))   m_external_view_rotx += 1;
+    if(L3D::KeyState(SDLK_LEFT))   m_external_view_roty -= 1;
+    if(L3D::KeyState(SDLK_EQUALS)) m_external_view_dist -= 10;
+    if(L3D::KeyState(SDLK_MINUS))  m_external_view_dist += 10;
+    m_external_view_dist = MAX(50, m_external_view_dist);
+  }
+  Ship::AITurn();
+}
+
+#define HUD_CROSSHAIR_SIZE  24.0f
+
+void Player::DrawHUD(const Frame* cam_frame) {
+  GLdouble modelMatrix[16];
+  GLdouble projMatrix[16];
+  GLint viewport[4];
+
+  glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
+  glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
+  glGetIntegerv(GL_VIEWPORT, viewport);
+
+  const dReal* vel = dBodyGetLinearVel(m_body);
+
+  const matrix4x4d& rot = L3D::world_view->viewingRotation;
+  vector3d loc_v = rot*vector3d(vel[0], vel[1], vel[2]);
+
+  Gui::Screen::EnterOrtho();
+  glColor3f(.7, .7, .7);
+
+  {
+    for(std::list<Body*>::iterator i = Space::bodies.begin(); i != Space::bodies.end(); ++i) {
+      Body* b = *i;
+      if(b == this) continue;
+      vector3d _pos = b->GetPositionRelTo(cam_frame);
+      vector3d cam_coord = rot*_pos;
+
+      if(cam_coord.z < 0)
+        if(Gui::Screen::Project(_pos.x, _pos.y, _pos.z, modelMatrix, projMatrix,
+                                viewport, &_pos.x, &_pos.y, &_pos.z)) {
+          Gui::Screen::RenderLabel(b->GetLabel(), _pos.x, _pos.y);
+        }
+    }
+  }
+  GLdouble pos[3];
+
+  const float sz = HUD_CROSSHAIR_SIZE;
+  /* If velocity vector is in front ofus. Draw indicator. */
+  if(loc_v.z < 0) {
+    if(Gui::Screen::Project(vel[0],vel[1],vel[2], modelMatrix, projMatrix, viewport,
+                            &pos[0], &pos[1], &pos[2])) {
+      glBegin(GL_LINES);
+        glVertex2f(pos[0]-sz, pos[1]-sz);
+        glVertex2f(pos[0]-0.5*sz, pos[1]-0.5*sz);
+
+        glVertex2f(pos[0]+sz, pos[1]-sz);
+        glVertex2f(pos[0]+0.5*sz, pos[1]-0.5*sz);
+
+        glVertex2f(pos[0]+sz, pos[1]+sz);
+        glVertex2f(pos[0]+0.5*sz, pos[1]+0.5*sz);
+
+        glVertex2f(pos[0]-sz, pos[1]+sz);
+        glVertex2f(pos[0]-0.5*sz, pos[1]+0.5*sz);
+      glEnd();
+    }
+  }
+
+  if(L3D::GetCamType() == L3D::CAM_FRONT) {
+    /* Normal crosshairs. */
+    float px = Gui::Screen::GetWidth()/2.0;
+    float py = Gui::Screen::GetHeight()/2.0;
+    glBegin(GL_LINES);
+      glVertex2f(px-sz, py);
+      glVertex2f(px-0.5*sz, py);
+
+      glVertex2f(px+sz, py);
+      glVertex2f(px+0.5*sz, py);
+
+      glVertex2f(px, py-sz);
+      glVertex2f(px, py-0.5*sz);
+
+      glVertex2f(px, py+sz);
+      glVertex2f(px, py+0.5*sz);
+    glEnd();
+  }
+
+  {
+    char buf[1024];
+    glPushMatrix();
+    glTranslatef(0, 440, 0);
+    vector3d pos = GetPosition();
+    vector3d abs_pos = GetPositionRelTo(Space::GetRootFrame());
+    const char* rel_to = (GetFrame() ? GetFrame()->GetLabel() : "System");
+    snprintf(buf, sizeof(buf), "Pos: %.1f,%.1f,%.1f\n"
+             "AbsPos: %.1f,%.1f,%.1f\n"
+             "Rel-to: %s",
+             pos.x, pos.y, pos.z,
+             abs_pos.x, abs_pos.y, abs_pos.z,
+             rel_to);
+    Gui::Screen::RenderString(buf);
+    glPopMatrix();
+  }
+
+  {
+    double _vel = sqrt(vel[0]*vel[0] + vel[1]*vel[1] + vel[2]*vel[2]);
+    char buf[128];
+    if(_vel > 1000) {
+      snprintf(buf, sizeof(buf), "Velocity: %.2f km/s", _vel*0.001);
+    } else {
+      snprintf(buf, sizeof(buf), "Velocity: %.0f m/s", _vel);
+    }
+    glPushMatrix();
+    glTranslatef(2, 66, 0);
+    Gui::Screen::RenderString(buf);
+    glPopMatrix();
+  }
+
+  Gui::Screen::LeaveOrtho();
+}
+
diff --git a/src/rigid_body.cpp b/src/rigid_body.cpp
new file mode 100644
index 0000000..00bde01
--- /dev/null
+++ b/src/rigid_body.cpp
@@ -0,0 +1,35 @@
+#include "libs.h"
+#include "rigid_body.h"
+#include "space.h"
+#include "objimport.h"
+#include "frame.h"
+
+RigidBody::RigidBody(void) : StaticRigidBody() {
+  m_flags = Body::FLAG_CAN_MOVE_FRAME;
+  m_body  = dBodyCreate(Space::world);
+  dMassSetBox(&m_mass, 1, 50, 50, 50);
+  dMassAdjust(&m_mass, 1.0f);
+
+  dBodySetMass(m_body, &m_mass);
+  dGeomSetBody(m_geom, m_body);
+  SetPosition(vector3d(0,0,0));
+}
+
+vector3d RigidBody::GetAngularMomentum(void) {
+  matrix4x4d I;
+  I.LoadFromOdeMatrix(m_mass.I);
+  return I * vector3d(dBodyGetAngularVel(m_body));
+}
+
+RigidBody::~RigidBody(void) {
+  dBodyDestroy(m_body);
+}
+
+void RigidBody::SetVelocity(vector3d v) {
+  dBodySetLinearVel(m_body, v.x, v.y, v.z);
+}
+
+void RigidBody::SetAngVelocity(vector3d v) {
+  dBodySetAngularVel(m_body, v.x, v.y, v.z);
+}
+
diff --git a/src/rigid_body.h b/src/rigid_body.h
new file mode 100644
index 0000000..6aae5ac
--- /dev/null
+++ b/src/rigid_body.h
@@ -0,0 +1,26 @@
+#pragma once
+#include "body.h"
+#include "static_rigid_body.h"
+#include "vector3.h"
+#include "matrix4x4.h"
+
+class ObjMesh;
+
+class RigidBody: public StaticRigidBody {
+public:
+  RigidBody(void);
+  virtual ~RigidBody(void);
+  void SetVelocity(vector3d v);
+  void SetAngVelocity(vector3d v);
+  void SetMesh(ObjMesh* m);
+  virtual bool OnCollision(Body* b) { return true; }
+  vector3d GetAngularMomentum(void);
+
+  dBodyID m_body;
+  dMass   m_mass;
+
+protected:
+
+private:
+  ObjMesh* m_mesh;
+};
diff --git a/src/sbre/Makefile.am b/src/sbre/Makefile.am
new file mode 100644
index 0000000..6bd15db
--- /dev/null
+++ b/src/sbre/Makefile.am
@@ -0,0 +1,6 @@
+#  Process this file with automake to produce Makefile.in
+
+noinst_LIBRARIES = libsbre.a
+libsbre_a_SOURCES = brender.cpp models.cpp primfunc.cpp simtriang.cpp transp.cpp jjvector.cpp
+
+include_HEADERS = fastmath.h sbre.h sbre_int.h sbre_anim.h jjtypes.h jjvector.h
diff --git a/src/sbre/brender.cpp b/src/sbre/brender.cpp
new file mode 100644
index 0000000..5225347
--- /dev/null
+++ b/src/sbre/brender.cpp
@@ -0,0 +1,220 @@
+#include <SDL_opengl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include "sbre.h"
+#include "sbre_int.h"
+#include "sbre_anim.h"
+
+float ResolveAnim(ObjParams* pObjParam, uint16 type) {
+  const AnimFunc* pFunc = pAFunc+type;
+  float anim = pObjParam->pAnim[pFunc->src];
+  anim = anim*anim*anim*pFunc->order3 + anim*anim*pFunc->order2
+          + anim*pFunc->order1 + pFunc->order0;
+
+  if(pFunc->mod == AMOD_REF) {
+    anim = fmod(anim, 2.002f);
+    if(anim > 1.0f) anim = 2.002f - anim;
+  } else if(pFunc->mod == AMOD_MOD1) anim = fmod(anim, 1.001f);
+
+  if(anim < 0.0f) anim = 0.0f;
+  if(anim > 1.0f) anim = 1.0f;
+  return anim;
+}
+
+static Vector pUnitVec[6] = {
+  {  1.0f, 0.0f, 0.0f }, { 0.0f,  1.0f, 0.0f }, { 0.0f, 0.0f,  1.0f },
+  { -1.0f, 0.0f, 0.0f }, { 0.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, -1.0f },
+};
+
+static void ResolveVertices(Model* pMod, Vector* pRes, ObjParams* pObjParam) {
+  Vector* pCur = pRes;
+  Vector tv1, tv2, xax, yax;
+  float anim;
+
+  int i; for(i=0; i < 6; i++) *(pCur++) = pUnitVec[i];
+
+  PlainVertex* pPVtx = pMod->pPVtx;
+  for(i = 0; i < pMod->numPVtx-6; i++, pCur++, pPVtx++) {
+    switch(pPVtx->type) {
+      case VTYPE_PLAIN: *pCur = pPVtx->pos; break;
+      case VTYPE_DIR:   VecNorm(&pPVtx->pos, pCur); break;
+    }
+  }
+
+  pCur = pRes + pMod->cvStart;
+  CompoundVertex* pCVtx = pMod->pCVtx;
+  for(i = 0; i < pMod->numCVtx; i++, pCur++, pCVtx++) {
+    switch(pCVtx->type) {
+    case VTYPE_NORM:
+      VecSub(pRes+pCVtx->pParam[2], pRes+pCVtx->pParam[1], &tv1);
+      VecSub(pRes+pCVtx->pParam[0], pRes+pCVtx->pParam[1], &tv2);
+      VecCross(&tv1, &tv2, pCur);
+      VecNorm(pCur, pCur);
+      break;
+    case VTYPE_CROSS:
+      VecCross(pRes+pCVtx->pParam[0], pRes+pCVtx->pParam[1], pCur);
+      VecNorm(pCur, pCur);
+      break;
+    case VTYPE_ANIMLIN:
+      anim = ResolveAnim(pObjParam, pCVtx->pParam[4]);
+      *pCur = pRes[pCVtx->pParam[0]];
+      VecSub(pRes+pCVtx->pParam[1], pCur, &tv1);
+      VecMul(&tv1, anim, &tv1);
+      VecAdd(&tv1, pCur, pCur);
+      break;
+    case VTYPE_ANIMCUBIC:
+      anim = ResolveAnim(pObjParam, pCVtx->pParam[4]);
+      ResolveCubicSpline(pRes+pCVtx->pParam[0], pRes+pCVtx->pParam[1],
+          pRes+pCVtx->pParam[2], pRes+pCVtx->pParam[3], anim, pCur);
+      break;
+    case VTYPE_ANIMHERM:
+      anim = ResolveAnim(pObjParam, pCVtx->pParam[4]);
+      ResolveHermiteSpline(pRes+pCVtx->pParam[0], pRes+pCVtx->pParam[1],
+          pRes+pCVtx->pParam[2], pRes+pCVtx->pParam[3], anim, pCur);
+      break;
+    case VTYPE_ANIMROTATE:
+      anim = ResolveAnim(pObjParam, pCVtx->pParam[4]) * 2.0f * 3.141592f;
+      xax = pRes[pCVtx->pParam[1]];
+      VecCross(pRes+pCVtx->pParam[0], &xax, &yax);
+      VecMul(&xax, sin(anim), &tv1);
+      VecMul(&yax, cos(anim), &tv2);
+      VecAdd(&tv1, &tv2, pCur);
+      break;
+    default: *pCur = zero_vector; break;
+    }
+  }
+}
+
+static float g_dn, g_df;
+static int g_wireframe = 0;
+
+void sbreSetViewport(int w, int h, int d, float zn, float zf, float dn, float df) {
+  glViewport(0, 0, w, h);
+
+  float pProjMat[16] = { 1.0f, 0.0f, 0.0f, 0.0f,
+                         0.0f, 1.0f, 0.0f, 0.0f,
+                         0.0f, 0.0f, 1.0f, 1.0f,
+                         0.0f, 0.0f, 1.0f, 0.0f };
+
+  pProjMat[ 0] = (2.0f*d)/w;
+  pProjMat[ 5] = (2.0f*d)/h;
+  pProjMat[10] = (zf+zn)/(zf-zn);
+  pProjMat[14] = (-2.0f*zn*zf)/(zf-zn);
+
+  glMatrixMode(GL_PROJECTION);
+  glLoadMatrixf(pProjMat);
+  glDepthRange(dn+SBRE_ZBIAS, df);
+  g_dn = dn; g_df = df;
+}
+
+void sbreSetDirLight(float* pColor, float* pDir) {
+  glMatrixMode(GL_MODELVIEW);
+  glLoadIdentity();
+
+  float pColor4[4]  = { 0, 0, 0, 1.0f };
+  float pDir4[4]    = { pDir[0], pDir[1], pDir[2], 0.0f };
+  for(int i = 0; i < 3; i++) pColor4[i] = SBRE_AMB + pColor[i] * (1.0f-SBRE_AMB);
+
+  glEnable(GL_LIGHT0);
+  glLightfv(GL_LIGHT0, GL_POSITION, pDir4);
+  glLightfv(GL_LIGHT0, GL_DIFFUSE, pColor4);
+  glLightfv(GL_LIGHT0, GL_SPECULAR, pColor4);
+}
+
+void sbreSetWireframe(int val) {
+  g_wireframe = val;
+}
+
+void SetGeneralState(void) {
+  float ambient[4] = { SBRE_AMB, SBRE_AMB, SBRE_AMB, 1.0f };
+  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
+
+  glDisable(GL_TEXTURE_2D);
+  glFrontFace(GL_CW);
+  glEnable(GL_DEPTH_TEST);
+
+  if(g_wireframe) {
+    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+    glDisable(GL_CULL_FACE);
+  } else {
+    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+    glEnable(GL_CULL_FACE);
+  }
+
+  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+  glDisableClientState(GL_COLOR_ARRAY);
+}
+
+void SetOpaqueState(void) {
+  glDisable(GL_BLEND);
+  glEnable(GL_LIGHTING);
+  glEnable(GL_NORMALIZE);
+
+  glEnableClientState(GL_VERTEX_ARRAY);
+  glEnableClientState(GL_NORMAL_ARRAY);
+}
+
+void SetTransState(void) {
+  glEnable(GL_BLEND);
+  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+  glDisable(GL_LIGHTING);
+  glDisable(GL_NORMALIZE);
+
+  glEnableClientState(GL_VERTEX_ARRAY);
+  glDisableClientState(GL_NORMAL_ARRAY);
+}
+
+void sbreRenderModel(Vector* pPos, Matrix* pOrient, int model, ObjParams* pParam,
+                     float s, Vector* pCompos) {
+  Model* pModel = ppModel[model];
+  s *= pModel->scale;
+  float pMV[16];
+  pMV[ 0] = s*pOrient->x1; pMV[1] = s*pOrient->y1; pMV[ 2] = s*pOrient->z1; pMV[ 3] = 0.0f;
+  pMV[ 4] = s*pOrient->x2; pMV[5] = s*pOrient->y2; pMV[ 6] = s*pOrient->z2; pMV[ 7] = 0.0f;
+  pMV[ 8] = s*pOrient->x3; pMV[9] = s*pOrient->y3; pMV[10] = s*pOrient->z3; pMV[11] = 0.0f;
+  pMV[12] = pPos->x; pMV[13] = pPos->y; pMV[14] = pPos->z; pMV[15] = 1.0f;
+  glMatrixMode(GL_MODELVIEW);
+  glLoadMatrixf(pMV);
+
+  Vector* pVtx = (Vector*)alloca(sizeof(Vector)*(pModel->cvStart+pModel->numCVtx));
+
+  ResolveVertices(pModel, pVtx, pParam);
+
+  RState rstate;
+  rstate.pVtx = pVtx;
+  rstate.objpos = *pPos;
+  rstate.objorient = *pOrient;
+  rstate.scale = s;
+  rstate.pModel = pModel;
+  rstate.pObjParam = pParam;
+  rstate.dn = g_dn;
+  rstate.df = g_df;
+  MatTVecMult(pOrient, pPos, &rstate.campos);
+  VecInv(&rstate.campos, &rstate.campos);
+  if(pCompos) rstate.compos = *pCompos;
+  else rstate.compos = zero_vector;
+
+  if(pModel->numCache && !pModel->ppVCache) {
+    pModel->pNumVtx = (int*)calloc(pModel->numCache, sizeof(int));
+    pModel->pNumIdx = (int*)calloc(pModel->numCache, sizeof(int));
+    pModel->ppVCache = (Vector**)calloc(pModel->numCache, sizeof(Vector*));
+    pModel->ppICache = (uint16**)calloc(pModel->numCache, sizeof(uint16*));
+  }
+
+  SetGeneralState();
+  SetOpaqueState();
+
+  uint16* pData = pModel->pData;
+  while(*pData != PTYPE_END) {
+    pData += pPrimFuncTable[*pData & 0xff] (pData, pModel, &rstate);
+  }
+
+  SetTransState();
+  RenderTransparencies(&rstate);
+
+  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+  glEnable(GL_CULL_FACE);
+}
+
diff --git a/src/sbre/fastmath.h b/src/sbre/fastmath.h
new file mode 100644
index 0000000..45b3ded
--- /dev/null
+++ b/src/sbre/fastmath.h
@@ -0,0 +1,80 @@
+#pragma once
+
+extern int g_pInvTable[64];
+extern int g_pInvTableLow[64];
+extern int g_pInvTableHigh[64];
+
+extern int g_pSqrtTable[64];
+extern int g_pSqrtTableLow[64];
+extern int g_pSqrtTableHigh[64];
+
+extern int g_pISqrtTable[64];
+extern int g_pISqrtTableLow[64];
+extern int g_pISqrtTableHigh[64];
+
+
+inline float FastInv(float f) {
+  int exp = 0x7e800000 - (0x7f800000 & *(int *)&f);
+  int mant = g_pInvTable[(*(int *)&f >> 17) & 0x3f];
+  int combo = mant | exp | (0x80000000 & *(int *)&f);
+  return *(float *)&combo;
+}
+
+inline float FastInvLow(float f) {
+  int exp = 0x7e800000 - (0x7f800000 & *(int *)&f);
+  int mant = g_pInvTableLow[(*(int *)&f >> 17) & 0x3f];
+  int combo = mant | exp | (0x80000000 & *(int *)&f);
+  return *(float *)&combo;
+}
+
+/* Fractionally off maximum for 2^n input. */
+inline float FastInvHigh(float f) {
+  int exp = 0x7e800000 - (0x7f800000 & *(int *)&f);
+  int mant = g_pInvTableHigh[(*(int *)&f >> 17) & 0x3f];
+  int combo = mant | exp | (0x80000000 & *(int *)&f);
+  return *(float *)&combo;
+}
+
+inline float FastSqrt(float f) {
+  int exp = 0x7f800000 & ((*(int *)&f >> 1) + 0x1fc00000);
+  int mant = g_pSqrtTable[(*(int *)&f >> 18) & 0x3f] | exp;
+  return *(float *)&mant;
+}
+
+inline float FastSqrtLow(float f) {
+  int exp = 0x7f800000 & ((*(int *)&f >> 1) + 0x1fc00000);
+  int mant = g_pSqrtTableLow[(*(int *)&f >> 18) & 0x3f] | exp;
+  return *(float *)&mant;
+}
+
+inline float FastSqrtHigh(float f) {
+  int exp = 0x7f800000 & ((*(int *)&f >> 1) + 0x1fc00000);
+  int mant = g_pSqrtTableHigh[(*(int *)&f >> 18) & 0x3f] | exp;
+  return *(float *)&mant;
+}
+
+inline float FastInvSqrt(float f) {
+  int exp = 0x5e800000 - ((*(int *)&f >> 1) & 0x3f800000);
+  int mant = g_pISqrtTable[(*(int *)&f >> 18) & 0x3f] + exp;
+  return *(float *)&mant;
+}
+
+inline float FastInvSqrtLow(float f) {
+  int exp = 0x5e800000 - ((*(int *)&f >> 1) & 0x3f800000);
+  int mant = g_pISqrtTableLow[(*(int *)&f >> 18) & 0x3f] + exp;
+  return *(float *)&mant;
+}
+
+inline float FastInvSqrtHigh (float f) {
+  int exp = 0x5e800000 - ((*(int *)&f >> 1) & 0x3f800000);
+  int mant = g_pISqrtTableHigh[(*(int *)&f >> 18) & 0x3f] + exp;
+  return *(float *)&mant;
+}
+
+inline void VecNormFast(Vector* v1, Vector* res) {
+  float temp = FastInvSqrt(VecDot (v1, v1));
+  res->x = v1->x * temp;
+  res->y = v1->y * temp;
+  res->z = v1->z * temp;
+}
+
diff --git a/src/sbre/jjtypes.h b/src/sbre/jjtypes.h
new file mode 100644
index 0000000..47cedfe
--- /dev/null
+++ b/src/sbre/jjtypes.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <SDL_stdinc.h>
+typedef Uint32 uint32;
+typedef Uint16 uint16;
+typedef Uint8  uint8;
+
diff --git a/src/sbre/jjvector.cpp b/src/sbre/jjvector.cpp
new file mode 100644
index 0000000..749f9d5
--- /dev/null
+++ b/src/sbre/jjvector.cpp
@@ -0,0 +1,207 @@
+#include <math.h>
+#include <float.h>
+
+#include "jjtypes.h"
+#include "jjvector.h"
+#include "fastmath.h"
+
+static Vector xaxis = { 1.0f, 0.0f, 0.0f };
+static Vector yaxis = { 0.0f, 1.0f, 0.0f };
+
+void MatToAxisAng(Matrix* m, AxisAng* aa) {
+  Vector v2 = { m->x2, m->y2, m->z2 };
+  aa->v.x = m->x1; aa->v.y = m->y1; aa->v.z = m->z1;
+  VecNorm(&aa->v, &aa->v);
+
+  Vector axis2, axis3;
+  float tv = VecDot(&xaxis, &aa->v);
+  if(tv*tv < 0.5) VecCross(&aa->v, &xaxis, &axis2);
+  else VecCross(&aa->v, &yaxis, &axis2);
+  VecNorm(&axis2, &axis2);
+  VecCross(&aa->v, &axis2, &axis3);
+
+  float ta = VecDot (&v2, &axis2);
+  //_ASSERTE (ta < 1.0f && ta > -1.0f);
+  if(ta > 1.0f) ta = 1.0f;
+  if(ta < -1.0f) ta = -1.0f;
+
+  aa->a = (float) acos (ta);
+  if(VecDot(&v2, &axis3) < 0.0f) aa->a *= -1.0f;
+}
+
+void AxisAngToMat(AxisAng* aa, Matrix* m) {
+  Vector v2, v3;
+  m->x1 = aa->v.x; m->y1 = aa->v.y; m->z1 = aa->v.z;
+
+  Vector axis2, axis3;
+  float tv = VecDot(&xaxis, &aa->v);
+  if (tv*tv < 0.5) VecCross(&aa->v, &xaxis, &axis2);
+  else VecCross(&aa->v, &yaxis, &axis2);
+  VecNorm(&axis2, &axis2);
+  VecCross(&aa->v, &axis2, &axis3);
+
+  VecMul(&axis2, cos (aa->a), &axis2);
+  VecMul(&axis3, sin (aa->a), &axis3);
+  VecAdd(&axis2, &axis3, &v2);
+  VecCross(&aa->v, &v2, &v3);
+
+  m->x2 = v2.x; m->y2 = v2.y; m->z2 = v2.z;
+  m->x3 = v3.x; m->y3 = v3.y; m->z3 = v3.z;
+}
+
+
+void MatToQuat(Matrix* m1, Quaternion* res) {
+  float trace = m1->x1 + m1->y2 + m1->z3 + 1.0f;
+  if(trace > 0.0f) {
+    float qs = 0.5f / sqrtf (trace);
+    res->w = 0.25f / qs;
+    res->x = (m1->z2 - m1->y3) * qs;
+    res->y = (m1->x3 - m1->z1) * qs;
+    res->z = (m1->y1 - m1->x2) * qs;
+  } else {
+    if(m1->x1 > m1->y2 && m1->x1 > m1->z3) {
+      float qs = 2.0f * sqrtf (1.0f + m1->x1 - m1->y2 - m1->z3);
+      res->x = 0.25f * qs;
+      res->y = (m1->x2 + m1->y1) / qs;
+      res->z = (m1->x3 + m1->z1) / qs;
+      res->w = (m1->z2 - m1->y3) / qs;
+    } else if(m1->y2 > m1->z3) {
+      float qs = 2.0f * sqrtf (1.0f + m1->y2 - m1->x1 - m1->z3);
+      res->x = (m1->x2 + m1->y1) / qs;
+      res->y = 0.25f * qs;
+      res->z = (m1->y3 + m1->z2) / qs;
+      res->w = (m1->x3 - m1->z1) / qs;
+    } else {
+      float qs = 2.0f * sqrtf (1.0f + m1->z3 - m1->x1 - m1->y2);
+      res->x = (m1->x3 + m1->z1) / qs;
+      res->y = (m1->y3 + m1->z2) / qs;
+      res->z = 0.25f * qs;
+      res->w = (m1->y1 - m1->x2) / qs;
+    }
+  }
+}
+
+void QuatToMat(Quaternion* q1, Matrix* res) {
+  double sqw = q1->w*q1->w;
+  double sqx = q1->x*q1->x;
+  double sqy = q1->y*q1->y;
+  double sqz = q1->z*q1->z;
+  res->x1 = (float)(sqx - sqy - sqz + sqw);
+  res->y2 = (float)(-sqx + sqy - sqz + sqw);
+  res->z3 = (float)(-sqx - sqy + sqz + sqw);
+
+  double tmp1 = q1->x*q1->y;
+  double tmp2 = q1->z*q1->w;
+  res->y1 = (float)(2.0 * (tmp1 + tmp2));
+  res->x2 = (float)(2.0 * (tmp1 - tmp2));
+
+  tmp1 = q1->x*q1->z;
+  tmp2 = q1->y*q1->w;
+  res->z1 = (float)(2.0 * (tmp1 - tmp2));
+  res->x3 = (float)(2.0 * (tmp1 + tmp2));
+
+  tmp1 = q1->y*q1->z;
+  tmp2 = q1->x*q1->w;
+  res->z2 = (float)(2.0 * (tmp1 + tmp2));
+  res->y3 = (float)(2.0 * (tmp1 - tmp2));
+}
+
+
+int g_pInvTable[64] = {
+  0x7e03f8, 0x7a232d, 0x76603e, 0x72b9d6, 0x6f2eb7, 0x6bbdb3, 0x6865ac, 0x652598,
+  0x61fc78, 0x5ee95c, 0x5beb62, 0x5901b2, 0x562b81, 0x53680d, 0x50b6a0, 0x4e168a,
+  0x4b8728, 0x4907da, 0x46980c, 0x443730, 0x41e4bc, 0x3fa030, 0x3d6910, 0x3b3ee7,
+  0x392144, 0x370fbb, 0x3509e7, 0x330f63, 0x311fd4, 0x2f3ade, 0x2d602b, 0x2b8f6a,
+  0x29c84a, 0x280a81, 0x2655c4, 0x24a9cf, 0x23065e, 0x216b31, 0x1fd80a, 0x1e4cad,
+  0x1cc8e1, 0x1b4c70, 0x19d723, 0x1868c8, 0x17012e, 0x15a025, 0x144581, 0x12f114,
+  0x11a2b4, 0x105a38, 0x0f177a, 0x0dda52, 0x0ca29c, 0x0b7034, 0x0a42f8, 0x091ac7,
+  0x07f781, 0x06d905, 0x05bf37, 0x04a9fa, 0x039930, 0x028cc0, 0x01848e, 0x008081,
+};
+
+int g_pInvTableLow[64] = {
+  0x7c0fc3, 0x783e11, 0x74898f, 0x70f0f3, 0x6d7305, 0x6a0ea3, 0x66c2b6, 0x638e3a,
+  0x60703a, 0x5d67ca, 0x5a740f, 0x579437, 0x54c77c, 0x520d22, 0x4f6476, 0x4cccce,
+  0x4a4589, 0x47ce0e, 0x4565ca, 0x430c32, 0x40c0c2, 0x3e82fb, 0x3c5265, 0x3a2e8d,
+  0x381704, 0x360b62, 0x340b42, 0x321644, 0x302c0c, 0x2e4c42, 0x2c7692, 0x2aaaac,
+  0x28e840, 0x272f06, 0x257eb6, 0x23d70b, 0x2237c4, 0x20a0a1, 0x1f1167, 0x1d89d9,
+  0x1c09c1, 0x1a90e9, 0x191f1b, 0x17b427, 0x164fdb, 0x14f20a, 0x139a86, 0x124925,
+  0x10fdbd, 0x0fb825, 0x0e7836, 0x0d3dcc, 0x0c08c1, 0x0ad8f4, 0x09ae41, 0x088889,
+  0x0767ac, 0x064b8b, 0x053409, 0x042109, 0x03126f, 0x020821, 0x010205, 0x000001,
+};
+
+int g_pInvTableHigh[64] = {
+  0x7fffff, 0x7c0fc1, 0x783e10, 0x74898d, 0x70f0f1, 0x6d7304, 0x6a0ea1, 0x66c2b4,
+  0x638e39, 0x607038, 0x5d67c9, 0x5a740e, 0x579436, 0x54c77b, 0x520d21, 0x4f6475,
+  0x4ccccd, 0x4a4588, 0x47ce0c, 0x4565c8, 0x430c31, 0x40c0c1, 0x3e82fa, 0x3c5264,
+  0x3a2e8c, 0x381703, 0x360b61, 0x340b41, 0x321643, 0x302c0b, 0x2e4c41, 0x2c7692,
+  0x2aaaab, 0x28e83f, 0x272f05, 0x257eb5, 0x23d70a, 0x2237c3, 0x20a0a1, 0x1f1166,
+  0x1d89d9, 0x1c09c1, 0x1a90e8, 0x191f1a, 0x17b426, 0x164fda, 0x14f209, 0x139a86,
+  0x124925, 0x10fdbc, 0x0fb824, 0x0e7835, 0x0d3dcb, 0x0c08c1, 0x0ad8f3, 0x09ae41,
+  0x088889, 0x0767ab, 0x064b8a, 0x053408, 0x042108, 0x03126f, 0x020821, 0x010204,
+};
+
+int g_pSqrtTable[64] = {
+  0x366d96, 0x3936a1, 0x3bf51b, 0x3ea979, 0x415428, 0x43f58d, 0x468e06, 0x491dec,
+  0x4ba592, 0x4e2545, 0x509d4e, 0x530df3, 0x557775, 0x57da10, 0x5a35fe, 0x5c8b77,
+  0x5edaae, 0x6123d4, 0x636719, 0x65a4a9, 0x67dcae, 0x6a0f50, 0x6c3cb7, 0x6e6507,
+  0x708862, 0x72a6ea, 0x74c0c0, 0x76d603, 0x78e6ce, 0x7af340, 0x7cfb72, 0x7eff7f,
+  0x00ff02, 0x02f734, 0x04e7ee, 0x06d182, 0x08b43d, 0x0a9067, 0x0c6641, 0x0e360b,
+  0x100000, 0x11c456, 0x138341, 0x153cf2, 0x16f196, 0x18a15a, 0x1a4c65, 0x1bf2df,
+  0x1d94ec, 0x1f32af, 0x20cc4a, 0x2261dc, 0x23f383, 0x25815a, 0x270b7f, 0x28920a,
+  0x2a1514, 0x2b94b5, 0x2d1104, 0x2e8a16, 0x300000, 0x3172d6, 0x32e2ac, 0x344f93,
+};
+
+int g_pSqrtTableLow[64] = {
+  0x3504f3, 0x37d375, 0x3a9728, 0x3d5087, 0x400000, 0x42a5fe, 0x4542e1, 0x47d706,
+  0x4a62c2, 0x4ce665, 0x4f623a, 0x51d689, 0x544395, 0x56a99b, 0x5908d9, 0x5b6186,
+  0x5db3d7, 0x600000, 0x624630, 0x648695, 0x66c15a, 0x68f6a9, 0x6b26a9, 0x6d517f,
+  0x6f7751, 0x71983e, 0x73b46a, 0x75cbf2, 0x77def6, 0x79ed91, 0x7bf7df, 0x7dfdfc,
+  0x000000, 0x01fc10, 0x03f07b, 0x05dd98, 0x07c3b6, 0x09a320, 0x0b7c1a, 0x0d4ee4,
+  0x0f1bbd, 0x10e2dc, 0x12a476, 0x1460be, 0x1617e3, 0x17ca11, 0x197774, 0x1b2032,
+  0x1cc471, 0x1e6455, 0x200000, 0x219792, 0x232b2b, 0x24bae7, 0x2646e1, 0x27cf36,
+  0x2953fd, 0x2ad550, 0x2c5345, 0x2dcdf3, 0x2f456f, 0x30b9cc, 0x322b20, 0x33997c,
+};
+
+int g_pSqrtTableHigh[64] = {
+  0x37d374, 0x3a9728, 0x3d5086, 0x3fffff, 0x42a5fd, 0x4542e1, 0x47d705, 0x4a62c1,
+  0x4ce664, 0x4f623a, 0x51d689, 0x544394, 0x56a99b, 0x5908d8, 0x5b6185, 0x5db3d7,
+  0x5fffff, 0x62462f, 0x648694, 0x66c15a, 0x68f6a8, 0x6b26a8, 0x6d517f, 0x6f7750,
+  0x71983e, 0x73b469, 0x75cbf2, 0x77def5, 0x79ed90, 0x7bf7df, 0x7dfdfb, 0x7fffff,
+  0x01fc0f, 0x03f07b, 0x05dd98, 0x07c3b6, 0x09a31f, 0x0b7c19, 0x0d4ee4, 0x0f1bbc,
+  0x10e2db, 0x12a475, 0x1460bd, 0x1617e2, 0x17ca11, 0x197773, 0x1b2031, 0x1cc470,
+  0x1e6454, 0x200000, 0x219792, 0x232b2b, 0x24bae6, 0x2646e1, 0x27cf35, 0x2953fd,
+  0x2ad550, 0x2c5345, 0x2dcdf3, 0x2f456e, 0x30b9cc, 0x322b20, 0x33997c, 0x3504f3,
+};
+
+int g_pISqrtTable[64] = {
+  0xb39f19, 0xb0eb96, 0xae565c, 0xabdd46, 0xa97e62, 0xa737f0, 0xa50855, 0xa2ee1d,
+  0xa0e7f5, 0x9ef4a4, 0x9d130e, 0x9b422c, 0x99810c, 0x97ced0, 0x962aa9, 0x9493d9,
+  0x9309af, 0x918b87, 0x9018c6, 0x8eb0e0, 0x8d534f, 0x8bff97, 0x8ab544, 0x8973e8,
+  0x883b1e, 0x870a87, 0x85e1c7, 0x84c08b, 0x83a682, 0x829362, 0x8186e2, 0x8080c1,
+  0x7e05ec, 0x7a33f9, 0x768cdc, 0x730d8a, 0x6fb345, 0x6c7b90, 0x69642a, 0x666b02,
+  0x638e39, 0x60cc16, 0x5e2305, 0x5b9193, 0x59166b, 0x56b051, 0x545e22, 0x521ed0,
+  0x4ff162, 0x4dd4ed, 0x4bc89b, 0x49cba2, 0x47dd45, 0x45fcd6, 0x4429af, 0x426337,
+  0x40a8de, 0x3efa1c, 0x3d5671, 0x3bbd67, 0x3a2e8c, 0x38a975, 0x372dbf, 0x35bb09,
+};
+
+int g_pISqrtTableLow[64] = {
+  0xb2416a, 0xaf9d54, 0xad166d, 0xaaaaab, 0xa85836, 0xa61d60, 0xa3f8a3, 0xa1e89c,
+  0x9fec04, 0x9e01b3, 0x9c2896, 0x9a5fb2, 0x98a61f, 0x96fb07, 0x955da2, 0x93cd3b,
+  0x924925, 0x90d0c3, 0x8f6381, 0x8e00d5, 0x8ca840, 0x8b5948, 0x8a137e, 0x88d677,
+  0x87a1d2, 0x867532, 0x85503e, 0x8432a5, 0x831c1a, 0x820c53, 0x81030a, 0x800000,
+  0x7c1765, 0x785b43, 0x74c868, 0x715bf0, 0x6e133f, 0x6aebf6, 0x67e3ee, 0x64f92f,
+  0x6229ed, 0x5f7483, 0x5cd76e, 0x5a514a, 0x57e0cf, 0x5584ce, 0x533c2e, 0x5105ec,
+  0x4ee116, 0x4ccccd, 0x4ac840, 0x48d2ac, 0x46eb5b, 0x4511a3, 0x4344e7, 0x418490,
+  0x3fd012, 0x3e26ec, 0x3c88a0, 0x3af4bb, 0x396acf, 0x37ea74, 0x36734a, 0x3504f4,
+};
+
+int g_pISqrtTableHigh[64] = {
+  0xb504f3, 0xb2416a, 0xaf9d53, 0xad166c, 0xaaaaab, 0xa85835, 0xa61d5f, 0xa3f8a2,
+  0xa1e89b, 0x9fec04, 0x9e01b3, 0x9c2896, 0x9a5fb2, 0x98a61f, 0x96fb06, 0x955da2,
+  0x93cd3a, 0x924925, 0x90d0c3, 0x8f6381, 0x8e00d5, 0x8ca83f, 0x8b5948, 0x8a137d,
+  0x88d677, 0x87a1d2, 0x867532, 0x85503e, 0x8432a5, 0x831c1a, 0x820c52, 0x81030a,
+  0x800000, 0x7c1764, 0x785b42, 0x74c867, 0x715bef, 0x6e133e, 0x6aebf5, 0x67e3ed,
+  0x64f92e, 0x6229ed, 0x5f7483, 0x5cd76e, 0x5a514a, 0x57e0cf, 0x5584cd, 0x533c2e,
+  0x5105ec, 0x4ee116, 0x4ccccd, 0x4ac83f, 0x48d2ab, 0x46eb5a, 0x4511a3, 0x4344e6,
+  0x41848f, 0x3fd012, 0x3e26eb, 0x3c889f, 0x3af4ba, 0x396ace, 0x37ea74, 0x36734a,
+};
diff --git a/src/sbre/jjvector.h b/src/sbre/jjvector.h
new file mode 100644
index 0000000..713be1a
--- /dev/null
+++ b/src/sbre/jjvector.h
@@ -0,0 +1,521 @@
+#pragma once
+#include <math.h>
+
+#define JJM_PI  3.141592653589793
+#define JJM_FPI 3.1415926f
+
+struct Vector {
+  float x, y, z;
+};
+
+struct DVector {
+  double x, y, z;
+};
+
+struct Quaternion {
+  float w, x, y, z;
+};
+
+struct AxisAng {
+  Vector v;
+  float a;
+};
+
+struct Plane64 {
+  DVector norm;
+  double dist;
+};
+
+struct Matrix {
+  float x1, x2, x3;  /* _11, _12, _13 */
+  float y1, y2, y3;  /* _21, _22, _23 */
+  float z1, z2, z3;  /* _31, _32, _33 */
+};
+
+struct DMatrix {
+  double x1, x2, x3;  /* _11, _12, _13 */
+  double y1, y2, y3;  /* _21, _22, _23 */
+  double z1, z2, z3;  /* _31, _32, _33 */
+};
+
+const Vector  zero_vector       = { 0, 0, 0 };
+const DVector zero_dvector      = { 0, 0, 0 };
+const Matrix  identity_matrix   = { 1, 0, 0, 0, 1, 0, 0, 0, 1 };
+const DMatrix identity_dmatrix  = {1, 0, 0, 0, 1, 0, 0, 0, 1};
+
+/* Conversion functions. */
+inline void DVecToVec(DVector* v1, Vector* v2) {
+  v2->x = (float)v1->x;
+  v2->y = (float)v1->y;
+  v2->z = (float)v1->z;
+}
+
+inline void VecToDVec(Vector* v1, DVector* v2) {
+  v2->x = v1->x;
+  v2->y = v1->y;
+  v2->z = v1->z;
+}
+
+inline void DMatToMat(DMatrix* m1, Matrix* m2) {
+  m2->x1 = (float)m1->x1;
+  m2->x2 = (float)m1->x2;
+  m2->x3 = (float)m1->x3;
+  m2->y1 = (float)m1->y1;
+  m2->y2 = (float)m1->y2;
+  m2->y3 = (float)m1->y3;
+  m2->z1 = (float)m1->z1;
+  m2->z2 = (float)m1->z2;
+  m2->z3 = (float)m1->z3;
+}
+
+inline void MatToDMat(Matrix* m1, DMatrix* m2) {
+  m2->x1 = m1->x1;
+  m2->x2 = m1->x2;
+  m2->x3 = m1->x3;
+  m2->y1 = m1->y1;
+  m2->y2 = m1->y2;
+  m2->y3 = m1->y3;
+  m2->z1 = m1->z1;
+  m2->z2 = m1->z2;
+  m2->z3 = m1->z3;
+}
+
+inline void DMatTrans(DMatrix* m1, DMatrix* m2) {
+  m2->x1 = m1->x1;
+  m2->x2 = m1->y1;
+  m2->x3 = m1->z1;
+  m2->y1 = m1->x2;
+  m2->y2 = m1->y2;
+  m2->y3 = m1->z2;
+  m2->z1 = m1->x3;
+  m2->z2 = m1->y3;
+  m2->z3 = m1->z3;
+}  
+
+inline void VecSet(float x, float y, float z, Vector* res) {
+  res->x = x;
+  res->y = y;
+  res->z = z;
+}
+
+inline void DVecSet(double x, double y, double z, DVector* res) {
+  res->x = x;
+  res->y = y;
+  res->z = z;
+}
+
+inline void VecRotate(Vector* v, Vector* res) {
+  res->x = v->y; res->y = v->z; res->z = v->x;
+}
+
+inline void VecRotate(DVector* v, DVector* res) {
+  res->x = v->y; res->y = v->z; res->z = v->x;
+}
+
+void MatToAxisAng(Matrix* m, AxisAng* aa);
+void AxisAngToMat(AxisAng* aa, Matrix* m);
+
+void MatToQuat(Matrix* m1, Quaternion* res);
+void QuatToMat(Quaternion* q1, Matrix* res);
+
+
+/*
+ * MatMult, MatTMult, VecCross and FindNormal will break if 
+ * called with output and input objects the same.
+ */
+
+/* Actual functions, maybe */
+inline void MatMatMult(Matrix* m1, Matrix* m2, Matrix* res) {
+  res->x1 = m1->x1*m2->x1 + m1->x2*m2->y1 + m1->x3*m2->z1;
+  res->x2 = m1->x1*m2->x2 + m1->x2*m2->y2 + m1->x3*m2->z2;
+  res->x3 = m1->x1*m2->x3 + m1->x2*m2->y3 + m1->x3*m2->z3;
+
+  res->y1 = m1->y1*m2->x1 + m1->y2*m2->y1 + m1->y3*m2->z1;
+  res->y2 = m1->y1*m2->x2 + m1->y2*m2->y2 + m1->y3*m2->z2;
+  res->y3 = m1->y1*m2->x3 + m1->y2*m2->y3 + m1->y3*m2->z3;
+
+  res->z1 = m1->z1*m2->x1 + m1->z2*m2->y1 + m1->z3*m2->z1;
+  res->z2 = m1->z1*m2->x2 + m1->z2*m2->y2 + m1->z3*m2->z2;
+  res->z3 = m1->z1*m2->x3 + m1->z2*m2->y3 + m1->z3*m2->z3;
+}
+
+inline void DMatDMatMult(DMatrix* m1, DMatrix* m2, DMatrix* res) {
+  res->x1 = m1->x1*m2->x1 + m1->x2*m2->y1 + m1->x3*m2->z1;
+  res->x2 = m1->x1*m2->x2 + m1->x2*m2->y2 + m1->x3*m2->z2;
+  res->x3 = m1->x1*m2->x3 + m1->x2*m2->y3 + m1->x3*m2->z3;
+
+  res->y1 = m1->y1*m2->x1 + m1->y2*m2->y1 + m1->y3*m2->z1;
+  res->y2 = m1->y1*m2->x2 + m1->y2*m2->y2 + m1->y3*m2->z2;
+  res->y3 = m1->y1*m2->x3 + m1->y2*m2->y3 + m1->y3*m2->z3;
+
+  res->z1 = m1->z1*m2->x1 + m1->z2*m2->y1 + m1->z3*m2->z1;
+  res->z2 = m1->z1*m2->x2 + m1->z2*m2->y2 + m1->z3*m2->z2;
+  res->z3 = m1->z1*m2->x3 + m1->z2*m2->y3 + m1->z3*m2->z3;
+}
+
+inline void MatTMatMult(Matrix* m1, Matrix* m2, Matrix* res) {
+  res->x1 = m1->x1*m2->x1 + m1->y1*m2->y1 + m1->z1*m2->z1;
+  res->x2 = m1->x1*m2->x2 + m1->y1*m2->y2 + m1->z1*m2->z2;
+  res->x3 = m1->x1*m2->x3 + m1->y1*m2->y3 + m1->z1*m2->z3;
+
+  res->y1 = m1->x2*m2->x1 + m1->y2*m2->y1 + m1->z2*m2->z1;
+  res->y2 = m1->x2*m2->x2 + m1->y2*m2->y2 + m1->z2*m2->z2;
+  res->y3 = m1->x2*m2->x3 + m1->y2*m2->y3 + m1->z2*m2->z3;
+
+  res->z1 = m1->x3*m2->x1 + m1->y3*m2->y1 + m1->z3*m2->z1;
+  res->z2 = m1->x3*m2->x2 + m1->y3*m2->y2 + m1->z3*m2->z2;
+  res->z3 = m1->x3*m2->x3 + m1->y3*m2->y3 + m1->z3*m2->z3;
+}
+
+inline void DMatTDMatMult(DMatrix* m1, DMatrix* m2, DMatrix* res) {
+  res->x1 = m1->x1*m2->x1 + m1->y1*m2->y1 + m1->z1*m2->z1;
+  res->x2 = m1->x1*m2->x2 + m1->y1*m2->y2 + m1->z1*m2->z2;
+  res->x3 = m1->x1*m2->x3 + m1->y1*m2->y3 + m1->z1*m2->z3;
+
+  res->y1 = m1->x2*m2->x1 + m1->y2*m2->y1 + m1->z2*m2->z1;
+  res->y2 = m1->x2*m2->x2 + m1->y2*m2->y2 + m1->z2*m2->z2;
+  res->y3 = m1->x2*m2->x3 + m1->y2*m2->y3 + m1->z2*m2->z3;
+
+  res->z1 = m1->x3*m2->x1 + m1->y3*m2->y1 + m1->z3*m2->z1;
+  res->z2 = m1->x3*m2->x2 + m1->y3*m2->y2 + m1->z3*m2->z2;
+  res->z3 = m1->x3*m2->x3 + m1->y3*m2->y3 + m1->z3*m2->z3;
+}
+
+
+inline void MatVecMult(Matrix* m1, Vector* v1, Vector* res) {
+  res->x = m1->x1*v1->x + m1->x2*v1->y + m1->x3*v1->z;
+  res->y = m1->y1*v1->x + m1->y2*v1->y + m1->y3*v1->z;
+  res->z = m1->z1*v1->x + m1->z2*v1->y + m1->z3*v1->z;
+}
+
+inline void MatDVecMult(Matrix* m1, DVector* v1, DVector* res) {
+  res->x = m1->x1*v1->x + m1->x2*v1->y + m1->x3*v1->z;
+  res->y = m1->y1*v1->x + m1->y2*v1->y + m1->y3*v1->z;
+  res->z = m1->z1*v1->x + m1->z2*v1->y + m1->z3*v1->z;
+}
+
+inline void DMatVecMult(DMatrix* m1, Vector* v1, DVector* res) {
+  res->x = m1->x1*v1->x + m1->x2*v1->y + m1->x3*v1->z;
+  res->y = m1->y1*v1->x + m1->y2*v1->y + m1->y3*v1->z;
+  res->z = m1->z1*v1->x + m1->z2*v1->y + m1->z3*v1->z;
+}
+
+inline void DMatVecMult(DMatrix* m1, Vector* v1, Vector* res) {
+  res->x = (float)(m1->x1*v1->x + m1->x2*v1->y + m1->x3*v1->z);
+  res->y = (float)(m1->y1*v1->x + m1->y2*v1->y + m1->y3*v1->z);
+  res->z = (float)(m1->z1*v1->x + m1->z2*v1->y + m1->z3*v1->z);
+}
+
+inline void DMatDVecMult(DMatrix* m1, DVector* v1, DVector* res) {
+  res->x = m1->x1*v1->x + m1->x2*v1->y + m1->x3*v1->z;
+  res->y = m1->y1*v1->x + m1->y2*v1->y + m1->y3*v1->z;
+  res->z = m1->z1*v1->x + m1->z2*v1->y + m1->z3*v1->z;
+}
+
+inline void MatTVecMult(Matrix* m1, Vector* v1, Vector* res) {
+  res->x = m1->x1*v1->x + m1->y1*v1->y + m1->z1*v1->z;
+  res->y = m1->x2*v1->x + m1->y2*v1->y + m1->z2*v1->z;
+  res->z = m1->x3*v1->x + m1->y3*v1->y + m1->z3*v1->z;
+}
+
+inline void MatTDVecMult(Matrix* m1, DVector* v1, DVector* res) {
+  res->x = m1->x1*v1->x + m1->y1*v1->y + m1->z1*v1->z;
+  res->y = m1->x2*v1->x + m1->y2*v1->y + m1->z2*v1->z;
+  res->z = m1->x3*v1->x + m1->y3*v1->y + m1->z3*v1->z;
+}
+
+inline void DMatTDVecMult(DMatrix* m1, DVector* v1, DVector* res) {
+  res->x = m1->x1*v1->x + m1->y1*v1->y + m1->z1*v1->z;
+  res->y = m1->x2*v1->x + m1->y2*v1->y + m1->z2*v1->z;
+  res->z = m1->x3*v1->x + m1->y3*v1->y + m1->z3*v1->z;
+}
+
+inline void VecMul(Vector* v1, double m, Vector* res) {
+  res->x = (float)(v1->x * m);
+  res->y = (float)(v1->y * m);
+  res->z = (float)(v1->z * m);
+}
+
+inline void VecMul(DVector* v1, double m, Vector* res) {
+  res->x = (float)(v1->x * m);
+  res->y = (float)(v1->y * m);
+  res->z = (float)(v1->z * m);
+}
+
+inline void VecMul(DVector* v1, double m, DVector* res) {
+  res->x = v1->x * m;
+  res->y = v1->y * m;
+  res->z = v1->z * m;
+}
+
+inline void VecMul(Vector* v1, double m, DVector* res) {
+  res->x = v1->x * m;
+  res->y = v1->y * m;
+  res->z = v1->z * m;
+}
+
+inline void VecInv(Vector* v1, Vector* res) {
+  res->x = -v1->x;
+  res->y = -v1->y;
+  res->z = -v1->z;
+}
+
+inline void VecInv(DVector* v1, DVector* res) {
+  res->x = -v1->x;
+  res->y = -v1->y;
+  res->z = -v1->z;
+}
+
+inline void VecSub(Vector* v1, Vector* v2, Vector* res) {
+  res->x = v1->x - v2->x;
+  res->y = v1->y - v2->y;
+  res->z = v1->z - v2->z;
+}
+
+inline void VecSub(DVector* v1, DVector* v2, DVector* res) {
+  res->x = v1->x - v2->x;
+  res->y = v1->y - v2->y;
+  res->z = v1->z - v2->z;
+}
+
+inline void VecSub(DVector* v1, DVector* v2, Vector* res) {
+  res->x = (float)(v1->x - v2->x);
+  res->y = (float)(v1->y - v2->y);
+  res->z = (float)(v1->z - v2->z);
+}
+
+inline void VecAdd(DVector* v1, DVector* v2, Vector* res) {
+  res->x = (float)(v1->x + v2->x);
+  res->y = (float)(v1->y + v2->y);
+  res->z = (float)(v1->z + v2->z);
+}
+
+inline void VecAdd(DVector* v1, Vector* v2, DVector* res) {
+  res->x = v1->x + v2->x;
+  res->y = v1->y + v2->y;
+  res->z = v1->z + v2->z;
+}
+
+inline void VecAdd(Vector* v1, Vector* v2, Vector* res) {
+  res->x = v1->x + v2->x;
+  res->y = v1->y + v2->y;
+  res->z = v1->z + v2->z;
+}
+
+inline void VecAdd(Vector* v1, DVector* v2, Vector* res) {
+  res->x = (float)(v1->x + v2->x);
+  res->y = (float)(v1->y + v2->y);
+  res->z = (float)(v1->z + v2->z);
+}
+
+inline void VecAdd(DVector* v1, DVector* v2, DVector* res) {
+  res->x = v1->x + v2->x;
+  res->y = v1->y + v2->y;
+  res->z = v1->z + v2->z;
+}
+
+inline float VecDot(Vector* v1, Vector* v2) {
+  return(v1->x*v2->x + v1->y*v2->y + v1->z*v2->z);
+}
+
+inline double VecDot(DVector* v1, DVector* v2) {
+  return(v1->x*v2->x + v1->y*v2->y + v1->z*v2->z);
+}
+
+inline double VecDot(DVector* v1, Vector* v2) {
+  return(v1->x*v2->x + v1->y*v2->y + v1->z*v2->z);
+}
+
+inline float VecDot(Vector* v) {
+  return(v->x*v->x + v->y*v->y + v->z*v->z);
+}
+
+inline double VecDot(DVector* v) {
+  return(v->x*v->x + v->y*v->y + v->z*v->z);
+}
+
+inline float VecMag(Vector* v1) {
+  return(float) sqrt(VecDot(v1, v1));
+}
+
+inline double VecMag(DVector* v1) {
+  return sqrt(VecDot(v1, v1));
+}
+
+inline void VecCross(Vector* v1, Vector* v2, Vector* res) {
+  res->x = v1->y*v2->z - v1->z*v2->y;
+  res->y = v1->z*v2->x - v1->x*v2->z;
+  res->z = v1->x*v2->y - v1->y*v2->x;
+}
+
+inline void VecCross(DVector* v1, DVector* v2, DVector* res) {
+  res->x = v1->y*v2->z - v1->z*v2->y;
+  res->y = v1->z*v2->x - v1->x*v2->z;
+  res->z = v1->x*v2->y - v1->y*v2->x;
+}
+
+inline int VecNorm(Vector* v1, Vector* res) {
+  float temp = VecDot(v1, v1);
+  if(temp == 0.0f) { *res = zero_vector; return 0; }
+  temp = 1.0f / float(sqrt(temp));
+
+  res->x = v1->x * temp;
+  res->y = v1->y * temp;
+  res->z = v1->z * temp;
+  return 1;
+} 
+
+inline int VecNorm(DVector* v1, DVector* res) {
+  double temp = VecDot(v1, v1);
+  if(temp == 0.0) { *res = zero_dvector; return 0; }
+  temp = 1.0 / sqrt(temp);
+
+  res->x = v1->x * temp;
+  res->y = v1->y * temp;
+  res->z = v1->z * temp;
+  return 1;
+} 
+
+inline void VecMax(Vector* in, Vector* max, Vector* res) {
+  if(in->x < max->x) res->x = max->x; else res->x = in->x;
+  if(in->y < max->y) res->y = max->y; else res->y = in->y;
+  if(in->z < max->z) res->z = max->z; else res->z = in->z;
+}
+
+inline void VecMin(Vector* in, Vector* min, Vector* res) {
+  if(in->x > min->x) res->x = min->x; else res->x = in->x;
+  if(in->y > min->y) res->y = min->y; else res->y = in->y;
+  if(in->z > min->z) res->z = min->z; else res->z = in->z;
+}
+
+inline void VecClip(Vector* in, Vector* max, Vector* min, Vector* res) {
+  if(in->x > max->x) res->x = max->x;
+  else if(in->x < min->x) res->x = min->x; else res->x = in->x;
+  if(in->y > max->y) res->y = max->y;
+  else if(in->y < min->y) res->y = min->y; else res->y = in->y;
+  if(in->z > max->z) res->z = max->z;
+  else if(in->z < min->z) res->z = min->z; else res->z = in->z;
+}
+
+/* Obeys RH rule p2-p1-p3 */
+inline int FindNormal(Vector* p1, Vector* p2, Vector* p3, Vector* res) {
+  res->x = (p2->y-p1->y) * (p3->z-p1->z) - (p2->z-p1->z) * (p3->y-p1->y);
+  res->y = (p2->z-p1->z) * (p3->x-p1->x) - (p2->x-p1->x) * (p3->z-p1->z);
+  res->z = (p2->x-p1->x) * (p3->y-p1->y) - (p2->y-p1->y) * (p3->x-p1->x);
+
+  return VecNorm(res, res);
+}
+
+inline int FindNormal(DVector* p1, DVector* p2, DVector* p3, DVector* res) {
+  res->x = (p2->y-p1->y) * (p3->z-p1->z) - (p2->z-p1->z) * (p3->y-p1->y);
+  res->y = (p2->z-p1->z) * (p3->x-p1->x) - (p2->x-p1->x) * (p3->z-p1->z);
+  res->z = (p2->x-p1->x) * (p3->y-p1->y) - (p2->y-p1->y) * (p3->x-p1->x);
+
+  return VecNorm(res, res);
+}
+
+inline int FindNormal(Vector* p1, Vector* p2, Vector* p3, DVector* res) {
+  res->x  = ((double)p2->y-p1->y) * ((double)p3->z-p1->z);
+  res->x -= ((double)p2->z-p1->z) * ((double)p3->y-p1->y);
+  res->y  = ((double)p2->z-p1->z) * ((double)p3->x-p1->x);
+  res->y -= ((double)p2->x-p1->x) * ((double)p3->z-p1->z);
+  res->z  = ((double)p2->x-p1->x) * ((double)p3->y-p1->y);
+  res->z -= ((double)p2->y-p1->y) * ((double)p3->x-p1->x);
+
+  return VecNorm(res, res);
+}
+
+inline void BuildMatrix(float xr, float yr, float zr, Matrix* m1) {
+  double sx = sin(xr), cx = cos(xr);
+  double sy = sin(yr), cy = cos(yr);
+  double sz = sin(zr), cz = cos(zr);
+
+  m1->x1 = float(cz*cy + sz*sx*sy);
+  m1->x2 = float(sz*cx);
+  m1->x3 = float(-cz*sy + sz*sx*cy);
+  m1->y1 = float(-sz*cy + cz*sx*sy);
+  m1->y2 = float(cz*cx);
+  m1->y3 = float(sz*sy + cz*sx*cy);
+  m1->z1 = float(cx*sy);
+  m1->z2 = float(-sx);
+  m1->z3 = float(cx*cy);
+}
+
+inline void BuildMatrix(double xr, double yr, double zr, DMatrix* m1) {
+  double sx = sin(xr), cx = cos(xr);
+  double sy = sin(yr), cy = cos(yr);
+  double sz = sin(zr), cz = cos(zr);
+
+  m1->x1 = cz*cy + sz*sx*sy;
+  m1->x2 = sz*cx;
+  m1->x3 = -cz*sy + sz*sx*cy;
+  m1->y1 = -sz*cy + cz*sx*sy;
+  m1->y2 = cz*cx;
+  m1->y3 = sz*sy + cz*sx*cy;
+  m1->z1 = cx*sy;
+  m1->z2 = -sx;
+  m1->z3 = cx*cy;
+}
+
+inline void MakeVecRotMatrix(Vector* v, Matrix* m) {
+  float r, div;
+  if((r = VecDot(v, v)) < 1.0e-20f) {
+    *m = identity_matrix;
+    return;
+  }
+  r = float(sqrt(r));
+  div = 1 / r;
+
+  Vector n;
+  n.x = v->x * div;
+  n.y = v->y * div;
+  n.z = v->z * div;
+
+  float cr = cosf(r);
+  float cri = 1.0f - cr;
+  float sr = sinf(r);
+
+  m->x1 = (n.x * n.x) * cri + cr;
+  m->x2 = (n.x * n.y) * cri - (n.z * sr);
+  m->x3 = (n.x * n.z) * cri + (n.y * sr);
+
+  m->y1 = (n.y * n.x) * cri + (n.z * sr);
+  m->y2 = (n.y * n.y) * cri + cr ;
+  m->y3 = (n.y * n.z) * cri - (n.x * sr);
+
+  m->z1 = (n.z * n.x) * cri - (n.y * sr);
+  m->z2 = (n.z * n.y) * cri + (n.x * sr);
+  m->z3 = (n.z * n.z) * cri + cr;
+}
+
+inline void MakeVecRotDMatrix(DVector* v, DMatrix* m) {
+  double r, div;
+  if ((r = VecDot(v, v)) < 1.0e-20f) {
+    *m = identity_dmatrix;
+    return;
+  }
+  r = sqrt(r);
+  div = 1 / r;
+
+  DVector n;
+  n.x = v->x * div;
+  n.y = v->y * div;
+  n.z = v->z * div;
+
+  double cr = cos(r);
+  double cri = 1.0 - cr;
+  double sr = sin(r);
+
+  m->x1 = (n.x * n.x) * cri + cr;
+  m->x2 = (n.x * n.y) * cri - (n.z * sr);
+  m->x3 = (n.x * n.z) * cri + (n.y * sr);
+
+  m->y1 = (n.y * n.x) * cri + (n.z * sr);
+  m->y2 = (n.y * n.y) * cri + cr ;
+  m->y3 = (n.y * n.z) * cri - (n.x * sr);
+
+  m->z1 = (n.z * n.x) * cri - (n.y * sr);
+  m->z2 = (n.z * n.y) * cri + (n.x * sr);
+  m->z3 = (n.z * n.z) * cri + cr;
+}
+
diff --git a/src/sbre/models.cpp b/src/sbre/models.cpp
new file mode 100644
index 0000000..de76567
--- /dev/null
+++ b/src/sbre/models.cpp
@@ -0,0 +1,1261 @@
+#include "sbre_int.h"
+#include "sbre_anim.h"
+
+const int SUB_WING2     = 1;
+const int SUB_DISH      = 2;
+const int SUB_NOSEWHEEL = 3;
+const int SUB_WING      = 4;
+const int SUB_NACELLE   = 5;
+const int SUB_NWUNIT    = 6;
+const int SUB_MAINWHEEL = 7;
+const int SUB_MWUNIT    = 8;
+
+enum AxisIndex {
+  A_X = 0, A_Y, A_Z, A_NX, A_NY, A_NZ,
+};
+
+static PlainVertex tetravtx1[] = {
+  { VTYPE_PLAIN, { 0.0f, 50.0f, 0.0f } },     /* 6. */
+  { VTYPE_PLAIN, { -50.0f, -30.0f, 30.0f } },
+  { VTYPE_PLAIN, { 50.0f, -30.0f, 30.0f } },
+  { VTYPE_PLAIN, { 0.0f, -30.0f, -50.0f } },
+  { VTYPE_PLAIN, { 0.0f, -30.0f, 0.0f } },    /* 10, wheel base. */
+};
+
+static CompoundVertex tetravtx2[] = {
+  { VTYPE_CROSS, { 0, 1, 2, static_cast<uint16>(-1), static_cast<uint16>(-1) } }, /* Dummy. */
+};
+
+static uint16 tetradata[] = {
+  PTYPE_MATFIXED, 100, 0, 100, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_TRIFLAT, 6, 7, 8,
+  PTYPE_TRIFLAT, 6, 8, 9,
+  PTYPE_TRIFLAT, 6, 9, 7,
+  PTYPE_TRIFLAT, 9, 8, 7,
+  PTYPE_SUBOBJECT, 0x8000, SUB_NOSEWHEEL, 10, 0, 4, 100,
+  PTYPE_END,
+};
+
+static Model tetramodel = { 1.0f, 11, tetravtx1, 20, 0, tetravtx2,
+                            0, 0, 0, 0, tetradata, 0 };
+
+
+static PlainVertex circlevtx1[] = {
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 0.0f } },
+};
+
+static CompoundVertex circlevtx2[] = {
+  { VTYPE_NORM, { 6, 7, 8, static_cast<uint16>(-1), static_cast<uint16>(-1) } },  /* Dummy. */
+};
+
+static uint16 circledata[] = {
+  PTYPE_MATANIM, AFUNC_THRUSTPULSE,
+  0, 0, 0, 0, 0, 0, 100, 50, 50, 100,
+  0, 0, 0, 0, 0, 0, 100, 0, 0, 50,
+  PTYPE_CIRCLE, 0, 12, 6, 5, 1, 2000,
+  PTYPE_END,
+};
+
+static Model circlemodel = { 1.0f, 7, circlevtx1, 20, 0, circlevtx2,
+                             0, 0, 0, 0, circledata, 1 };
+
+
+static PlainVertex cylvtx1[] = {
+  { VTYPE_PLAIN, { -100.0f, 20.0f, 0.0f } },
+  { VTYPE_PLAIN, { 100.0f, -10.0f, 0.0f } },
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 0.0f } },
+};
+
+static CompoundVertex cylvtx2[] = {
+  { VTYPE_NORM, { 6, 7, 8, static_cast<uint16>(-1), static_cast<uint16>(-1) } },
+};
+
+static uint16 cyldata[] = {
+  PTYPE_MATFIXED, 100, 0, 100, 0, 0, 0, 100, 0, 0, 0,
+  //  PTYPE_CYLINDER, 0, 8, 6, 7, 20, 2000,
+  PTYPE_TUBE, 0, 8, 6, 7, 20, 2000, 1000,
+  PTYPE_END,
+};
+
+static Model cylmodel = { 1.0f, 9, cylvtx1, 20, 1, cylvtx2,
+                          0, 0, 0, 0, cyldata, 1 };
+
+
+static PlainVertex nwunitvtx1[] = {
+  { VTYPE_PLAIN, { 1.5f, 0.0f, 6.0f } },  /* 6 flap. */
+  { VTYPE_PLAIN, { 1.5f, 0.0f, -1.0f } },
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 6.0f } },
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -1.0f } },
+  { VTYPE_PLAIN, { 1.5f, 1.5f, 6.0f } },
+  { VTYPE_PLAIN, { 1.5f, 1.5f, -1.0f } },
+
+  { VTYPE_PLAIN, { 0.0f, 1.5f, 0.0f } },  /* 12, tangents. */
+  { VTYPE_PLAIN, { 1.5f, 0.0f, 0.0f } },
+
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 0.0f } },  /* 14, wheel pos. */
+};
+
+static CompoundVertex nwunitvtx2[] = {
+  { VTYPE_ANIMHERM, { 8, 10, 12, 13, AFUNC_GFLAP } },  /* 20, flap paths. */
+  { VTYPE_ANIMHERM, { 9, 11, 12, 13, AFUNC_GFLAP } },    
+  { VTYPE_ANIMLIN, { 2, 1, static_cast<uint16>(-1), static_cast<uint16>(-1), AFUNC_GEAR } },  /* Gear y axis. */
+};
+
+static uint16 nwunitdata[] = {
+  PTYPE_MATFIXED, 20, 20, 20, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_QUADFLAT | RFLAG_XREF, 8, 6, 7, 9,  /* Flap internal. */
+
+  PTYPE_MATFIXED, 100, 0, 100, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_QUADFLAT | RFLAG_XREF, 6, 20, 21, 7,  /* Flaps. */
+  PTYPE_QUADFLAT | RFLAG_XREF, 6, 7, 21, 20,
+
+  //  PTYPE_CYLINDER, 3, 8, 6, 7, 0, 1000,
+
+  PTYPE_SUBOBJECT, 0x8000, SUB_NOSEWHEEL, 14, 22, 2, 100,
+
+  PTYPE_END,
+};
+
+static Model nwunitmodel = { 1.0f, 15, nwunitvtx1, 20, 3, nwunitvtx2,
+                             0, 0, 0, 0, nwunitdata, 0 };
+
+
+static PlainVertex nosewheelvtx1[] = {
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 0.0f } },    /* 6, strut. */
+  { VTYPE_PLAIN, { 0.0f, 3.0f, 0.0f } },
+  { VTYPE_PLAIN, { 0.0f, 5.0f, 0.0f } },
+  { VTYPE_PLAIN, { 0.5f, 5.0f, 0.0f } },    /* 9, wheel. */
+  { VTYPE_PLAIN, { 1.0f, 5.0f, 0.0f } },
+};
+
+static CompoundVertex nosewheelvtx2[] = {
+  { VTYPE_CROSS, { 0, 1, 2, static_cast<uint16>(-1), static_cast<uint16>(-1) } },    /* Dummy. */
+};
+
+static uint16 nosewheeldata[] = {
+  PTYPE_MATFIXED, 50, 50, 50, 100, 100, 100, 200, 0, 0, 0,
+  PTYPE_CYLINDER, 0, 8, 6, 8, 2, 40,
+  PTYPE_CYLINDER, 1, 8, 7, 8, 2, 50,
+  PTYPE_MATFIXED, 30, 30, 30, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_CYLINDER | RFLAG_XREF, 2, 8, 9, 10, 2, 100,
+  PTYPE_END,
+};
+
+static Model nosewheelmodel = { 1.0f, 11, nosewheelvtx1, 20, 0, nosewheelvtx2,
+                                0, 0, 0, 0, nosewheeldata, 4 };
+
+
+static PlainVertex mwunitvtx1[] = {
+  { VTYPE_PLAIN, { 1.5f, 0.0f, 6.0f } },        /* 6, flap. */
+  { VTYPE_PLAIN, { 1.5f, 0.0f, -1.0f } },
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 6.0f } },
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -1.0f } },
+  { VTYPE_PLAIN, { 1.5f, 1.5f, 6.0f } },
+  { VTYPE_PLAIN, { 1.5f, 1.5f, -1.0f } },
+
+  { VTYPE_PLAIN, { 0.0f, 1.5f, 0.0f } },        /* 12, tangents. */
+  { VTYPE_PLAIN, { 1.5f, 0.0f, 0.0f } },
+
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 0.0f } },        /* 14, wheel pos. */
+};
+
+static CompoundVertex mwunitvtx2[] = {
+  { VTYPE_ANIMHERM, { 8, 10, 12, 13, AFUNC_GFLAP } },     /* 20, flap paths. */
+  { VTYPE_ANIMHERM, { 9, 11, 12, 13, AFUNC_GFLAP } },    
+  { VTYPE_ANIMLIN, { 2, 1, static_cast<uint16>(-1), static_cast<uint16>(-1), AFUNC_GEAR } },        /* Gear y axis. */
+};
+
+static uint16 mwunitdata[] = {
+  PTYPE_MATFIXED, 20, 20, 20, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_QUADFLAT | RFLAG_XREF, 8, 6, 7, 9,                /* Flap internal. */
+
+  PTYPE_MATFIXED, 100, 0, 100, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_QUADFLAT | RFLAG_XREF, 6, 20, 21, 7,  /* Flaps. */
+  PTYPE_QUADFLAT | RFLAG_XREF, 6, 7, 21, 20,
+
+  //  PTYPE_CYLINDER, 3, 8, 6, 7, 0, 1000,
+
+  PTYPE_SUBOBJECT, 0x8000, SUB_MAINWHEEL, 14, 22, 2, 100,
+
+  PTYPE_END,
+};
+
+static Model mwunitmodel = { 1.0f, 15, mwunitvtx1, 20, 3, mwunitvtx2,
+                             0, 0, 0, 0, mwunitdata, 0 };
+
+
+static PlainVertex mainwheelvtx1[] = {
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 0.0f } },    /* 6, strut. */
+  { VTYPE_PLAIN, { 0.0f, 3.0f, 0.0f } },
+  { VTYPE_PLAIN, { 0.0f, 5.0f, 0.0f } },
+  { VTYPE_PLAIN, { 0.5f, 5.0f, 1.1f } },    /* 9, wheel 1. */
+  { VTYPE_PLAIN, { 1.0f, 5.0f, 1.1f } },
+  { VTYPE_PLAIN, { 0.5f, 5.0f, -1.1f } },   /* 11, wheel 2. */
+  { VTYPE_PLAIN, { 1.0f, 5.0f, -1.1f } },
+  { VTYPE_PLAIN, { 0.0f, 5.0f, 1.4f } },    /* 13, crossbar. */
+  { VTYPE_PLAIN, { 0.0f, 5.0f, -1.4f } },
+};
+
+static CompoundVertex mainwheelvtx2[] = {
+  { VTYPE_CROSS, { 0, 1, 2, static_cast<uint16>(-1), static_cast<uint16>(-1) } },      /* Dummy. */
+};
+
+static uint16 mainwheeldata[] = {
+  PTYPE_MATFIXED, 50, 50, 50, 100, 100, 100, 200, 0, 0, 0,
+  PTYPE_CYLINDER, 0, 8, 6, 8, 2, 40,
+  PTYPE_CYLINDER, 1, 8, 7, 8, 2, 50,
+  PTYPE_CYLINDER, 4, 4, 13, 14, 0, 50,
+  PTYPE_MATFIXED, 30, 30, 30, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_CYLINDER | RFLAG_XREF, 2, 8, 9, 10, 2, 100,
+  PTYPE_CYLINDER | RFLAG_XREF, 3, 8, 11, 12, 2, 100,
+  PTYPE_END,
+};
+
+static Model mainwheelmodel = { 1.0f, 15, mainwheelvtx1, 20, 0, mainwheelvtx2,
+                                0, 0, 0, 0, mainwheeldata, 5 };
+
+static PlainVertex nacellevtx1[] = {
+  { VTYPE_PLAIN, { 30.0f, 0.0f, 30.0f } },    /* 6 */
+  { VTYPE_PLAIN, { 30.0f, 0.0f, -30.0f } },
+  { VTYPE_PLAIN, { 30.0f, 10.0f, 0.0f } },
+  { VTYPE_PLAIN, { 40.0f, 0.0f, 0.0f } },
+  { VTYPE_PLAIN, { 30.0f, -10.0f, 0.0f } },
+  { VTYPE_PLAIN, { 20.0f, 0.0f, 0.0f } },
+
+  { VTYPE_PLAIN, { 14.0f, 0.0f, 0.0f } },     /* 12, tangents. */
+  { VTYPE_PLAIN, { -14.0f, 0.0f, 0.0f } },
+  { VTYPE_PLAIN, { 0.0f, 14.0f, 0.0f } },
+  { VTYPE_PLAIN, { 0.0f, -14.0f, 0.0f } },
+};
+
+static CompoundVertex nacellevtx2[] = {
+  { VTYPE_CROSS, { 0, 1, 2, static_cast<uint16>(-1), static_cast<uint16>(-1) } },       /* Dummy. */
+};
+
+static uint16 nacelledata[] = {
+  PTYPE_MATFIXED, 100, 0, 100, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_COMPSMOOTH | RFLAG_XREF, 0, 5, 6, 2, 8, 1,
+  COMP_HERMITE, 11, 3, 13, 15,
+  COMP_HERMITE, 10, 4, 15, 12,
+  COMP_HERMITE, 9, 0, 12, 14,
+  COMP_HERMITE, 8, 1, 14, 13,
+  COMP_END,
+  PTYPE_COMPSMOOTH | RFLAG_XREF, 1, 5, 7, 5, 8, 1,
+  COMP_HERMITE, 9, 0, 12, 15,
+  COMP_HERMITE, 10, 4, 15, 13,
+  COMP_HERMITE, 11, 3, 13, 14,
+  COMP_HERMITE, 8, 1, 14, 12,
+  COMP_END,
+  PTYPE_END,
+};
+
+static Model nacellemodel = { 1.0f, 16, nacellevtx1, 20, 0, nacellevtx2,
+                              0, 0, 0, 0, nacelledata, 2 };
+
+
+/* Do wings as subobjects. */
+static PlainVertex shipvtx1[] = {
+  { VTYPE_PLAIN, { 5.0f, 10.0f, 30.0f } },  /* 6, top four body verts. */
+  { VTYPE_PLAIN, { -5.0f, 10.0f, 30.0f } },
+  { VTYPE_PLAIN, { 5.0f, 10.0f, -30.0f } },
+  { VTYPE_PLAIN, { -5.0f, 10.0f, -30.0f } },
+
+  { VTYPE_PLAIN, { 11.16025f, -0.6698729f, 25.0f } }, /* 10, right four body verts. */
+  { VTYPE_PLAIN, { 6.160254f, -9.330127f, 35.0f } },
+  { VTYPE_PLAIN, { 11.16025f, -0.6698729f, -35.0f } },
+  { VTYPE_PLAIN, { 6.160254f, -9.330127f, -30.0f } },
+
+  { VTYPE_PLAIN, { -11.16025f, -0.6698729f, 25.0f } },  /* 14, left four body verts. */
+  { VTYPE_PLAIN, { -6.160254f, -9.330127f, 35.0f } },
+  { VTYPE_PLAIN, { -11.16025f, -0.6698729f, -35.0f } },
+  { VTYPE_PLAIN, { -6.160254f, -9.330127f, -30.0f } },
+
+  { VTYPE_PLAIN, { 5.0f, -0.6698729f, 60.0f } },  /* 18, front two verts. */
+  { VTYPE_PLAIN, { -5.0f, -0.6698729f, 60.0f } },
+
+  { VTYPE_PLAIN, { 0.0f, 10.0f, 0.0f } },    /* 20, top wing. */
+  { VTYPE_PLAIN, { 1.0f, 0.0f, 0.0f } },
+  { VTYPE_PLAIN, { 0.0f, 1.0f, 0.0f } },
+
+  { VTYPE_PLAIN, { 8.660254f, -5.0f, 0.0f } },    /* 23, right wing. */
+  { VTYPE_PLAIN, { -0.5f, -0.8660254f, 0.0f } },
+  { VTYPE_PLAIN, { 0.8660254f, -0.5f, 0.0f } },
+
+  { VTYPE_PLAIN, { -8.660254f, -5.0f, 0.0f } },    /* 26, left wing. */
+  { VTYPE_PLAIN, { -0.5f, 0.8660254f, 0.0f } },
+  { VTYPE_PLAIN, { -0.8660254f, -0.5f, 0.0f } },
+
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -40.0f } },    /* 29, main thruster. */
+  { VTYPE_PLAIN, { 11.0f, 0.0f, 35.0f } },    /* 30, retro. */
+  { VTYPE_PLAIN, { -11.0f, 0.0f, 35.0f } },
+  { VTYPE_PLAIN, { 9.0f, 5.0f, 30.0f } },    /* 32, right. */
+  { VTYPE_PLAIN, { 12.0f, -5.0f, -30.0f } },
+  { VTYPE_PLAIN, { -12.0f, -5.0f, 30.0f } },    /* 34, left. */
+  { VTYPE_PLAIN, { -9.0f, 5.0f, -30.0f } },
+  { VTYPE_PLAIN, { 0.0f, 12.0f, 30.0f } },    /* 36, top. */
+  { VTYPE_PLAIN, { 0.0f, 12.0f, -30.0f } },
+  { VTYPE_PLAIN, { 0.0f, -12.0f, 30.0f } },    /* 38, bottom. */
+  { VTYPE_PLAIN, { 0.0f, -12.0f, -30.0f } },
+
+  { VTYPE_PLAIN, { 0.0f, -9.330127f, 30.0f } },    /* 40, nosewheel. */
+  { VTYPE_PLAIN, { 0.0f, -9.330127f, -13.0f } },  /* 41, mainwheel. */
+
+};
+
+static CompoundVertex shipvtx2[] = {
+  { VTYPE_ANIMLIN, { 25, 0, static_cast<uint16>(-1), static_cast<uint16>(-1), 0 } },    /* 50, right wing yaxis. */
+  { VTYPE_CROSS, { 50, 2, static_cast<uint16>(-1), static_cast<uint16>(-1), 0 } },    /* Right wing xaxis. */
+
+  { VTYPE_ANIMLIN, { 28, 3, static_cast<uint16>(-1), static_cast<uint16>(-1), 0 } },    /* 52, left wing yaxis. */
+  { VTYPE_CROSS, { 52, 2, static_cast<uint16>(-1), static_cast<uint16>(-1), static_cast<uint16>(-1) } },    /* Right wing xaxis. */
+
+  { VTYPE_NORM, { 16, 14, 7, static_cast<uint16>(-1), static_cast<uint16>(-1) } },    /* 54, left text normal. */
+  { VTYPE_NORM, { 12, 8, 6, static_cast<uint16>(-1), static_cast<uint16>(-1) } },      /* 55, right text normal. */
+
+};
+
+static uint16 shipdata[] = {
+  PTYPE_MATFIXED, 100, 0, 100, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_QUADFLAT, 7, 6, 8, 9,        /* Top. */
+  PTYPE_QUADFLAT, 13, 11, 15, 17,      /* Bottom. */
+  PTYPE_QUADFLAT | RFLAG_XREF, 8, 6, 10, 12,          /* Side top. */
+  PTYPE_QUADFLAT | RFLAG_XREF, 12, 10, 11, 13,          /* Side bottom. */
+  PTYPE_QUADFLAT, 9, 8, 12, 16,        /* Back top. */
+  PTYPE_QUADFLAT, 16, 12, 13, 17,      /* Back bottom. */
+
+  PTYPE_QUADFLAT, 6, 7, 19, 18,       /* Front top. */
+  PTYPE_QUADFLAT, 18, 19, 15, 11,             /* Front bottom. */
+  PTYPE_TRIFLAT | RFLAG_XREF, 6, 18, 10,    /* Front side top. */
+  PTYPE_TRIFLAT | RFLAG_XREF, 10, 18, 11,    /* Front side bottom. */
+
+  PTYPE_SUBOBJECT, 0x8000, SUB_WING, 20, 22, 2, 100,
+  PTYPE_SUBOBJECT, 0x8000, SUB_WING, 23, 50, 2, 100,
+  PTYPE_SUBOBJECT, 0x8000, SUB_WING, 26, 52, 2, 100,
+
+  PTYPE_MATFIXED, 20, 20, 20, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_ZBIAS, 54, 5,
+  PTYPE_TEXT, 0, 0x8000, 14, 54, 5, 500, 200, 1000,
+  PTYPE_ZBIAS, 55, 5,
+  PTYPE_TEXT, 0, 0x8000, 12, 55, 2, 1100, 200, 1000,
+
+  PTYPE_ZBIAS, 4, 5,
+  PTYPE_SUBOBJECT, 0, SUB_NWUNIT, 40, 4, 5, 200,
+  PTYPE_SUBOBJECT, 0, SUB_MWUNIT, 41, 4, 5, 200,
+
+  //  PTYPE_TEXT, -1, -1, 12, 0, 1, 5000,
+  PTYPE_ZBIAS, 0x8000, 5,
+
+  PTYPE_END,
+};
+
+static Thruster shipthruster[] = {
+  { 29, 5 | THRUST_NOANG, 50.0f },
+  { 30, 2 | THRUST_NOANG, 35.0f },  /* Retros. */
+  { 31, 2 | THRUST_NOANG, 35.0f },
+  { 32, 0, 25.0f }, { 33, 0, 25.0f },  /* Right. */
+  { 34, 3, 25.0f }, { 35, 3, 25.0f },  /* Left. */
+  { 36, 1, 25.0f }, { 37, 1, 25.0f },  /* Top. */
+  { 38, 4, 25.0f }, { 39, 4, 25.0f },  /* Bottom. */
+};
+
+static Model shipmodel = { 1.0f, 42, shipvtx1, 50, 6, shipvtx2,
+                           0, 0, 11, shipthruster, shipdata, 0 };
+
+
+static PlainVertex wingvtx1[] = {
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 1.0f } },    /* 6, bottom front. */
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -1.0f } },   /* Bottom back. */
+  { VTYPE_PLAIN, { 0.0f, 1.5f, 0.0f } },    /* Top front. */
+  { VTYPE_PLAIN, { 0.0f, 1.5f, -1.5f } },   /* Top back. */
+
+  { VTYPE_PLAIN, { 0.1f, 0.75f, -0.5f } },  /* 10, sidecentre. */
+  { VTYPE_PLAIN, { 0.0f, 1.5f, -0.75f } },  /* Topcentre. */
+
+  { VTYPE_PLAIN, { 1.0f, 0.0f, 0.0f } },    /* 12, norm, sidecentre. */
+  { VTYPE_PLAIN, { 0.0f, 1.0f, 0.0f } },    /* norm, topcentre. */
+
+  { VTYPE_PLAIN, { 0.4f, 0.0f, -2.0f } },   /* 14, tan 0->1, 0. */
+  { VTYPE_PLAIN, { -0.4f, 0.0f, -2.0f } },  /* tan 0->1, 1. */
+  { VTYPE_PLAIN, { 0.0f, 1.5f, -0.5f } },   /* 16, tan 1->3, 0, 1. */
+  { VTYPE_PLAIN, { 0.4f, 0.0f, 1.5f } },    /* 17, tan 3->2, 0. */
+  { VTYPE_PLAIN, { -0.4f, 0.0f, 1.5f } },   /* tan 3->2, 1. */
+  { VTYPE_PLAIN, { 0.0f, -1.5f, 1.0f } },   /* 19, tan 2->0, 0, 1. */
+
+  { VTYPE_PLAIN, { 0.4f, 0.0f, -1.5f } },   /* 20, tan 2->3 top, 0. */
+  { VTYPE_PLAIN, { -0.4f, 0.0f, -1.5f } },  /* tan 2->3 top, 1. */
+  { VTYPE_PLAIN, { -0.4f, 0.0f, 1.5f } },   /* 22, tan 3->2 top, 0. */
+  { VTYPE_PLAIN, { 0.4f, 0.0f, 1.5f } },    /* tan 3->2 top, 1. */
+};
+
+static CompoundVertex wingvtx2[] = {
+  { VTYPE_CROSS, { 19, 14, static_cast<uint16>(-1),static_cast<uint16>(-1),static_cast<uint16>(-1), } },  /* 30, norm 0. */
+  { VTYPE_CROSS, { 15, 16, static_cast<uint16>(-1),static_cast<uint16>(-1),static_cast<uint16>(-1), } },  /* norm 1. */
+  { VTYPE_CROSS, { 16, 17, static_cast<uint16>(-1),static_cast<uint16>(-1),static_cast<uint16>(-1), } },  /* norm 3. */
+  { VTYPE_CROSS, { 18, 19, static_cast<uint16>(-1),static_cast<uint16>(-1),static_cast<uint16>(-1), } },  /* norm 2. */
+};
+
+static uint16 wingdata[] = {
+  PTYPE_MATFIXED, 100, 0, 100, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_COMPSMOOTH | RFLAG_XREF, 0, 5, 10, 12, 6, 30,   /* Side. */
+  COMP_HERMITE, 7, 31, 14, 15,
+  COMP_HERMITE, 9, 32, 16, 16,
+  COMP_HERMITE, 8, 33, 17, 18,
+  COMP_HERMITE, 6, 30, 19, 19,
+  COMP_END,
+  PTYPE_COMPSMOOTH, 1, 5, 11, 13, 8, 13,    /* Top. */
+  COMP_HERMITE, 9, 13, 20, 21,
+  COMP_HERMITE, 8, 13, 22, 23,
+  COMP_END,
+  PTYPE_END,
+};
+
+static Model wingmodel = { 25.0f, 24, wingvtx1, 30, 4, wingvtx2,
+                           0, 0, 0, 0, wingdata, 2 };
+
+static PlainVertex ship2vtx1[] = {
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 35.0f } },    /* 6, nose point. */
+  { VTYPE_DIR, { 0.0f, 1.0f, 0.2f } },      /* nose normal. */
+  { VTYPE_PLAIN, { 6.0f, 0.0f, 18.0f } },    /* 8, r edge forward mid. */
+  { VTYPE_DIR, { 0.2f, 1.0f, 0.1f } },      /* norm. */
+  { VTYPE_PLAIN, { 12.0f, 0.0f, -2.0f } },    /* 10, r edge back mid. */
+  { VTYPE_DIR, { 0.2f, 1.0f, 0.1f } },      /* norm. */
+  { VTYPE_PLAIN, { 7.5f, 0.0f, -25.0f } },    /* 12, r edge back. */
+  { VTYPE_DIR, { 0.0f, 1.0f, -0.2f } },      /* norm. */
+
+  { VTYPE_PLAIN, { 0.0f, 3.0f, 15.0f } },    /* 14, cockpit front. */
+  { VTYPE_DIR, { 0.0f, 1.0f, -0.08f } },    /* norm. */
+  { VTYPE_PLAIN, { 1.5f, 3.0f, 13.5f } },    /* 16, cockpit right. */
+  { VTYPE_DIR, { 0.0f, 1.0f, -0.08f } },    /* norm. */
+  { VTYPE_PLAIN, { 0.0f, 3.0f, 10.0f } },    /* 18, cockpit back. */
+  { VTYPE_DIR, { 0.0f, 1.0f, -0.08f } },    /* norm. */
+  { VTYPE_PLAIN, { -1.5f, 3.0f, 13.5f } },    /* 20, cockpit left. */
+  { VTYPE_DIR, { 0.0f, 1.0f, -0.08f } },    /* norm. */
+
+  { VTYPE_PLAIN, { 6.0f, 3.0f, -5.0f } },    /* 22, inner right. */
+  { VTYPE_DIR, { 0.2f, 1.0f, 0.2f } },      /* norm. */
+  { VTYPE_PLAIN, { 0.0f, 3.0f, -5.0f } },    /* 24, inner mid. */
+  { VTYPE_DIR, { -0.2f, 1.0f, 0.2f } },            /* norm. */
+
+  { VTYPE_PLAIN, { 2.0f, 2.0f, 23.0f } },    /* 26, fwd midpoint. */
+  { VTYPE_DIR, { 0.0f, 1.0f, 0.1f } },            /* norm. */
+  { VTYPE_PLAIN, { 5.0f, 2.5f, 5.0f } },    /* 28, right midpoint. */
+  { VTYPE_DIR, { 0.08f, 1.0f, 0.04f } },    /* norm. */
+  { VTYPE_PLAIN, { 7.0f, 2.0f, -14.0f } },    /* 30, rear right midpoint. */
+  { VTYPE_DIR, { 0.04f, 1.0f, -0.1f } },    /* norm. */
+
+  { VTYPE_PLAIN, { 3.0f, 3.0f, 5.0f } },    /* 32, central midpoint. */
+  { VTYPE_PLAIN, { 0.0f, 4.0f, 12.5f } },    /* 33, cockpit midpoint. */
+  { VTYPE_PLAIN, { 3.75f, 4.0f, -20.0f } },    /* 34, nacelle midpoint. */
+
+  { VTYPE_PLAIN, { 7.5f, 0.0f, -30.0f } },    /* 35, nacelle outer. */
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -30.0f } },    /* 36, nacelle inner. */
+
+  /* Edge tangents. */
+  { VTYPE_PLAIN, { -6.0f, 4.0f, -3.0f } },    /* 37, edge to mid. */
+  { VTYPE_PLAIN, { -6.0f, 0.0f, -3.0f } },    
+  { VTYPE_PLAIN, { 0.0f, 4.0f, 20.0f } },    /* 39, rear to mid. */
+  { VTYPE_PLAIN, { -2.5f, 0.0f, 20.0f } },
+
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 20.0f } },    /* 41, mid to nose. */
+  { VTYPE_PLAIN, { 0.0f, -4.0f, 20.0f } },
+  { VTYPE_PLAIN, { 6.0f, 0.0f, 3.0f } },    /* 43, mid to edge. */
+  { VTYPE_PLAIN, { 6.0f, -4.0f, 3.0f } },
+  { VTYPE_PLAIN, { 2.5f, 0.0f, -20.0f } },    /* 45, mid to rear. */
+  { VTYPE_PLAIN, { 0.0f, -4.0f, -20.0f } },
+
+  { VTYPE_PLAIN, { 1.5f, 0.0f, 0.0f } },    /* 47, cockpit CW tangents. */
+  { VTYPE_PLAIN, { -1.5f, 0.0f, 0.0f } },
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 1.5f } },
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -1.5f } },
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 3.5f } },    /* 51 */
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -3.5f } },
+
+  { VTYPE_PLAIN, { 10.0f, 0.0f, -20.0f } },    /* 53, rear edge tangents. */
+  { VTYPE_PLAIN, { -10.0f, 0.0f, 0.0f } },
+  { VTYPE_PLAIN, { -10.0f, 0.0f, 20.0f } },    /* 55, CCW. */
+  { VTYPE_PLAIN, { 10.0f, 0.0f, 0.0f } },
+
+  { VTYPE_PLAIN, { 0.0f, 5.0f, 0.0f } },    /* 57, nacelle tangents. */
+  { VTYPE_PLAIN, { 0.0f, -5.0f, 0.0f } },
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 12.0f } },
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -12.0f } },
+
+  { VTYPE_PLAIN, { 3.75f, 4.0f, -30.0f } },    /* 61, nacelle rear midpoint. */
+  { VTYPE_PLAIN, { 4.0f, 0.0f, 0.0f } },    /* And tangents. */
+  { VTYPE_PLAIN, { -4.0f, 0.0f, 0.0f } },
+
+  /* Underside points. */
+  { VTYPE_PLAIN, { 5.0f, 0.0f, 5.0f } },    /* 64, upper outer vent. */
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 5.0f } },    /* 65, upper inner vent. */
+  { VTYPE_PLAIN, { 5.0f, -2.0f, 3.0f } },    /* 66, lower outer vent. */
+  { VTYPE_PLAIN, { 0.0f, -2.0f, 3.0f } },    /* 67, lower inner vent. */
+  { VTYPE_PLAIN, { 5.0f, -2.0f, -30.0f } },    /* 68, nacelle outer underside. */
+  { VTYPE_PLAIN, { 0.0f, -2.0f, -30.0f } },    /* 69, nacelle inner underside. */
+  { VTYPE_PLAIN, { 13.0f, 0.0f, -14.0f } },    /* 70, rear underside centre. */
+  { VTYPE_PLAIN, { 7.5f, 0.0f, 3.0f } },    /* 71, vent outer edge. */
+
+  { VTYPE_PLAIN, { 3.75f, 0.7f, -30.0f } },    /* 72, engine midpoint. */
+
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 15.0f } },    /* 73, nose gear pos. */
+  { VTYPE_PLAIN, { 3.75f, -2.0f, -15.0f } },    /* 74, rear right gear. */
+  { VTYPE_PLAIN, { -3.75f, -2.0f, -15.0f } },    /* 75, rear left gear. */
+
+  { VTYPE_PLAIN, { 3.75f, 0.7f, -32.0f } },    /* 76, engine end. */
+
+  { VTYPE_PLAIN, { 4.5f, -0.3f, 4.7f } },    /* 77, retro vent. */
+  { VTYPE_PLAIN, { 0.5f, -0.3f, 4.7f } },
+  { VTYPE_PLAIN, { 4.5f, -1.7f, 3.3f } },
+  { VTYPE_PLAIN, { 0.5f, -1.7f, 3.3f } },
+
+  /* Main & retro thrusters. */
+  { VTYPE_PLAIN, { 3.75f, 0.7f, -32.0f } },    /* 81 */
+  { VTYPE_PLAIN, { -3.75f, 0.7f, -32.0f } },
+  { VTYPE_PLAIN, { 2.5f, -1.0f, 5.0f } },
+  { VTYPE_PLAIN, { -2.5f, -1.0f, 5.0f } },
+
+  /* Vertical thrusters. */
+  { VTYPE_PLAIN, { 9.0f, 1.5f, -10.0f } },    /* 85 */
+  { VTYPE_PLAIN, { 9.0f, -0.5f, -10.0f } },
+  { VTYPE_PLAIN, { -9.0f, 1.5f, -10.0f } },
+  { VTYPE_PLAIN, { -9.0f, -0.5f, -10.0f } },
+  { VTYPE_PLAIN, { 0.0f, 3.5f, 8.0f } },
+  { VTYPE_PLAIN, { 0.0f, -0.5f, 25.0f } },
+
+  /* Horizontal thrusters. */
+  { VTYPE_PLAIN, { 8.0f, 0.0f, -28.0f } },    /* 91 */
+  { VTYPE_PLAIN, { -8.0f, 0.0f, -28.0f } },
+  { VTYPE_PLAIN, { 3.5f, 0.0f, 25.0f } },
+  { VTYPE_PLAIN, { -3.5f, 0.0f, 25.0f } },
+
+  /* Text norms. */
+  { VTYPE_DIR, { 2.0f, -2.5f, 0.0f } },      /* 95 */
+  { VTYPE_DIR, { -2.0f, -2.5f, 0.0f } },
+  { VTYPE_PLAIN, { -5.0f, -2.0f, 3.0f } },    /* 97, lower outer vent, left side. */
+};
+
+static CompoundVertex ship2vtx2[] = {
+  { VTYPE_NORM, { 77, 78, 80, static_cast<uint16>(-1),static_cast<uint16>(-1)} },    /* 120, retro norm. */
+};
+
+static uint16 ship2data[] = {
+  PTYPE_MATFIXED, 100, 0, 100, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_COMPSMOOTH | RFLAG_XREF, 0, 5, 26, 27, 6, 7,  /* Front edge. */
+  COMP_HERM_NOTAN, 8, 9,
+  COMP_HERMITE, 16, 1, 37, 38,
+  COMP_HERMITE, 14, 1, 49, 48,
+  COMP_HERMITE, 6, 7, 41, 42,
+  COMP_END,
+  PTYPE_COMPSMOOTH | RFLAG_XREF, 1, 5, 28, 29, 8, 9,  /* Mid edge. */
+  COMP_HERM_NOTAN, 10, 11,
+  COMP_HERMITE, 22, 1, 37, 38,
+  COMP_HERM_NOTAN, 16, 1,
+  COMP_HERMITE, 8, 9, 43, 44,
+  COMP_END,
+  PTYPE_COMPSMOOTH | RFLAG_XREF, 2, 5, 30, 31, 10, 11,  /* Rear edge. */
+  COMP_HERMITE, 12, 13, 53, 54,
+  COMP_HERMITE, 22, 1, 39, 40,
+  COMP_HERMITE, 10, 11, 43, 44,
+  COMP_END,
+  PTYPE_COMPFLAT | RFLAG_XREF, 3, 5, 32, 1, 16, 1,  /* Centre. */
+  COMP_HERM_NOTAN, 22, 1,
+  COMP_HERMITE, 24, 1, 59, 60,
+  COMP_HERM_NOTAN, 18, 1,
+  COMP_HERMITE, 16, 1, 47, 51,
+  COMP_END,
+  PTYPE_COMPSMOOTH | RFLAG_XREF, 5, 5, 34, 1, 22, 23,  /* nacelle. */
+  COMP_HERMITE, 12, 0, 45, 46,
+  COMP_HERM_NOTAN, 35, 0,
+  COMP_HERMITE, 61, 1, 57, 63,
+  COMP_HERMITE, 36, 3, 63, 58,
+  COMP_HERM_NOTAN, 24, 25,
+  COMP_HERMITE, 22, 23, 59, 60,
+  COMP_END,
+  PTYPE_COMPFLAT | RFLAG_XREF, 6, 5, 70, 4, 12, 4,    /* Rear underside. */
+  COMP_HERMITE, 10, 4, 56, 55,
+  COMP_LINE, 71, 4,
+  COMP_LINE, 12, 4,
+  COMP_END,
+  PTYPE_QUADFLAT | RFLAG_XREF, 8, 6, 65, 64,      /* Other underside. */
+  PTYPE_QUADFLAT | RFLAG_XREF, 8, 64, 71, 10,
+  PTYPE_QUADFLAT | RFLAG_XREF, 64, 65, 67, 66,
+  PTYPE_TRIFLAT | RFLAG_XREF, 71, 64, 66,
+  PTYPE_QUADFLAT | RFLAG_XREF, 71, 66, 68, 12,
+  PTYPE_TRIFLAT | RFLAG_XREF, 12, 68, 35,
+  PTYPE_QUADFLAT | RFLAG_XREF, 66, 67, 69, 68,
+  PTYPE_COMPFLAT | RFLAG_XREF, 7, 5, 72, 5, 36, 5,    /* Engine back face. */
+  COMP_HERMITE, 61, 5, 57, 62,
+  COMP_HERMITE, 35, 5, 62, 58,
+  COMP_LINE, 68, 5,
+  COMP_LINE, 69, 5,
+  COMP_LINE, 36, 5,
+  COMP_END,
+
+  PTYPE_MATFIXED, 30, 30, 30, 30, 30, 30, 200, 0, 0, 0,
+  PTYPE_COMPSMOOTH, 4, 5, 33, 1, 16, 0,              /* Cockpit. */
+  COMP_HERMITE, 18, 5, 52, 48,
+  COMP_HERMITE, 20, 3, 48, 51,
+  COMP_HERMITE, 14, 2, 49, 47,
+  COMP_HERMITE, 16, 0, 47, 50,
+  COMP_END,
+
+  PTYPE_ZBIAS, 5, 5,
+  PTYPE_MATFIXED, 30, 30, 30, 30, 30, 30, 200, 0, 0, 0,
+  PTYPE_TUBE | RFLAG_XREF, 8, 12, 72, 76, 1, 250, 200,
+  PTYPE_MATANIM, AFUNC_THRUSTPULSE,
+  0, 0, 0, 0, 0, 0, 100, 50, 50, 100,
+  0, 0, 0, 0, 0, 0, 100, 0, 0, 50,
+  PTYPE_CIRCLE | RFLAG_XREF, 9, 12, 72, 5, 1, 200,
+
+  PTYPE_ZBIAS, 120, 5,
+  //  PTYPE_MATFIXED, 30, 30, 30, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_QUADFLAT | RFLAG_XREF, 77, 78, 80, 79,
+
+  PTYPE_MATFIXED, 20, 20, 20, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_ZBIAS, 95, 5,
+  PTYPE_TEXT, 0, 0x8000, 68, 95, 2, 1900, 50, 250,
+  PTYPE_ZBIAS, 96, 5,
+  PTYPE_TEXT, 0, 0x8000, 97, 96, 5, 400, 50, 250,
+
+  PTYPE_ZBIAS, 4, 5,
+  PTYPE_SUBOBJECT, 0, SUB_NWUNIT, 73, 4, 5, 100,
+  PTYPE_SUBOBJECT, 0, SUB_NWUNIT, 74, 4, 5, 64,
+  PTYPE_SUBOBJECT, 0, SUB_NWUNIT, 75, 4, 5, 64,
+
+  PTYPE_END,
+
+};
+
+static Thruster ship2thruster[] = {
+  { 81, 5 | THRUST_NOANG, 30.0f },
+  { 82, 5 | THRUST_NOANG, 30.0f },
+  { 83, 2 | THRUST_NOANG, 20.0f },
+  { 84, 2 | THRUST_NOANG, 20.0f },
+
+  { 85, 1, 15.0f }, { 86, 4, 15.0f },
+  { 87, 1, 15.0f }, { 88, 4, 15.0f },
+  { 89, 1, 15.0f }, { 90, 4, 15.0f },
+
+  { 91, 0, 15.0f }, { 92, 3, 15.0f },
+  { 93, 0, 15.0f }, { 94, 3, 15.0f },
+};
+
+static Model ship2model = { 1.0f, 98, ship2vtx1, 120, 1, ship2vtx2,
+                            0, 0, 14, ship2thruster, ship2data, 10 };
+
+
+static PlainVertex station1vtx1[] = {
+  { VTYPE_PLAIN, { -15.0f, 30.0f, 20.0f } },    /* 6, front octagon. */
+  { VTYPE_PLAIN, { 15.0f, 30.0f, 20.0f } },
+  { VTYPE_PLAIN, { 30.0f, 15.0f, 20.0f } },
+  { VTYPE_PLAIN, { 30.0f, -15.0f, 20.0f } },
+  { VTYPE_PLAIN, { 15.0f, -30.0f, 20.0f } },
+  { VTYPE_PLAIN, { -15.0f, -30.0f, 20.0f } },
+
+  { VTYPE_PLAIN, { -15.0f, 30.0f, -20.0f } },   /* 12, back octagon. */
+  { VTYPE_PLAIN, { 15.0f, 30.0f, -20.0f } },
+  { VTYPE_PLAIN, { 30.0f, 15.0f, -20.0f } },
+  { VTYPE_PLAIN, { 30.0f, -15.0f, -20.0f } },
+  { VTYPE_PLAIN, { 15.0f, -30.0f, -20.0f } },
+  { VTYPE_PLAIN, { -15.0f, -30.0f, -20.0f } },
+
+  { VTYPE_PLAIN, { -10.0f, 5.0f, 20.0f } },  /* 18, inlet front. */
+  { VTYPE_PLAIN, { 10.0f, 5.0f, 20.0f } },
+  { VTYPE_PLAIN, { 10.0f, -5.0f, 20.0f } },
+  { VTYPE_PLAIN, { -10.0f, -5.0f, 20.0f } },
+
+  { VTYPE_PLAIN, { -10.0f, 5.0f, 0.0f } },  /* 22, inlet rear. */
+  { VTYPE_PLAIN, { 10.0f, 5.0f, 0.0f } },
+  { VTYPE_PLAIN, { 10.0f, -5.0f, 0.0f } },
+  { VTYPE_PLAIN, { -10.0f, -5.0f, 0.0f } },
+
+
+  { VTYPE_PLAIN, { 30.0f, 10.0f, 10.0f } },  /* 26, strut inner. */
+  { VTYPE_PLAIN, { 30.0f, -10.0f, 10.0f } },
+  { VTYPE_PLAIN, { 30.0f, -10.0f, -10.0f } },
+  { VTYPE_PLAIN, { 30.0f, 10.0f, -10.0f } },
+
+  { VTYPE_PLAIN, { 100.0f, 10.0f, 10.0f } },  /* 30, strut outer. */
+  { VTYPE_PLAIN, { 100.0f, -10.0f, 10.0f } },
+  { VTYPE_PLAIN, { 100.0f, -10.0f, -10.0f } },
+  { VTYPE_PLAIN, { 100.0f, 10.0f, -10.0f } },
+
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 25.0f } },  /* 34, ring start, end. */
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -25.0f } },
+
+#if 0
+    { VTYPE_PLAIN, { 0.0f, 120.0f, 15.0f } },   /* 34, ring top. */
+    { VTYPE_PLAIN, { 0.0f, 100.0f, 15.0f } },
+    { VTYPE_PLAIN, { 0.0f, 100.0f, -15.0f } },
+    { VTYPE_PLAIN, { 0.0f, 120.0f, -15.0f } },
+
+    { VTYPE_PLAIN, { 103.9230f, 60.0f, 15.0f } }, /* 38, ring top right. */
+    { VTYPE_PLAIN, { 86.60254f, 50.0f, 15.0f } },
+    { VTYPE_PLAIN, { 86.60254f, 50.0f, -15.0f } },
+    { VTYPE_PLAIN, { 103.9230f, 60.0f, -15.0f } },
+
+    { VTYPE_PLAIN, { 103.9230f, -60.0f, 15.0f } },  /* 42, ring bottom right. */
+    { VTYPE_PLAIN, { 86.60254f, -50.0f, 15.0f } },
+    { VTYPE_PLAIN, { 86.60254f, -50.0f, -15.0f } },
+    { VTYPE_PLAIN, { 103.9230f, -60.0f, -15.0f } },
+
+    { VTYPE_PLAIN, { 0.0f, -120.0f, 15.0f } },      /* 46, ring bottom. */
+    { VTYPE_PLAIN, { 0.0f, -100.0f, 15.0f } },
+    { VTYPE_PLAIN, { 0.0f, -100.0f, -15.0f } },
+    { VTYPE_PLAIN, { 0.0f, -120.0f, -15.0f } },
+
+    { VTYPE_DIR, { 0.8660254f, 0.5f, 0.0f } },      /* 50, ring normals. */
+    { VTYPE_DIR, { -0.8660254ff, -0.5f, 0.0f } },
+    { VTYPE_DIR, { 0.8660254ff, -0.5f, 0.0f } },
+    { VTYPE_DIR, { -0.8660254ff, 0.5f, 0.0f } },
+
+    { VTYPE_PLAIN, { 120.0f, 0.0f, 0.0f } },      /* 54, top tangents. */
+    { VTYPE_PLAIN, { 100.0f, 0.0f, 0.0f } },
+    { VTYPE_PLAIN, { -60.0f, 104.0f, 0.0f } },      /* 56, top right tangents. */
+    { VTYPE_PLAIN, { -50.0f, 86.6f, 0.0f } },
+    { VTYPE_PLAIN, { -60.0f, -104.0f, 0.0f } },      /* 58, bottom right tangents. */
+    { VTYPE_PLAIN, { -50.0f, -86.6f, 0.0f } },
+    { VTYPE_PLAIN, { -120.0f, 0.0f, 0.0f } },      /* 60, bottom tangents. */
+    { VTYPE_PLAIN, { -100.0f, 0.0f, 0.0f } },
+
+    { VTYPE_PLAIN, { 60.0f, 103.9230f, 0.0f } },    /* 61, outer midpoints. */
+    { VTYPE_PLAIN, { 120.0f, 0.0f, 0.0f } },
+    { VTYPE_PLAIN, { 60.0f, -103.9230f, 0.0f } },
+
+    { VTYPE_PLAIN, { 50.0f, 86.60254f, 0.0f } },    /* 64, inner midpoints. */
+    { VTYPE_PLAIN, { 100.0f, 0.0f, 0.0f } },
+    { VTYPE_PLAIN, { 50.0f, -86.60254f, 0.0f } },
+
+    { VTYPE_DIR, { 0.5f, 0.8660254f, 0.0f } },      /* 67, midpoint normals. */
+    { VTYPE_DIR, { -0.5f, -0.8660254f, 0.0f } },
+    { VTYPE_DIR, { 0.5f, -0.8660254f, 0.0f } },
+    { VTYPE_DIR, { -0.5f, 0.8660254f, 0.0f } },
+
+    { VTYPE_PLAIN, { 60.0f, 103.9230f, 15.0f } },   /* 71, forward ring midpoints. */
+    { VTYPE_PLAIN, { 120.0f, 0.0f, 15.0f } },
+    { VTYPE_PLAIN, { 60.0f, -103.9230f, 15.0f } },
+
+    { VTYPE_PLAIN, { 60.0f, 103.9230f, -15.0f } },  /* 74, back ring midpoints. */
+    { VTYPE_PLAIN, { 120.0f, 0.0f, -15.0f } },
+    { VTYPE_PLAIN, { 60.0f, -103.9230f, -15.0f } },
+
+    { VTYPE_PLAIN, { -60.0f, 104.0f, 0.0f } },      /* 77, top right AC tangents. */
+    { VTYPE_PLAIN, { -50.0f, 86.6f, 0.0f } },
+    { VTYPE_PLAIN, { -60.0f, -104.0f, 0.0f } },      /* 79, bottom right AC tangents. */
+    { VTYPE_PLAIN, { -50.0f, -86.6f, 0.0f } },
+#endif
+};
+
+static CompoundVertex station1vtx2[] = {
+  { VTYPE_CROSS, { 0, 1, 2, static_cast<uint16>(-1),static_cast<uint16>(-1) } },        /* Dummy. */
+};
+
+static uint16 station1data[] = {
+  PTYPE_MATFIXED, 100, 0, 100, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_QUADFLAT, 7, 6, 18, 19,          /* Front face. */
+  PTYPE_QUADFLAT | RFLAG_XREF, 9, 8, 7, 19,
+  PTYPE_QUADFLAT | RFLAG_XREF, 10, 9, 19, 20,
+  PTYPE_QUADFLAT, 11, 10, 20, 21,
+
+  PTYPE_QUADFLAT | RFLAG_XREF, 13, 14, 15, 16,      /* Back face. */
+  PTYPE_QUADFLAT, 12, 13, 16, 17,
+
+  PTYPE_QUADFLAT, 6, 7, 13, 12,          /* Sides. */
+  PTYPE_QUADFLAT | RFLAG_XREF, 7, 8, 14, 13,
+  PTYPE_QUADFLAT | RFLAG_XREF, 8, 9, 15, 14,
+  PTYPE_QUADFLAT | RFLAG_XREF, 9, 10, 16, 15,
+  PTYPE_QUADFLAT, 10, 11, 17, 16,
+
+  PTYPE_QUADFLAT, 19, 18, 22, 23,        /* Inlet. */
+  PTYPE_QUADFLAT | RFLAG_XREF, 20, 19, 23, 24,
+  PTYPE_QUADFLAT, 21, 20, 24, 25,
+  PTYPE_QUADFLAT, 23, 22, 25, 24,
+
+  PTYPE_QUADFLAT | RFLAG_XREF, 26, 27, 31, 30,
+  PTYPE_QUADFLAT | RFLAG_XREF, 27, 28, 32, 31,
+  PTYPE_QUADFLAT | RFLAG_XREF, 28, 29, 33, 32,
+  PTYPE_QUADFLAT | RFLAG_XREF, 29, 26, 30, 33,
+
+  PTYPE_TUBE | RFLAG_XREF, 0, 38, 34, 35, 1, 11500, 10000,
+
+  //  PTYPE_QUADFLAT | RFLAG_XREF,
+  //  PTYPE_SUBOBJECT, 0x8000, SUB_NOSEWHEEL, 10, 0, 4, 100,
+
+  PTYPE_END,
+};
+
+static Model station1model = { 1.0f, 36, station1vtx1, 100, 0, station1vtx2,
+                               0, 0, 0, 0, station1data, 1 };
+
+static PlainVertex ship3vtx1[] = {
+  { VTYPE_PLAIN, { 4.0f, -5.0f, 20.0f } },        /* 6, nose pair. */
+  { VTYPE_PLAIN, { -4.0f, -5.0f, 20.0f } },
+
+  { VTYPE_PLAIN, { 6.0f, 4.0f, 10.0f } },        /* 8, mid vertices. */
+  { VTYPE_PLAIN, { -6.0f, 4.0f, 10.0f } },
+
+  { VTYPE_PLAIN, { 14.0f, -5.0f, 10.0f } },        /* 10, front quarter. */
+  { VTYPE_PLAIN, { 10.0f, 5.0f, -10.0f } },        /* Back mid. */
+  { VTYPE_PLAIN, { 30.0f, -5.0f, -10.0f } },        /* Back end. */
+
+  //  { VTYPE_PLAIN, { 20.0f, 1.0f, 0.0f } },    /* 13, curve midpoint. */
+  //  { VTYPE_DIR, { 2.0f, 2.0f, 1.0f } },    /* norm. */
+
+  { VTYPE_PLAIN, { 18.75f, 1.25f, -2.1875f } },        /* 13, curve midpoint. */
+  { VTYPE_DIR, { 0.707f, 1.0f, 0.707f } },        /* norm. */
+
+  { VTYPE_PLAIN, { 15.0f, 0.0f, -10.0f } },        /* 15, back midpoint. */
+  { VTYPE_PLAIN, { 30.0f, -5.0f, -10.0f } },        /* Underside midpoint. */
+
+  // CW tangents
+  { VTYPE_PLAIN, { -4.0f, -1.0f, 20.0f } },        /* 17, 11->10 start. */
+  { VTYPE_PLAIN, { 8.0f, -9.0f, 0.0f } },        /* 11->10 end. */
+
+  { VTYPE_PLAIN, { 16.0f, 0.0f, 0.0f } },        /* 19, 10->12 start. */
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -20.0f } },        /* 10-12 end. */
+
+  { VTYPE_PLAIN, { 0.0f, 10.0f, 0.0f } },        /* 21, 12->11 start. */
+  { VTYPE_PLAIN, { -20.0f, 0.0f, 0.0f } },        /* 12-11 end. */
+
+  // CCW tangents
+  { VTYPE_PLAIN, { -8.0f, 9.0f, 0.0f } },        /* 23, 10->11 start. */
+  { VTYPE_PLAIN, { 4.0f, 1.0f, -20.0f } },        /* 10->11 end. */
+
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 20.0f } },        /* 25, 12-10 start. */
+  { VTYPE_PLAIN, { -16.0f, 0.0f, 0.0f } },        /* 12->10 end. */
+
+  { VTYPE_PLAIN, { 20.0f, 0.0f, 0.0f } },        /* 27, 11-12 start. */
+  { VTYPE_PLAIN, { 0.0f, -10.0f, 0.0f } },        /* 11->12 end. */
+
+  { VTYPE_PLAIN, { -10.0f, 5.0f, -10.0f } },        /* 29, back mid, left side. */
+  { VTYPE_PLAIN, { -14.0f, -5.0f, 10.0f } },        /* 30 front quarter, left side. */
+  { VTYPE_PLAIN, { 10.0f, -5.0f, -10.0f } },        /* Back mid, right underside. */
+  { VTYPE_PLAIN, { -10.0f, -5.0f, -10.0f } },        /* Back mid, left underside. */
+
+  { VTYPE_PLAIN, { 12.0f, 0.0f, -10.0f } },        /* 33, back thruster. */
+  { VTYPE_PLAIN, { 12.0f, 0.0f, -13.0f } },        /* Back thruster end. */
+
+  { VTYPE_PLAIN, { 0.0f, -5.0f, 13.0f } },        /* 35, nose gear. */
+  { VTYPE_PLAIN, { 15.0f, -5.0f, -3.0f } },        /* 36, back gear. */
+  { VTYPE_PLAIN, { -15.0f, -5.0f, -3.0f } },        /* 37, back gear. */
+
+  // thruster jets
+  { VTYPE_PLAIN, { 12.0f, 0.0f, -13.0f } },        /* 38, main. */
+  { VTYPE_PLAIN, { 15.0f, -3.0f, 9.0f } },        /* Retro. */
+
+  { VTYPE_PLAIN, { 30.0f, -4.0f, -9.0f } },        /* 40, corner clusters. */
+  { VTYPE_PLAIN, { 29.0f, -5.5f, -9.0f } },        /* Down. */
+  { VTYPE_PLAIN, { 29.0f, -4.0f, -9.0f } },        /* Up. */
+  { VTYPE_PLAIN, { 10.0f, 0.0f, 11.0f } },        /* Lateral front. */
+
+};
+
+static CompoundVertex ship3vtx2[] = {
+  { VTYPE_NORM, { 15, 8, 10, static_cast<uint16>(-1), static_cast<uint16>(-1) } },  /* 100, mid curve norm. */
+  { VTYPE_NORM, { 9, 8, 11, static_cast<uint16>(-1), static_cast<uint16>(-1) } },    /* 101, top curve norm. */
+};
+
+static uint16 ship3data[] = {
+  PTYPE_MATFIXED, 100, 0, 100, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_QUADFLAT, 6, 8, 9, 7,
+  PTYPE_QUADFLAT, 9, 8, 11, 29,
+  PTYPE_TRIFLAT | RFLAG_XREF, 8, 6, 10,
+  PTYPE_QUADFLAT, 10, 6, 7, 30,
+  PTYPE_QUADFLAT, 32, 31, 10, 30,
+  PTYPE_QUADFLAT, 29, 11, 31, 32,
+
+  PTYPE_COMPFLAT | RFLAG_XREF, 0, 5, 8, 100, 8, 100,    /* mid curve. */
+  COMP_LINE, 10, 100,
+  COMP_HERMITE, 11, 100, 23, 24,
+  COMP_LINE, 8, 100,
+  COMP_END,
+  PTYPE_COMPSMOOTH | RFLAG_XREF, 1, 5, 13, 14, 11, 101,    /* top curve. */
+  COMP_HERMITE, 10, 2, 17, 18,
+  COMP_HERMITE, 12, 0, 19, 20,
+  COMP_HERMITE, 11, 101, 21, 22,
+  COMP_STEPS, 10,
+  COMP_END,
+  PTYPE_COMPFLAT | RFLAG_XREF, 2, 5, 15, 5, 11, 5,    /* back curve. */
+  COMP_HERMITE, 12, 5, 27, 28,
+  COMP_LINE, 31, 5,
+  COMP_LINE, 11, 5,
+  COMP_END,
+  PTYPE_COMPFLAT | RFLAG_XREF, 3, 5, 16, 4, 12, 4,    /* underside curve. */
+  COMP_HERMITE, 10, 4, 25, 26,
+  COMP_LINE, 31, 4,
+  COMP_LINE, 12, 4,
+  COMP_END,
+
+  PTYPE_ZBIAS, 5, 5,
+  PTYPE_MATFIXED, 30, 30, 30, 30, 30, 30, 200, 0, 0, 0,
+  PTYPE_TUBE | RFLAG_XREF, 4, 12, 33, 34, 1, 300, 250,
+  PTYPE_MATANIM, AFUNC_THRUSTPULSE,
+  0, 0, 0, 0, 0, 0, 100, 50, 50, 100,
+  0, 0, 0, 0, 0, 0, 100, 0, 0, 50,
+  PTYPE_CIRCLE | RFLAG_XREF, 5, 12, 33, 5, 1, 250,
+
+  //  PTYPE_ZBIAS, 120, 5,
+  //  PTYPE_MATFIXED, 30, 30, 30, 0, 0, 0, 100, 0, 0, 0,
+  //  PTYPE_QUADFLAT | RFLAG_XREF, 77, 78, 80, 79,
+
+  /*  PTYPE_MATFIXED, 20, 20, 20, 0, 0, 0, 100, 0, 0, 0,
+    PTYPE_ZBIAS, 95, 5,
+    PTYPE_TEXT, 0, 0x8000, 68, 95, 2, 1900, 50, 250,
+    PTYPE_ZBIAS, 96, 5,
+    PTYPE_TEXT, 0, 0x8000, 97, 96, 5, 400, 50, 250,
+  */
+  PTYPE_ZBIAS, 4, 5,
+  PTYPE_SUBOBJECT, 0, SUB_NWUNIT, 35, 4, 5, 100,
+  PTYPE_SUBOBJECT, 0, SUB_MWUNIT, 36, 4, 5, 100,
+  PTYPE_SUBOBJECT, 0, SUB_MWUNIT, 37, 4, 5, 100,
+
+  PTYPE_END,
+};
+
+static Thruster ship3thruster[] = {
+  { 38, 5 | THRUST_NOANG | THRUST_XREF, 30.0f },
+  { 39, 2 | THRUST_NOANG | THRUST_XREF, 20.0f },
+  { 40, 0 | THRUST_XREF, 15.0f },
+  { 41, 4 | THRUST_XREF, 15.0f },
+  { 42, 1 | THRUST_XREF, 15.0f },
+  { 43, 0 | THRUST_XREF, 15.0f },
+};
+
+static Model ship3model = { 1.0f, 44, ship3vtx1, 100, 2, ship3vtx2,
+                            0, 0, 6, ship3thruster, ship3data, 6 };
+
+static PlainVertex ship4vtx1[] = {
+  { VTYPE_PLAIN, { -4.0f, -3.0f, 35.0f } },      /* 6, nose vertices. */
+  { VTYPE_PLAIN, { 4.0f, -3.0f, 35.0f } },
+  { VTYPE_PLAIN, { 1.0f, -7.0f, 32.0f } },
+  { VTYPE_PLAIN, { -1.0f, -7.0f, 32.0f } },
+
+  { VTYPE_PLAIN, { -6.0f, 8.0f, 20.0f } },      /* 10, nose section back. */
+  { VTYPE_PLAIN, { 6.0f, 8.0f, 20.0f } },      /* And extrusion area. */
+  { VTYPE_PLAIN, { 10.0f, 4.0f, 20.0f } },
+  { VTYPE_PLAIN, { 10.0f, -4.0f, 20.0f } },
+  { VTYPE_PLAIN, { 6.0f, -8.0f, 20.0f } },
+  { VTYPE_PLAIN, { -6.0f, -8.0f, 20.0f } },
+  { VTYPE_PLAIN, { -10.0f, -4.0f, 20.0f } },
+  { VTYPE_PLAIN, { -10.0f, 4.0f, 20.0f } },
+
+  /* Midpoints. */
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 20.0f } },      /* 18 */
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 16.0f } },
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -4.0f } },
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -8.0f } },
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -26.0f } },
+
+  { VTYPE_PLAIN, { 0.3826834f, 0.9238795f, 0.0f } },    /* 23, tube norm. */
+
+  { VTYPE_PLAIN, { 12.5f, 2.0f, -10.0f } },      /* 24, top engine. */
+  { VTYPE_PLAIN, { 12.5f, 2.0f, -30.0f } },
+  { VTYPE_PLAIN, { 12.5f, 2.0f, -13.0f } },
+  { VTYPE_PLAIN, { 12.5f, 2.0f, -27.0f } },
+
+  { VTYPE_PLAIN, { 11.5f, -6.0f, -10.0f } },      /* 28, bottom engine. */
+  { VTYPE_PLAIN, { 11.5f, -6.0f, -30.0f } },
+  { VTYPE_PLAIN, { 11.5f, -6.0f, -13.0f } },
+  { VTYPE_PLAIN, { 11.5f, -6.0f, -27.0f } },
+
+  { VTYPE_PLAIN, { 10.0f, -4.0f, -4.0f } },      /* 32, right text pos. */
+  { VTYPE_PLAIN, { -10.0f, -4.0f, 16.0f } },      /* Left text pos. */
+
+  { VTYPE_PLAIN, { 5.0f, -8.0f, 13.0f } },      /* 34, gear pos. */
+  { VTYPE_PLAIN, { -5.0f, -8.0f, 13.0f } },
+  { VTYPE_PLAIN, { 11.5f, -8.309f, -25.0f } },      /* 36, gear pos. */
+  { VTYPE_PLAIN, { -11.5f, -8.309f, -25.0f } },
+  { VTYPE_PLAIN, { 11.5f, -8.309f, -13.0f } },      /* 38, gear pos. */
+  { VTYPE_PLAIN, { -11.5f, -8.309f, -13.0f } },
+
+  { VTYPE_PLAIN, { 0.05f, 8.0f, -15.0f } },      /* 40, dish pos. */
+};
+
+static CompoundVertex ship4vtx2[] = {
+  { VTYPE_ANIMROTATE,{1,0,static_cast<uint16>(-1),static_cast<uint16>(-1),AFUNC_LIN4SEC } },/* Dummy. */
+};
+
+static uint16 ship4data[] = {
+  PTYPE_MATFIXED, 100, 0, 100, 0, 0, 0, 100, 0, 0, 0,
+
+  PTYPE_QUADFLAT, 6, 7, 11, 10,       /* Front section. */
+  PTYPE_TRIFLAT | RFLAG_XREF, 7, 12, 11,
+  PTYPE_TRIFLAT | RFLAG_XREF, 7, 13, 12,
+  PTYPE_TRIFLAT | RFLAG_XREF, 7, 14, 13,
+  PTYPE_TRIFLAT | RFLAG_XREF, 7, 9, 14,
+  PTYPE_QUADFLAT, 7, 6, 9, 8,
+  PTYPE_QUADFLAT, 8, 9, 15, 14,
+
+  PTYPE_QUADFLAT, 10, 11, 14, 15,
+  PTYPE_QUADFLAT | RFLAG_XREF, 11, 12, 13, 14,
+
+  PTYPE_EXTRUSION, 0, 8, 19, 20, 1, 100, 10,
+  PTYPE_EXTRUSION, 1, 8, 21, 22, 1, 100, 10,
+
+  PTYPE_TUBE | RFLAG_XREF, 2, 8, 24, 25, 23, 250, 200,
+  PTYPE_TUBE | RFLAG_XREF, 3, 8, 28, 29, 23, 250, 200,
+
+  PTYPE_MATANIM, AFUNC_THRUSTPULSE,
+  0, 0, 0, 0, 0, 0, 100, 50, 50, 100,
+  0, 0, 0, 0, 0, 0, 100, 0, 0, 50,
+  PTYPE_CIRCLE | RFLAG_XREF, 4, 8, 26, 2, 23, 200,
+  PTYPE_CIRCLE | RFLAG_XREF, 5, 8, 27, 5, 23, 200,
+  PTYPE_CIRCLE | RFLAG_XREF, 6, 8, 30, 2, 23, 200,
+  PTYPE_CIRCLE | RFLAG_XREF, 7, 8, 31, 5, 23, 200,
+
+  PTYPE_MATFIXED, 30, 30, 30, 10, 10, 10, 100, 0, 0, 0,
+  PTYPE_EXTRUSION, 8, 8, 18, 19, 1, 85, 10,
+  PTYPE_EXTRUSION, 9, 8, 20, 21, 1, 85, 10,
+
+  PTYPE_MATFIXED, 20, 20, 20, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_ZBIAS, 0, 5,
+  PTYPE_TEXT, 0, 0x8000, 32, 0, 2, 300, 250, 400,
+  PTYPE_ZBIAS, 3, 5,
+  PTYPE_TEXT, 0, 0x8000, 33, 3, 5, 300, 250, 400,
+
+  PTYPE_ZBIAS, 4, 5,
+  PTYPE_SUBOBJECT, 0, SUB_MWUNIT, 34, 4, 5, 60,
+  PTYPE_SUBOBJECT, 0, SUB_MWUNIT, 35, 4, 5, 60,
+  PTYPE_SUBOBJECT, 0, SUB_MWUNIT, 36, 4, 5, 50,
+  PTYPE_SUBOBJECT, 0, SUB_MWUNIT, 37, 4, 5, 50,
+  PTYPE_SUBOBJECT, 0, SUB_MWUNIT, 38, 4, 5, 50,
+  PTYPE_SUBOBJECT, 0, SUB_MWUNIT, 39, 4, 5, 50,
+
+  PTYPE_ZBIAS, 1, 5,
+  PTYPE_SUBOBJECT, 0x8000, SUB_DISH, 40, 1, 100, 200,
+
+  PTYPE_END,
+};
+
+static Thruster ship4thruster[] = {
+  { 25, 5 | THRUST_NOANG | THRUST_XREF, 30.0f },
+  { 29, 5 | THRUST_NOANG | THRUST_XREF, 30.0f },
+  { 24, 2 | THRUST_NOANG | THRUST_XREF, 20.0f },
+  { 28, 2 | THRUST_NOANG | THRUST_XREF, 20.0f },
+  //  { 40, 0 | THRUST_XREF, 15.0f },
+  //  { 41, 4 | THRUST_XREF, 15.0f },
+  //  { 42, 1 | THRUST_XREF, 15.0f },
+  //  { 43, 0 | THRUST_XREF, 15.0f },
+};
+static Model ship4model = { 1.0f, 41, ship4vtx1, 100, 1, ship4vtx2,
+                            0, 0, 4, ship4thruster, ship4data, 10 };
+
+static PlainVertex dishvtx1[] = {
+  { VTYPE_PLAIN, { 0.0f, 3.0f, 1.2f } },  /* 6, dish. */
+  { VTYPE_PLAIN, { 1.0f, 2.0f, 1.2f } },
+  { VTYPE_PLAIN, { 0.0f, 1.0f, 1.2f } },
+  { VTYPE_PLAIN, { -1.0f, 2.0f, 1.2f } },
+  { VTYPE_PLAIN, { 0.0f, 2.0f, 0.2f } },
+
+  { VTYPE_PLAIN, { 0.0f, 2.2f, 0.0f } },  /* 11, stand. */
+  { VTYPE_PLAIN, { 0.0f, 0.6f, 0.0f } },
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 0.0f } },
+
+  { VTYPE_PLAIN, { 0.0f, 2.0f, 1.7f } },  /* 14, antenna. */
+
+  { VTYPE_PLAIN, { 1.5f, 0.0f, 0.0f } },  /* 15, tangents. */
+  { VTYPE_PLAIN, { -1.5f, 0.0f, 0.0f } },
+  { VTYPE_PLAIN, { 0.0f, 1.5f, 0.0f } },
+  { VTYPE_PLAIN, { 0.0f, -1.5f, 0.0f } },
+
+};
+
+static CompoundVertex dishvtx2[] = {
+  { VTYPE_CROSS, { 0, 1, 2, static_cast<uint16>(-1),static_cast<uint16>(-1) } },      /* Dummy. */
+};
+
+static uint16 dishdata[] = {
+  PTYPE_MATFIXED, 50, 50, 50, 100, 100, 100, 200, 0, 0, 0,
+  PTYPE_COMPSMOOTH, 0, 5, 10, 5, 6, 1,
+  COMP_HERMITE, 7, 0, 15, 18,
+  COMP_HERMITE, 8, 4, 18, 16,
+  COMP_HERMITE, 9, 3, 16, 17,
+  COMP_HERMITE, 6, 1, 17, 15,
+  COMP_END,
+  PTYPE_COMPSMOOTH, 1, 5, 10, 2, 6, 4,
+  COMP_HERMITE, 9, 0, 16, 18,
+  COMP_HERMITE, 8, 1, 18, 15,
+  COMP_HERMITE, 7, 3, 15, 17,
+  COMP_HERMITE, 6, 4, 17, 16,
+  COMP_END,
+  PTYPE_CYLINDER, 4, 6, 10, 14, 0, 10,
+
+  PTYPE_MATFIXED, 100, 0, 100, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_CYLINDER, 2, 6, 11, 12, 0, 20,
+  PTYPE_CYLINDER, 3, 6, 12, 13, 0, 70,
+
+  PTYPE_END,
+};
+
+static Model dishmodel = { 1.0f, 19, dishvtx1, 40, 0, dishvtx2,
+                           0, 0, 0, 0, dishdata, 5 };
+
+static PlainVertex ship5vtx1[] = {
+  { VTYPE_PLAIN, { -1.0f, 0.0f, 20.0f } },      /* 6, right nose vertex. */
+  { VTYPE_PLAIN, { 0.0f, 1.0f, 0.4f } },
+  { VTYPE_PLAIN, { 0.0f, -1.0f, 0.4f } },
+  
+  { VTYPE_PLAIN, { 1.0f, 0.0f, 20.0f } },       /* 9, left nose vertex. */
+  { VTYPE_PLAIN, { 0.0f, 1.0f, 0.4f } },
+  { VTYPE_PLAIN, { 0.0f, -1.0f, 0.4f } },
+
+  { VTYPE_PLAIN, { -2.0f, 2.0f, 10.0f } },      /* 12, nose section back */
+  { VTYPE_PLAIN, { 2.0f, 2.0f, 10.0f } },       /* and extrusion area. */
+  { VTYPE_PLAIN, { 3.0f, 0.0f, 10.0f } },
+  { VTYPE_PLAIN, { 2.0f, -2.0f, 10.0f } },
+  { VTYPE_PLAIN, { -2.0f, -2.0f, 10.0f } },
+  { VTYPE_PLAIN, { -3.0f, 0.0f, 10.0f } },
+
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 10.0f } },       /* 18, extrusion start/end. */
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -15.0f } },
+
+  /* Tangents. */
+  { VTYPE_PLAIN, { 1.0f, 0.0f, 10.0f } },       /* 20, 12->6 and 16->6 s. */
+  { VTYPE_PLAIN, { 1.0f, -4.0f, 10.f } },       /* 12->6 e. */
+  { VTYPE_PLAIN, { -1.0f, 4.0f, -10.0f } },     /* 6-12 e. */
+  { VTYPE_PLAIN, { -1.0f, 0.0f, -10.0f } },     /* 6->12 and 6->16 s. */
+
+  { VTYPE_PLAIN, { -1.0f, 0.0f, 10.0f } },      /* 24, 13->9. */
+  { VTYPE_PLAIN, { -1.0f, -4.0f, 10.0f } },
+  { VTYPE_PLAIN, { 1.0f, 4.0f, -10.0f } },
+  { VTYPE_PLAIN, { 1.0f, 0.0f, -10.0f } },      /* 9->13 and 9-> */
+
+  { VTYPE_PLAIN, { 1.0f, 4.0f, 10.0f } },       /* 28, 16->6 e. */
+  { VTYPE_PLAIN, { -1.0f, -4.0f, -10.0f } },    /* 6-16 e. */
+
+  { VTYPE_PLAIN, { -1.0f, 4.0f, 10.0f } },      /* 30, 15->9 e. */
+  { VTYPE_PLAIN, { 1.0f, -4.0f, 10.0f } },      /* 9-15 e. */
+
+  { VTYPE_PLAIN, { 0.0f, 1.333f, 15.0f } },     /* 32, nose top midpoint. */
+  { VTYPE_PLAIN, { 0.0f, 1.0f, 0.2f } },
+
+  { VTYPE_PLAIN, { 0.0f, -1.333f, 15.0f } },    /* 34, nose bottom midpoint. */
+  { VTYPE_PLAIN, { 0.0f, -1.0f, 0.2f } },
+
+  /* Wing positions. */
+  { VTYPE_PLAIN, { 2.5f, 1.0f, -5.0f } },       /* 36. */
+  { VTYPE_PLAIN, { 2.5f, -1.0f, -5.0f } },
+  { VTYPE_PLAIN, { -2.5f, -1.0f, -5.0f } },
+  { VTYPE_PLAIN, { -2.5f, 1.0f, -5.0f } },
+
+  /* Wing normals. */
+  { VTYPE_DIR, { 2.0f, 1.0f, 0.0f } },          /* 40. */
+  { VTYPE_DIR, { 2.0f, -1.0f, 0.0f } },
+  { VTYPE_DIR, { -2.0f, -1.0f, 0.0f } },
+  { VTYPE_DIR, { -2.0f, 1.0f, 0.0f } },
+};
+
+static CompoundVertex ship5vtx2[] = {
+  { VTYPE_NORM, {9,14,13,static_cast<uint16>(-1),static_cast<uint16>(-1)}}, /* 100, nose side normals. */
+  { VTYPE_NORM, {9,15,14,static_cast<uint16>(-1),static_cast<uint16>(-1)}},
+};
+
+static uint16 ship5data[] = {
+  PTYPE_MATFIXED, 100, 0, 100, 0, 0, 0, 100, 0, 0, 0,
+
+  PTYPE_EXTRUSION, 0, 6, 18, 19, 1, 100, 12,
+
+  PTYPE_COMPSMOOTH, 1, 5, 32, 33, 13, 1,
+    COMP_LINE, 12, 1,
+    COMP_HERMITE, 6, 7, 20, 21,
+    COMP_LINE, 9, 10,
+    COMP_HERMITE, 13, 1, 26, 27,
+    COMP_END,
+  PTYPE_COMPSMOOTH, 2, 5, 34, 35, 16, 4,
+    COMP_LINE, 15, 4,
+    COMP_HERMITE, 9, 11, 24, 30,
+    COMP_LINE, 6, 8,
+    COMP_HERMITE, 16, 4, 29, 23,
+    COMP_END,
+
+  PTYPE_COMPFLAT | RFLAG_XREF, 3, 5, 14, 100, 13, 100,
+    COMP_HERMITE, 0, 100, 24, 25,
+    COMP_LINE, 14, 100,
+    COMP_LINE, 13, 100,
+    COMP_END,
+  PTYPE_COMPFLAT | RFLAG_XREF, 4, 5, 14, 101, 9, 101,
+    COMP_HERMITE, 15, 101, 31, 27,
+    COMP_LINE, 14, 101,
+    COMP_LINE, 9, 101,
+    COMP_END,
+
+  PTYPE_SUBOBJECT, 0x8000, SUB_WING2, 36, 40, 2, 70,
+  PTYPE_SUBOBJECT, 0x8000, SUB_WING2, 37, 41, 2, 70,
+  PTYPE_SUBOBJECT, 0x8000, SUB_WING2, 38, 42, 2, 70,
+  PTYPE_SUBOBJECT, 0x8000, SUB_WING2, 39, 43, 2, 70,
+
+  PTYPE_END,
+};
+
+static Model ship5model = { 1.0f, 44, ship5vtx1, 100, 2, ship5vtx2,
+                            0, 0, 0, 0, ship5data, 5 };
+
+static PlainVertex wing2vtx1[] = {
+  { VTYPE_PLAIN, { 0.0f, 0.0f, 3.5f } },    /* 6, bottom front. */
+  { VTYPE_PLAIN, { 0.0f, 0.0f, -3.5f } },   /* Bottom back. */
+  { VTYPE_PLAIN, { 0.0f, 20.0f, 3.5f } },   /* Top front. */
+  { VTYPE_PLAIN, { 0.0f, 20.0f, -3.5f } },  /* Top back. */
+
+  { VTYPE_DIR, { 0.0f, 0.0f, 1.0f } },      /* 10, front norm. */
+  { VTYPE_DIR, { 3.0f, 0.0f, -1.0f } },     /* Back, norm. */
+  
+  { VTYPE_PLAIN, { 0.8f, 10.0f, 0.0f } },    /* 12, sidecenter. */
+  { VTYPE_DIR, { 1.0f, 0.0f, 0.20f } },      /* Sidenorm. */
+
+  { VTYPE_PLAIN, { -2.8f, 0.0f, 0.0f } },    /* 14, front tan, forward. */
+  { VTYPE_PLAIN, { 2.8f, 0.0f, 0.0f } },     /* Front tan,  backward. */
+
+  { VTYPE_PLAIN, { 1.0f, 0.0f, 3.0f } },     /* 16, back tan, forward. */
+  { VTYPE_PLAIN, { -1.0f, 0.0f, -3.0f } },   /* Back tan, backward. */
+
+  { VTYPE_PLAIN, { 0.3826834f, 0.9238795f, 0.0f } }, /* 18, tube norm. */
+  { VTYPE_PLAIN, { 0.0f, 21.5f, 5.0f } },     /* Tube start. */
+  { VTYPE_PLAIN, { 0.0f, 21.5f, -5.0f } },
+  { VTYPE_PLAIN, { 0.0f, 22.0f, 4.0f } },
+  { VTYPE_PLAIN, { 0.0f, 22.0f, -4.0f } },
+};
+
+static CompoundVertex wing2vtx2[] = {
+  {VTYPE_CROSS,{19,14,static_cast<uint16>(-1),static_cast<uint16>(-1),static_cast<uint16>(-1)}},/*Dummy.*/ 
+};
+
+static uint16 wing2data[] = {
+  PTYPE_MATFIXED, 100, 0, 100, 0, 0, 0, 100, 0, 0, 0,
+  PTYPE_COMPSMOOTH | RFLAG_XREF, 0, 10, 12, 13, 6, 10, /* Side. */
+    COMP_HERMITE, 7, 11, 15, 17,
+    COMP_HERM_NOTAN, 9, 11,
+    COMP_HERMITE, 8, 10, 16, 14,
+    COMP_HERM_NOTAN, 6, 10,
+    COMP_END,
+
+  PTYPE_TUBE, 1, 8, 19, 20, 18, 162, 140,
+
+  PTYPE_END,
+};
+
+static Thruster wing2thruster[] = {
+  { 20, 5, 25.0f },
+  { 19, 2, 20.0f },
+};
+
+static Model wing2model = { 1.0f, 23, wing2vtx1, 30, 0, wing2vtx2,
+                            0, 0, 2, wing2thruster, wing2data, 2 };
+
+Model* ppModel[] = {
+  &ship5model,
+  &wing2model,
+  &dishmodel,
+  &nosewheelmodel,
+  &wingmodel,
+  &nacellemodel,
+  &nwunitmodel,
+  &mainwheelmodel,
+  &mwunitmodel,
+  &cylmodel,
+  &ship2model,
+  &shipmodel,
+  &station1model,
+  &ship3model,
+  &ship4model,
+  0,
+};
+
diff --git a/src/sbre/primfunc.cpp b/src/sbre/primfunc.cpp
new file mode 100644
index 0000000..9f75bed
--- /dev/null
+++ b/src/sbre/primfunc.cpp
@@ -0,0 +1,734 @@
+#include <SDL_opengl.h>
+#include <malloc.h>
+#include "sbre_int.h"
+#include "sbre_anim.h"
+#include "sbre.h"      /* For subobject. */
+#include "../glfreetype.h"
+
+
+/*
+uint16 PFUNC_MATANIM
+  uint16 animfunc
+  uint16 dr1, dg1, db1, sr1, sg1, sb1, sh1, er1, eg1, eb1
+  uint16 dr2, dg2, db2, sr2, sg2, sb2, sh2, er2, eg2, eb2
+*/
+
+static int PrimFuncMatAnim(uint16* pData, Model* pMod, RState* pState) {
+  float anim = 0.01f * ResolveAnim(pState->pObjParam, pData[1]);
+  float ianim = 0.01f - anim;
+
+  int i;
+  float pDiff[4] = { 0, 0, 0, 1.0f }, shiny;
+  float pSpec[4] = { 0, 0, 0, 1.0f };
+  float pEmis[4] = { 0, 0, 0, 1.0f };
+  for (i=0; i<3; i++) pDiff[i] = pData[i+2] * anim + pData[i+12] * ianim;
+  for (i=0; i<3; i++) pSpec[i] = pData[i+5] * anim + pData[i+15] * ianim;
+  for (i=0; i<3; i++) pEmis[i] = pData[i+9] * anim + pData[i+19] * ianim;
+  shiny = pData[8] * anim + pData[18] * ianim;
+
+  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, pDiff);
+  glMaterialfv(GL_FRONT, GL_SPECULAR, pSpec);
+  glMaterialfv(GL_FRONT, GL_EMISSION, pEmis);
+  glMaterialf(GL_FRONT, GL_SHININESS, shiny);
+
+  return 22;
+}
+
+/*
+uint16 PFUNC_MATFIXED
+  uint16 dr, dg, db
+  uint16 sr, sg, sb
+  uint16 shiny
+  uint16 er, eg, eb
+*/
+
+static int PrimFuncMatFixed(uint16* pData, Model* pMod, RState* pState) {
+  int i;
+  float pDiff[4] = { 0, 0, 0, 1.0f }, shiny;
+  float pSpec[4] = { 0, 0, 0, 1.0f };
+  float pEmis[4] = { 0, 0, 0, 1.0f };
+  for(i=0; i<3; i++) pDiff[i] = pData[i+1] * 0.01f;
+  for(i=0; i<3; i++) pSpec[i] = pData[i+4] * 0.01f;
+  for(i=0; i<3; i++) pEmis[i] = pData[i+8] * 0.01f;
+  shiny = pData[7] * 0.01f;
+
+  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, pDiff);
+  glMaterialfv(GL_FRONT, GL_SPECULAR, pSpec);
+  glMaterialfv(GL_FRONT, GL_EMISSION, pEmis);
+  glMaterialf(GL_FRONT, GL_SHININESS, shiny);
+
+  return 11;
+}
+
+/*
+uint16 PFUNC_MATVAR
+  uint16 index
+*/
+
+static int PrimFuncMatVar(uint16* pData, Model* pMod, RState* pState) {
+  int i;
+  float pDiff[4] = { 0, 0, 0, 1.0f }, shiny;
+  float pSpec[4] = { 0, 0, 0, 1.0f };
+  float pEmis[4] = { 0, 0, 0, 1.0f };
+  ObjParams* pParam = pState->pObjParam;
+  for(i=0; i<3; i++) pDiff[i] = pParam->pColor[pData[1]].pDiff[i];
+  for(i=0; i<3; i++) pSpec[i] = pParam->pColor[pData[1]].pSpec[i];
+  for(i=0; i<3; i++) pEmis[i] = pParam->pColor[pData[1]].pEmis[i];
+  shiny = pParam->pColor[pData[1]].shiny;
+
+  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, pDiff);
+  glMaterialfv(GL_FRONT, GL_SPECULAR, pSpec);
+  glMaterialfv(GL_FRONT, GL_EMISSION, pEmis);
+  glMaterialf(GL_FRONT, GL_SHININESS, shiny);
+
+  return 2;
+}
+
+#if 0
+uint16 PFUNC_ZBIAS
+  uint16 offset     /* To test if nearer - 0x8000 = reset. */
+  uint16 units      /* Integer units. not used. */
+#endif
+
+static int PrimFuncZBias(uint16* pData, Model* pMod, RState* pState) {
+  if(pData[1] & 0x8000) glDepthRange (pState->dn+SBRE_ZBIAS, pState->df);
+  else if(VecDot(pState->pVtx+pData[1], &pState->campos) > 0.0f)
+    glDepthRange(pState->dn, pState->df-SBRE_ZBIAS);
+  return 3;
+}
+
+static int PrimFuncTriFlat(uint16* pData, Model* pMod, RState* pState) {
+  Vector* pVtx = pState->pVtx;
+  Vector* pVec;
+  Vector norm, tv1, tv2;
+  VecSub(pVtx+pData[1], pVtx+pData[2], &tv1);
+  VecSub(pVtx+pData[3], pVtx+pData[2], &tv2);
+  VecCross(&tv2, &tv1, &norm);
+  VecNorm(&norm, &norm);
+
+  glBegin(GL_TRIANGLES);
+  glNormal3f(norm.x, norm.y, norm.z);
+  pVec = pVtx + pData[1];
+  glVertex3f(pVec->x, pVec->y, pVec->z);
+  pVec = pVtx + pData[2];
+  glVertex3f(pVec->x, pVec->y, pVec->z);
+  pVec = pVtx + pData[3];
+  glVertex3f(pVec->x, pVec->y, pVec->z);
+
+  if(pData[0] & RFLAG_XREF) {
+    glNormal3f(-norm.x, norm.y, norm.z);
+    pVec = pVtx + pData[3];
+    glVertex3f(-pVec->x, pVec->y, pVec->z);
+    pVec = pVtx + pData[2];
+    glVertex3f(-pVec->x, pVec->y, pVec->z);
+    pVec = pVtx + pData[1];
+    glVertex3f(-pVec->x, pVec->y, pVec->z);
+  }
+  glEnd();
+  return 4;
+}
+
+static int PrimFuncQuadFlat(uint16* pData, Model* pMod, RState* pState) {
+  Vector* pVtx = pState->pVtx;
+  Vector* pVec;
+  Vector norm, tv1, tv2;
+  VecSub(pVtx+pData[1], pVtx+pData[2], &tv1);
+  VecSub(pVtx+pData[3], pVtx+pData[2], &tv2);
+  VecCross(&tv2, &tv1, &norm);
+  VecNorm(&norm, &norm);
+
+  glBegin(GL_TRIANGLE_FAN);
+  glNormal3f(norm.x, norm.y, norm.z);
+  pVec = pVtx + pData[1];
+  glVertex3f(pVec->x, pVec->y, pVec->z);
+  pVec = pVtx + pData[2];
+  glVertex3f(pVec->x, pVec->y, pVec->z);
+  pVec = pVtx + pData[3];
+  glVertex3f(pVec->x, pVec->y, pVec->z);
+  pVec = pVtx + pData[4];
+  glVertex3f(pVec->x, pVec->y, pVec->z);
+  glEnd();
+
+  if(pData[0] & RFLAG_XREF) {
+    glBegin(GL_TRIANGLE_FAN);
+    glNormal3f(-norm.x, norm.y, norm.z);
+    pVec = pVtx + pData[3];
+    glVertex3f(-pVec->x, pVec->y, pVec->z);
+    pVec = pVtx + pData[2];
+    glVertex3f(-pVec->x, pVec->y, pVec->z);
+    pVec = pVtx + pData[1];
+    glVertex3f (-pVec->x, pVec->y, pVec->z);
+    pVec = pVtx + pData[4];
+    glVertex3f(-pVec->x, pVec->y, pVec->z);
+    glEnd();
+  }
+  return 5;
+}
+
+void RenderArray(int nv, int ni, Vector* pVertex, uint16* pIndex, uint16 flags) {
+  glNormalPointer(GL_FLOAT, 2*sizeof(Vector), pVertex+1);
+  glVertexPointer(3, GL_FLOAT, 2*sizeof(Vector), pVertex);
+  glDrawElements(GL_TRIANGLES, ni, GL_UNSIGNED_SHORT, pIndex);
+
+  if(flags & RFLAG_XREF) {
+    glPushMatrix();
+    glFrontFace(GL_CCW);
+    const float pMV[16] = { -1, 0, 0, 0,  0, 1, 0, 0,  0, 0, 1, 0,  0, 0, 0, 1 };
+    glMultMatrixf(pMV);
+    glDrawElements(GL_TRIANGLES, ni, GL_UNSIGNED_SHORT, pIndex);
+    glFrontFace(GL_CW);
+    glPopMatrix();
+  }
+}
+
+void CopyArrayToCache(int nv, int ni, Vector* pVertex, uint16* pIndex, int ci, Model* pModel) {
+  pModel->pNumIdx[ci] = ni;
+  pModel->pNumVtx[ci] = nv;
+  pModel->ppICache[ci] = (uint16 *) malloc (ni*sizeof(uint16));
+  memcpy(pModel->ppICache[ci], pIndex, ni*sizeof(uint16));
+  pModel->ppVCache[ci] = (Vector *) malloc (2*nv*sizeof(Vector));
+  memcpy(pModel->ppVCache[ci], pVertex, 2*nv*sizeof(Vector));
+}
+
+#if 0
+uint16 PFUNC_COMPSMOOTH
+  uint16 cacheidx
+  uint16 steps
+  uint16 centpos
+  uint16 centnorm
+  uint16 startpos
+  uint16 startnorm
+  uint16 COMP_END
+  uint16 COMP_LINE
+  uint16 pos
+  uint16 norm
+  uint16 COMP_HERMITE
+  uint16 pos
+  uint16 norm
+  uint16 tan0
+  uint16 tan1
+
+/* Tangents should be prescaled. */
+#endif
+
+int PrimFuncCompoundSmooth(uint16* pData, Model* pMod, RState* pState) {
+  Vector *pVtx = pState->pVtx;
+  Model *pModel = pState->pModel;
+
+  uint16 ci = pData[1];
+  if(ci != 0x8000 && pModel->pNumIdx[ci]) {
+    RenderArray(pModel->pNumVtx[ci], pModel->pNumIdx[ci],
+      pModel->ppVCache[ci], pModel->ppICache[ci], pData[0]);
+    int c; for(c=7; pData[c] != COMP_END; c+=pCompSize[pData[c]]);
+    return c+1;
+  }
+
+  int steps = pData[2]; /* Detail factor... */
+  Vector *pCPos = pVtx+pData[3];
+  Vector *pCNorm = pVtx+pData[4]; /* Centre point. */
+  Vector *pLPos = pVtx+pData[5];
+  Vector *pLNorm = pVtx+pData[6];
+  int c = 7;
+
+  while(pData[c] != COMP_END) {
+    if(pData[c] == COMP_STEPS)  steps = pData[c+1];
+    else if(pData[c] == COMP_LINE) {
+      pLPos = pVtx+pData[c+1], pLNorm = pVtx+pData[c+2];
+      TriangAddPoint (pLPos, pLNorm);
+    } else {
+      Vector t0, t1;
+      if(pData[c] == COMP_HERMITE)
+        { t0 = pVtx[pData[c+3]]; t1 = pVtx[pData[c+4]]; }
+      else if(pData[c] == COMP_HERM_NOTAN)
+        { VecSub (pVtx+pData[c+1], pLPos, &t0); t1 = t0; }
+      else if(pData[c] == COMP_HERM_AUTOTAN) {
+        Vector tv; VecSub (pVtx+pData[c+1], pLPos, &tv);
+        VecMul (pLNorm, VecDot (&tv, pLNorm), &t0);
+        VecSub (&tv, &t0, &t0);
+        VecMul (pVtx+pData[c+2], VecDot (&tv, pVtx+pData[c+2]), &t1);
+        VecSub (&tv, &t1, &t1);
+      }
+//      else { //crash? };
+
+      /* Now add points along spline. */
+      float t, incstep = 1.0f / (steps+1);
+      int i; for(i=0, t=incstep; i<steps; i++, t+=incstep) {
+        Vector pos, norm;
+        ResolveHermiteSpline (pLPos, pVtx+pData[c+1], &t0, &t1, t, &pos);
+        ResolveLinearInterp (pLNorm, pVtx+pData[c+2], t, &norm);
+        VecNorm (&norm, &norm);
+        TriangAddPoint (&pos, &norm);
+      }
+      /* Add end point. */
+      pLPos = pVtx+pData[c+1], pLNorm = pVtx+pData[c+2];
+      TriangAddPoint (pLPos, pLNorm);
+    }
+    c += pCompSize[pData[c]];
+  }
+
+  int ni, nv;
+  Vector* pVertex;
+  uint16* pIndex;
+  if((pData[0]&0xff) == PTYPE_COMPFLAT) steps = 0;
+  Triangulate(pCPos, pCNorm, steps, &pVertex, &nv, &pIndex, &ni);
+
+  RenderArray(nv, ni, pVertex, pIndex, pData[0]);
+  if(ci != 0x8000) CopyArrayToCache(nv, ni, pVertex, pIndex, ci, pModel);
+  return c+1;    /* Yeyy for COMP_END */
+}
+
+/*
+uint16 PFUNC_CYLINDER
+  uint16 cacheidx    -1 => uncacheable
+  uint16 steps    8 => octagonal
+  uint16 startvtx
+  uint16 endvtx
+  uint16 updir
+  uint16 rad
+*/
+
+static int PrimFuncCylinder(uint16* pData, Model* pMod, RState* pState) {
+  Vector* pVtx = pState->pVtx;
+  Model* pModel = pState->pModel;
+
+  uint16 ci = pData[1];
+  if(ci != 0x8000 && pModel->pNumIdx[ci]) {
+    RenderArray(pModel->pNumVtx[ci], pModel->pNumIdx[ci],
+      pModel->ppVCache[ci], pModel->ppICache[ci], pData[0]);
+    return 7;
+  }
+
+  int steps = pData[2];
+  float rad = pData[6] * 0.01f;
+  Vector* pVertex = (Vector *) alloca (8*steps*sizeof(Vector));
+  uint16* pIndex = (uint16 *) alloca (12*steps*sizeof(Vector));
+  int ni = 0;
+
+  /* Generate cylinder axes. */
+  Vector yax = pVtx[pData[5]], xax, zax;
+  VecSub(pVtx+pData[4], pVtx+pData[3], &zax);  // dir = end-start
+  VecNorm(&zax, &zax);
+  VecCross(&yax, &zax, &xax);
+
+  float angstep = 2.0f * 3.141592f / steps, ang = 0.0f;
+  int i; for (i=0; i<steps; i++, ang+=angstep) {
+    Vector tv, norm;
+    VecMul(&xax, sin(ang), &tv);
+    VecMul(&yax, cos(ang), &norm);
+    VecAdd(&tv, &norm, &norm);
+    pVertex[1+(i+steps)*2] = pVertex[1+i*2] = norm;
+    pVertex[1+(i+steps*3)*2] = zax; VecInv (&zax, &tv);  /* endcap. */
+    pVertex[1+(i+steps*2)*2] = tv;                       /* startcap. */
+
+    VecMul(&norm, rad, &tv);
+    VecAdd(pVtx+pData[3], &tv, pVertex+i*2);            /* Start. */
+    VecAdd (pVtx+pData[4], &tv, pVertex+(i+steps)*2);   /* End. */
+    pVertex[(i+steps*2)*2] = pVertex[i*2];              /* startcap. */
+    pVertex[(i+steps*3)*2] = pVertex[(i+steps)*2];      /* endcap. */
+  }
+
+  /* Render sides. */
+  for(i=0; i<steps; i++) {
+    int i1 = i+1==steps?0:i+1;
+    pIndex[ni++] = i; pIndex[ni++] = i+steps; pIndex[ni++] = i1+steps;
+    pIndex[ni++] = i1+steps; pIndex[ni++] = i1; pIndex[ni++] = i;
+  }  
+
+  /* Render ends. */
+  for(i=1; i<steps-1; i++) {
+    pIndex[ni++] = steps*2;
+    pIndex[ni++] = i+steps*2;
+    pIndex[ni++] = i+1+steps*2;
+  }
+  for(i=steps-1; i>1; i--) {
+    pIndex[ni++] = steps*3;
+    pIndex[ni++] = i+steps*3;
+    pIndex[ni++] = i-1+steps*3;
+  }
+
+  RenderArray(4*steps, ni, pVertex, pIndex, pData[0]);
+  if(ci != 0x8000) CopyArrayToCache(4*steps, ni, pVertex, pIndex, ci, pModel);
+  return 7;
+}
+
+/*
+uint16 PFUNC_CIRCLE
+  uint16 cacheidx    -1 => uncacheable
+  uint16 steps    8 => octagonal
+  uint16 vtx
+  uint16 norm
+  uint16 updir    
+  uint16 rad
+*/
+
+static int PrimFuncCircle(uint16* pData, Model* pMod, RState* pState) {
+  Vector* pVtx = pState->pVtx;
+  Model* pModel = pState->pModel;
+
+  uint16 ci = pData[1];
+  if(ci != 0x8000 && pModel->pNumIdx[ci]) {
+    RenderArray (pModel->pNumVtx[ci], pModel->pNumIdx[ci],
+      pModel->ppVCache[ci], pModel->ppICache[ci], pData[0]);
+    return 7;
+  }
+
+  int steps = pData[2];
+  float rad = pData[6] * 0.01f;
+  Vector* pVertex = (Vector *) alloca (2*steps*sizeof(Vector));
+  uint16* pIndex = (uint16 *) alloca (3*steps*sizeof(Vector));
+  int ni = 0;
+
+  /* Generate axes. */
+
+  Vector yax = pVtx[pData[5]], xax, zax = pVtx[pData[4]];
+  VecCross(&yax, &zax, &xax);
+
+  float angstep = 2.0f * 3.141592f / steps, ang = 0.0f;
+  int i; for(i=0; i<steps; i++, ang+=angstep) {
+    Vector tv, norm;
+    VecMul(&xax, sin(ang), &tv);
+    VecMul(&yax, cos(ang), &norm);
+    VecAdd(&tv, &norm, &norm);
+    VecMul(&norm, rad, &tv);
+    VecAdd(pVtx+pData[3], &tv, pVertex+i*2);
+    pVertex[i*2+1] = zax;
+  }
+
+  for(i=1; i<steps-1; i++) {
+    pIndex[ni++] = 0;
+    pIndex[ni++] = i+1;
+    pIndex[ni++] = i;
+  }
+
+  RenderArray(steps, ni, pVertex, pIndex, pData[0]);
+  if(ci != 0x8000) CopyArrayToCache(steps, ni, pVertex, pIndex, ci, pModel);
+  return 7;
+}
+
+/*
+uint16 PFUNC_TUBE
+  uint16 cacheidx    -1 => uncacheable
+  uint16 steps    8 => octagonal
+  uint16 startvtx
+  uint16 endvtx
+  uint16 updir
+  uint16 outerrad
+  uint16 innerrad
+*/
+
+static int PrimFuncTube(uint16* pData, Model* pMod, RState* pState) {
+  Vector* pVtx = pState->pVtx;
+  Model* pModel = pState->pModel;
+
+  uint16 ci = pData[1];
+  if(ci != 0x8000 && pModel->pNumIdx[ci]) {
+    RenderArray(pModel->pNumVtx[ci], pModel->pNumIdx[ci],
+      pModel->ppVCache[ci], pModel->ppICache[ci], pData[0]);
+    return 8;
+  }
+
+  int steps = pData[2];
+  Vector *pVertex = (Vector *) alloca (16*steps*sizeof(Vector));
+  uint16 *pIndex = (uint16 *) alloca (24*steps*sizeof(Vector));
+  int ni = 0;
+
+  /* Generate cylinder axes. */
+  Vector yax = pVtx[pData[5]], xax, zax;
+  VecSub(pVtx+pData[4], pVtx+pData[3], &zax);  // dir = end-start
+  VecNorm(&zax, &zax);
+  VecCross(&yax, &zax, &xax);
+
+/*
+0: start, outer, radial
+steps: end, outer, radial
+steps*2: start, inner, radial
+steps*3: end, inner, radial
+steps*4: start, outer, axial
+steps*5: end, outer, axial
+steps*6: start, inner, axial
+steps*7: end, inner, axial
+*/
+
+  float angstep = 2.0f * 3.141592f / steps, ang = 0.0f;
+  int i; for (i=0; i<steps; i++, ang+=angstep) {
+    Vector tv, norm, invnorm;
+    VecMul(&xax, sin(ang), &tv);
+    VecMul(&yax, cos(ang), &norm);
+    VecAdd(&tv, &norm, &norm); VecInv (&norm, &invnorm);
+    pVertex[1+(i+steps)*2] = pVertex[1+i*2] = norm;
+    pVertex[1+(i+steps*2)*2] = pVertex[1+(i+steps*3)*2] = invnorm;
+    VecInv(&zax, &tv);
+    pVertex[1+(i+steps*5)*2] = pVertex[1+(i+steps*7)*2] = zax;    // endcap
+    pVertex[1+(i+steps*4)*2] = pVertex[1+(i+steps*6)*2] = tv;    // startcap
+
+    VecMul(&norm, pData[6] * 0.01f, &tv);      // outer
+    VecAdd(pVtx+pData[3], &tv, pVertex+i*2);      // start
+    VecAdd(pVtx+pData[4], &tv, pVertex+(i+steps)*2);  // end
+    pVertex[(i+steps*4)*2] = pVertex[i*2];        // startcap
+    pVertex[(i+steps*5)*2] = pVertex[(i+steps)*2];    // endcap
+
+    VecMul(&norm, pData[7] * 0.01f, &tv);      // inner 
+    VecAdd(pVtx+pData[3], &tv, pVertex+(i+steps*2)*2);  // start
+    VecAdd(pVtx+pData[4], &tv, pVertex+(i+steps*3)*2);  // end
+    pVertex[(i+steps*6)*2] = pVertex[(i+steps*2)*2];  // startcap
+    pVertex[(i+steps*7)*2] = pVertex[(i+steps*3)*2];  // endcap
+  }
+
+  /* Render sides. */
+  for(i=0; i<steps; i++) {
+    int i1 = i+1==steps?0:i+1;
+    /* Outer radial surface. */
+    pIndex[ni++] = i; pIndex[ni++] = i+steps; pIndex[ni++] = i1+steps;
+    pIndex[ni++] = i1+steps; pIndex[ni++] = i1; pIndex[ni++] = i;
+    /* Inner radial surface. */
+    pIndex[ni++] = i+steps*2; pIndex[ni++] = i1+steps*2; pIndex[ni++] = i1+steps*3;
+    pIndex[ni++] = i1+steps*3; pIndex[ni++] = i+steps*3; pIndex[ni++] = i+steps*2;
+    // startcap
+    pIndex[ni++] = i+steps*4; pIndex[ni++] = i1+steps*4; pIndex[ni++] = i1+steps*6;
+    pIndex[ni++] = i1+steps*6; pIndex[ni++] = i+steps*6; pIndex[ni++] = i+steps*4;
+    // endcap
+    pIndex[ni++] = i+steps*5; pIndex[ni++] = i+steps*7; pIndex[ni++] = i1+steps*7;
+    pIndex[ni++] = i1+steps*7; pIndex[ni++] = i1+steps*5; pIndex[ni++] = i+steps*5;
+  }  
+
+  RenderArray(8*steps, ni, pVertex, pIndex, pData[0]);
+  if(ci != 0x8000) CopyArrayToCache (8*steps, ni, pVertex, pIndex, ci, pModel);
+  return 8;
+}
+
+/*
+uint16 PFUNC_SUBOBJECT
+  uint16 anim
+  uint16 modelnum
+  uint16 offset
+  uint16 norm
+  uint16 zaxis
+  uint16 scale
+*/
+
+static int PrimFuncSubObject (uint16 *pData, Model *pMod, RState *pState)
+{
+  // return immediately if object is not present
+  if (pData[1] != 0x8000 && !pState->pObjParam->pFlag[pData[1]]) return 7;
+  
+  // build transform matrix, offset
+  Vector v1, v2, v3, pos; Matrix m, orient;
+  VecNorm (pState->pVtx+pData[4], &v2);
+  VecNorm (pState->pVtx+pData[5], &v3);
+  VecCross (&v2, &v3, &v1);
+  m.x1 = v1.x; m.x2 = v2.x; m.x3 = v3.x;
+  m.y1 = v1.y; m.y2 = v2.y; m.y3 = v3.y;
+  m.z1 = v1.z; m.z2 = v2.z; m.z3 = v3.z;
+  MatMatMult (&pState->objorient, &m, &orient);
+
+  MatVecMult (&pState->objorient, pState->pVtx+pData[3], &pos);
+  VecAdd (&pos, &pState->objpos, &pos);
+  float scale = pState->scale*pData[6]*0.01f;
+  
+  glPushAttrib (GL_LIGHTING_BIT);
+  glPushMatrix ();
+
+  /* Transform lin & ang thrust. */
+  if(ppModel[pData[2]]->numThrusters) {
+    Vector compos;
+    MatTVecMult(&m, pState->pVtx+pData[3], &compos);
+    VecInv(&compos, &compos);
+
+    ObjParams* pParam = pState->pObjParam;
+    Vector oldlin = *(Vector*)pParam->linthrust;
+    Vector oldang = *(Vector*)pParam->angthrust;
+    MatTVecMult(&m, &oldlin, (Vector*)pParam->linthrust);
+    MatTVecMult(&m, &oldang, (Vector*)pParam->angthrust);
+
+    sbreRenderModel(&pos, &orient, pData[2], pParam, scale, &compos);
+    *(Vector*)pParam->linthrust = oldlin;
+    *(Vector*)pParam->angthrust = oldang;
+  } else sbreRenderModel (&pos, &orient, pData[2], pState->pObjParam, scale);
+
+  glPopMatrix ();
+  glPopAttrib ();
+  return 7;
+}
+
+static int glfinit = 0;
+static FontFace *pFace = 0;
+
+/*
+uint16 PFUNC_TEXT
+  uint16 anim
+  uint16 textnum
+  uint16 pos
+  uint16 norm
+  uint16 xaxis
+  uint16 xoff
+  uint16 yoff
+  uint16 scale
+*/
+
+static int PrimFuncText (uint16 *pData, Model *pMod, RState *pState)
+{
+  if (!glfinit) {
+    GLFTInit ();
+    pFace = new FontFace ("arial.ttf");
+    glfinit = 1;
+  }
+
+  // return immediately if object is not present
+//  if (pData[1] != 0x8000 && !pState->pObjParam->pFlag[pData[1]]) return 7;
+  
+  // build transform matrix, offset
+  Vector v1, v2, v3, pos, tv; Matrix m, m2;
+  VecNorm (pState->pVtx+pData[4], &v3);
+  VecNorm (pState->pVtx+pData[5], &v1);
+  VecInv (&v3, &v3); VecCross (&v3, &v1, &v2);
+  m.x1 = v1.x; m.x2 = v2.x; m.x3 = v3.x;
+  m.y1 = v1.y; m.y2 = v2.y; m.y3 = v3.y;
+  m.z1 = v1.z; m.z2 = v2.z; m.z3 = v3.z;
+  MatMatMult (&pState->objorient, &m, &m2);
+
+  VecMul (&v1, pData[6]*0.01f, &tv);
+  VecMul (&v2, pData[7]*0.01f, &pos);
+  VecAdd (&pos, &tv, &tv);
+  VecAdd (pState->pVtx+pData[3], &tv, &tv);
+  MatVecMult (&pState->objorient, &tv, &pos);
+  VecAdd (&pos, &pState->objpos, &pos);
+
+  float s = pState->scale*pData[8]*0.01f / pFace->GetHeight();
+  glPushMatrix ();
+
+  float pMV[16];
+  pMV[0] = s*m2.x1; pMV[1] = s*m2.y1; pMV[2] = s*m2.z1; pMV[3] = 0.0f;
+  pMV[4] = s*m2.x2; pMV[5] = s*m2.y2; pMV[6] = s*m2.z2; pMV[7] = 0.0f;
+  pMV[8] = s*m2.x3; pMV[9] = s*m2.y3; pMV[10] = s*m2.z3; pMV[11] = 0.0f;
+  pMV[12] = pos.x; pMV[13] = pos.y; pMV[14] = pos.z; pMV[15] = 1.0f;
+  glLoadMatrixf (pMV);
+
+  glFrontFace (GL_CCW);
+  glNormal3f (0.0f, 0.0f, -1.0f);
+//  glColor4f (1.0f, 1.0f, 1.0f, 1.0f);
+
+  const char *pText;
+  if (pData[2] != 0x8000) pText = pModelString[pData[2]];
+  else pText = pState->pObjParam->pText[pData[1]];
+  pFace->RenderString(pText);
+
+  glFrontFace (GL_CW);
+  glPopMatrix ();
+  return 9;
+}
+
+/*
+uint16 PFUNC_EXTRUSION
+  uint16 cacheidx    -1 => uncacheable
+  uint16 count
+  uint16 startvtx
+  uint16 endvtx
+  uint16 updir
+  uint16 rad
+  uint16 firstvtx
+*/
+
+static int PrimFuncExtrusion (uint16 *pData, Model *pMod, RState *pState)
+{
+  Vector *pVtx = pState->pVtx;
+  Model *pModel = pState->pModel;
+
+  uint16 ci = pData[1];
+  if (ci != 0x8000 && pModel->pNumIdx[ci])
+  {
+    glShadeModel (GL_FLAT);
+    RenderArray (pModel->pNumVtx[ci], pModel->pNumIdx[ci],
+      pModel->ppVCache[ci], pModel->ppICache[ci], pData[0]);
+    glShadeModel (GL_SMOOTH);
+    return 8;
+  }
+
+  int steps = pData[2];
+  float rad = pData[6] * 0.01f;
+  Vector *pVertex = (Vector *) alloca (8*steps*sizeof(Vector));
+  uint16 *pIndex = (uint16 *) alloca (12*steps*sizeof(Vector));
+  int ni = 0;
+
+  // generate cylinder axes
+
+  Vector yax = pVtx[pData[5]], xax, zax;
+  VecSub (pVtx+pData[4], pVtx+pData[3], &zax);  // dir = end-start
+  VecNorm (&zax, &zax);
+  VecCross (&yax, &zax, &xax);
+
+  int i; for (i=0; i<steps; i++)
+  {
+    Vector tv, norm;
+    VecMul (&xax, pVtx[pData[7]+i].x, &tv);
+    VecMul (&yax, pVtx[pData[7]+i].y, &norm);
+    VecAdd (&tv, &norm, &norm);
+    pVertex[1+(i+steps*3)*2] = zax; VecInv (&zax, &tv);  // endcap
+    pVertex[1+(i+steps*2)*2] = tv;            // startcap
+    
+    VecMul (&norm, rad, &tv);
+    VecAdd (pVtx+pData[3], &tv, pVertex+i*2);      // start
+    VecAdd (pVtx+pData[4], &tv, pVertex+(i+steps)*2);  // end
+    pVertex[(i+steps*2)*2] = pVertex[i*2];        // startcap
+    pVertex[(i+steps*3)*2] = pVertex[(i+steps)*2];    // endcap
+  }
+
+  /* Render sides. */
+  for(i=0; i<steps; i++) {
+    int i1 = i+1==steps?0:i+1;
+
+    FindNormal(pVertex+i*2, pVertex+(i+steps)*2, pVertex+i1*2, pVertex+i1*2+1);
+    pIndex[ni++] = i; pIndex[ni++] = i+steps; pIndex[ni++] = i1;
+    pIndex[ni++] = i+steps; pIndex[ni++] = i1+steps; pIndex[ni++] = i1;
+  }  
+
+  /* Render ends. */
+  for (i=1; i<steps-1; i++) {
+    pIndex[ni++] = steps*2;
+    pIndex[ni++] = i+steps*2;
+    pIndex[ni++] = i+1+steps*2;
+  }
+  for (i=steps-1; i>1; i--) {
+    pIndex[ni++] = steps*3;
+    pIndex[ni++] = i+steps*3;
+    pIndex[ni++] = i-1+steps*3;
+  }
+
+  RenderArray(4*steps, ni, pVertex, pIndex, pData[0]);
+  if(ci != 0x8000) CopyArrayToCache(4*steps, ni, pVertex, pIndex, ci, pModel);
+  return 8;
+}
+
+
+
+/*
+uint16 PFUNC_WINDOWS
+  uint16 
+  uint16 textnum
+  uint16 pos
+  uint16 norm
+  uint16 xaxis
+  uint16 xoff
+  uint16 yoff
+  uint16 scale
+*/
+
+
+int (*pPrimFuncTable[])(uint16 *, Model *, RState *) = {
+  0,    // end
+  PrimFuncMatAnim,
+  PrimFuncMatFixed,
+  PrimFuncMatVar,
+  PrimFuncZBias,
+  PrimFuncTriFlat,
+  PrimFuncQuadFlat,
+  PrimFuncCompoundSmooth,    // just uses steps = 0
+  PrimFuncCompoundSmooth,
+  PrimFuncCircle,
+  PrimFuncCylinder,
+  PrimFuncTube,
+  PrimFuncSubObject,
+  PrimFuncText,
+  PrimFuncExtrusion,
+};
+
+
diff --git a/src/sbre/sbre.h b/src/sbre/sbre.h
new file mode 100644
index 0000000..51b4f64
--- /dev/null
+++ b/src/sbre/sbre.h
@@ -0,0 +1,39 @@
+#pragma once
+#include "jjtypes.h"
+#include "jjvector.h"
+
+enum animsrc {
+  ASRC_GEAR = 0,
+  ASRC_SECFRAC,
+  ASRC_MINFRAC,
+  ASRC_HOURFRAC,
+  ASRC_DAYFRAC,
+};
+
+enum animflag {
+  AFLAG_GEAR = 0,
+};
+
+struct ObjParams {
+  float pAnim[10];
+  uint8 pFlag[10];
+
+  float linthrust[3];    /* 1.0 to -1.0 */
+  float angthrust[3];    /* 1.0 to -1.0 */
+
+  struct {
+    float pDiff[3];
+    float pSpec[3];
+    float pEmis[3];
+    float shiny;
+  } pColor [3];
+
+  char pText[3][256];
+};
+
+void sbreSetViewport(int w, int h, int d, float zn, float zf, float dn, float df);
+void sbreSetDirLight(float* pColor, float* pDir);
+void sbreSetWireframe(int val);
+void sbreRenderModel(Vector* pPos, Matrix* pOrient, int model, ObjParams* pParam,
+                      float s=1.0f, Vector* pCompos=0);
+
diff --git a/src/sbre/sbre_anim.h b/src/sbre/sbre_anim.h
new file mode 100644
index 0000000..f9e8ecf
--- /dev/null
+++ b/src/sbre/sbre_anim.h
@@ -0,0 +1,35 @@
+#pragma once
+#include "jjtypes.h"
+#include "jjvector.h"
+#include "sbre.h"
+#include "sbre_int.h"
+
+enum animmod {
+  AMOD_CLIP = 0,  /* Just clip result to 0-1. */
+  AMOD_MOD1,      /* fmod(1), then clip. */
+  AMOD_REF,       /* fmod(2), reflect around 1, then clip. */
+};
+
+struct AnimFunc {
+  int src;
+  int mod;
+  float order0;
+  float order1;
+  float order2;
+  float order3;
+};
+
+enum animfunc {
+  AFUNC_GEAR = 0,
+  AFUNC_GFLAP,
+  AFUNC_THRUSTPULSE,
+  AFUNC_LIN4SEC,
+};
+
+const AnimFunc pAFunc[] = {
+  { ASRC_GEAR, AMOD_CLIP, -1.0f, 2.0f, 0.0f, 0.0f },
+  { ASRC_GEAR, AMOD_CLIP, 0.0f, 2.0f, 0.0f, 0.0f },
+  { ASRC_MINFRAC, AMOD_REF, 0.0f, 30.0f, 0.0f, 0.0f },
+  { ASRC_MINFRAC, AMOD_MOD1, 0.0f, 15.0f, 0.0f, 0.0f },
+};
+
diff --git a/src/sbre/sbre_int.h b/src/sbre/sbre_int.h
new file mode 100644
index 0000000..a322180
--- /dev/null
+++ b/src/sbre/sbre_int.h
@@ -0,0 +1,145 @@
+#pragma once
+#include "jjtypes.h"
+#include "jjvector.h"
+#include "sbre.h"
+
+/******************************************************************************/
+
+enum vtxtype {
+  VTYPE_PLAIN = 0,
+  VTYPE_DIR,
+  VTYPE_CROSS,
+  VTYPE_NORM,
+  VTYPE_ANIMLIN,
+  VTYPE_ANIMCUBIC,
+  VTYPE_ANIMHERM,
+  VTYPE_ANIMROTATE,
+};
+
+struct PlainVertex {
+  uint32 type;
+  Vector pos;
+};
+
+struct CompoundVertex {
+  uint16 type;
+  uint16 pParam[5];
+};
+
+/******************************************************************************/
+
+struct Thruster {
+  uint16 pos; /* Index into vertices. */
+  uint16 dir;
+  float power;
+};
+
+struct Light {
+  uint8 animcolor;
+  uint8 animpower;
+  uint16 vtx;
+  float power;
+  float pColor[3];
+};
+
+struct Model {
+  float scale;
+
+  int numPVtx;
+  PlainVertex* pPVtx;
+
+  int cvStart;
+  int numCVtx;
+  CompoundVertex* pCVtx;
+
+  int numLights;
+  Light* pLight;
+
+  int numThrusters;
+  Thruster* pThruster;
+
+  uint16* pData;
+
+  int numCache; /* Number of cached primitives. */
+  int* pNumVtx, *pNumIdx;
+  Vector** ppVCache;
+  uint16** ppICache;
+};
+
+extern Model* ppModel[];
+
+/******************************************************************************/
+
+#define TRIANG_MAXPOINTS 64
+#define TRIANG_MAXSTEPS   5
+
+void ResolveLinearInterp(Vector* p0, Vector* p1, float t, Vector* pRes);
+void ResolveQuadraticSpline(Vector* p0, Vector* p1, Vector* p2, float t, Vector* pRes);
+void ResolveCubicSpline(Vector* p0, Vector* p1, Vector* p2, Vector* p3, float t, Vector* pRes);
+void ResolveHermiteSpline(Vector* p0, Vector* p1, Vector* n0, Vector* n1, float t, Vector* pRes);
+void ResolveHermiteNormal(Vector* p0, Vector* p1, Vector* n0, Vector* n1, float t, Vector* pRes);
+
+void TriangAddPoint(Vector* pPos, Vector* pNorm);
+void Triangulate(Vector* pCPos, Vector* pCNorm, int steps,
+                  Vector** ppVtx, int* pNV, uint16** ppIndex, int* pNI);
+
+/******************************************************************************/
+
+struct RState {
+  Model*      pModel;     /* Original model. */
+  Vector*     pVtx;       /* Transformed vertices. */
+  Vector      campos;     /* Camera pos relative to model. */
+  Vector      objpos;     /* Object pos relative to camera. */
+  Matrix      objorient;  /* Object orient relative to camera. */
+  float       scale;      /* Current GL modelview scale. */
+  ObjParams*  pObjParam;  /* Dynamic object parameters. */
+  float dn, df;           /* Near/far depth range. */
+  Vector compos;          /* Object relative center of mass. */
+};
+
+enum primtype {
+  PTYPE_END = 0,
+  PTYPE_MATANIM,
+  PTYPE_MATFIXED,
+  PTYPE_MATVAR,
+  PTYPE_ZBIAS,
+  PTYPE_TRIFLAT,
+  PTYPE_QUADFLAT,
+  PTYPE_COMPFLAT,
+  PTYPE_COMPSMOOTH,
+  PTYPE_CIRCLE,
+  PTYPE_CYLINDER,
+  PTYPE_TUBE,
+  PTYPE_SUBOBJECT,
+  PTYPE_TEXT,
+  PTYPE_EXTRUSION,
+};
+
+extern int (*pPrimFuncTable[])(uint16*, Model*, RState*);
+
+static const int RFLAG_XREF   = 0x8000;
+
+static const int THRUST_XREF  = 0x8000;
+static const int THRUST_NOANG = 0x4000;
+
+static const float SBRE_ZBIAS = 0.00002f;
+static const float SBRE_AMB   = 0.3f;
+
+enum comptype {
+  COMP_END = 0,
+  COMP_LINE,
+  COMP_HERMITE,
+  COMP_HERM_NOTAN,
+  COMP_HERM_AUTOTAN,
+  COMP_STEPS,
+};
+
+const int pCompSize[] = { 1, 3, 5, 3, 3, 2 };
+
+const char pModelString[1][256] = {
+  "IZRILGOOD",
+};
+
+void RenderTransparencies(RState* pState);
+float ResolveAnim (ObjParams* pObjParam, uint16 type);
+
diff --git a/src/sbre/simtriang.cpp b/src/sbre/simtriang.cpp
new file mode 100644
index 0000000..05d4e6d
--- /dev/null
+++ b/src/sbre/simtriang.cpp
@@ -0,0 +1,155 @@
+#include <stdlib.h>
+#include "jjtypes.h"
+#include "sbre_int.h"
+
+void ResolveLinearInterp(Vector* p0, Vector* p1, float t, Vector* pRes) {
+  Vector tv;
+  VecSub(p1, p0, &tv);
+  VecMul(&tv, t, &tv);
+  VecAdd(&tv, p0, pRes);
+}
+
+void ResolveQuadraticSpline(Vector* p0, Vector* p1, Vector* p2, float t, Vector* pRes) {
+  float invt = 1.0f - t;
+  Vector tv;
+  VecMul(p0, invt*invt, pRes);
+  VecMul(p1, 2.0f*t*invt, &tv);
+  VecAdd(&tv, pRes, pRes);
+  VecMul(p2, t*t, &tv);
+  VecAdd(&tv, pRes, pRes);
+}
+
+void ResolveCubicSpline(Vector* p0, Vector* p1, Vector* p2, Vector* p3, float t, Vector* pRes) {
+  float invt = 1.0f - t;
+  Vector tv1, tv2, tv;
+  VecMul(p0, invt*invt*invt, &tv1);
+  VecMul(p3, t*t*t, &tv);
+  VecAdd(&tv, &tv1, &tv1);
+  VecMul(p1, 3.0f*t*invt*invt, &tv2);
+  VecMul(p2, 3.0f*t*t*invt, &tv);
+  VecAdd(&tv, &tv2, &tv2);
+  VecAdd(&tv1, &tv2, pRes);
+}
+
+void ResolveHermiteSpline(Vector* p0, Vector* p1, Vector* n0, Vector* n1, float t, Vector* pRes) {
+  float t2 = t*t, t3 = t*t*t;
+  Vector tv1, tv2, tv;
+  VecMul(p0, 2*t3-3*t2+1, &tv1);
+  VecMul(n0, t3-2*t2+t, &tv);
+  VecAdd(&tv, &tv1, &tv1);
+  VecMul(p1, -2*t3+3*t2, &tv2);
+  VecMul(n1, t3-t2, &tv);
+  VecAdd(&tv, &tv2, &tv2);
+  VecAdd(&tv1, &tv2, pRes);
+}
+
+void ResolveHermiteTangent(Vector* p0, Vector* p1, Vector* n0, Vector* n1, float t, Vector* pRes) {
+  float t2 = t*t;
+  Vector tv1, tv2, tv;
+  VecMul(p0, 6*t2-6*t, &tv1);
+  VecMul(n0, 3*t2-4*t+1, &tv);
+  VecAdd(&tv, &tv1, &tv1);
+  VecMul(p1, -6*t2+6*t, &tv2);
+  VecMul(n1, 3*t2-2*t, &tv);
+  VecAdd(&tv, &tv2, &tv2);
+  VecAdd(&tv1, &tv2, pRes);
+}
+
+/******************************************************************************/
+
+struct TriangPoint {
+  Vector pos;   /* Base pos, norm. */
+  Vector norm;
+  int num;      /* Count of valid vertices. */
+  uint16 pIndex[TRIANG_MAXSTEPS+1]; /* Index of each vertex, inside to outside. */
+};
+
+static uint16 pIndex[6*TRIANG_MAXPOINTS*(TRIANG_MAXSTEPS+1)];
+static Vector pVertex[2*TRIANG_MAXPOINTS*(TRIANG_MAXSTEPS+1)];
+
+static TriangPoint pPoint[TRIANG_MAXPOINTS];
+static int numPoints = 0;
+
+
+void TriangAddPoint(Vector* pPos, Vector* pNorm) {
+  if(numPoints == TRIANG_MAXPOINTS) return;
+  TriangPoint *tp = pPoint + numPoints++;
+  tp->pos = *pPos; tp->norm = *pNorm;
+}
+
+void Triangulate(Vector* pCPos, Vector* pCNorm, int steps,
+                  Vector** ppVtx, int* pNV, uint16** ppIndex, int* pNI) {
+  /*
+   *Ok. For each point, find number of increments
+   * and generate intermediate values.
+   */
+  int nv = 0, ni = 0;
+  int i; for(int i=0; i<numPoints; i++) {
+    Vector tv; //, tnorm, tnorm2;
+    TriangPoint* pCur = pPoint+i;
+    VecSub(pCPos, &pCur->pos, &tv);
+    //    float len = sqrt (VecDot (&tv, &tv));
+    //    VecCross (&pCur->norm, pCNorm, &tnorm);
+    //    VecCross (pCNorm, &tv, &tnorm2);
+    //    if (VecDot (&tnorm, &tnorm2) < 0.0f) VecInv (&tnorm, &tnorm);
+
+    pVertex[nv] = pCur->pos;  /* Add first vertex to array. */
+    pVertex[nv+1] = pCur->norm;
+    pCur->pIndex[0] = nv>>1; nv+=2;
+
+    Vector t0, t1; /*Find tangents. */
+    VecMul(&pCur->norm, VecDot (&tv, &pCur->norm), &t0);
+    VecSub(&tv, &t0, &t0);
+    VecMul(pCNorm, VecDot (&tv, pCNorm), &t1);
+    VecSub(&tv, &t1, &t1);
+
+    if(steps > TRIANG_MAXSTEPS) pCur->num = TRIANG_MAXSTEPS;
+    else pCur->num = steps;
+    static const float pInv[] = { 1.0f, 0.5f, 0.3333333f, 0.25f, 0.2f,
+                                  0.1666667f, 0.1428571f, 0.125f, 0.1111111f, 0.1f };
+
+    float t, inc = pInv[pCur->num];
+    int j; for(t=inc, j=1; j<=pCur->num; j++, t+=inc) {
+      ResolveHermiteSpline(&pCur->pos, pCPos, &t0, &t1, t, pVertex+nv);
+      //      ResolveHermiteTangent(&pCur->pos, pCPos, &t0, &t1, t, &tv);
+      //      VecCross(&tv, &tnorm, pVertex+nv+1);
+      ResolveLinearInterp (&pCur->norm, pCNorm, t, pVertex+nv+1);
+      pCur->pIndex[j] = nv>>1; nv+=2;
+    }
+  }
+  pVertex[nv] = *pCPos;   /* Add centre vertex to array. */
+  pVertex[nv+1] = *pCNorm;
+  int cindex = nv>>1; nv+=2;
+
+  /* Now render each radial strip from the centre. */
+  for(i=0; i<numPoints; i++) {
+    TriangPoint *pCur = pPoint + i;
+    TriangPoint *pNext = pPoint + (i+1==numPoints?0:i+1);
+    pIndex[ni++] = cindex;
+    pIndex[ni++] = pCur->pIndex[pCur->num];
+    pIndex[ni++] = pNext->pIndex[pNext->num];
+
+    int vc = pCur->num, vn = pNext->num;
+    while(vc|vn) {
+      if(vc) {
+        pIndex[ni++] = pCur->pIndex[vc];
+        pIndex[ni++] = pCur->pIndex[--vc];
+        pIndex[ni++] = pNext->pIndex[vn];
+      }
+      if(vn) {
+        pIndex[ni++] = pNext->pIndex[vn];
+        pIndex[ni++] = pCur->pIndex[vc];
+        pIndex[ni++] = pNext->pIndex[--vn];
+      }
+    }
+  }
+
+  // return data
+  *pNI = ni; *pNV = nv>>1;
+  *ppIndex = pIndex;
+  *ppVtx = pVertex;
+
+  numPoints = 0;
+  return;
+}
+
diff --git a/src/sbre/transp.cpp b/src/sbre/transp.cpp
new file mode 100644
index 0000000..0adac36
--- /dev/null
+++ b/src/sbre/transp.cpp
@@ -0,0 +1,221 @@
+#include <SDL_opengl.h>
+#include <malloc.h>
+#include "sbre_int.h"
+
+static const int pNumIndex[3] =
+{ (2*4+1*8)*3, (2*8+5*16)*3, (2*16+13*32)*3 };
+
+static Vector pTVertex4pt[2*4+2];
+static Vector pTVertex8pt[6*8+2];
+static Vector pTVertex16pt[14*16+2];
+
+static uint16 pTIndex4pt[(2*4+1*8)*3];
+static uint16 pTIndex8pt[(2*8+5*16)*3];
+static uint16 pTIndex16pt[(2*16+13*32)*3];
+
+static Vector *ppTVertex[3] =
+{ pTVertex4pt, pTVertex8pt, pTVertex16pt };
+
+static uint16 *ppTIndex[3] =
+{ pTIndex4pt, pTIndex8pt, pTIndex16pt };
+
+
+/*
+static void MakeRotationalSolid(Vector* pPos, int n, int r, Vector* pRes) {
+  int i, j, k;
+  for (i=1; i<n-1; i++) {
+    float angstep = 2.0f * 3.141592f / steps, ang = 0.0f;
+    for (=1; i<n; i++, ang+=angstep, pCur++) {
+      pCur->x = sin(ang) * pPos->y;
+      pCur->y = cos(ang) * pPos->y;
+      pCur->z = pPos->z;
+    }
+  }
+    *pCur = pos0; pCur++;
+    *pCur = pos1; pCur++;
+}
+*/
+
+static void GenerateThrusters(void) {
+  Vector pos0 = { 0.0f, 0.0f, 0.0f };
+  Vector pos1 = { 0.0f, 0.0f, 1.0f };
+  Vector tan0 = { 0.0f, 1.0f, 0.2f };
+  Vector tan1 = { 0.0f, -0.2f, 1.0f };
+
+  int j, n;
+  for(j=0, n=4; j<3; j++, n<<=1) {
+    Vector* pCur = ppTVertex[j];
+    float t, incstep = 1.0f / (n-1);
+    int i; for(i=0, t=incstep; i<n-2; i++, t+=incstep) {
+      Vector* pPos = pCur; pCur++;
+      ResolveHermiteSpline(&pos0, &pos1, &tan0, &tan1, t, pPos);
+
+      float angstep = 2.0f * 3.141592f / n, ang = angstep;
+      int i; for(i=1; i<n; i++, ang+=angstep, pCur++) {
+        pCur->x = sin(ang) * pPos->y;
+        pCur->y = cos(ang) * pPos->y;
+        pCur->z = pPos->z;
+      }
+    }
+    *pCur = pos0; pCur++;
+    *pCur = pos1; pCur++;
+
+    int ni=0, k;
+    uint16 *pIndex = ppTIndex[j];
+
+    for(k=0; k<n; k++) {
+      int k1 = k+1==n ? 0 : k+1;
+      pIndex[ni++] = (n-2)*n; pIndex[ni++] = k; pIndex[ni++] = k1;
+      pIndex[ni++] = (n-2)*n+1; pIndex[ni++] = k1+(n-3)*n; pIndex[ni++] = k+(n-3)*n;
+    }
+    for(i=0; i<n-3; i++) {
+      for(k=0; k<n; k++) {
+        int k1 = k+1==n ? 0 : k+1;
+        pIndex[ni++] = k+i*n; pIndex[ni++] = k+i*n+n; pIndex[ni++] = k1+i*n;
+        pIndex[ni++] = k1+i*n; pIndex[ni++] = k+i*n+n;  pIndex[ni++] = k1+i*n+n;
+      }
+    }
+  }
+}
+
+static void RenderThruster(RState* pState, Thruster* pThruster, Vector* pPos, Vector* pDir) {
+  //  Thruster *pThruster = pState->pModel->pThruster + index;
+
+  Vector start, end, dir = *pDir;
+  VecMul(pPos, pState->scale, &start);
+  float power = -VecDot(&dir, (Vector*)pState->pObjParam->linthrust);
+
+  if(!(pThruster->dir & THRUST_NOANG)) {
+    Vector angdir, *pAT = (Vector*)pState->pObjParam->angthrust, cpos;
+    VecAdd(&pState->compos, &start, &cpos);
+    VecCross(&cpos, &dir, &angdir);
+    //    VecNorm(&angdir, &angdir);
+    float xp = angdir.x * pAT->x;
+    float yp = angdir.y * pAT->y;
+    float zp = angdir.z * pAT->z;
+    if(xp+yp+zp > 0) {
+      if(xp > yp && xp > zp && fabs(pAT->x) > power) power = fabs(pAT->x);
+      else if(yp > xp && yp > zp && fabs(pAT->y) > power) power = fabs(pAT->y);
+      else if(zp > xp && zp > yp && fabs(pAT->z) > power) power = fabs(pAT->z);
+    }
+  }
+
+  if(power <= 0.001f) return;
+  power *= pState->scale;
+  float width = sqrt(power)*pThruster->power*0.6f;
+  float len = power*pThruster->power;
+  VecMul(&dir, len, &end);
+  VecAdd(&end, &start, &end);
+
+  Vector v1, v2, pos; Matrix m, m2;
+  v1.x = dir.y; v1.y = dir.z; v1.z = dir.x;
+  VecCross(&v1, &dir, &v2);  VecNorm(&v2, &v2);
+  VecCross(&v2, &dir, &v1);
+  m.x1 = v1.x; m.x2 = v2.x; m.x3 = dir.x;
+  m.y1 = v1.y; m.y2 = v2.y; m.y3 = dir.y;
+  m.z1 = v1.z; m.z2 = v2.z; m.z3 = dir.z;
+  MatMatMult(&pState->objorient, &m, &m2);
+
+  MatVecMult(&pState->objorient, &start, &pos);
+  VecAdd(&pos, &pState->objpos, &pos);
+
+  float pMV[16];
+  pMV[ 0] = m2.x1; pMV[ 1] = m2.y1; pMV[ 2] = m2.z1; pMV[ 3] = 0.0f;
+  pMV[ 4] = m2.x2; pMV[ 5] = m2.y2; pMV[ 6] = m2.z2; pMV[ 7] = 0.0f;
+  pMV[ 8] = m2.x3; pMV[ 9] = m2.y3; pMV[10] = m2.z3; pMV[11] = 0.0f;
+  pMV[12] = pos.x; pMV[13] = pos.y; pMV[14] = pos.z; pMV[15] = 1.0f;
+  glPushMatrix();
+  glLoadMatrixf(pMV);
+
+  glScalef(width*0.5f, width*0.5f, len*0.666f);
+  glColor4f(0.0f, 0.4f, 1.0f, 0.6f);
+  glVertexPointer(3, GL_FLOAT, sizeof(Vector), pTVertex8pt);
+  glDrawElements(GL_TRIANGLES, pNumIndex[1], GL_UNSIGNED_SHORT, pTIndex8pt);
+
+  glScalef(2.0f, 2.0f, 1.5f);
+  glColor4f(0.4f, 0.0f, 1.0f, 0.6f);
+  glVertexPointer(3, GL_FLOAT, sizeof(Vector), pTVertex8pt);
+  glDrawElements(GL_TRIANGLES, pNumIndex[1], GL_UNSIGNED_SHORT, pTIndex8pt);
+
+  glPopMatrix();
+  return;
+}
+
+// Occlusion problems
+static void RenderLight (RState* pState, int index) {
+  /*  Light *pLight = pState->pModel->pLight + index;
+
+  float *pColor = pLight->pColor;
+  if (pLight->animcolor != -1) pColor = pState->pObjParam->ppColor[pLight->animcolor];
+
+  float power = pLight->power;
+  if (pLight->animpower != -1) power *= pState->pObjParam->pAnim[pLight->animpower];
+
+  Vector xax, zax;
+  VecAdd (&pState->campos, pState->pVtx+pLight->vtx, &zax);
+  Vector yax = { pState->objorient.x2, pState->objorient.y2, pState->objorient.z2 };
+  VecNorm (&zax, &zax); VecCross (&yax, &zax, &xax);
+  VecNorm (&xax, &xax); VecCross (&zax, &xax, &yax);
+*/
+}
+
+struct TransElem {
+  Vector pos, dir;
+  Thruster* pThruster;
+  float dist;
+};
+
+inline void SWAP (TransElem* a, TransElem* b) { TransElem t = *a; *a = *b; *b = t; }
+
+static void QuickSort (TransElem* pA, int end) {
+  if(end <= 0) return;
+  float pivotval = pA[end].dist;
+  int i, j; for(i=0, j=0; i<end; i++)
+    if(pA[i].dist < pivotval) { SWAP (pA+i, pA+j); j++; }
+  SWAP (pA+end, pA+j);
+  if(j > 1) QuickSort (pA, j-1); j++;
+  if(j < end) QuickSort (pA+j, end-j);
+}
+
+static int thrustgen = 0;
+
+void RenderTransparencies (RState *pState) {
+  Vector* pVtx = pState->pVtx;
+  Model* pModel = pState->pModel;
+
+  if(!thrustgen) GenerateThrusters();
+  thrustgen = 1;
+
+  int maxElem = pModel->numThrusters*2;
+  if(!maxElem) return;
+  TransElem *pList = (TransElem*)alloca(maxElem*sizeof(TransElem));
+
+  Vector tv;
+  int i, numElem = 0;
+  for(i=0; i<pModel->numThrusters; i++) {
+    Thruster *pThruster = pModel->pThruster+i;
+    pList[numElem].pThruster = pThruster;
+    pList[numElem].pos = pVtx[pThruster->pos];
+    pList[numElem].dir = pVtx[pThruster->dir&0xff];
+    VecAdd(&pState->campos, &pList[numElem].pos, &tv);
+    pList[numElem].dist = VecDot(&tv, &tv);
+
+    if(pThruster->dir & THRUST_XREF) {
+      pList[numElem+1] = pList[numElem];
+      pList[numElem+1].pos.x = -pList[numElem].pos.x;
+      pList[numElem+1].dir.x = -pList[numElem].dir.x;
+      VecAdd(&pState->campos, &pList[numElem+1].pos, &tv);
+      pList[numElem+1].dist = VecDot(&tv, &tv);
+      numElem++;
+    }
+    numElem++;
+  }
+
+  QuickSort(pList, numElem-1);
+
+  //  for(i=numElem-1; i>=0; i--)
+  for(i=0; i<numElem; i++) {
+    RenderThruster(pState, pList[i].pThruster, &pList[i].pos, &pList[i].dir);
+  }
+}
+
diff --git a/src/sector.cpp b/src/sector.cpp
new file mode 100644
index 0000000..fe7ebc3
--- /dev/null
+++ b/src/sector.cpp
@@ -0,0 +1,54 @@
+#include "sector.h"
+
+#define SYS_NAME_FLAGS 32
+static const char* sys_names[SYS_NAME_FLAGS] = 
+  { "en", "la", "can", "be", "and", "phi", "eth", "ol", "ve", "ho", "a",
+    "lia", "an", "ar", "ur", "mi", "in", "ti", "qu", "so", "ed", "ess",
+    "ex", "io", "ce", "ze", "fa", "ay", "wa", "da", "ack", "gre" };
+
+Sector::Sector(int x, int y) {
+  unsigned long _init[2] = { x, y };
+  MTRand rand(_init, 2);
+
+  m_numSystems = rand(3,6);
+
+  for(int i = 0; i < m_numSystems; i++) {
+    System s;
+    s.p.x = rand(1.0);
+    s.p.y = rand(1.0);
+    s.p.z = 20.0*(rand(1.0)-0.5);
+    s.name = GenName(rand);
+
+    float spec = rand(1.0);
+    /* Frequences are from our friends over at wikipedia. ^.^ */
+    if(spec < 0.0000003) {
+      s.primaryStarClass = StarSystem::SBody::SUBTYPE_STAR_O;
+    } else if(spec < 0.0013) {
+      s.primaryStarClass = StarSystem::SBody::SUBTYPE_STAR_B;
+    } else if(spec < 0.0073) {
+      s.primaryStarClass = StarSystem::SBody::SUBTYPE_STAR_A;
+    } else if(spec < 0.0373) {
+      s.primaryStarClass = StarSystem::SBody::SUBTYPE_STAR_F;
+    } else if(spec < 0.1133) {
+      s.primaryStarClass = StarSystem::SBody::SUBTYPE_STAR_G;
+    } else if(spec < 0.2343) {
+      s.primaryStarClass = StarSystem::SBody::SUBTYPE_STAR_K;
+    } else {
+      s.primaryStarClass = StarSystem::SBody::SUBTYPE_STAR_M;
+    }
+
+    m_systems.push_back(s);
+  }
+}
+
+std::string Sector::GenName(MTRand& rand) {
+  std::string name;
+
+  int len = rand(2, 4);
+  for(int i = 0; i < len; i++) {
+    name += sys_names[rand(0, SYS_NAME_FLAGS-1)];
+  }
+  name[0] = toupper(name[0]);
+  return name;
+}
+
diff --git a/src/sector.h b/src/sector.h
new file mode 100644
index 0000000..d15db5b
--- /dev/null
+++ b/src/sector.h
@@ -0,0 +1,22 @@
+#pragma once
+#include <string>
+#include <vector>
+#include "libs.h"
+#include "star_system.h"
+
+class Sector {
+public:
+  Sector(int x, int y);
+
+  int m_numSystems;
+  struct System {
+    std::string name;
+    vector3f p;
+    StarSystem::SBody::SubType primaryStarClass;
+  };
+  std::vector<System>m_systems;
+
+private:
+  std::string GenName(MTRand& rand);
+};
+
diff --git a/src/sector_view.cpp b/src/sector_view.cpp
new file mode 100644
index 0000000..fceac79
--- /dev/null
+++ b/src/sector_view.cpp
@@ -0,0 +1,199 @@
+#include "libs.h"
+#include "gui.h"
+#include "l3d.h"
+#include "sector_view.h"
+#include "sector.h"
+#include "system_info_view.h"
+
+SectorView::SectorView(void) : GenericSystemView() {
+  SetTransparency(true);
+  m_px = m_py = 0.5;
+  m_rot_x     = m_rot_z = 0;
+  m_secx      = m_secy = 0;
+  m_selected  = -1;
+  m_zoom      = 1;
+
+  m_infoLabel = new Gui::Label("");
+  Add(m_infoLabel, 2, 2);
+
+  Gui::ImageButton* ib = new Gui::ImageButton("icons/sectorview_f6_systeminfo.png");
+  ib->onClick.connect(sigc::mem_fun(this, &SectorView::OnClickSystemInfo));
+  ib->SetShortcut(SDLK_F6, KMOD_NONE);
+  m_rightButtonBar->Add(ib, 2, 2);
+
+  m_zoomInButton = new Gui::ImageButton("icons/zoom_in_f7.png");
+  m_zoomInButton->SetShortcut(SDLK_F7, KMOD_NONE);
+  m_rightButtonBar->Add(m_zoomInButton, 34, 2);
+
+  m_zoomOutButton = new Gui::ImageButton("icons/zoom_out_f8.png");
+  m_zoomOutButton->SetShortcut(SDLK_F8, KMOD_NONE);
+  m_rightButtonBar->Add(m_zoomOutButton, 66, 2);
+
+  GLUquadricObj* qobj = gluNewQuadric();
+
+/*
+  m_gluSphereDlist = glGenLists(1);
+  glNewList(m_gluSphereDlist, GL_COMPILE);
+  gluDisk(qobj, 0.0, 0.2, 32, 1);
+  glEndList();
+*/
+
+  m_gluDiskDlist = glGenLists(1);
+  glNewList(m_gluDiskDlist, GL_COMPILE);
+  gluDisk(qobj, 0.0, 0.2, 20, 1);
+  glEndList();
+
+  gluDeleteQuadric(qobj);
+}
+
+SectorView::~SectorView(void) {
+  glDeleteLists(m_gluDiskDlist, 1);
+}
+
+void SectorView::OnClickSystemInfo(void) {
+  L3D::SetView(L3D::system_info_view);
+}
+
+bool SectorView::GetSelectedSystem(int* sector_x, int* sector_y, int* system_idx) {
+  *sector_x = m_secx;
+  *sector_y = m_secy;
+  *system_idx = m_selected;
+  return m_selected != -1;
+}
+
+#define SEC_SIZE    8
+#define DRAW_RAD    2
+
+#define FFRAC(_x)   ((_x)-floor(_x))
+
+void SectorView::Draw3D(void) {
+  GenericSystemView::Draw3D();
+
+  glMatrixMode(GL_PROJECTION);
+  glLoadIdentity();
+  gluPerspective(50, L3D::GetScrAspect(), 1.0, 100.0);
+
+  glMatrixMode(GL_MODELVIEW);
+  glLoadIdentity();
+
+  char buf[80];
+  snprintf(buf, sizeof(buf), "Sector: %d,%d", m_secx, m_secy);
+  m_infoLabel->SetText(buf);
+
+  /* Units are lightyears. */
+  glTranslatef(0, 0, -10-10*m_zoom);
+  glRotatef(m_rot_x, 1, 0, 0);
+  glRotatef(m_rot_z, 0, 0, 1);
+  glTranslatef(-FFRAC(m_px)*SEC_SIZE, -FFRAC(m_py)*SEC_SIZE, 0);
+  glDisable(GL_LIGHTING);
+
+  for(int sx = -DRAW_RAD; sx <= DRAW_RAD; sx++) {
+    for(int sy = -DRAW_RAD; sy <= DRAW_RAD; sy++) {
+      glPushMatrix();
+      glTranslatef(sx*SEC_SIZE, sy*SEC_SIZE, 0);
+      DrawSector(m_secx+sx, m_secy+sy);
+      glPopMatrix();
+    }
+  }
+  glEnable(GL_LIGHTING);
+}
+
+void SectorView::PutText(std::string& text) {
+  /* Highly optimal.. */
+  GLdouble modelMatrix[16];
+  GLdouble projMatrix[16];
+  GLint viewport[4];
+
+  glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
+  glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
+  glGetIntegerv(GL_VIEWPORT, viewport);
+
+  Gui::Screen::EnterOrtho();
+  vector3d _pos;
+  if(Gui::Screen::Project(0,0,0, modelMatrix, projMatrix, viewport, &_pos.x, &_pos.y, &_pos.z)) {
+    Gui::Screen::RenderLabel(text, _pos.x, _pos.y);
+  }
+  Gui::Screen::LeaveOrtho();
+  glDisable(GL_LIGHTING);
+}
+
+void SectorView::DrawSector(int sx, int sy) {
+  Sector s = Sector(sx, sy);
+  glColor3f(0, .8, 0);
+  glBegin(GL_LINE_LOOP);
+    glVertex3f(0, 0, 0);
+    glVertex3f(0, SEC_SIZE, 0);
+    glVertex3f(SEC_SIZE, SEC_SIZE, 0);
+    glVertex3f(SEC_SIZE, 0, 0);
+  glEnd();
+
+  if(!(sx || sy)) glColor3f(1, 1, 0);
+  int num = 0;
+  for(std::vector<Sector::System>::iterator i = s.m_systems.begin(); i != s.m_systems.end(); ++i) {
+    glColor3fv(StarSystem::starColors[(*i).primaryStarClass]);
+    glPushMatrix();
+    glTranslatef((*i).p.x*SEC_SIZE, (*i).p.y*SEC_SIZE, 0);
+    glBegin(GL_LINES);
+      glVertex3f(0, 0, 0);
+      glVertex3f(0, 0, (*i).p.z);
+    glEnd();
+    glTranslatef(0, 0, (*i).p.z);
+
+    glPushMatrix();
+    glRotatef(-m_rot_z, 0, 0, 1);
+    glRotatef(-m_rot_x, 1, 0, 0);
+    glCallList(m_gluDiskDlist);
+    /* Selected indicator. */
+    if((sx == m_secx) && (sy == m_secy) && (num == m_selected)) {
+      glColor3f(0, 0.8, 0);
+      glScalef(2, 2, 2);
+      glCallList(m_gluDiskDlist);
+    }
+    glPopMatrix();
+    glColor3f(.7, .7, .7);
+    PutText((*i).name);
+
+    glPopMatrix();
+    num++;
+  }
+}
+
+void SectorView::Update(void) {
+  if(L3D::KeyState(SDLK_LEFT))       m_px   -= 0.01;
+  if(L3D::KeyState(SDLK_RIGHT))      m_px   += 0.01;
+  if(L3D::KeyState(SDLK_UP))         m_py   += 0.01;
+  if(L3D::KeyState(SDLK_DOWN))       m_py   -= 0.01;
+  if(L3D::KeyState(SDLK_EQUALS))     m_zoom *= 0.99;
+  if(L3D::KeyState(SDLK_MINUS))      m_zoom *= 1.01;
+  if(m_zoomInButton->IsPressed())   m_zoom *= 0.99;
+  if(m_zoomOutButton->IsPressed())  m_zoom *= 1.01;
+  m_zoom = CLAMP(m_zoom, 0.1, 5.0);
+
+  if(L3D::MouseButtonState(3)) {
+    int motion[2];
+    L3D::GetMouseMotion(motion);
+    m_rot_x += motion[1];
+    m_rot_z += motion[0];
+  }
+
+  m_secx = (int)floor(m_px);
+  m_secy = (int)floor(m_py);
+
+  Sector s = Sector(m_secx, m_secy);
+  float px = FFRAC(m_px);
+  float py = FFRAC(m_py);
+
+  m_selected = -1;
+  float min_dist = FLT_MAX;
+  for(unsigned int i = 0; i < s.m_systems.size(); i++) {
+    Sector::System* ss = &s.m_systems[i];
+    float dx = px - ss->p.x;
+    float dy = py - ss->p.y;
+    float dist = sqrtf(dx*dx + dy*dy);
+    if(dist < min_dist) {
+      min_dist = dist;
+      m_selected = i;
+    }
+  }
+}
+
diff --git a/src/sector_view.h b/src/sector_view.h
new file mode 100644
index 0000000..e658236
--- /dev/null
+++ b/src/sector_view.h
@@ -0,0 +1,33 @@
+#pragma once
+#include <list>
+#include <vector>
+#include <string>
+#include "libs.h"
+#include "gui.h"
+#include "view.h"
+#include "generic_system_view.h"
+
+class SectorView : public GenericSystemView {
+public:
+  SectorView(void);
+  virtual ~SectorView(void);
+  virtual void Update(void);
+  virtual void Draw3D(void);
+  bool GetSelectedSystem(int* sector_x, int* sector_y, int* system_idx);
+private:
+  void DrawSector(int x, int y);
+  void PutText(std::string& text);
+  void OnClickSystemInfo(void);
+
+  float m_zoom;
+  int m_secx, m_secy;
+  int m_selected;
+  float m_px, m_py;
+  float m_rot_x, m_rot_z;
+  Gui::Label* m_infoLabel;
+  Gui::ImageButton* m_zoomInButton;
+  Gui::ImageButton* m_zoomOutButton;
+  GLuint m_gluDiskDlist;
+};
+
+
diff --git a/src/ship.cpp b/src/ship.cpp
new file mode 100644
index 0000000..6526139
--- /dev/null
+++ b/src/ship.cpp
@@ -0,0 +1,140 @@
+#include "ship.h"
+#include "objimport.h"
+#include "frame.h"
+#include "l3d.h"
+#include "world_view.h"
+#include "sbre/sbre.h"
+#include "space.h"
+
+Ship::Ship(void) : RigidBody() {
+  m_dockedWith = 0;
+  m_mesh = 0;
+  m_shipType = ShipType::COBRA3;
+  m_angThrusters[0] = m_angThrusters[1] = m_angThrusters[2] = 0;
+  m_laserCollisionObj.owner = this;
+  for(int i = 0; i < ShipType::GUNMOUNT_MAX; i++) {
+    m_tempLaserGeom[i] = 0;
+    m_gunState[i] = 0;
+  }
+  dGeomSetData(m_geom, static_cast<Body*>(this));
+}
+
+void Ship::SetThrusterState(enum ShipType::Thruster t, float level) {
+  m_thrusters[t] = level;
+}
+
+void Ship::ClearThrusterState(void) {
+  for(int i = 0; i < ShipType::THRUSTER_MAX; i++) m_thrusters[i] = 0;
+}
+
+void Ship::AITurn(void) {
+  const ShipType& stype = GetShipType();
+  float timeStep = L3D::GetTimeStep();
+  for(int i = 0; i < ShipType::THRUSTER_MAX; i++) {
+    float force = timeStep * stype.linThrust[i] * m_thrusters[i];
+    switch(i) {
+    case ShipType::THRUSTER_REAR:
+    case ShipType::THRUSTER_FRONT:
+      dBodyAddRelForce(m_body, 0, 0, force); break;
+    case ShipType::THRUSTER_TOP:
+    case ShipType::THRUSTER_BOTTOM:
+      dBodyAddRelForce(m_body, 0, force, 0); break;
+    case ShipType::THRUSTER_LEFT:
+    case ShipType::THRUSTER_RIGHT:
+      dBodyAddRelForce(m_body, force, 0, 0); break;
+    }
+  }
+  dBodyAddRelTorque(m_body, stype.angThrust*m_angThrusters[0],
+                            stype.angThrust*m_angThrusters[1],
+                            stype.angThrust*m_angThrusters[2]);
+
+  /* LASERZ!! */
+  for(int i = 0; i < ShipType::GUNMOUNT_MAX; i++) {
+    /* Free old temp laser geoms. */
+    if(m_tempLaserGeom[i]) dGeomDestroy(m_tempLaserGeom[i]);
+    m_tempLaserGeom[i] = 0;
+    if(!m_gunState[i]) continue;
+    dGeomID ray = dCreateRay(GetFrame()->GetSpaceID(), 10000);
+    const dReal* r = dGeomGetRotation(m_geom);
+    const vector3d pos = GetPosition();
+    const vector3f _dir = stype.gunMount[i].dir;
+    vector3d dir = vector3d(_dir.x, _dir.y, _dir.z);
+    matrix4x4d m;
+    GetRotMatrix(m);
+    dir = m.ApplyRotationOnly(dir);
+    dGeomRaySet(ray, pos.x, pos.y, pos.z, dir.x, dir.y, dir.z);
+    dGeomSetData(ray, static_cast<Object*>(&m_laserCollisionObj));
+    m_tempLaserGeom[i] = ray;
+  }
+}
+
+const ShipType& Ship::GetShipType(void) {
+  return ShipType::types[m_shipType];
+}
+
+void Ship::SetDockedWith(SpaceStation* s) {
+  if(m_dockedWith && !s) {
+    /* Launching. */
+    printf("Buhbai!\n");
+    m_dockedWith = 0;
+    vector3d pos = GetPosition();
+    pos.x += 5000;
+    SetPosition(pos);
+  } else {
+    m_dockedWith = s;
+    SetVelocity(vector3d(0, 0, 0));
+    SetAngVelocity(vector3d(0, 0, 0));
+  }
+}
+
+void Ship::SetMesh(ObjMesh* m) {
+  m_mesh = m;
+}
+
+void Ship::SetGunState(int idx, int state) {
+  m_gunState[idx] = state;
+}
+
+/* Assumed to be at model coords. */
+void Ship::RenderLaserfire(void) {
+  const ShipType& stype = GetShipType();
+  glDisable(GL_LIGHTING);
+    for(int i = 0; i < ShipType::GUNMOUNT_MAX; i++) {
+      if(!m_gunState[i]) continue;
+      glColor3f(1, 0, 0);
+      glBegin(GL_LINES);
+      vector3f pos = stype.gunMount[i].pos;
+      glVertex3f(pos.x, pos.y, pos.z);
+      glVertex3fv(&((10000)*stype.gunMount[i].dir)[0]);
+      glEnd();
+    }
+  glEnable(GL_LIGHTING);
+}
+
+static ObjParams params = {
+  { 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f, 0.0f },
+
+  /* pColor[3] */
+  {
+    { { 1.0f, 0.0f, 1.0f }, { 0, 0, 0 }, { 0, 0, 0 }, 0 },
+    { { 0.8f, 0.6f, 0.5f }, { 0, 0, 0 }, { 0, 0, 0 }, 0 },
+    { { 0.5f, 0.5f, 0.5f }, { 0, 0, 0 }, { 0, 0, 0 }, 0 }
+  },
+  /* pText[3][256] */
+  { "IZ-L33T", "ME TOO" },
+};
+
+void Ship::Render(const Frame* camFrame) {
+  params.angthrust[0] = m_angThrusters[0];
+  params.angthrust[1] = m_angThrusters[1];
+  params.angthrust[2] = m_angThrusters[2];
+  params.linthrust[0] = m_thrusters[ShipType::THRUSTER_RIGHT] - m_thrusters[ShipType::THRUSTER_LEFT];
+  params.linthrust[1] = m_thrusters[ShipType::THRUSTER_TOP]   - m_thrusters[ShipType::THRUSTER_BOTTOM];
+  params.linthrust[2] = m_thrusters[ShipType::THRUSTER_REAR]  - m_thrusters[ShipType::THRUSTER_FRONT];
+  strncpy(params.pText[0], GetLabel().c_str(), sizeof(params.pText));
+
+  RenderSbreModel(camFrame, 13, &params);
+}
+
diff --git a/src/ship_cpanel.cpp b/src/ship_cpanel.cpp
new file mode 100644
index 0000000..20906a0
--- /dev/null
+++ b/src/ship_cpanel.cpp
@@ -0,0 +1,102 @@
+#include "libs.h"
+#include "l3d.h"
+#include "ship_cpanel.h"
+#include "space_station_view.h"
+#include "player.h"
+
+ShipCpanel::ShipCpanel(void) : Gui::Fixed(0, 0, 640, 64) {
+  //SetBgColor(1, 0, 0);
+  SetTransparency(true);
+
+  Gui::Image* img = new Gui::Image("icons/cpanel.png");
+  Add(img, 0, 0);
+
+  Gui::RadioGroup* g = new Gui::RadioGroup();
+  Gui::ImageRadioButton* b = new Gui::ImageRadioButton(g, "icons/timeaccel0.png",
+                                                      "icons/timeaccel0_on.png");
+  b->onSelect.connect(sigc::bind(sigc::mem_fun(this, &ShipCpanel::OnClickTimeaccel), 0.0));
+  b->SetShortcut(SDLK_ESCAPE, KMOD_LSHIFT);
+  Add(b, 0, 26);
+
+  b = new Gui::ImageRadioButton(g, "icons/timeaccel1.png", "icons/timeaccel1_on.png");
+  b->onSelect.connect(sigc::bind(sigc::mem_fun(this, &ShipCpanel::OnClickTimeaccel), 1.0));
+  b->SetShortcut(SDLK_F1, KMOD_LSHIFT);
+  b->SetSelected(true);
+  Add(b, 22, 26);
+
+  b = new Gui::ImageRadioButton(g, "icons/timeaccel2.png", "icons/timeaccel2_on.png");
+  b->onSelect.connect(sigc::bind(sigc::mem_fun(this, &ShipCpanel::OnClickTimeaccel), 10.0));
+  b->SetShortcut(SDLK_F2, KMOD_LSHIFT);
+  Add(b, 44, 26);
+
+  b = new Gui::ImageRadioButton(g, "icons/timeaccel3.png", "icons/timeaccel3_on.png");
+  b->onSelect.connect(sigc::bind(sigc::mem_fun(this, &ShipCpanel::OnClickTimeaccel), 100.0));
+  b->SetShortcut(SDLK_F3, KMOD_LSHIFT);
+  Add(b, 66, 26);
+
+  b = new Gui::ImageRadioButton(g, "icons/timeaccel4.png", "icons/timeaccel4_on.png");
+  b->onSelect.connect(sigc::bind(sigc::mem_fun(this, &ShipCpanel::OnClickTimeaccel), 1000.0));
+  b->SetShortcut(SDLK_F4, KMOD_LSHIFT);
+  Add(b, 88, 26);
+
+  g = new Gui::RadioGroup();
+  Gui::MultiStateImageButton* cam_button = new Gui::MultiStateImageButton();
+  g->Add(cam_button);
+  cam_button->SetSelected(true);
+  cam_button->AddState(L3D::CAM_FRONT,     "icons/cam_front.png");
+  cam_button->AddState(L3D::CAM_REAR,      "icons/cam_rear.png");
+  cam_button->AddState(L3D::CAM_EXTERNAL,  "icons/cam_external.png");
+  cam_button->SetShortcut(SDLK_F1, KMOD_NONE);
+  cam_button->onClick.connect(sigc::mem_fun(this, &ShipCpanel::OnChangeCamView));
+  Add(cam_button, 2, 2);
+
+  Gui::MultiStateImageButton* map_button = new Gui::MultiStateImageButton();
+  g->Add(map_button);
+  map_button->SetSelected(false);
+  map_button->SetShortcut(SDLK_F2, KMOD_NONE);
+  map_button->AddState(L3D::MAP_SECTOR, "icons/cpan_f2_map.png");
+  map_button->AddState(L3D::MAP_SYSTEM, "icons/cpan_f2_normal.png");
+  map_button->onClick.connect(sigc::mem_fun(this, &ShipCpanel::OnChangeMapView));
+  Add(map_button, 34, 2);
+
+  Gui::ImageButton* comms_button = new Gui::ImageButton("icons/comms_f4.png");
+  //g->Add(comms_button);
+  comms_button->SetShortcut(SDLK_F4, KMOD_NONE);
+  comms_button->onClick.connect(sigc::mem_fun(this, &ShipCpanel::OnClickComms));
+  Add(comms_button, 98, 2);
+
+  m_clock = new Gui::Label("");
+  m_clock->SetColor(1, 0.7, 0);
+  Add(m_clock, 2, 48);
+}
+
+void ShipCpanel::Draw(void) {
+  std::string time = date_format(L3D::GetGameTime());
+  m_clock->SetText(time);
+
+  Gui::Fixed::Draw();
+  Remove(m_scannerWidget);
+}
+
+void ShipCpanel::SetScannerWidget(Widget* w) {
+  m_scannerWidget = w;
+  Add(w, 150, 64);
+  w->Show();
+}
+
+void ShipCpanel::OnChangeCamView(Gui::MultiStateImageButton* b) {
+  L3D::SetCamType((enum L3D::CamType)b->GetState());
+}
+
+void ShipCpanel::OnChangeMapView(Gui::MultiStateImageButton* b) {
+  L3D::SetMapView((enum L3D::MapView)b->GetState());
+}
+
+void ShipCpanel::OnClickTimeaccel(Gui::ISelectable* i, double step) {
+  L3D::SetTimeStep(step);
+}
+
+void ShipCpanel::OnClickComms(void) {
+  if(L3D::player->GetDockedWith()) L3D::SetView(L3D::spaceStationView);
+}
+
diff --git a/src/ship_cpanel.h b/src/ship_cpanel.h
new file mode 100644
index 0000000..b5fdf77
--- /dev/null
+++ b/src/ship_cpanel.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "libs.h"
+#include "gui.h"
+
+class ShipCpanel : public Gui::Fixed {
+public:
+  ShipCpanel(void);
+  virtual void Draw(void);
+  void SetScannerWidget(Widget* w); /* Must be done each frame. */
+private:
+  void OnChangeCamView(Gui::MultiStateImageButton* b);
+  void OnChangeMapView(Gui::MultiStateImageButton* b);
+  void OnClickTimeaccel(Gui::ISelectable* i, double step);
+  void OnClickComms(void);
+
+  Widget* m_scannerWidget;
+  Gui::Label* m_clock;
+};
+
diff --git a/src/ship_type.cpp b/src/ship_type.cpp
new file mode 100644
index 0000000..d316162
--- /dev/null
+++ b/src/ship_type.cpp
@@ -0,0 +1,13 @@
+#include "ship_type.h"
+
+const ShipType ShipType::types[1] = {
+  {
+    { 250, -250, 50, -50, -50, 50 },
+    500.0,
+    {
+      { vector3f(0, -0.5, 0), vector3f(0, 0, -1) },
+      { vector3f(0, 0, 0),    vector3f(0, 0,  1) }
+    }
+  }
+};
+
diff --git a/src/ship_type.h b/src/ship_type.h
new file mode 100644
index 0000000..73a40de
--- /dev/null
+++ b/src/ship_type.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "libs.h"
+#include "vector3.h"
+
+struct ShipType {
+public:
+  enum Thruster { THRUSTER_FRONT, THRUSTER_REAR, THRUSTER_TOP, THRUSTER_BOTTOM,
+                  THRUSTER_LEFT, THRUSTER_RIGHT, THRUSTER_MAX };
+  enum Type { COBRA3 };
+  enum { GUNMOUNT_MAX = 2 };
+
+  /*******************************/
+  float linThrust[THRUSTER_MAX];
+  float angThrust;
+  struct GunMount {
+    vector3f pos;
+    vector3f dir;
+  } gunMount[GUNMOUNT_MAX];
+  /*******************************/
+  
+  static const ShipType types[1];
+};
+
diff --git a/src/space.cpp b/src/space.cpp
new file mode 100644
index 0000000..c651058
--- /dev/null
+++ b/src/space.cpp
@@ -0,0 +1,228 @@
+#include <algorithm>
+#include <functional>
+#include "libs.h"
+#include "space.h"
+#include "body.h"
+#include "frame.h"
+#include "star.h"
+#include "planet.h"
+#include "l3d.h"
+#include "player.h"
+#include "star_system.h"
+
+dWorldID Space::world;
+std::list<Body*> Space::bodies;
+Frame* Space::rootFrame;
+static dJointGroupID _contactgroup;
+
+void Space::Init(void) {
+  world = dWorldCreate();
+  rootFrame = new Frame(NULL, "System");
+  rootFrame->SetRadius(FLT_MAX);
+  _contactgroup = dJointGroupCreate(0);
+  //dWorldSetGravity(world, 0, -9.81, 0);
+}
+
+void Space::Clear(void) {
+  for(std::list<Body*>::iterator i = bodies.begin(); i != bodies.end(); ++i) {
+    (*i)->SetFrame(NULL);
+    if((*i) != (Body*)L3D::player) delete *i;
+  }
+  bodies.clear();
+  /* Player now removed also, but not freed. */
+  for(std::list<Frame*>::iterator i = rootFrame->m_children.begin();
+      i != rootFrame->m_children.end(); ++i) delete *i;
+  rootFrame->m_children.clear();
+
+  L3D::player->SetFrame(rootFrame);
+  bodies.push_back(L3D::player);
+}
+
+void Space::GenBody(StarSystem* system, StarSystem::SBody* sbody, Frame* f) {
+  Body* b;
+  if(sbody->type == StarSystem::SBody::TYPE_STAR) {
+    Star* star = new Star(sbody->subtype);
+    star->SetRadius(sbody->radius);
+    b = star;
+  } else {
+    Planet* planet = new Planet(sbody->subtype);
+    planet->SetRadius(sbody->radius);
+    b = planet;
+  }
+  b->SetLabel(sbody->name.c_str());
+
+  Frame* myframe;
+
+  if(sbody->parent) {
+    myframe = new Frame(f, sbody->name.c_str());
+    vector3d pos = sbody->orbit.CartesianPosAtTime(0);
+    myframe->SetPosition(pos);
+    myframe->SetRadius(10*sbody->radius);
+    b->SetFrame(myframe);
+  } else {
+    b->SetFrame(f);
+    myframe = f;
+  }
+
+  b->SetPosition(vector3d(0, 0, 0));
+
+  AddBody(b);
+
+  for(std::vector<StarSystem::SBody*>::iterator i = sbody->children.begin();
+      i != sbody->children.end(); ++i) {
+    GenBody(system, *i, myframe);
+  }
+}
+
+void Space::BuildSystem(StarSystem* system) {
+  GenBody(system, system->rootBody, rootFrame);
+}
+
+void Space::AddBody(Body* b) {
+  bodies.push_back(b);
+}
+
+void Space::UpdateFramesOfReference(void) {
+  for(std::list<Body*>::iterator i = bodies.begin(); i != bodies.end(); ++i) {
+    Body* b = *i;
+
+    if(!b->GetFlags() & Body::FLAG_CAN_MOVE_FRAME) continue;
+
+    /* Falling out of frames. */
+    if(!b->GetFrame()->IsLocalPosInFrame(b->GetPosition())) {
+      printf("%s leaves frame %s\n", b->GetLabel().c_str(), b->GetFrame()->GetLabel());
+
+      Frame* new_frame = b->GetFrame()->m_parent;
+      if(new_frame) { /* Don't fall out of root frame. */
+        vector3d new_pos = b->GetPositionRelTo(new_frame);
+        b->SetFrame(new_frame);
+        b->SetPosition(new_pos);
+      }
+    }
+
+    /* Entering into frames. */
+    for(std::list<Frame*>::iterator j = b->GetFrame()->m_children.begin();
+        j != b->GetFrame()->m_children.end(); ++j) {
+      Frame* kid = *j;
+      vector3d pos = b->GetFrame()->GetPosRelativeToOtherFrame(kid) + b->GetPosition();
+      if(kid->IsLocalPosInFrame(pos)) {
+        printf("%s enters frame %s\n", b->GetLabel().c_str(), kid->GetLabel());
+        b->SetPosition(pos);
+        b->SetFrame(kid);
+        break;
+      }
+    }
+  }
+}
+
+static bool _OnCollision(dGeomID g1, dGeomID g2, Object* o1, Object* o2,
+                         int numContacts, dContact contacts[]) {
+  if((o1->GetType() == Object::LASER) || (o2->GetType() == Object::LASER)) {
+    if(o1->GetType() == Object::LASER) {
+      std::swap<Object*>(o1, o2);
+      std::swap<dGeomID>(g1, g2);
+    }
+    Ship::LaserObj* lobj = static_cast<Ship::LaserObj*>(o2);
+    if(o1 == lobj->owner) return false;
+    printf("%s was shot by %s\n", ((Body*)o1)->GetLabel().c_str(), lobj->owner->GetLabel().c_str());
+    if(o1->GetType() == Object::SHIP) {
+      RigidBody* rb = (RigidBody*)o1;
+      dVector3 start, dir;
+      dGeomRayGet(g2, start, dir);
+      dBodyAddForceAtPos(rb->m_body,
+                         100*dir[0],
+                         100*dir[1],
+                         100*dir[2],
+                         contacts[0].geom.pos[0],
+                         contacts[0].geom.pos[1],
+                         contacts[0].geom.pos[2]);
+    }
+    return false;
+  } else {
+    Body* pb1 = static_cast<Body*>(o1);
+    Body* pb2 = static_cast<Body*>(o2);
+    if((pb1 && !pb1->OnCollision(pb2)) || (pb2 && !pb2->OnCollision(pb1))) return false;
+  }
+  return true;
+}
+
+static void nearCallback(void* data, dGeomID oO, dGeomID o1) {
+  /* Create an array of dContact objects to hold the contact joints. */
+  static const int MAX_CONTACTS = 10;
+  dContact contact[MAX_CONTACTS];
+
+  for(int i = 0; i < MAX_CONTACTS; i++) {
+    contact[i].surface.mode       = dContactBounce | dContactSoftCFM;
+    contact[i].surface.mu         = dInfinity;
+    contact[i].surface.mu2        = 0;
+    contact[i].surface.bounce     = 0.8;
+    contact[i].surface.bounce_vel = 0.1;
+    contact[i].surface.soft_cfm   = 0.01;
+  }
+  if(int numc = dCollide(oO, o1, MAX_CONTACTS, &contact[0].geom, sizeof(dContact))) {
+    Object* po1 = static_cast<Object*>(dGeomGetData(oO));
+    Object* po2 = static_cast<Object*>(dGeomGetData(o1));
+    if(!_OnCollision(oO, o1, po1, po2, numc, contact)) return;
+    /* Get the dynamics body for each geom. */
+    dBodyID b1 = dGeomGetBody(oO);
+    dBodyID b2 = dGeomGetBody(o1);
+    /* 
+     * To add contact point found to our joint group we call dJointCreateContact
+     * which is just one of the many different types available.
+     */
+    for(int i = 0; i < numc; i++) {
+      /*
+       * dJointCreateContact needs to know which world and joint group to work
+       * with as well as the dContact object itself.
+       * It returns a new dJointID which we then use with dJointAttach to
+       * finally create the temp contact joint between the two geom bodies.
+       */
+      dJointID c = dJointCreateContact(Space::world, _contactgroup, contact + i);
+      dJointAttach(c, b1, b2);
+    }
+  }
+}
+
+void Space::CollideFrame(Frame* f) {
+  dSpaceCollide(f->GetSpaceID(), NULL, &nearCallback);
+  for(std::list<Frame*>::iterator i = f->m_children.begin(); i != f->m_children.end(); ++i) {
+    CollideFrame(*i);
+  }
+}
+
+void Space::TimeStep(float step) {
+  CollideFrame(rootFrame);
+  //CollideFrame(L3D::player->GetFrame());
+  dWorldQuickStep(world, step);
+  dJointGroupEmpty(_contactgroup);
+
+  /* TODO: Does not need to be done this often. */
+  UpdateFramesOfReference();
+}
+
+struct body_zsort_t {
+  double dist;
+  Body* b;
+};
+
+struct body_zsort_compare : public std::binary_function<body_zsort_t, body_zsort_t, bool> {
+  bool operator()(body_zsort_t a, body_zsort_t b) { return a.dist > b.dist; }
+};
+
+void Space::Render(const Frame* cam_frame) {
+  /* Simple z-sort. */
+  body_zsort_t* bz = new body_zsort_t[bodies.size()];
+  int idx = 0;
+  for(std::list<Body*>::iterator i = bodies.begin(); i != bodies.end(); ++i) {
+    vector3d toBody = (*i)->GetPositionRelTo(cam_frame);
+    bz[idx].dist = toBody.Length();
+    bz[idx].b = *i;
+    idx++;
+  }
+  sort(bz, bz+bodies.size(), body_zsort_compare());
+  for(unsigned int i = 0; i < bodies.size(); i++) {
+    bz[i].b->Render(cam_frame);
+  }
+  delete [] bz;
+}
+
diff --git a/src/space_station.cpp b/src/space_station.cpp
new file mode 100644
index 0000000..6599696
--- /dev/null
+++ b/src/space_station.cpp
@@ -0,0 +1,55 @@
+#include "space_station.h"
+#include "ship.h"
+#include "objimport.h"
+
+SpaceStation::SpaceStation(void) : StaticRigidBody() {
+  dGeomSphereSetRadius(m_geom, 100.0);
+  dGeomSetData(m_geom, static_cast<Body*>(this));
+  matrix4x4d m = matrix4x4d::RotateYMatrix(M_PI);
+  dMatrix3 _m;
+  m.SaveToOdeMatrix(_m);
+  dGeomSetRotation(m_geom, _m);
+  m_mesh = 0;
+}
+
+SpaceStation::~SpaceStation(void) {
+  //dGeomDestroy(m_geom);
+}
+
+bool SpaceStation::OnCollision(Body* b) {
+  if(b->GetType() == Object::SHIP) {
+    Ship* s = static_cast<Ship*>(b);
+    if(!s->GetDockedWith()) {
+      s->SetDockedWith(this);
+      printf("docking!\n");
+    }
+    return false;
+  } else {
+    return true;
+  }
+}
+
+void SpaceStation::SetMesh(ObjMesh* m) {
+  m_mesh = m;
+}
+
+static ObjParams params = {
+  { 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f, 0.0f },
+
+  /* pColor[3] */
+  {
+    { { 1.0f, 0.0f, 1.0f }, { 0, 0, 0 }, { 0, 0, 0 }, 0 },
+    { { 0.8f, 0.6f, 0.5f }, { 0, 0, 0 }, { 0, 0, 0 }, 0 },
+    { { 0.5f, 0.5f, 0.5f }, { 0, 0, 0 }, { 0, 0, 0 }, 0 }
+  },
+
+  /* pText[3][256] */
+  { "Hello old bean", "CATZ!" },
+};
+
+void SpaceStation::Render(const Frame* camFrame) {
+  RenderSbreModel(camFrame, 12, &params);
+}
+
diff --git a/src/space_station.h b/src/space_station.h
new file mode 100644
index 0000000..fb66114
--- /dev/null
+++ b/src/space_station.h
@@ -0,0 +1,16 @@
+#pragma once
+#include "libs.h"
+#include "static_rigid_body.h"
+
+class SpaceStation : public StaticRigidBody {
+public:
+  SpaceStation(void);
+  virtual ~SpaceStation(void);
+  virtual bool OnCollision(Body* b);
+  virtual Object::Type GetType(void) { return Object::SPACESTATION; }
+  virtual void Render(const Frame* camFrame);
+  void SetMesh(ObjMesh* m);
+protected:
+  ObjMesh* m_mesh;
+};
+
diff --git a/src/space_station_view.cpp b/src/space_station_view.cpp
new file mode 100644
index 0000000..e6ede4a
--- /dev/null
+++ b/src/space_station_view.cpp
@@ -0,0 +1,47 @@
+#include "space_station_view.h"
+#include "l3d.h"
+#include "player.h"
+#include "world_view.h"
+
+SpaceStationView::SpaceStationView(void) : View() {
+  SetTransparency(false);
+
+  Gui::Label* l = new Gui::Label("Hello friend! Thankyou for docking with this space station!\n"
+    "You may have noticed that the docking procedure was not entirely\n"
+    "physically correct. this is a result of unimplemented physics in this\n"
+    "region of the galaxy. We hope to have things back to normal within a\n"
+    "few weeks, and in the mean time would like to offer our apologies for\n"
+    "any loss of earnings, immersion or lunch.\n\n"
+    "Currently the usual space station services are not available, but we\n"
+    "can offer you this promotional message from one of the station's sponsors:\n\n"
+    "         ADOPT A CAT: THEY CHEW IMPORTANT CABLES!");
+
+  float size[2];
+  GetSize(size);
+  Add(l, 40, size[1]-100);
+
+  Gui::SolidButton* b = new Gui::SolidButton();
+  b->onClick.connect(sigc::mem_fun(this, &SpaceStationView::OnClickRequestLaunch));
+  Add(b, 40, size[1]-300);
+  l = new Gui::Label("Request Launch");
+  Add(l, 65, size[1]-300);
+
+  l = new Gui::Label("Comms Link");
+  l->SetColor(1, .7, 0);
+  m_rightRegion2->Add(l, 10, 3);
+}
+
+void SpaceStationView::OnClickRequestLaunch(void) {
+  printf("Launching!\n");
+  L3D::player->SetDockedWith(0);
+  L3D::SetView(L3D::world_view);
+}
+
+void SpaceStationView::Draw3D(void) {
+
+}
+
+void SpaceStationView::Update(void) {
+
+}
+
diff --git a/src/space_station_view.h b/src/space_station_view.h
new file mode 100644
index 0000000..ef4e8b8
--- /dev/null
+++ b/src/space_station_view.h
@@ -0,0 +1,14 @@
+#pragma once
+#include "libs.h"
+#include "gui.h"
+#include "view.h"
+
+class SpaceStationView : public View {
+public:
+  SpaceStationView(void);
+  virtual void Update(void);
+  virtual void Draw3D(void);
+private:
+  void OnClickRequestLaunch(void);
+};
+
diff --git a/src/star.cpp b/src/star.cpp
new file mode 100644
index 0000000..4718ef1
--- /dev/null
+++ b/src/star.cpp
@@ -0,0 +1,52 @@
+#include "libs.h"
+#include "star.h"
+
+Star::Star(StarSystem::SBody::SubType subtype): Body() {
+  m_subtype = subtype;
+  m_radius  = 6378135.0;
+  m_pos     = vector3d(0,0,0);
+}
+
+vector3d Star::GetPosition(void) {
+  return m_pos;
+}
+
+void Star::SetPosition(vector3d p) {
+  m_pos = p;
+}
+
+void Star::TransformToModelCoords(const Frame* camFrame) {
+  vector3d fpos = GetPositionRelTo(camFrame);
+  glTranslatef(m_pos[0]+fpos.x, m_pos[1]+fpos.y, m_pos[2]+fpos.z);
+}
+
+void Star::Render(const Frame* a_camFrame) {
+  static GLUquadricObj* qobj = NULL;
+
+  if(!qobj) qobj = gluNewQuadric();
+
+  glDisable(GL_LIGHTING);
+  glDisable(GL_DEPTH_TEST);
+  glPushMatrix();
+
+  /* TODO duplicates code from Planet.cpp, not good. */
+  double rad = m_radius;
+  vector3d fpos = GetPositionRelTo(a_camFrame);
+  double len = fpos.Length();
+
+  while(len > 1000.0f) {
+    rad *= 0.25;
+    fpos = 0.25*fpos;
+    len *= 0.25;
+  }
+  
+  glTranslatef(fpos.x, fpos.y, fpos.z);
+  
+  //TransformToModelCoords(a_camFrame);
+  glColor3fv(StarSystem::starColors[m_subtype]);
+  gluSphere(qobj, rad, 100, 100);
+  glPopMatrix();
+  glEnable(GL_DEPTH_TEST);
+  glEnable(GL_LIGHTING);
+}
+
diff --git a/src/star.h b/src/star.h
new file mode 100644
index 0000000..59f5108
--- /dev/null
+++ b/src/star.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "body.h"
+#include "star_system.h"
+
+class Frame;
+
+class Star: public Body {
+public:
+  Star(StarSystem::SBody::SubType subtype);
+  virtual ~Star(void) { };
+  virtual void SetPosition(vector3d p);
+  virtual vector3d GetPosition(void);
+  void SetRadius(double radius) { m_radius = radius; }
+  double GetRadius(void) { return m_radius; }
+  virtual void Render(const Frame* camFrame);
+  virtual void TransformToModelCoords(const Frame* camFrame);
+  virtual void TransformCameraTo(void) { };
+
+private:
+  StarSystem::SBody::SubType m_subtype;
+  vector3d  m_pos;
+  double    m_radius;
+};
+
diff --git a/src/star_system.cpp b/src/star_system.cpp
new file mode 100644
index 0000000..2a8242d
--- /dev/null
+++ b/src/star_system.cpp
@@ -0,0 +1,453 @@
+#include "star_system.h"
+#include "sector.h"
+
+#define CELSIUS 273.15
+
+/* Indexed by enum subtype. */
+float StarSystem::starColors[7][3] = {
+  { 1.0, 0.2, 0.0 }, /* M */
+  { 1.0, 0.6, 0.1 }, /* K */
+  { 1.0, 1.0, 0.4 }, /* G */
+  { 1.0, 1.0, 0.8 }, /* F */
+  { 1.0, 1.0, 1.0 }, /* A */
+  { 0.7, 0.7, 1.0 }, /* B */
+  { 1.0, 0.7, 1.0 }  /* O */
+};
+
+static const struct SBodySubTypeInfo {
+  float       mass;
+  float       radius;
+  const char *description;
+  const char *icon;
+  float tempMin, tempMax;
+} subTypeInfo[StarSystem::SBody::SUBTYPE_MAX] = {
+  {
+    0.4, 0.5, "Type 'M' red star",
+    "icons/object_star_m.png",
+    2000, 3500
+  },
+  {
+    0.8, 0.9, "Type 'K' orange star",
+    "icons/object_star_k.png",
+    3500, 5000
+  },
+  {
+    1.1, 1.1, "Type 'G' yellow star",
+    "icons/object_star_g.png",
+    5000, 6000
+  },
+  {
+    1.7, 1.4, "Type 'F' white star",
+    "icons/object_star_f.png",
+    6000, 7500
+  },
+  {
+    3.1, 2.1, "Type 'A' hot white star",
+    "icons/object_star_a.png",
+    7500, 10000
+  },
+  {
+    18.0, 7.0, "Bright type 'B' blue star",
+    "icons/object_star_b.png",
+    10000, 30000
+  },
+  {
+    64.0, 16.0, "Hot, massive type 'O' blue star",
+    "icons/object_star_o.png",
+    30000, 60000
+  },
+  {
+    0, 0, "Brown dwarf sub-stellar object",
+    "icons/object_brown_dwarf.png"
+  },
+  {
+    0, 0, "Small gas giant",
+    "icons/object_planet_small_gas_giant.png"
+  },
+  {
+    0, 0, "Medium gas giant",
+    "icons/object_planet_medium_gas_giant.png"
+  },
+  {
+    0, 0, "Large gas giant",
+    "icons/object_planet_large_gas_giant.png"
+  },
+  {
+    0, 0, "Very large gas giant",
+    "icons/object_planet_large_gas_giant.png"
+  },
+  {
+    0, 0, "Small, rocky dwarf planet",
+    "icons/object_planet_dwarf.png"
+  },
+  {
+    0, 0, "Small, rocky planet with a thin atmosphere",
+    "icons/object_planet_small.png"
+  },
+  {
+    0, 0, "Rocky planet with liquid water and a nitrogen atmosphere",
+    "icons/object_planet_small.png"
+  },
+  {
+    0, 0, "Rocky planet with a carbon dioxide atmosphere",
+    "icons/object_planet_small.png"
+  },
+  {
+    0, 0, "Rocky planet with a methane atmosphere",
+    "icons/object_planet_small.png"
+  },
+  {
+    0, 0, "Rocky planet with running water and a thick nitrogen atmosphere",
+    "icons/object_planet_small.png"
+  },
+  {
+    0, 0, "Rocky planet with a thick carbon dioxide atmosphere",
+    "icons/object_planet_small.png"
+  },
+  {
+    0, 0, "Rocky planet with a thick methane atmosphere",
+    "icons/object_planet_small.png"
+  },
+  {
+    0, 0, "Highly volcanic world",
+    "icons/object_planet_small.png"
+  },
+  {
+    0, 0, "World with indigenous life and an oxygen atmosphere",
+    "icons/object_planet_life.png"
+  }
+};
+
+const char* StarSystem::SBody::GetAstroDescription(void) {
+  return subTypeInfo[subtype].description;
+}
+
+const char* StarSystem::SBody::GetIcon(void) {
+  return subTypeInfo[subtype].icon;
+}
+
+static const double boltzman_const = 5.6704e-8;
+
+static double calcEnergyPerUnitAreaAtDist(double star_radius, double star_temp,
+                                          double object_dist) {
+
+  const double total_solar_emission = boltzman_const * pow(star_temp, 4) *
+                                      4*M_PI*star_radius*star_radius;
+
+  return total_solar_emission / (4*M_PI*object_dist*object_dist);
+}
+
+/* Bond albedo, not geometric. */
+static double calcSurfaceTemp(double star_radius, double star_temp,
+                              double object_dist, double albedo,
+                              double greenhouse) {
+  
+  const double energy_per_meter2 = calcEnergyPerUnitAreaAtDist(star_radius, star_temp,
+                                                               object_dist);
+  const double surface_temp = pow(energy_per_meter2*(1-albedo)/(4*(1-greenhouse)*boltzman_const), 0.25);
+  return surface_temp;
+}
+
+void StarSystem::Orbit::KeplerPosAtTime(double t, double* dist, double* ang) {
+  double e = eccentricity;
+  double a = semiMajorAxis;
+  /* Mean anomaly. */
+  double M = 2*M_PI*t / period;
+  /* Eccentic anomaly. */
+  double E = M + (e - (1/8.0)*e*e*e)*sin(M) +
+                 (1/2.0)*e*e*sin(2*M) +
+                 (3/8.0)*e*e*e*sin(3*M);
+  /* True anomaly (angle of orbit position). */
+  double v = 2*atan(sqrt((1+e)/(1-e)) * tan(E/2.0));
+  /* Heliocentric distance. */
+  double r = a * (1 - e*e) / (1 + e*cos(v));
+  *ang = v;
+  *dist = r;
+}
+
+vector3d StarSystem::Orbit::CartesianPosAtTime(double t) {
+  double dist, ang;
+  KeplerPosAtTime(t, &dist, &ang);
+  vector3d pos = vector3d(cos(ang)*dist, sin(ang)*dist, 0);
+  pos = rotMatrix * pos;
+  return pos;
+}
+
+static std::vector<float>* AccreteDisc(int size, float density, MTRand& rand) {
+  std::vector<float>* disc = new std::vector<float>();
+
+  for(int i = 0; i < size; i++) {
+    disc->push_back(density*rand(1.0));
+  }
+
+  for(int iter = 0; iter < 20; iter++) {
+    for(int i = 0; i < (signed)disc->size(); i++) {
+      for(int d = ceil(sqrtf((*disc)[i])+(i/5)); d > 0; d--) {
+        if((i+d < (signed)disc->size()) && ((*disc)[i] > (*disc)[i+d])) {
+          (*disc)[i] += (*disc)[i+d];
+          (*disc)[i+d] = 0;
+        }
+        if(((i-d) >= 0) && ((*disc)[i] > (*disc)[i-d])) {
+          (*disc)[i] += (*disc)[i-d];
+          (*disc)[i-d] = 0;
+        }
+      }
+    }
+  }
+  return disc;
+}
+
+double calc_orbital_period(double semiMajorAxis, double centralMass) {
+  return 2.0*M_PI*sqrtf((semiMajorAxis*semiMajorAxis*semiMajorAxis)/(G*centralMass));
+}
+
+void StarSystem::SBody::EliminateBadChildren(void) {
+  /* Check for overlapping & unacceptably close orbits. Merge planets. */
+  for (std::vector<SBody*>::iterator i = children.begin(); i != children.end(); ++i) {
+    if((*i)->temp) continue;
+    for(std::vector<SBody*>::iterator j = children.begin(); j != children.end(); ++j) {
+      if((*j) == (*i)) continue;
+      /* Don't eat anything bigger than self. */
+      if((*j)->mass > (*i)->mass) continue;
+      double i_min = (*i)->radMin;
+      double i_max = (*i)->radMax;
+      double j_min = (*j)->radMin;
+      double j_max = (*j)->radMax;
+      bool eat = false;
+      if((*i)->orbit.semiMajorAxis > (*j)->orbit.semiMajorAxis) {
+        if(i_min < j_max*1.2) eat = true;
+      } else {
+        if(i_max > j_min*0.8) eat = true;
+      }
+      if(eat) {
+        (*i)->mass += (*j)->mass;
+        (*j)->temp = 1;
+      }
+    }
+  }
+  /* Kill the eaten ones. */
+  for(std::vector<SBody*>::iterator i = children.begin(); i != children.end();) {
+    if((*i)->temp) {
+      i = children.erase(i);
+    }
+    else i++;
+  }
+}
+
+StarSystem::StarSystem(int sector_x, int sector_y, int system_idx) {
+  unsigned long _init[3] = { system_idx, sector_x, sector_y };
+  m_sectorX   = sector_x;
+  m_sectorY   = sector_y;
+  m_systemIdx = system_idx;
+  rootBody    = 0;
+  if(system_idx == -1) return;
+  rand.seed(_init, 3);
+
+  Sector s = Sector(sector_x, sector_y);
+  /* Primary. */
+  SBody* primary = new SBody;
+
+  StarSystem::SBody::SubType subtype = s.m_systems[system_idx].primaryStarClass;
+  primary->subtype  = subtype;
+  primary->parent   = NULL;
+  primary->radius   = SOL_RADIUS*subTypeInfo[subtype].radius;
+  primary->mass     = SOL_MASS*subTypeInfo[subtype].mass;
+  primary->type     = SBody::TYPE_STAR;
+  primary->averageTemp = rand((int)subTypeInfo[subtype].tempMin,
+                          (int)subTypeInfo[subtype].tempMax);
+  rootBody = primary;
+
+  int disc_size = rand(6, 100) + rand(60,140)*primary->subtype*primary->subtype;
+  //printf("disc_size %.1fAU\n", disc_size/10.0);
+
+  std::vector<float>* disc = AccreteDisc(disc_size, 0.1+rand(1.5), rand);
+  for(unsigned int i = 0; i < disc->size(); i++) {
+    float mass = (*disc)[i];
+    if(mass == 0) continue;
+
+    SBody* planet = new SBody;
+    planet->subtype             = SBody::SUBTYPE_PLANET_DWARF;
+    planet->temp                = 0;
+    planet->parent              = primary;
+    planet->radius              = EARTH_RADIUS;
+    planet->mass                = mass * EARTH_MASS;
+    planet->orbit.eccentricity  = rand.pdrand(3);
+    planet->orbit.semiMajorAxis = ((i+1)*0.1)*AU;
+    planet->orbit.period = calc_orbital_period(planet->orbit.semiMajorAxis, primary->mass);
+    planet->orbit.rotMatrix = matrix4x4d::RotateYMatrix(rand.pdrand(5)*M_PI/2.0) *
+                           matrix4x4d::RotateZMatrix(rand(M_PI));
+    primary->children.push_back(planet);
+
+    double ang;
+    planet->orbit.KeplerPosAtTime(0, &planet->radMin, &ang);
+    planet->orbit.KeplerPosAtTime(planet->orbit.period*0.5, &planet->radMax, &ang);
+    //printf(%f,%f\n", min/AU, max/AU);
+    //printf(%f year orbital period\n", planet->orbit.period / (60*60*24*365));
+  }
+  delete disc;
+
+  /* Merge children with overlapping or very close orbits. */
+  primary->EliminateBadChildren();
+  primary->name = s.m_systems[system_idx].name;
+
+  int idx = 0;
+  for(std::vector<SBody*>::iterator i = primary->children.begin(); i != primary->children.end(); ++i) {
+    /* Turn them into... something! */
+    char buf[3];
+    buf[0] = ' ';
+    buf[1] = 'b'+(idx++);
+    buf[2] = 0;
+    (*i)->name = primary->name+buf;
+    double d = 0.5*((*i)->radMin + (*i)->radMax);
+    (*i)->L3DckPlanetType(primary, d, rand, true);
+  }
+}
+
+void StarSystem::SBody::L3DckPlanetType(SBody* star, double distToPrimary, MTRand& rand, bool genMoons) {
+  float emass = mass / EARTH_MASS;
+
+  /* Surface temperature. */
+  /* https::/en.wikipedia.org/wiki/Black_body - Thanks again wiki. */
+  const double d = distToPrimary;
+  double albedo = rand(0.5);
+  double globalwarming = rand(0.9);
+  /* Light planets have like.. no atmosphere. */
+  if(emass < 1) globalwarming *= emass;
+  /* Big planets get high global warming owing to it's thick atmos. */
+  if(emass > 3) globalwarming *= (emass-2.0f);
+  globalwarming = CLAMP(globalwarming, 0, 0.95);
+
+  //printf("====\ndist %f, mass %f, albedo %f, globalwarming %f\n", d, emass, albedo, globalwarming);
+  
+  /* This is all of course a total joke and un-physical.. Sorry. */
+  double bbody_temp;
+  bool fiddle = false;
+  for(int i = 0; i < 10; i++) {
+    bbody_temp = calcSurfaceTemp(star->radius, star->averageTemp, d, albedo, globalwarming);
+    //printf(temp %f, albedo %f, globalwarming %f\n", bbody_temp, albedo, globalwarming);
+    /* Extreme high temperature and low mass causes atmosphere loss. */
+#define ATMOS_LOSS_MASS_CUTOFF  2.0
+#define ATMOS_TEMP_CUTOFF       400
+#define FREEZE_TEMP_CUTOFF      220
+    if((bbody_temp > ATMOS_TEMP_CUTOFF) &&
+      (emass < ATMOS_LOSS_MASS_CUTOFF)) {
+      //printf("atmos loss\n");
+      globalwarming = globalwarming * (emass/ATMOS_LOSS_MASS_CUTOFF);
+      fiddle = true;
+    }
+    if(!fiddle) break;
+    fiddle = false;
+  }
+  /* This is bs. Should decide atmosphere composition and then freeze out
+   * components of it in the previous loop.
+   */
+  if((bbody_temp < FREEZE_TEMP_CUTOFF) && (emass < 5)) {
+    globalwarming *= 0.2;
+    albedo = rand(0.05) + 0.9;
+  }
+  bbody_temp = calcSurfaceTemp(star->radius, star->averageTemp, d, albedo, globalwarming);
+  // printf("= temp %f, albedo %f, globalwarming %f\n", bbody_temp, albedo, globalwarming);
+
+  averageTemp = bbody_temp;
+
+  if(emass > 317.8*13) {
+    /* More than 13 jupiter masses can fuse deuterium - is a brown dwarf. */
+    subtype = SBody::SUBTYPE_BROWN_DWARF;
+    /* TODO Should prevent mass exceeding 65 jupiter masses or so,
+     * when it becomes a star.
+     */
+  } else if(emass > 300) {
+    subtype = SBody::SUBTYPE_PLANET_LARGE_GAS_GIANT;
+  } else if(emass > 90) {
+    subtype = SBody::SUBTYPE_PLANET_MEDIUM_GAS_GIANT;
+  } else if(emass > 6) {
+    subtype = SBody::SUBTYPE_PLANET_SMALL_GAS_GIANT;
+  } else {
+    /* Terrestrial planets. */
+    if(emass < 0.02) {
+      subtype = SBody::SUBTYPE_PLANET_DWARF;
+    } else if((emass < 0.2) && (globalwarming < 0.05)) {
+      subtype = SBody::SUBTYPE_PLANET_SMALL;
+    } else if(emass < 3) {
+      if((averageTemp > CELSIUS-10) && (averageTemp < CELSIUS+70)) {
+        /* Try for life.. */
+        double minTemp = calcSurfaceTemp(star->radius, star->averageTemp, radMax, albedo, globalwarming);
+        double maxTemp = calcSurfaceTemp(star->radius, star->averageTemp, radMin, albedo, globalwarming);
+        if((minTemp > CELSIUS-10) && (minTemp < CELSIUS+70) &&
+           (maxTemp > CELSIUS-10) && (maxTemp < CELSIUS+70)) {
+          subtype = SBody::SUBTYPE_PLANET_INDIGENOUS_LIFE;
+        } else {
+          subtype = SBody::SUBTYPE_PLANET_WATER;
+        }
+      } else {
+        if(rand(0,1)) subtype = SBody::SUBTYPE_PLANET_CO2;
+        else subtype = SBody::SUBTYPE_PLANET_METHANE;
+      }
+    } else { /* 3 < emass < 6 */
+      if((averageTemp > CELSIUS-10) && (averageTemp < CELSIUS+70)) {
+        subtype = SBody::SUBTYPE_PLANET_WATER_THICK_ATMOS;
+      } else {
+        if(rand(0,1)) subtype = SBody::SUBTYPE_PLANET_CO2_THICK_ATMOS;
+        else subtype = SBody::SUBTYPE_PLANET_METHANE_THICK_ATMOS;
+      }
+    }
+    /* Kinda crappy. */
+    if((emass > 0.8) && (!rand(0,15))) subtype = SBody::SUBTYPE_PLANET_HIGHLY_VOLCANIC;
+  }
+  /* Generate moons. */
+  if(genMoons) {
+    std::vector<float>* disc = AccreteDisc(2*sqrt(emass), 0.001, rand);
+    for(unsigned int i = 0; i < disc->size(); i++) {
+      float mass = (*disc)[i];
+      if(mass == 0) continue;
+
+      SBody* moon = new SBody;
+      moon->subtype   = SBody::SUBTYPE_PLANET_DWARF;
+      moon->temp      = 0;
+      moon->parent    = this;
+      moon->radius    = EARTH_RADIUS;
+      moon->mass      = mass * EARTH_MASS;
+      moon->orbit.eccentricity = rand.pdrand(3);
+      moon->orbit.semiMajorAxis = ((i+1)*0.001)*AU;
+      moon->orbit.period = calc_orbital_period(moon->orbit.semiMajorAxis, this->mass);
+      moon->orbit.rotMatrix = matrix4x4d::RotateYMatrix(rand.pdrand(5)*M_PI/2.0) *
+                              matrix4x4d::RotateZMatrix(rand(M_PI));
+      this->children.push_back(moon);
+
+      double ang;
+      moon->orbit.KeplerPosAtTime(0, &moon->radMin, &ang);
+      moon->orbit.KeplerPosAtTime(moon->orbit.period*0.5, &moon->radMax, &ang);
+      //printf("%f,%f\n", min/AU, max/AU);
+      //printf("%f year orbital period\n", moon->orbit.period / (60*60*24*365));
+    }
+    delete disc;
+
+    /* Merge moons with overlapping or very close orbits. */
+    EliminateBadChildren();
+
+    int idx = 0;
+    for(std::vector<SBody*>::iterator i = children.begin(); i != children.end(); ++i) {
+      /* Turn them into.. Something. */
+      char buf[2];
+      buf[0] = '1'+(idx++);
+      buf[1] = 0;
+      (*i)->name = name+buf;
+      (*i)->L3DckPlanetType(star, d, rand, false);
+    }
+  }
+}
+
+StarSystem::~StarSystem(void) {
+  if(rootBody) delete rootBody;
+}
+
+bool StarSystem::IsSystem(int sector_x, int sector_y, int system_idx) {
+  return (sector_x == m_sectorX) && (sector_y == m_sectorY) && (system_idx == m_systemIdx);
+}
+
+StarSystem::SBody::~SBody(void) {
+  for(std::vector<SBody*>::iterator i = children.begin(); i != children.end(); ++i) {
+    delete (*i);
+  }
+}
+
diff --git a/src/star_system.h b/src/star_system.h
new file mode 100644
index 0000000..af14855
--- /dev/null
+++ b/src/star_system.h
@@ -0,0 +1,91 @@
+#pragma once
+#include <vector>
+#include <string>
+#include "libs.h"
+
+#define EARTH_RADIUS    6378135.0
+#define EARTH_MASS      5.9742e24
+#define JUPITER_MASS    317.8*EARTH_MASS
+/* Brown dwarfs above 13 jupiter masses fuse deuterium. */
+#define MIN_BROWN_DWARF (13.0*JUPITER_MASS)
+#define SOL_RADIUS      6.955e8
+#define SOL_MASS        1.98892e30
+#define AU              149598000000.0
+#define G               6.67428e-11
+
+/* All masses are in Kg, all lengths in meters. */
+class StarSystem {
+public:
+  StarSystem(int sector_x, int sector_y, int system_idx);
+  ~StarSystem(void);
+  bool IsSystem(int sector_x, int sector_y, int system_idx);
+  void GetPos(int* sec_x, int* sec_y, int* sys_idx) {
+    *sec_x = m_sectorX; *sec_y = m_sectorY; *sys_idx = m_systemIdx;
+  }
+
+  static float starColors[7][3];
+
+  struct Orbit {
+    void KeplerPosAtTime(double t, double* dist, double* ang);
+    vector3d CartesianPosAtTime(double t);
+    double eccentricity;
+    double semiMajorAxis;
+    double period; /* Seconds. */
+    matrix4x4d rotMatrix;
+  };
+
+  struct SBody {
+    ~SBody(void);
+    void EliminateBadChildren(void); /* :D */
+    void L3DckPlanetType(SBody*, double distToPrimary, MTRand& drand, bool genMoons);
+    SBody* parent;
+    std::vector<SBody*> children;
+    Orbit orbit;
+
+    const char* GetAstroDescription(void);
+    const char* GetIcon(void);
+
+    int temp;
+    std::string name;
+    double radius;
+    double mass;
+    double radMin, radMax;
+    double averageTemp;
+    enum { TYPE_STAR, TYPE_ROCKY_PLANET, TYPE_GAS_GIANT } type;
+
+    enum SubType {
+      SUBTYPE_STAR_M,
+      SUBTYPE_STAR_K,
+      SUBTYPE_STAR_G,
+      SUBTYPE_STAR_F,
+      SUBTYPE_STAR_A,
+      SUBTYPE_STAR_B,
+      SUBTYPE_STAR_O,
+      SUBTYPE_BROWN_DWARF,
+      SUBTYPE_PLANET_SMALL_GAS_GIANT,
+      SUBTYPE_PLANET_MEDIUM_GAS_GIANT,
+      SUBTYPE_PLANET_LARGE_GAS_GIANT,
+      SUBTYPE_PLANET_VERY_LARGE_GAS_GIANT,
+      SUBTYPE_PLANET_DWARF,
+      SUBTYPE_PLANET_SMALL,
+      SUBTYPE_PLANET_WATER,
+      SUBTYPE_PLANET_CO2,
+      SUBTYPE_PLANET_METHANE,
+      SUBTYPE_PLANET_WATER_THICK_ATMOS,
+      SUBTYPE_PLANET_CO2_THICK_ATMOS,
+      SUBTYPE_PLANET_METHANE_THICK_ATMOS,
+      SUBTYPE_PLANET_HIGHLY_VOLCANIC,
+      SUBTYPE_PLANET_INDIGENOUS_LIFE,
+      SUBTYPE_MAX
+      /* TODO: Need larger atmospherless things. */
+    } subtype;
+  };
+
+  SBody* rootBody;
+
+private:
+  int m_sectorX, m_sectorY, m_systemIdx;
+
+  MTRand rand;
+};
+
diff --git a/src/static_rigid_body.cpp b/src/static_rigid_body.cpp
new file mode 100644
index 0000000..d100394
--- /dev/null
+++ b/src/static_rigid_body.cpp
@@ -0,0 +1,118 @@
+#include "libs.h"
+#include "static_rigid_body.h"
+#include "space.h"
+#include "matrix4x4.h"
+#include "frame.h"
+#include "l3d.h"
+#include "world_view.h"
+
+StaticRigidBody::StaticRigidBody(void): Body() {
+  m_geom = dCreateSphere(0, 50.0f);
+  dGeomSetBody(m_geom, 0);
+  SetPosition(vector3d(0,0,0));
+}
+
+StaticRigidBody::~StaticRigidBody(void) {
+  dGeomDestroy(m_geom);
+}
+
+void StaticRigidBody::SetPosition(vector3d p) {
+  dGeomSetPosition(m_geom, p.x, p.y, p.z);
+}
+
+void StaticRigidBody::SetVelocity(vector3d v) {
+  assert(0);
+}
+
+vector3d StaticRigidBody::GetPosition(void) {
+  const dReal* pos = dGeomGetPosition(m_geom);
+  return vector3d(pos[0], pos[1], pos[2]);
+}
+
+void StaticRigidBody::GetRotMatrix(matrix4x4d& m) {
+  m.LoadFromOdeMatrix(dGeomGetRotation(m_geom));
+}
+
+void StaticRigidBody::ViewingRotation(void) {
+  matrix4x4d m;
+  GetRotMatrix(m);
+  m = m.InverseOf();
+  glMultMatrixd(&m[0]);
+}
+
+void StaticRigidBody::TransformCameraTo(void) {
+  const dReal* p = dGeomGetPosition(m_geom);
+  matrix4x4d m;
+  GetRotMatrix(m);
+  m = m.InverseOf();
+  glMultMatrixd(&m[0]);
+  glTranslated(-p[0], -p[1], -p[2]);
+}
+
+void StaticRigidBody::TransformToModelCoords(const Frame* camFrame) {
+  vector3d fpos = GetPositionRelTo(camFrame);
+
+  const dReal* r = dGeomGetRotation(m_geom);
+  matrix4x4d m;
+  m[ 0] = r[ 0]; m[ 1] = r[ 4]; m[ 2] = r[ 8]; m[ 3] = 0;
+  m[ 4] = r[ 1]; m[ 5] = r[ 5]; m[ 6] = r[ 9]; m[ 7] = 0;
+  m[ 8] = r[ 2]; m[ 9] = r[ 6]; m[10] = r[10]; m[11] = 0;
+  m[12] = fpos.x; m[13] = fpos.y; m[14] = fpos.z; m[15] = 1;
+  glMultMatrixd(&m[0]);
+}
+
+void StaticRigidBody::SetFrame(Frame* f) {
+  if(GetFrame()) GetFrame()->RemoveGeom(m_geom);
+  Body::SetFrame(f);
+  if(f) f->AddGeom(m_geom);
+}
+
+void StaticRigidBody::RenderSbreModel(const Frame* camFrame, int model, ObjParams* params) {
+  glPushMatrix();
+
+  glMatrixMode(GL_PROJECTION);
+  glPushMatrix();
+  /* TODO Reduce. */
+  glPushAttrib(GL_ALL_ATTRIB_BITS);
+  {
+    /* TODO Need to use correct starlight colour. */
+    float lightCol[3] = { 1,1,1 };
+    float lightDir[3];
+    vector3d _lightDir = Frame::GetFramePosRelativeToOther(Space::GetRootFrame(), camFrame);
+
+    matrix4x4d poo = L3D::world_view->viewingRotation;
+    poo[2] = -poo[2];
+    poo[6] = -poo[6];
+    poo[8] = -poo[8];
+    poo[9] = -poo[9];
+    _lightDir = poo*_lightDir;
+
+    lightDir[0] = _lightDir.x;
+    lightDir[1] = _lightDir.y;
+    lightDir[2] = _lightDir.z;
+
+    sbreSetDirLight(lightCol, lightDir);
+  }
+
+  sbreSetViewport(L3D::GetScrWidth(), L3D::GetScrHeight(), L3D::GetScrWidth()
+                  *0.5, 5.0f, 100000.0f, 0.0f, 1.0f);
+  vector3d pos = GetPositionRelTo(camFrame);
+  pos = L3D::world_view->viewingRotation*pos;
+  Vector p; p.x = pos.x; p.y = pos.y; p.z = -pos.z;
+  matrix4x4d rot;
+  rot.LoadFromOdeMatrix(dGeomGetRotation(m_geom));
+  rot = L3D::world_view->viewingRotation * rot;
+  Matrix m;
+  m.x1 =  rot[0]; m.x2 =  rot[4]; m.x3 = -rot[8];
+  m.y1 =  rot[1]; m.y2 =  rot[5]; m.y3 = -rot[9];
+  m.z1 = -rot[2]; m.z2 = -rot[6]; m.z3 = rot[10];
+  
+  sbreRenderModel(&p, &m, model, params);
+
+  glPopAttrib();
+  glMatrixMode(GL_PROJECTION);
+  glPopMatrix();
+  glMatrixMode(GL_MODELVIEW);
+  glPopMatrix();
+}
+
diff --git a/src/static_rigid_body.h b/src/static_rigid_body.h
new file mode 100644
index 0000000..292ce18
--- /dev/null
+++ b/src/static_rigid_body.h
@@ -0,0 +1,29 @@
+#pragma once
+#include "body.h"
+#include "vector3.h"
+#include "matrix4x4.h"
+#include "sbre/sbre.h"
+
+class ObjMesh;
+
+class StaticRigidBody: public Body {
+public:
+  StaticRigidBody(void);
+  virtual ~StaticRigidBody(void);
+  void SetPosition(vector3d p);
+  /* Not valid to do SetVelocity on these. They are for huge things like
+   * space stations and will be static relative to their frame of reference.
+   */
+  void SetVelocity(vector3d v);
+  vector3d GetPosition(void);
+  void TransformToModelCoords(const Frame* camFrame);
+  void TransformCameraTo(void);
+  void ViewingRotation(void);
+  void GetRotMatrix(matrix4x4d& m);
+  virtual void SetFrame(Frame* f);
+
+  void RenderSbreModel(const Frame* camFrame, int model, ObjParams* params);
+protected:
+  dGeomID m_geom;
+};
+
diff --git a/src/system_info_view.cpp b/src/system_info_view.cpp
new file mode 100644
index 0000000..190bca0
--- /dev/null
+++ b/src/system_info_view.cpp
@@ -0,0 +1,103 @@
+#include "l3d.h"
+#include "sector.h"
+#include "sector_view.h"
+#include "system_info_view.h"
+#include "ship_cpanel.h"
+
+SystemInfoView::SystemInfoView(void) : GenericSystemView() {
+  SetTransparency(true);
+  m_bodySelected = 0;
+  onSelectedSystemChanged.connect(sigc::mem_fun(this, &SystemInfoView::SystemChanged));
+}
+
+void SystemInfoView::OnBodySelected(StarSystem::SBody* b) {
+  m_bodySelected = b;
+  std::string desc;
+  char buf[1024];
+
+  snprintf(buf, sizeof(buf), "%s: %s\n"
+          "Mass   %.2f Earth masses\n",
+          b->name.c_str(), b->GetAstroDescription(), b->mass/EARTH_MASS);
+  desc += buf;
+
+  snprintf(buf, sizeof(buf), "Surface temperature   %.0f C\n", b->averageTemp-273.15);
+  desc += buf;
+
+  /*
+   * Surface temperature.
+   * Major starports.
+   * Orbital period.
+   * Orbiatal radius.
+   * ecc, incl.
+   */
+
+  if(b->parent) {
+    float days = b->orbit.period / (60*60*24);
+    if(days > 1000) {
+      snprintf(buf, sizeof(buf), "Orbital period   %.1f years\n", days / 365);
+    } else {
+      snprintf(buf, sizeof(buf), "Orbital period   %.1f days\n", b->orbit.period/(60*60*24));
+    }
+    desc += buf;
+    snprintf(buf, sizeof(buf), "Perihelion distance   %.2f AU\n", b->radMin / AU);
+    desc += buf;
+    snprintf(buf, sizeof(buf), "Aphelion distance   %.2f AU\n", b->radMax / AU);
+    desc += buf;
+    snprintf(buf, sizeof(buf), "Eccentricity    %.2f\n", b->orbit.eccentricity);
+    desc += buf;
+  }
+  m_infoText->SetText(desc);
+}
+
+void SystemInfoView::SystemChanged(StarSystem* s) {
+  DeleteAllChildren();
+  float csize[2];
+  GetSize(csize);
+
+  float xpos = 0;
+  float size[2];
+  Gui::ImageButton* ib = new Gui::ImageButton(s->rootBody->GetIcon());
+  ib->GetSize(size);
+  ib->onClick.connect(sigc::bind(sigc::mem_fun(this, &SystemInfoView::OnBodySelected), s->rootBody));
+  Add(ib, 0, csize[1] - size[1]);
+  xpos += size[0];
+  float ycent = csize[1] - size[1]*0.5;
+
+  for(std::vector<StarSystem::SBody*>::iterator i = s->rootBody->children.begin();
+      i != s->rootBody->children.end(); ++i) {
+    Gui::ImageButton* ib = new Gui::ImageButton((*i)->GetIcon());
+    ib->GetSize(size);
+    ib->onClick.connect(sigc::bind(sigc::mem_fun(this, &SystemInfoView::OnBodySelected), *i));
+    Add(ib, xpos, ycent - 0.5*size[1]);
+
+    float moon_ypos = ycent - size[1] - 5;
+    if((*i)->children.size())
+      for(std::vector<StarSystem::SBody*>::iterator moon = (*i)->children.begin();
+          moon != (*i)->children.end(); ++moon) {
+        float msize[2];
+        Gui::ImageButton* ib = new Gui::ImageButton((*moon)->GetIcon());
+        ib->GetSize(msize);
+        ib->onClick.connect(sigc::bind(sigc::mem_fun(this, &SystemInfoView::OnBodySelected), *moon));
+        Add(ib, xpos + 0.5*size[0] - 0.5*msize[0], moon_ypos);
+        moon_ypos -= msize[1];
+      }
+      xpos += size[0];
+  }
+
+  char buf[512];
+  snprintf(buf, sizeof(buf), "Stable system with %d major bodies.", 1+s->rootBody->children.size());
+  m_infoText = new Gui::Label(buf);
+  m_infoText->SetColor(1, 1, 0);
+  Add(m_infoText, 50, 200);
+
+  ShowAll();
+}
+
+void SystemInfoView::Draw3D(void) {
+  GenericSystemView::Draw3D();
+}
+
+void SystemInfoView::Update(void) {
+
+}
+
diff --git a/src/system_info_view.h b/src/system_info_view.h
new file mode 100644
index 0000000..141c96e
--- /dev/null
+++ b/src/system_info_view.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "libs.h"
+#include "gui.h"
+#include "view.h"
+#include "star_system.h"
+#include "generic_system_view.h"
+
+class SystemInfoView : public GenericSystemView {
+public:
+  SystemInfoView(void);
+  virtual void Update(void);
+  virtual void Draw3D(void);
+private:
+  void SystemChanged(StarSystem* s);
+  void OnBodySelected(StarSystem::SBody* b);
+  StarSystem::SBody* m_bodySelected;
+  Gui::Label* m_infoText;
+};
+
diff --git a/src/system_view.cpp b/src/system_view.cpp
new file mode 100644
index 0000000..5ebf558
--- /dev/null
+++ b/src/system_view.cpp
@@ -0,0 +1,209 @@
+#include "system_view.h"
+#include "l3d.h"
+#include "sector_view.h"
+#include "star_system.h"
+
+SystemView::SystemView(void): View() {
+  m_system = 0;
+  SetTransparency(true);
+
+  m_timePoint = new Gui::Label("");
+  m_timePoint->SetColor(.7, .7, .7);
+  Add(m_timePoint, 24, 5);
+
+  m_zoomInButton = new Gui::ImageButton("icons/zoom_in_f7.png");
+  m_zoomInButton->SetShortcut(SDLK_F7, KMOD_NONE);
+  m_rightButtonBar->Add(m_zoomInButton, 34, 2);
+
+  m_zoomOutButton = new Gui::ImageButton("icons/zoom_out_f8.png");
+  m_zoomOutButton->SetShortcut(SDLK_F8, KMOD_NONE);
+  m_rightButtonBar->Add(m_zoomOutButton, 66, 2);
+
+  Gui::ImageButton* b = new Gui::ImageButton("icons/sysview_accel_r3.png",
+                                             "icons/sysview_accel_r3_on.png");
+  b->onPress.connect(sigc::bind(sigc::mem_fun(this, &SystemView::OnClickAccel), -10000000.0));
+  b->onRelease.connect(sigc::bind(sigc::mem_fun(this, &SystemView::OnClickAccel), 0.0));
+  m_rightRegion2->Add(b, 0, 0);
+
+  b = new Gui::ImageButton("icons/sysview_accel_r2.png",
+                           "icons/sysview_accel_r2_on.png");
+  b->onPress.connect(sigc::bind(sigc::mem_fun(this, &SystemView::OnClickAccel), -1000000.0));
+  b->onRelease.connect(sigc::bind(sigc::mem_fun(this, &SystemView::OnClickAccel), 0.0));
+  m_rightRegion2->Add(b, 26, 0);
+
+  b = new Gui::ImageButton("icons/sysview_accel_r1.png",
+                           "icons/sysview_accel_r1_on.png");
+  b->onPress.connect(sigc::bind(sigc::mem_fun(this, &SystemView::OnClickAccel), -100000.0));
+  b->onRelease.connect(sigc::bind(sigc::mem_fun(this, &SystemView::OnClickAccel), 0.0));
+  m_rightRegion2->Add(b, 45, 0);
+
+  b = new Gui::ImageButton("icons/sysview_accel_f1.png",
+                           "icons/sysview_accel_f1_on.png");
+  b->onPress.connect(sigc::bind(sigc::mem_fun(this, &SystemView::OnClickAccel), 100000.0));
+  b->onRelease.connect(sigc::bind(sigc::mem_fun(this, &SystemView::OnClickAccel), 0.0));
+  m_rightRegion2->Add(b, 64, 0);
+
+  b = new Gui::ImageButton("icons/sysview_accel_f2.png",
+                           "icons/sysview_accel_f2_on.png");
+  b->onPress.connect(sigc::bind(sigc::mem_fun(this, &SystemView::OnClickAccel), 1000000.0));
+  b->onRelease.connect(sigc::bind(sigc::mem_fun(this, &SystemView::OnClickAccel), 0.0));
+  m_rightRegion2->Add(b, 83, 0);
+
+  b= new Gui::ImageButton("icons/sysview_accel_f3.png",
+                          "icons/sysview_accel_f3_on.png");
+  b->onPress.connect(sigc::bind(sigc::mem_fun(this, &SystemView::OnClickAccel), 10000000.0));
+  b->onRelease.connect(sigc::bind(sigc::mem_fun(this, &SystemView::OnClickAccel), 0.0));
+  m_rightRegion2->Add(b, 102, 0);
+
+  ResetViewpoint();
+}
+
+SystemView::~SystemView(void) {
+
+}
+
+void SystemView::OnClickAccel(float step) {
+  m_timeStep = step;
+}
+
+void SystemView::ResetViewpoint(void) {
+  m_selectedObject = 0;
+  m_rot_x = m_rot_z = 0;
+  m_zoom = 1.0f/AU;
+  m_timeStep = 1.0f;
+  m_time = L3D::GetGameTime();
+}
+
+void SystemView::PutOrbit(StarSystem::SBody* b) {
+  glColor3f(0, 1, 0);
+  glBegin(GL_LINE_LOOP);
+  double inc = b->orbit.period/100.0;
+  for(double t = 0.0; t < b->orbit.period; t += inc) {
+    vector3d pos = b->orbit.CartesianPosAtTime(t);
+    pos = pos * m_zoom;
+    glVertex3dv(&pos[0]);
+  }
+  glEnd();
+}
+
+void SystemView::OnClickObject(StarSystem::SBody* b, const Gui::MouseButtonEvent* ev) {
+  m_selectedObject = b;
+}
+
+void SystemView::PutLabel(StarSystem::SBody* b) {
+  GLdouble modelMatrix[16];
+  GLdouble projMatrix[16];
+  GLint viewport[4];
+
+  glGetDoublev (GL_MODELVIEW_MATRIX, modelMatrix);
+  glGetDoublev (GL_PROJECTION_MATRIX, projMatrix);
+  glGetIntegerv(GL_VIEWPORT, viewport);
+
+  Gui::Screen::EnterOrtho();
+
+  vector3d pos;
+  if(Gui::Screen::Project(0, 0, 0, modelMatrix, projMatrix, viewport, &pos[0], &pos[1], &pos[2])) {
+    /* libsigc++ is a lovely thing! */
+    Gui::Screen::PutClickableLabel(b->name, pos.x, pos.y,
+                  sigc::bind<0>(sigc::mem_fun(this, &SystemView::OnClickObject), b));
+  }
+
+  Gui::Screen::LeaveOrtho();
+  glDisable(GL_LIGHTING);
+}
+
+/* Yeah, I'm having trouble with a suitable name. :/ idc.*/
+#define ROUGH_SIZE_OF_THING 10.0
+
+void SystemView::PutBody(StarSystem::SBody* b) {
+  glPointSize(5);
+  glColor3f(1,1,1);
+  glBegin(GL_POINTS);
+  glVertex3f(0,0,0);
+  glEnd();
+
+  PutLabel(b);
+
+  if(b->children.size())
+    for(std::vector<StarSystem::SBody*>::iterator kid = b->children.begin(); 
+          kid != b->children.end(); ++kid) {
+      if((*kid)->orbit.semiMajorAxis * m_zoom < ROUGH_SIZE_OF_THING)
+        PutOrbit(*kid);
+
+      glPushMatrix();
+      {
+        /* Not using current time yet. */
+        vector3d pos = (*kid)->orbit.CartesianPosAtTime(m_time);
+        pos = pos * m_zoom;
+        glTranslatef(pos.x, pos.y, pos.z);
+        PutBody(*kid);
+      }
+      glPopMatrix();
+    }
+}
+
+static const GLfloat fogDensity = 0.1;
+static const GLfloat fogColor[4] = { 0, 0, 0, 1.0 };
+
+void SystemView::ViewingTransformTo(StarSystem::SBody* b) {
+  if(b->parent) {
+    ViewingTransformTo(b->parent);
+    vector3d pos = b->orbit.CartesianPosAtTime(m_time);
+    pos = pos * m_zoom;
+    glTranslatef(-pos.x, -pos.y, -pos.z);
+  }
+}
+
+void SystemView::Draw3D(void) {
+  glMatrixMode(GL_PROJECTION);
+  glLoadIdentity();
+  gluPerspective(50, L3D::GetScrAspect(), 1.0, 1000.0);
+  glMatrixMode(GL_MODELVIEW);
+  glLoadIdentity();
+
+  int sector_x, sector_y, system_idx;
+  L3D::sector_view->GetSelectedSystem(&sector_x, &sector_y, &system_idx);
+  if(m_system) {
+    if(!m_system->IsSystem(sector_x, sector_y, system_idx)) {
+      delete m_system;
+      m_system = 0;
+      ResetViewpoint();
+    }
+  }
+  m_time += m_timeStep*L3D::GetFrameTime();
+  std::string t = "Time point: "+date_format(m_time);
+  m_timePoint->SetText(t);
+
+  if(!m_system) m_system = new StarSystem(sector_x, sector_y, system_idx);
+
+  glDisable(GL_LIGHTING);
+  glEnable(GL_FOG);
+  glFogi(GL_FOG_MODE, GL_EXP2);
+  glFogfv(GL_FOG_COLOR, fogColor);
+  glFogf(GL_FOG_DENSITY, fogDensity);
+  glHint(GL_FOG_HINT, GL_NICEST);
+
+  glTranslatef(0, 0, -ROUGH_SIZE_OF_THING);
+  glRotatef(m_rot_x, 1, 0, 0);
+  glRotatef(m_rot_z, 0, 0, 1);
+
+  if(m_selectedObject) ViewingTransformTo(m_selectedObject);
+  if(m_system->rootBody) PutBody(m_system->rootBody);
+
+  glEnable(GL_LIGHTING);
+  glDisable(GL_FOG);
+}
+
+void SystemView::Update(void) {
+  if(L3D::KeyState(SDLK_EQUALS) ||
+     m_zoomInButton->IsPressed()) m_zoom *= 1.01;
+  if(L3D::KeyState(SDLK_MINUS) ||
+     m_zoomOutButton->IsPressed()) m_zoom *= 0.99;
+  if(L3D::MouseButtonState(3)) {
+    int motion[2];
+    L3D::GetMouseMotion(motion);
+    m_rot_x += motion[1]/4.0;
+    m_rot_z += motion[0]/4.0;
+  }
+}
+
diff --git a/src/system_view.h b/src/system_view.h
new file mode 100644
index 0000000..25d60b6
--- /dev/null
+++ b/src/system_view.h
@@ -0,0 +1,33 @@
+#pragma once
+#include "libs.h"
+#include "gui.h"
+#include "view.h"
+#include "star_system.h"
+
+class SystemView: public View {
+public:
+  SystemView(void);
+  virtual ~SystemView(void);
+  virtual void Update(void);
+  virtual void Draw3D(void);
+
+private:
+  void PutOrbit(StarSystem::SBody* b);
+  void PutBody(StarSystem::SBody* b);
+  void PutLabel(StarSystem::SBody* b);
+  void ViewingTransformTo(StarSystem::SBody* b);
+  void OnClickObject(StarSystem::SBody* b, const Gui::MouseButtonEvent* ev);
+  void OnClickAccel(float step);
+  void ResetViewpoint(void);
+
+  StarSystem* m_system;
+  StarSystem::SBody* m_selectedObject;
+  float m_rot_x, m_rot_z;
+  float m_zoom;
+  double m_time;
+  double m_timeStep;
+  Gui::ImageButton* m_zoomInButton;
+  Gui::ImageButton* m_zoomOutButton;
+  Gui::Label* m_timePoint;
+};
+
diff --git a/src/vector3.h b/src/vector3.h
new file mode 100644
index 0000000..2a1d76f
--- /dev/null
+++ b/src/vector3.h
@@ -0,0 +1,97 @@
+#pragma once
+#include <math.h>
+
+template <typename T>
+class vector3 {
+public:
+  T x,y,z;
+
+  vector3(void)                                                 { }
+  vector3(T val): x(val), y(val), z(val)                        { }
+  vector3(T _x, T _y, T _z): x(_x), y(_y), z(_z)                { }
+  vector3(const T vals[3]): x(vals[0]), y(vals[1]), z(vals[2])  { }
+
+  T& operator[]           (const int i) { return ((T*)this)[i]; }
+  vector3 operator+       (const vector3 a) const { return vector3 (a.x+x, a.y+y, a.z+z); }
+  vector3& operator+=     (const vector3 a) { x+=a.x; y+=a.y; z+=a.z; return *this; }
+  vector3& operator-=     (const vector3 a) { x-=a.x; y-=a.y; z-=a.z; return *this; }
+  vector3& operator*=     (const T a) { x*=a; y*=a; z*=a; return *this; }
+  vector3 operator-       (const vector3 a) const { return vector3 (x-a.x, y-a.y, z-a.z); }
+  vector3 operator-(void) const { return vector3 (-x, -y, -z); }
+  bool operator==(const vector3 a) const { return ((a.x==x)&&(a.y==y)&&(a.z==z)); }
+  bool operator!=(const vector3 a) const { return ((a.x!=x)||(a.y!=y)||(a.z!=z)); }
+  friend vector3 operator*(const vector3 a, const vector3 b) { 
+    return vector3 (a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x); }
+  friend vector3 operator*(const vector3 a, const T scalar) { 
+    return vector3 (a.x*scalar, a.y*scalar, a.z*scalar); }
+  friend vector3 operator*(const T scalar, const vector3 a) { return a*scalar; }
+
+  static vector3 Cross(const vector3 a, const vector3 b) { return a*b; }
+  static T Dot(const vector3 a, const vector3 b) { return a.x*b.x + a.y*b.y + a.z*b.z; }
+
+  T Length(void) const {
+    return sqrt (x*x + y*y + z*z);
+  }
+
+  void Normalize(void) {
+    T l = 1.0f / sqrtf(x*x + y*y + z*z);
+    x *= l;	y *= l;	z *= l;
+  }
+
+  static vector3 Normalize(const vector3 v) {
+    vector3 r;
+    T l = 1.0f / sqrtf(v.x*v.x + v.y*v.y + v.z*v.z);
+    r.x = v.x * l;
+    r.y = v.y * l;
+    r.z = v.z * l;
+    return r;
+  }
+
+  /* Rotate this vector about point o, in axis defined by v. */
+  void ArbRotateAroundPoint(const vector3& o, const vector3& __v, T ang) {
+    vector3 t;
+    T a = o.x;
+    T b = o.y;
+    T c = o.z;
+    T u = __v.x;
+    T v = __v.y;
+    T w = __v.z;
+    T cos_a = cos(ang);
+    T sin_a = sin(ang);
+    T inv_poo = 1.0f/(u*u+v*v+w*w);
+    t.x = a*(v*v+w*w)+u*(-b*v-c*w+u*x+v*y+w*z)+(-a*(v*v+w*w)+u*(b*v+c*w-v*y-w*z)+(v*v+w*w)*x)*cos_a+
+        sqrtf (u*u+v*v+w*w)*(-c*v+b*w-w*y+v*z)*sin_a;
+    t.x *= inv_poo;
+    t.y = b*(u*u+w*w)+v*(-a*u-c*w+u*x+v*y+w*z)+(-b*(u*u+w*w)+v*(a*u+c*w-u*x-w*z)+(u*u+w*w)*y)*cos_a+
+        sqrtf (u*u+v*v+w*w)*(-c*u-a*w+w*x-u*z)*sin_a;
+    t.y *= inv_poo;
+    t.z = c*(u*u+v*v)+w*(-a*u+b*v+u*x+v*y+w*z)+(-c*(u*u+v*v)+w*(a*u+b*v-u*x-v*y)+(u*u+v*v)*z)*cos_a+
+        sqrtf (u*u+v*v+w*w)*(-b*u+a*v-v*x+u*y)*sin_a;
+    t.z *= inv_poo;
+    *this = t;
+  }
+  /* Rotate this vector about origin, in axis defined by v. */
+  void ArbRotate (const vector3 &__v, T ang) {
+    vector3 t;
+    T u = __v.x;
+    T v = __v.y;
+    T w = __v.z;
+    T cos_a = cos (ang);
+    T sin_a = sin (ang);
+    T inv_poo = 1.0f/(u*u+v*v+w*w);
+    t.x = u*(u*x+v*y+w*z)+(u*(-v*y-w*z)+(v*v+w*w)*x)*cos_a+
+        sqrtf (u*u+v*v+w*w)*(-w*y+v*z)*sin_a;
+    t.x *= inv_poo;
+    t.y = v*(u*x+v*y+w*z)+(v*(-u*x-w*z)+(u*u+w*w)*y)*cos_a+
+        sqrtf (u*u+v*v+w*w)*(w*x-u*z)*sin_a;
+    t.y *= inv_poo;
+    t.z = w*(u*x+v*y+w*z)+(w*(-u*x-v*y)+(u*u+v*v)*z)*cos_a+
+        sqrtf (u*u+v*v+w*w)*(-v*x+u*y)*sin_a;
+    t.z *= inv_poo;
+    *this = t;
+  }
+};
+
+typedef vector3<float> vector3f;
+typedef vector3<double> vector3d;
+
diff --git a/src/view.h b/src/view.h
new file mode 100644
index 0000000..281aa6c
--- /dev/null
+++ b/src/view.h
@@ -0,0 +1,42 @@
+#pragma once
+#include "libs.h"
+#include "gui.h"
+
+/*
+ * For whatever draws into the main area of the screen.
+ * Eg:
+ *  Game 3D view.
+ *  System map.
+ *  Sector map.
+ */
+ class View : public Gui::Fixed {
+ public:
+  View(void) : Gui::Fixed(0, 64, 640, 416) {
+    m_rightButtonBar = new Gui::Fixed(512, 0, 128, 26);
+    m_rightButtonBar->SetBgColor(.65, .65, .65);
+
+    m_rightRegion2 = new Gui::Fixed(517, 26, 122, 17);
+    m_rightRegion2->SetTransparency(true);
+  }
+  virtual ~View(void) { delete m_rightButtonBar; delete m_rightRegion2; }
+  virtual void ShowAll(void) {
+    m_rightButtonBar->ShowAll();
+    m_rightRegion2->ShowAll();
+    Gui::Fixed::ShowAll();
+  }
+  virtual void HideAll(void) {
+    m_rightButtonBar->HideAll();
+    m_rightRegion2->HideAll();
+    Gui::Fixed::HideAll();
+  }
+  /* Called before Gui::Draw will call widget ::Draw methods. */
+  virtual void Draw3D(void) = 0;
+  /* For checking key states, mouse stuff. */
+  virtual void Update(void) = 0;
+protected:
+  /* Each view can put some buttons in the bottom right of the cpanel. */
+  Gui::Fixed* m_rightButtonBar;
+  //Gui::Fixed* m_rightRegion1;
+  Gui::Fixed* m_rightRegion2;
+ };
+
diff --git a/src/world_view.cpp b/src/world_view.cpp
new file mode 100644
index 0000000..0d413a8
--- /dev/null
+++ b/src/world_view.cpp
@@ -0,0 +1,103 @@
+#include "world_view.h"
+#include "l3d.h"
+#include "frame.h"
+#include "player.h"
+#include "space.h"
+
+static const float lightCol[] = { 1, 1, .9, 0 };
+
+#define BG_STAR_MAX 2000
+
+WorldView::WorldView(void): View() {
+  SetTransparency(true);
+  
+  m_hyperspaceButton = new Gui::ImageButton("icons/hyperspace_f8.png");
+  m_hyperspaceButton->SetShortcut(SDLK_F8, KMOD_NONE);
+  m_hyperspaceButton->onClick.connect(sigc::mem_fun(this, &WorldView::OnClickHyperspace));
+  m_rightButtonBar->Add(m_hyperspaceButton, 66, 2);
+
+  m_bgstarsDlist = glGenLists(1);
+
+  glNewList(m_bgstarsDlist, GL_COMPILE);
+
+  glDisable(GL_LIGHTING);
+  glDisable(GL_DEPTH_TEST);
+  glPointSize(1.0);
+  glBegin(GL_POINTS);
+  for(int i = 0; i < BG_STAR_MAX; i++) {
+    float col = 0.2+L3D::rng(0.8);
+    glColor3f(col, col, col);
+    glVertex3f(1000-L3D::rng(2000.0), 1000-L3D::rng(2000.0), 1000-L3D::rng(2000.0));
+  }
+
+  glEnd();
+  glEnable(GL_DEPTH_TEST);
+  glEnable(GL_LIGHTING);
+
+  glEndList();
+}
+
+void WorldView::OnClickHyperspace(void) {
+  StarSystem* s = L3D::GetSelectedSystem();
+  if(s /* && isn's current system. */) {
+    printf("Traveling through hyperspace. WWEEEEEE!!\n");
+    L3D::HyperspaceTo(s);
+  }
+}
+
+void WorldView::Draw3D(void) {
+  glMatrixMode(GL_PROJECTION);
+  glLoadIdentity();
+  /* Wth did I give these functions large names for.. :/ */
+  glFrustum(-L3D::GetScrWidth()*.5, L3D::GetScrWidth()*.5,
+            -L3D::GetScrHeight()*.5, L3D::GetScrHeight()*.5,
+             L3D::GetScrWidth()*.5, 100000);
+  glDepthRange(-10, -100000);
+  glMatrixMode(GL_MODELVIEW);
+
+  /* Make temporary camera frame at player. */
+  Frame* cam_frame = new Frame(L3D::player->GetFrame(), "", Frame::TEMP_VIEWING);
+
+  if(L3D::GetCamType() == L3D::CAM_FRONT) {
+    cam_frame->SetPosition(L3D::player->GetPosition());
+  } else if(L3D::GetCamType() == L3D::CAM_REAR) {
+    glRotatef(180.0f, 0, 1, 0);
+    cam_frame->SetPosition(L3D::player->GetPosition());
+  } else { /* CAM_EXTERNAL */
+    cam_frame->SetPosition(L3D::player->GetPosition());
+    L3D::player->ApplyExternalViewRotation();
+  }
+    
+  L3D::player->ViewingRotation();
+
+  glGetDoublev(GL_MODELVIEW_MATRIX, &viewingRotation[0]);
+
+  glCallList(m_bgstarsDlist);
+  /* Position light at sol. */
+  vector3d lpos = Frame::GetFramePosRelativeToOther(Space::GetRootFrame(), cam_frame);
+  float lightPos[4];
+  lightPos[0] = lpos.x;
+  lightPos[1] = lpos.y;
+  lightPos[2] = lpos.z;
+  lightPos[3] = 0;
+  glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
+  glLightfv(GL_LIGHT0, GL_AMBIENT_AND_DIFFUSE, lightCol);
+  glLightfv(GL_LIGHT0, GL_SPECULAR, lightCol);
+
+  Space::Render(cam_frame);
+  L3D::player->DrawHUD(cam_frame);
+  
+  L3D::player->GetFrame()->RemoveChild(cam_frame);
+  delete cam_frame;
+}
+
+void WorldView::Update(void) {
+  if(L3D::GetSelectedSystem() /* && isn't current system */) {
+    m_hyperspaceButton->Show();
+  } else {
+    m_hyperspaceButton->Hide();
+  }
+  /* Player control inputs. */
+  L3D::player->AITurn();
+}
+
diff --git a/src/world_view.h b/src/world_view.h
new file mode 100644
index 0000000..77d86c8
--- /dev/null
+++ b/src/world_view.h
@@ -0,0 +1,18 @@
+#pragma once
+#include "libs.h"
+#include "gui.h"
+#include "view.h"
+
+class WorldView: public View {
+public:
+  WorldView(void);
+  virtual void Update(void);
+  virtual void Draw3D(void);
+  matrix4x4d viewingRotation;
+
+private:
+  void OnClickHyperspace(void);
+  Gui::ImageButton* m_hyperspaceButton;
+  GLuint m_bgstarsDlist;
+};
+