From 5afece9c00f2438fe72bbfc7740c3ca94956e8e3 Mon Sep 17 00:00:00 2001
From: Allanis <allanis.saracraft.studios@gmail.com>
Date: Sun, 21 Jan 2018 14:00:00 +0000
Subject: [PATCH] [Fix] Position ships correctly when sat on ground so they
 don't sink in.

---
 src/Makefile.am              |  3 +-
 src/aabb.h                   |  8 ++++
 src/libs.h                   |  1 +
 src/main.cpp                 |  2 +-
 src/model_body.cpp           | 14 ++++--
 src/model_body.h             |  7 ++-
 src/model_coll_mesh_data.cpp | 11 ++++-
 src/model_coll_mesh_data.h   |  1 +
 src/player.cpp               |  4 +-
 src/player.h                 |  2 +-
 src/sbre/models.cpp          | 18 ++++++--
 src/sbre/sbre_int.h          |  2 +-
 src/ship.cpp                 | 20 ++++++---
 src/ship.h                   |  3 +-
 src/space_station.cpp        | 84 +++++++++++++++++++++---------------
 src/space_station.h          |  9 ++--
 src/space_station_view.cpp   |  2 +-
 17 files changed, 129 insertions(+), 62 deletions(-)
 create mode 100644 src/aabb.h

diff --git a/src/Makefile.am b/src/Makefile.am
index 20b0628..a663bec 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -9,7 +9,8 @@ include_HEADERS = body.h frame.h generic_system_view.h glfreetype.h gui_button.h
 		gui_radio_group.h gui_screen.h gui_toggle_button.h gui_widget.h libs.h matrix4x4.h mtrand.h l3d.h \
 		planet.h player.h dynamic_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 model_body.h gui_iselectable.h \
-		ship_type.h object.h info_view.h model_coll_mesh_data.h object_viewer_view.h fixed.h custom_starsystems.h gameconsts.h
+		ship_type.h object.h info_view.h model_coll_mesh_data.h object_viewer_view.h fixed.h custom_starsystems.h gameconsts.h \
+		aabb.h
 
 libgui_a_SOURCES = gui_button.cpp gui.cpp gui_fixed.cpp gui_screen.cpp gui_label.cpp gui_toggle_button.cpp gui_radio_button.cpp \
 									 gui_radio_group.cpp gui_image_button.cpp gui_image.cpp gui_image_radio_button.cpp gui_multi_state_image_button.cpp gui_widget.cpp \
diff --git a/src/aabb.h b/src/aabb.h
new file mode 100644
index 0000000..408564f
--- /dev/null
+++ b/src/aabb.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "vector3.h"
+
+struct Aabb {
+  vector3d max, min;
+};
+
diff --git a/src/libs.h b/src/libs.h
index 4de3565..8af5be3 100644
--- a/src/libs.h
+++ b/src/libs.h
@@ -17,6 +17,7 @@
 
 #include "fixed.h"
 #include "vector3.h"
+#include "aabb.h"
 #include "matrix4x4.h"
 #include "mtrand.h"
 
diff --git a/src/main.cpp b/src/main.cpp
index af21a43..3c8e43b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -259,7 +259,7 @@ void L3D::MainLoop(void) {
   infoView          = new InfoView();
 
   SetView(worldView);
-  player->SetDockedWith(station2);
+  player->SetDockedWith(station2, 0);
 
   Uint32 last_stats = SDL_GetTicks();
   int frame_stat = 0;
diff --git a/src/model_body.cpp b/src/model_body.cpp
index b4aa7fa..b11a273 100644
--- a/src/model_body.cpp
+++ b/src/model_body.cpp
@@ -8,7 +8,8 @@
 #include "model_coll_mesh_data.h"
 
 ModelBody::ModelBody(void): Body() {
-  triMeshLastMatrixIndex = 0;
+  m_triMeshLastMatrixIndex = 0;
+  m_collMeshSet = 0;
 }
 
 ModelBody::~ModelBody(void) {
@@ -36,6 +37,10 @@ void ModelBody::GeomsSetBody(dBodyID body) {
   }
 }
 
+void ModelBody::GetAabb(Aabb& aabb) {
+  aabb = m_collMeshSet->aabb;
+}
+
 void ModelBody::SetModel(int sbreModel) {
   assert(geoms.size() == 0);
   CollMeshSet* mset = GetModelCollMeshSet(sbreModel);
@@ -49,6 +54,7 @@ void ModelBody::SetModel(int sbreModel) {
     geomColl[i].flags = mset->meshInfo[i].flags;
     dGeomSetData(geoms[i], static_cast<Object*>(&geomColl[i]));
   }
+  m_collMeshSet = mset;
 }
 
 void ModelBody::SetPosition(vector3d p) {
@@ -134,14 +140,14 @@ void ModelBody::TriMeshUpdateLastPos(void) {
   /* ODE tri mesh likes to know our old position. */
   const dReal* r = dGeomGetRotation(geoms[0]);
   vector3d pos = GetPosition();
-  dReal* t = triMeshTrans + 16*triMeshLastMatrixIndex;
+  dReal* t = m_triMeshTrans + 16*m_triMeshLastMatrixIndex;
   t[ 0] = r[0]; t[1] = r[1]; t[ 2] = r[ 2]; t[ 3] = 0;
   t[ 4] = r[4]; t[5] = r[5]; t[ 6] = r[ 6]; t[ 7] = 0;
   t[ 8] = r[8]; t[9] = r[9]; t[10] = r[10]; t[11] = 0;
   t[12] = pos.x; t[13] = pos.y; t[14] = pos.z; t[15] = 1;
-  triMeshLastMatrixIndex = !triMeshLastMatrixIndex;
+  m_triMeshLastMatrixIndex = !m_triMeshLastMatrixIndex;
   for(unsigned int i = 0; i < geoms.size(); i++) {
-    dGeomTriMeshSetLastTransform(geoms[i], *(dMatrix4*)(triMeshTrans + 16*triMeshLastMatrixIndex));
+    dGeomTriMeshSetLastTransform(geoms[i], *(dMatrix4*)(m_triMeshTrans + 16*m_triMeshLastMatrixIndex));
   }
 }
 
diff --git a/src/model_body.h b/src/model_body.h
index d98ab2a..226f6c3 100644
--- a/src/model_body.h
+++ b/src/model_body.h
@@ -6,6 +6,7 @@
 #include "sbre/sbre.h"
 
 class ObjMesh;
+class CollMeshSet;
 
 class ModelBody: public Body {
 public:
@@ -23,6 +24,7 @@ public:
   /* To remove from simulation for a period. */
   virtual void Disable(void);
   virtual void Enable(void);
+  void GetAabb(Aabb& aabb);
 
   void TriMeshUpdateLastPos(void);
   void SetModel(int sbreModel);
@@ -38,8 +40,9 @@ public:
 protected:
   std::vector<Geom> geomColl;
 private:
+  CollMeshSet* m_collMeshSet;
   std::vector<dGeomID> geoms;
-  dReal triMeshTrans[32];
-  int triMeshLastMatrixIndex;
+  dReal m_triMeshTrans[32];
+  int m_triMeshLastMatrixIndex;
 };
 
diff --git a/src/model_coll_mesh_data.cpp b/src/model_coll_mesh_data.cpp
index d368d17..962618c 100644
--- a/src/model_coll_mesh_data.cpp
+++ b/src/model_coll_mesh_data.cpp
@@ -15,7 +15,7 @@ struct coltri_compare : public std::binary_function<coltri_t, coltri_t, bool> {
 };
 
 static ObjParams params = {
-  { 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
   { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
   { 0.f, 0.0f, 1.0f }, { 0.0f, 0.0f, 0.0f },
 
@@ -65,12 +65,21 @@ void CollMeshSet::GetMeshParts(void) {
 }
 
 CollMeshSet::CollMeshSet(int sbreModel) {
+  aabb.min = vector3d(FLT_MAX, FLT_MAX, FLT_MAX);
+  aabb.max = vector3d(-FLT_MIN, -FLT_MIN, -FLT_MIN);
   sbreCollMesh = (CollMesh*)calloc(1, sizeof(CollMesh));
   sbreGenCollMesh(sbreCollMesh, sbreModel, &params);
   /* TODO: Flip Z & X because sbre is in magicspace. */
   for(int i = 0; i < 3*sbreCollMesh->nv; i += 3) {
     sbreCollMesh->pVertex[i] = -sbreCollMesh->pVertex[i];
     sbreCollMesh->pVertex[i+2] = -sbreCollMesh->pVertex[i+2];
+    /* Make axis aligned bounding box. */
+    for(int a = 0; a < 3; a++) {
+      if(sbreCollMesh->pVertex[i+a] < aabb.min[a])
+        aabb.min[a] = sbreCollMesh->pVertex[i+a];
+      if(sbreCollMesh->pVertex[i+a] < aabb.max[a])
+        aabb.max[a] = sbreCollMesh->pVertex[i+a];
+    }
   }
 
   triIndices = new coltri_t[sbreCollMesh->ni/3];
diff --git a/src/model_coll_mesh_data.h b/src/model_coll_mesh_data.h
index 8cbca35..feade17 100644
--- a/src/model_coll_mesh_data.h
+++ b/src/model_coll_mesh_data.h
@@ -19,6 +19,7 @@ public:
   dTriMeshDataID* meshParts;
   meshinfo_t* meshInfo;
   int numMeshParts;
+  Aabb aabb;
 
   CollMeshSet(int sbreModel);
 private:
diff --git a/src/player.cpp b/src/player.cpp
index 61164f0..be405e5 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -30,8 +30,8 @@ void Player::Render(const Frame* camFrame) {
   }
 }
 
-void Player::SetDockedWith(SpaceStation* s) {
-  Ship::SetDockedWith(s);
+void Player::SetDockedWith(SpaceStation* s, int port) {
+  Ship::SetDockedWith(s, port);
   if(s) {
     L3D::SetView(L3D::spaceStationView);
   }
diff --git a/src/player.h b/src/player.h
index 7cb2081..52ebfc5 100644
--- a/src/player.h
+++ b/src/player.h
@@ -9,7 +9,7 @@ public:
   void PollControls(void);
   virtual void Render(const Frame* camFrame);
   void DrawHUD(const Frame* cam_frame);
-  virtual void SetDockedWith(SpaceStation*);
+  virtual void SetDockedWith(SpaceStation*, int port);
   vector3d GetExternalViewTranslation(void);
   void ApplyExternalViewRotation(matrix4x4d &m);
   void TimeStepUpdate(const float timeStep);
diff --git a/src/sbre/models.cpp b/src/sbre/models.cpp
index 15f0825..06145aa 100644
--- a/src/sbre/models.cpp
+++ b/src/sbre/models.cpp
@@ -776,7 +776,7 @@ static uint16 station1data[] = {
 
   PTYPE_TUBE | RFLAG_XREF, 0, 38, 34, 35, 1, 11500, 10000,
 
-  PTYPE_SETCFLAG, 1,
+  PTYPE_SETCFLAG, 0x10,
   PTYPE_QUADFLAT | RFLAG_INVISIBLE, 39, 38, 37, 36,
   PTYPE_SETCFLAG, 0,
   //  PTYPE_QUADFLAT | RFLAG_XREF,
@@ -1287,6 +1287,11 @@ static PlainVertex starport1vtx1[] = {
   { VTYPE_PLAIN, { 0, 0, 2 } },
   { VTYPE_PLAIN, { 0.5, 0, 0 } },
   { VTYPE_PLAIN, { -0.5, 0, 0 } },
+
+  { VTYPE_PLAIN, { 0, 0, 2 } },
+  { VTYPE_PLAIN, { 0.5, 0, 2 } },
+  { VTYPE_PLAIN, { -0.5, 0, 2 } },
+  { VTYPE_PLAIN, { -0.1, .01, 2-0.1 } },
 };
 
 #if 0
@@ -1310,22 +1315,27 @@ uint16 PFUNC_COMPSMOOTH
 
 static uint16 starport1data[] = {
   PTYPE_MATFIXED, 30, 30, 30, 0, 0, 0, 0, 0, 0, 0,
-  PTYPE_SETCFLAG, 1,
+  PTYPE_SETCFLAG, 0x10,
   PTYPE_COMPFLAT, 0x8000, 20, 6, 1, 11, 1,
     COMP_HERMITE, 12, 1, 9, 10,
     COMP_HERMITE, 11, 1, 10, 9,
     COMP_END,
-  //PTYPE_CYLINDER, 0x8000, 8, 6, 7, 0, 50,
+  PTYPE_SETCFLAG, 0x11,
+  PTYPE_COMPFLAT, 0x8000, 20, 13, 1, 14, 1,
+    COMP_HERMITE, 15, 1, 9, 10,
+    COMP_HERMITE, 14, 1, 10, 9,
+    COMP_END,
   PTYPE_SETCFLAG, 0,
   PTYPE_MATFIXED, 100, 100, 100, 0, 0, 0, 0, 0, 0, 0,
   PTYPE_ZBIAS, 6, 1, 0,
   PTYPE_TEXT, 0, 10, 8, 1, 0, 0, 0, 20,
+  PTYPE_TEXT, 0, 11, 16, 1, 0, 0, 0, 20,
 	PTYPE_ZBIAS, 0x8000, 0, 0,
 	PTYPE_SUBOBJECT, 0x8000, 100, 0, 1, 2, 100,
 	PTYPE_SUBOBJECT, 0x8000, 100, 3, 1, 2, 100,
   PTYPE_END,
 };
 
-Model starport1model = { 100.0f, 55.0f, 13, starport1vtx1, 13, 0, dummyvtx2, 1,
+Model starport1model = { 100.0f, 55.0f, 17, starport1vtx1, 17, 0, dummyvtx2, 1,
   { { 0, starport1data, 0, 0, 0 } } };
 
diff --git a/src/sbre/sbre_int.h b/src/sbre/sbre_int.h
index f4731df..b76c728 100644
--- a/src/sbre/sbre_int.h
+++ b/src/sbre/sbre_int.h
@@ -175,7 +175,7 @@ const int pCompSize[] = { 1, 3, 5, 3, 3, 2 };
 const char pModelString[][256] = {
   "IZRILGOOD", "Rawr", "", "", "", "", "", "", "", "",
   /* 10 - Landing pad messages. */
-  "1"
+  "1", "2", "3","4",
 };
 
 void RenderThrusters(RState* pState, int numThrusters, Thruster* pThrusters);
diff --git a/src/ship.cpp b/src/ship.cpp
index 428e928..8e89c02 100644
--- a/src/ship.cpp
+++ b/src/ship.cpp
@@ -29,6 +29,7 @@ Ship::Ship(ShipType::Type shipType) : DynamicBody() {
   m_wheelTransition   = 0;
   m_wheelState        = 0;
   m_dockedWith        = 0;
+  m_dockedWithPort    = 0;
   m_dockingTimer      = 0;
   m_navTarget         = 0;
   m_combatTarget      = 0;
@@ -121,14 +122,17 @@ void Ship::Blastoff(void) {
   m_launchLockTimeout = 1.0; /* One second of applying thrusters. */
 
   Enable();
-  const double planetRadius = GetFrame()->m_astroBody->GetRadius();
+  const double planetRadius = 0.1 + GetFrame()->m_astroBody->GetRadius();
   vector3d up = vector3d::Normalize(GetPosition());
   dBodySetLinearVel(m_body, 0, 0, 0);
   dBodySetAngularVel(m_body, 0, 0, 0);
   dBodySetForce(m_body, 0, 0, 0);
   dBodySetTorque(m_body, 0, 0, 0);
+
+  Aabb aabb;
+  GetAabb(aabb);
   /* TODO: We need to be able to get sbre aabb. */
-  SetPosition(up*planetRadius+10.0*up);
+  SetPosition(up*planetRadius - aabb.min.y*up);
   SetThrusterState(ShipType::THRUSTER_TOP, 1.0f);
 }
 
@@ -153,8 +157,11 @@ void Ship::TestLanded(void) {
       /* Check player is sortof sensibly oriented for landing. */
       const double dot = vector3d::Dot(vector3d::Normalize(vector3d(invRot[1], invRot[5], invRot[9])), up);
       if(dot > 0.99) {
+        Aabb aabb;
+        GetAabb(aabb);
+
         /* Position at zero altitude. */
-        SetPosition(up * planetRadius);
+        SetPosition(up * (planetRadius - aabb.min.y));
 
         vector3d forward = rot * vector3d(0,0,1);
         vector3d other = vector3d::Normalize(vector3d::Cross(up, forward));
@@ -195,7 +202,7 @@ void Ship::TimeStepUpdate(const float timeStep) {
    * and can't be positioned. Instead we do it every freaking
    * update which is stupid.
    */
-  if(m_dockedWith) m_dockedWith->OrientDockedShip(this);
+  if(m_dockedWith) m_dockedWith->OrientDockedShip(this, m_dockedWithPort);
 
   const ShipType& stype = GetShipType();
   for(int i = 0; i < ShipType::THRUSTER_MAX; i++) {
@@ -254,14 +261,15 @@ const ShipType& Ship::GetShipType(void) {
   return ShipType::types[m_shipType];
 }
 
-void Ship::SetDockedWith(SpaceStation* s) {
+void Ship::SetDockedWith(SpaceStation* s, int port) {
   if(m_dockedWith && !s) {
-    m_dockedWith->OrientLaunchingShip(this);
+    m_dockedWith->OrientLaunchingShip(this, port);
     Enable();
 
     m_dockedWith = 0;
   } else {
     m_dockedWith = s;
+    m_dockedWithPort = port;
     m_dockingTimer = 0.0f;
     if(s->IsGroundStation()) m_flightState = LANDED;
     SetVelocity(vector3d(0, 0, 0));
diff --git a/src/ship.h b/src/ship.h
index ec77b00..606c9e0 100644
--- a/src/ship.h
+++ b/src/ship.h
@@ -18,7 +18,7 @@ class Ship : public DynamicBody {
 public:
   Ship(ShipType::Type shipType);
   virtual Object::Type GetType(void) { return Object::SHIP; }
-  virtual void SetDockedWith(SpaceStation*);
+  virtual void SetDockedWith(SpaceStation*, int port);
   SpaceStation* GetDockedWith(void) { return m_dockedWith; }
   void SetNavTarget(Body* const target);
   Body* GetNavTarget(void) const { return m_navTarget; }
@@ -55,6 +55,7 @@ protected:
   void RenderLaserfire(void);
   
   SpaceStation* m_dockedWith;
+  int m_dockedWithPort;
   enum ShipType::Type m_shipType;
   Uint32 m_gunState[ShipType::GUNMOUNT_MAX];
 private:
diff --git a/src/space_station.cpp b/src/space_station.cpp
index acbbc6d..925edfe 100644
--- a/src/space_station.cpp
+++ b/src/space_station.cpp
@@ -30,10 +30,16 @@ static ObjParams params = {
 };
 
 void SpaceStation::GetDockingSurface(CollMeshSet* mset, int midx) {
-  meshinfo_t* minfo = &mset->meshInfo[midx];
-  assert(minfo->flags == 0x1);
+  meshinfo_t* const minfo = &mset->meshInfo[midx];
+  dockingport_t* const dport = &port[minfo->flags & 0xf];
+  m_numPorts++;
+
+  assert(m_numPorts <= MAX_DOCKING_PORTS);
+  assert((minfo->flags & 0xf) < MAX_DOCKING_PORTS);
+  assert(minfo->flags & 0x10);
   assert(minfo->numTris);
-  port.center = vector3d(0.0);
+  
+  dport->center = vector3d(0.0);
   const int t = minfo->triStart;
   float* const vts = mset->sbreCollMesh->pVertex;
   for(int pos = 0; pos < minfo->numTris; pos++) {
@@ -45,33 +51,34 @@ void SpaceStation::GetDockingSurface(CollMeshSet* mset, int midx) {
      * docking port).
      */
     if(pos == 0) {
-      port.normal = vector3d::Cross(v2-v1, v2-v3);
-      port.normal.Normalize();
-      port.horiz = vector3d::Normalize(v1-v2);
+      dport->normal = vector3d::Cross(v2-v1, v2-v3);
+      dport->normal.Normalize();
+      dport->horiz = vector3d::Normalize(v1-v2);
     }
-    port.center += v1+v2+v3;
+    dport->center += v1+v2+v3;
   }
-  port.center *= 1.0/(3.0*minfo->numTris);
+  dport->center *= 1.0/(3.0*minfo->numTris);
   /*printf("Docking port center %f,%f,%f, normal %f,%f,%f, horiz %f,%f,%f\n",
-          port.center.x,
-          port.center.y,
-          port.center.z,
-          port.normal.x,
-          port.normal.y,
-          port.normal.z,
-          port.horiz.x,
-          port.horiz.y,
-          port.horiz.z); */
+          dport->center.x,
+          dport->center.y,
+          dport->center.z,
+          dport->normal.x,
+          dport->normal.y,
+          dport->normal.z,
+          dport->horiz.x,
+          dport->horiz.y,
+          dport->horiz.z); */
 }
 
 SpaceStation::SpaceStation(TYPE type) : ModelBody() {
   const Uint32 sbreModel = stationTypes[type].sbreModel;
   m_type = type;
+  m_numPorts = 0;
   SetModel(sbreModel);
 
   CollMeshSet* mset = GetModelCollMeshSet(sbreModel);
   for(unsigned int i = 0; i < geomColl.size(); i++) {
-    if(geomColl[i].flags == 0x1) {
+    if(geomColl[i].flags & 0x10) {
       /* Docking surface. */
       GetDockingSurface(mset, i);
       //mset->meshInfo[i];
@@ -94,20 +101,28 @@ bool SpaceStation::IsGroundStation(void) const {
   return (stationTypes[m_type].dockMethod == SpaceStationType::SURFACE);
 }
 
-void SpaceStation::OrientDockedShip(Ship* ship) const {
+void SpaceStation::OrientDockedShip(Ship* ship, int port) const {
+  const dockingport_t* dport = &this->port[port];
   const int dockMethod = stationTypes[m_type].dockMethod;
   if(dockMethod == SpaceStationType::SURFACE) {
     matrix4x4d stationRot;
     GetRotMatrix(stationRot);
-    vector3d port_y = vector3d::Cross(-port.horiz, port.normal);
-    matrix4x4d rot = stationRot * matrix4x4d::MakeRotMatrix(-port.horiz, -port.normal, -port_y);
-    vector3d pos = GetPosition() + stationRot*port.center;
-    ship->SetPosition(pos - stationRot*port.normal);
+    vector3d port_y = vector3d::Cross(-dport->horiz, dport->normal);
+    matrix4x4d rot = stationRot * matrix4x4d::MakeRotMatrix(-dport->horiz, -dport->normal, -port_y);
+    vector3d pos = GetPosition() + stationRot*dport->center;
+
+    /* Position with wheels perfectly on ground. :D */
+    Aabb aabb;
+    ship->GetAabb(aabb);
+    pos += stationRot*vector3d(0, -aabb.min.y, 0);
+
+    ship->SetPosition(pos);
     ship->SetRotMatrix(rot);
   }
 }
 
-void SpaceStation::OrientLaunchingShip(Ship* ship) const {
+void SpaceStation::OrientLaunchingShip(Ship* ship, int port) const {
+  const dockingport_t* dport = &this->port[port];
   const int dockMethod = stationTypes[m_type].dockMethod;
   if(dockMethod == SpaceStationType::ORBITAL) {
     /*
@@ -117,9 +132,9 @@ void SpaceStation::OrientLaunchingShip(Ship* ship) const {
      */
     matrix4x4d stationRot;
     GetRotMatrix(stationRot);
-    vector3d port_y = vector3d::Cross(-port.horiz, port.normal);
-    matrix4x4d rot = stationRot * matrix4x4d::MakeRotMatrix(port.horiz, port_y, port.normal);
-    vector3d pos = GetPosition() + stationRot*port.center;
+    vector3d port_y = vector3d::Cross(-dport->horiz, dport->normal);
+    matrix4x4d rot = stationRot * matrix4x4d::MakeRotMatrix(dport->horiz, port_y, dport->normal);
+    vector3d pos = GetPosition() + stationRot*dport->center;
     ship->SetPosition(pos);
     ship->SetRotMatrix(rot);
     ship->SetVelocity(vector3d(0,0,0));
@@ -135,10 +150,10 @@ void SpaceStation::OrientLaunchingShip(Ship* ship) const {
 #if 0
     matrix4x4d stationRot;
     GetRotMatrix(stationRot);
-    vector3d port_y = vector3d::Cross(-port.horiz, port.normal);
-    matrix4x4d rot = stationRot * matrix4x4d::MakeRotMatrix(-port.horiz, -port.normal, -port_y);
-    vector3d pos = GetPosition() + stationRot*port.center;
-    ship->SetPosition(pos - stationRot*(10*port.normal));
+    vector3d port_y = vector3d::Cross(-dport->horiz, dport->normal);
+    matrix4x4d rot = stationRot * matrix4x4d::MakeRotMatrix(-dport->horiz, -dport->normal, -port_y);
+    vector3d pos = GetPosition() + stationRot*dport->center;
+    ship->SetPosition(pos - stationRot*(10*dport->normal));
     ship->SetRotMatrix(rot);
     ship->SetVelocity(vector3d(0,0,0));
     ship->SetAngVelocity(vector3d(0,0,0));
@@ -154,7 +169,8 @@ bool SpaceStation::GetDockingClearance(Ship* s) {
 }
 
 bool SpaceStation::OnCollision(Body* b, Uint32 flags) {
-  if(flags == 1) {
+  if(flags & 0x10) {
+    dockingport_t* dport = &port[flags & 0xf];
     /* Hitting docking area of a station. */
     if(b->GetType() == Object::SHIP) {
       Ship* s = static_cast<Ship*>(b);
@@ -169,7 +185,7 @@ bool SpaceStation::OnCollision(Body* b, Uint32 flags) {
 
         matrix4x4d stationRot;
         GetRotMatrix(stationRot);
-        vector3d dockingNormal = stationRot*port.normal;
+        vector3d dockingNormal = stationRot*dport->normal;
 
         /* Check player is sort of sensibly oriented for landing. */
         const double dot = vector3d::Dot(vector3d(-invRot[1], -invRot[5], -invRot[9]), dockingNormal);
@@ -179,7 +195,7 @@ bool SpaceStation::OnCollision(Body* b, Uint32 flags) {
       if((speed < MAX_LANDING_SPEED) &&
           (!s->GetDockedWith()) &&
           (s->GetDockingTimer() != 0.0f)) {
-        s->SetDockedWith(this);
+        s->SetDockedWith(this, flags & 0xf);
       }
     }
     return false;
diff --git a/src/space_station.h b/src/space_station.h
index c528b39..89bb921 100644
--- a/src/space_station.h
+++ b/src/space_station.h
@@ -2,6 +2,8 @@
 #include "libs.h"
 #include "model_body.h"
 
+#define MAX_DOCKING_PORTS 4
+
 class CollMeshSet;
 class Ship;
 
@@ -13,8 +15,8 @@ public:
   virtual bool OnCollision(Body* b, Uint32 flags);
   virtual Object::Type GetType(void) { return Object::SPACESTATION; }
   virtual void Render(const Frame* camFrame);
-  void OrientLaunchingShip(Ship* ship) const;
-  void OrientDockedShip(Ship* ship) const;
+  void OrientLaunchingShip(Ship* ship, int port) const;
+  void OrientDockedShip(Ship* ship, int port) const;
   void GetDockingSurface(CollMeshSet* mset, int midx);
   bool GetDockingClearance(Ship* s);
   bool IsGroundStation(void) const;
@@ -22,9 +24,10 @@ public:
     vector3d center;
     vector3d normal;
     vector3d horiz;
-  } port;
+  } port[MAX_DOCKING_PORTS];
 
 private:
   TYPE m_type;
+  int m_numPorts;
 };
 
diff --git a/src/space_station_view.cpp b/src/space_station_view.cpp
index 1070039..6a9834b 100644
--- a/src/space_station_view.cpp
+++ b/src/space_station_view.cpp
@@ -32,7 +32,7 @@ SpaceStationView::SpaceStationView(void) : View() {
 }
 
 void SpaceStationView::OnClickRequestLaunch(void) {
-  L3D::player->SetDockedWith(0);
+  L3D::player->SetDockedWith(0,0);
   L3D::SetView(L3D::worldView);
 }