Пример #1
0
void SpaceStation::PositionDockedShip(Ship *ship, int port)
{
	SpaceStationType::positionOrient_t dport;
	PiVerify(m_type->GetDockAnimPositionOrient(port, m_type->numDockingStages, 1.0f, vector3d(0.0), dport, ship));
//	const positionOrient_t *dport = &this->port[port];
	const int dockMethod = m_type->dockMethod;
	if (dockMethod == SpaceStationType::ORBITAL) {
		matrix4x4d rot;
		GetRotMatrix(rot);
		vector3d p = GetPosition() + rot*dport.pos;

		ship->SetFrame(GetFrame());
		ship->SetPosition(p);
		// duplicated from DoDockingAnimation()
		vector3d zaxis = dport.xaxis.Cross(dport.yaxis);
		ship->SetRotMatrix(matrix4x4d::MakeRotMatrix(dport.xaxis,
					dport.yaxis, zaxis) * rot);
	} else {
		Aabb aabb;
		ship->GetAabb(aabb);

	 	matrix4x4d stationRot;
		GetRotMatrix(stationRot);
		vector3d port_z = dport.xaxis.Cross(dport.yaxis);
		matrix4x4d rot = stationRot * matrix4x4d::MakeRotMatrix(dport.xaxis, dport.yaxis, port_z);
		// position slightly (1m) off landing surface
		vector3d pos = GetPosition() + stationRot*(dport.pos +
				dport.yaxis -
				dport.yaxis*aabb.min.y);
		ship->SetPosition(pos);
		ship->SetRotMatrix(rot);
	}
}
Пример #2
0
/* The drawbacks of stuffing stuff into integers */
static int GetCrimeIdxFromEnum(enum Crime crime)
{
	PiVerify(crime);
	for (int i=0; i<64; i++) {
		if (crime & (1<<i)) return i;
	}
	return 0;
}
Пример #3
0
bool SpaceStation::OnCollision(Object *b, Uint32 flags, double relVel)
{
	if ((flags & 0x10) && (b->IsType(Object::SHIP))) {
		Ship *s = static_cast<Ship*>(b);

		int port = -1;
		for (int i=0; i<MAX_DOCKING_PORTS; i++) {
			if (m_shipDocking[i].ship == s) { port = i; break; }
		}
		if (port == -1) return false;					// no permission
		if (!m_type->dockOneAtATimePlease) {
			if (port != int(flags & 0xf)) return false;		// wrong port
		}
		if (m_shipDocking[port].stage != 1) return false;	// already docking?

		SpaceStationType::positionOrient_t dport;
		// why stage 2? Because stage 1 is permission to dock
		// granted, stage 2 is start of docking animation.
		PiVerify(m_type->GetDockAnimPositionOrient(port, 2, 0.0, vector3d(0.0), dport, s));

		// must be oriented sensibly and have wheels down
		if (IsGroundStation()) {
			vector3d dockingNormal = GetOrient()*dport.yaxis;
			const double dot = s->GetOrient().VectorY().Dot(dockingNormal);
			if ((dot < 0.99) || (s->GetWheelState() < 1.0)) return false;	// <0.99 harsh?
			if (s->GetVelocity().Length() > MAX_LANDING_SPEED) return false;
		}

		// if there is more docking port anim to do, don't set docked yet
		if (m_type->numDockingStages >= 2) {
			shipDocking_t &sd = m_shipDocking[port];
			sd.ship = s;
			sd.stage = 2;
			sd.stagePos = 0;
			sd.fromPos = (s->GetPosition() - GetPosition()) * GetOrient();	// station space
			sd.fromRot = Quaterniond::FromMatrix3x3(GetOrient().Transpose() * s->GetOrient());
			if (m_type->dockOneAtATimePlease) m_dockingLock = true;

			s->SetFlightState(Ship::DOCKING);
			s->SetVelocity(vector3d(0.0));
			s->SetAngVelocity(vector3d(0.0));
			s->ClearThrusterState();
		} else {
			s->SetDockedWith(this, port);				// bounces back to SS::SetDocked()
			LuaEvent::Queue("onShipDocked", s, this);
		}
		return false;
	} else {
		return true;
	}
}
Пример #4
0
vector3d SpaceStation::GetTargetIndicatorPosition(const Frame *relTo) const
{
	// return the next waypoint if permission has been granted for player,
	// and the docking point's position once the docking anim starts
	for (int i=0; i<MAX_DOCKING_PORTS; i++) {
		if (i >= m_type->numDockingPorts) break;
		if ((m_shipDocking[i].ship == Pi::player) && (m_shipDocking[i].stage > 0)) {

			SpaceStationType::positionOrient_t dport;
			if (!m_type->GetShipApproachWaypoints(i, m_shipDocking[i].stage+1, dport))
				PiVerify(m_type->GetDockAnimPositionOrient(i, m_type->numDockingStages,
				1.0f, vector3d(0.0), dport, m_shipDocking[i].ship));

			vector3d v = GetInterpPositionRelTo(relTo);
			return v + GetInterpOrientRelTo(relTo) * dport.pos;
		}
	}
	return GetInterpPositionRelTo(relTo);
}
Пример #5
0
vector3d SpaceStation::GetTargetIndicatorPosition(const Frame *relTo) const
{
	// return the next waypoint if permission has been granted for player,
	// and the docking point's position once the docking anim starts
	for (int i=0; i<MAX_DOCKING_PORTS; i++) {
		if (i >= m_type->numDockingPorts) break;
		if ((m_shipDocking[i].ship == Pi::player) && (m_shipDocking[i].stage > 0)) {

			SpaceStationType::positionOrient_t dport;
			if (!m_type->GetShipApproachWaypoints(i, m_shipDocking[i].stage+1, dport))
				PiVerify(m_type->GetDockAnimPositionOrient(i, m_type->numDockingStages,
				1.0f, vector3d(0.0), dport, m_shipDocking[i].ship));
			matrix4x4d rot;
			GetRotMatrix(rot);

			matrix4x4d m;
			Frame::GetFrameRenderTransform(GetFrame(), relTo, m);
			return m * (GetInterpolatedPosition() + (rot*dport.pos));
		}
	}
	return GetInterpolatedPositionRelTo(relTo);
}
Пример #6
0
void SpaceStation::PositionDockedShip(Ship *ship, int port) const
{
	const shipDocking_t &dt = m_shipDocking[port];
	SpaceStationType::positionOrient_t dport;
	PiVerify(m_type->GetDockAnimPositionOrient(port, dt.stage, dt.stagePos, dt.fromPos, dport, ship));
	assert(dt.ship == ship);

	ship->SetPosition(GetPosition() + GetOrient()*dport.pos);

	// Still in docking animation process?
	if (dt.stage <= m_type->numDockingStages) {
		matrix3x3d wantRot = matrix3x3d::FromVectors(dport.xaxis, dport.yaxis);
		// use quaternion spherical linear interpolation to do
		// rotation smoothly
		Quaterniond wantQuat = Quaterniond::FromMatrix3x3(wantRot);
		Quaterniond q = Quaterniond::Nlerp(dt.fromRot, wantQuat, dt.stagePos);
		wantRot = q.ToMatrix3x3<double>();
		ship->SetOrient(GetOrient() * wantRot);
	} else {
		// Note: ship bounding box is used to generate dport.pos
		ship->SetOrient(GetOrient() * matrix3x3d::FromVectors(dport.xaxis, dport.yaxis));
	}
}
Пример #7
0
void SpaceStation::DockingUpdate(const double timeStep)
{
	vector3d p1, p2, zaxis;
	for (int i=0; i<MAX_DOCKING_PORTS; i++) {
		shipDocking_t &dt = m_shipDocking[i];
		if (!dt.ship) continue;
		// docked stage is m_type->numDockingPorts + 1 => ship docked
		if (dt.stage > m_type->numDockingStages) continue;

		double stageDuration = (dt.stage > 0 ?
				m_type->dockAnimStageDuration[dt.stage-1] :
				m_type->undockAnimStageDuration[abs(dt.stage)-1]);
		dt.stagePos += timeStep / stageDuration;

		if (dt.stage == 1) {
			// SPECIAL stage! Docking granted but waiting for ship to dock

			m_doorAnimationStep = 0.3; // open door

			if (dt.stagePos >= 1.0) {
				if (dt.ship == static_cast<Ship*>(Pi::player)) Pi::onDockingClearanceExpired.emit(this);
				dt.ship = 0;
				dt.stage = 0;
				m_doorAnimationStep = -0.3; // close door
			}
			continue;
		}

		if (dt.stagePos > 1.0) {
			// use end position of last segment for start position of new segment
			SpaceStationType::positionOrient_t dport;
			PiVerify(m_type->GetDockAnimPositionOrient(i, dt.stage, 1.0f, dt.fromPos, dport, dt.ship));
			matrix3x3d fromRot = matrix3x3d::FromVectors(dport.xaxis, dport.yaxis);
			dt.fromRot = Quaterniond::FromMatrix3x3(fromRot);
			dt.fromPos = dport.pos;

			// transition between docking stages
			dt.stagePos = 0;
			if (dt.stage >= 0) dt.stage++;
			else dt.stage--;
		}

		if (dt.stage < -m_type->shipLaunchStage && dt.ship->GetFlightState() != Ship::FLYING) {
			// launch ship
			dt.ship->SetFlightState(Ship::FLYING);
			dt.ship->SetAngVelocity(GetAngVelocity());
			if (m_type->dockMethod == SpaceStationType::SURFACE) {
				dt.ship->SetThrusterState(1, 1.0);	// up
			} else {
				dt.ship->SetThrusterState(2, -1.0);	// forward
			}
			LuaEvent::Queue("onShipUndocked", dt.ship, this);
		}
		if (dt.stage < -m_type->numUndockStages) {
			// undock animation finished, clear port
			dt.stage = 0;
			dt.ship = 0;
			if (m_type->dockOneAtATimePlease) m_dockingLock = false;
			m_doorAnimationStep = -0.3; // close door
		}
		else if (dt.stage > m_type->numDockingStages) {
			// set docked
			dt.ship->SetDockedWith(this, i);
			LuaEvent::Queue("onShipDocked", dt.ship, this);
			if (m_type->dockOneAtATimePlease) m_dockingLock = false;
			m_doorAnimationStep = -0.3; // close door
		}
	}

	m_doorAnimationState = Clamp(m_doorAnimationState + m_doorAnimationStep*timeStep, 0.0, 1.0);
	if (m_doorAnimation)
		m_doorAnimation->SetProgress(m_doorAnimationState);
}
Пример #8
0
void SpaceStation::DockingUpdate(const double timeStep)
{
	vector3d p1, p2, zaxis;
	for (Uint32 i=0; i<m_shipDocking.size(); i++) {
		shipDocking_t &dt = m_shipDocking[i];
		if (!dt.ship) continue;
		// docked stage is m_type->NumDockingPorts() + 1 => ship docked
		if (dt.stage > m_type->NumDockingStages()) continue;

		double stageDuration = (dt.stage > 0 ?
				m_type->GetDockAnimStageDuration(dt.stage-1) :
				m_type->GetUndockAnimStageDuration(abs(dt.stage)-1));
		dt.stagePos += timeStep / stageDuration;

		if (dt.stage == 1) {
			// SPECIAL stage! Docking granted but waiting for ship to dock

			m_doorAnimationStep = 0.3; // open door

			if (dt.stagePos >= 1.0) {
				if (dt.ship == Pi::player)
					Pi::game->log->Add(GetLabel(), Lang::DOCKING_CLEARANCE_EXPIRED);
				dt.ship = 0;
				dt.stage = 0;
				m_doorAnimationStep = -0.3; // close door
			}
			continue;
		}

		if (dt.stagePos > 1.0) {
			// use end position of last segment for start position of new segment
			SpaceStationType::positionOrient_t dport;
			PiVerify(m_type->GetDockAnimPositionOrient(i, dt.stage, 1.0f, dt.fromPos, dport, dt.ship));
			matrix3x3d fromRot = matrix3x3d::FromVectors(dport.xaxis, dport.yaxis, dport.zaxis);
			dt.fromRot = Quaterniond::FromMatrix3x3(fromRot);
			dt.fromPos = dport.pos;

			// transition between docking stages
			dt.stagePos = 0;
			if (dt.stage >= 0) dt.stage++;
			else dt.stage--;
		}

		if (dt.stage < -m_type->ShipLaunchStage() && dt.ship->GetFlightState() != Ship::FLYING) {
			// launch ship
			dt.ship->SetFlightState(Ship::FLYING);
			dt.ship->SetAngVelocity(GetAngVelocity());
			if (m_type->IsSurfaceStation()) {
				dt.ship->SetThrusterState(1, 1.0);	// up
			} else {
				dt.ship->SetThrusterState(2, -1.0);	// forward
			}
			LuaEvent::Queue("onShipUndocked", dt.ship, this);
		}
		if (dt.stage < -m_type->NumUndockStages()) {
			// undock animation finished, clear port
			dt.stage = 0;
			dt.ship = 0;
			LockPort(i, false);
			m_doorAnimationStep = -0.3; // close door
		}
		else if (dt.stage > m_type->NumDockingStages()) {
			// set docked
			dt.ship->SetDockedWith(this, i);
			LuaEvent::Queue("onShipDocked", dt.ship, this);
			LockPort(i, false);
			m_doorAnimationStep = -0.3; // close door
		}
	}

	m_doorAnimationState = Clamp(m_doorAnimationState + m_doorAnimationStep*timeStep, 0.0, 1.0);
	if (m_doorAnimation)
		m_doorAnimation->SetProgress(m_doorAnimationState);
}
Пример #9
0
bool SpaceStation::OnCollision(Object *b, Uint32 flags, double relVel)
{
	if ((flags & 0x10) && (b->IsType(Object::SHIP))) {
		Ship *s = static_cast<Ship*>(b);
		matrix4x4d rot;
		GetRotMatrix(rot);

		bool canDock = true;
		int port = -1;
		for (int i=0; i<MAX_DOCKING_PORTS; i++) {
			if (m_shipDocking[i].ship == s) { port = i; break; }
		}
		if (m_type->dockOneAtATimePlease) {
			for (int i=0; i<m_type->numDockingPorts; i++) {
				if (m_shipDocking[i].ship && m_shipDocking[i].stage != 1 &&
				    (m_shipDocking[i].stage != m_type->numDockingStages+1)) {
					canDock = false;
					break;
				}
			}
		} else {
			// for non-dockOneAtATimePlease, the ship is expected
			// to hit the right docking trigger surface for that port
			if (m_shipDocking[flags&0xf].ship != s) canDock = false;
		}
		if (port == -1) canDock = false;

		// hitting docking area of a station
		if (canDock) {
			SpaceStationType::positionOrient_t dport;
			// why stage 2? Because stage 1 is permission to dock
			// granted, stage 2 is start of docking animation.
			PiVerify(m_type->GetDockAnimPositionOrient(port, 2, 0.0f, vector3d(0.0), dport, s));

			double speed = s->GetVelocity().Length();

			// must be oriented sensibly and have wheels down
			if (IsGroundStation()) {
				matrix4x4d shiprot;
				s->GetRotMatrix(shiprot);
				matrix4x4d invShipRot = shiprot.InverseOf();

				vector3d dockingNormal = rot*dport.yaxis;

				// check player is sortof sensibly oriented for landing
				const double dot = vector3d(invShipRot[1], invShipRot[5], invShipRot[9]).Dot(dockingNormal);
				if ((dot < 0.99) || (s->GetWheelState() < 1.0)) return false;
			}

			if ((speed < MAX_LANDING_SPEED) &&
			    (!s->GetDockedWith()) &&
			    (m_shipDocking[port].stage == 1)) {
				// if there is more docking port anim to do,
				// don't set docked yet
				if (m_type->numDockingStages >= 2) {
					shipDocking_t &sd = m_shipDocking[port];
					sd.ship = s;
					sd.stage = 2;
					sd.stagePos = 0;
					sd.fromPos = rot.InverseOf() * (s->GetPosition() - GetPosition());
					matrix4x4d temp;
					s->GetRotMatrix(temp);
					sd.fromRot = Quaterniond::FromMatrix4x4(temp);
					s->Disable();
					s->ClearThrusterState();
					s->SetFlightState(Ship::DOCKING);
				} else {
					s->SetDockedWith(this, port);
					LuaEvent::Queue("onShipDocked", s, this);
				}
			}
		}
		return false;
	} else {
		return true;
	}
}
Пример #10
0
void SpaceStationType::OnSetupComplete()
{
	SceneGraph::Model::TVecMT approach_mts;
	SceneGraph::Model::TVecMT docking_mts;
	SceneGraph::Model::TVecMT leaving_mts;
	model->FindTagsByStartOfName("approach_", approach_mts);
	model->FindTagsByStartOfName("docking_", docking_mts);
	model->FindTagsByStartOfName("leaving_", leaving_mts);

	{
		SceneGraph::Model::TVecMT::const_iterator apprIter = approach_mts.begin();
		for (; apprIter!=approach_mts.end() ; ++apprIter)
		{
			int bay, stage;
			PiVerify(2 == sscanf((*apprIter)->GetName().c_str(), "approach_stage%d_bay%d", &stage, &bay));
			PiVerify(bay>0 && stage>0);
			SBayGroup* pGroup = GetGroupByBay(bay-1);
			assert(pGroup);
			pGroup->m_approach[stage] = (*apprIter)->GetTransform();
		}

		SceneGraph::Model::TVecMT::const_iterator dockIter = docking_mts.begin();
		for (; dockIter!=docking_mts.end() ; ++dockIter)
		{
			int bay, stage;
			PiVerify(2 == sscanf((*dockIter)->GetName().c_str(), "docking_stage%d_bay%d", &stage, &bay));
			PiVerify(bay>0 && stage>0);
			m_ports[bay].m_docking[stage+1] = (*dockIter)->GetTransform();
		}

		SceneGraph::Model::TVecMT::const_iterator leaveIter = leaving_mts.begin();
		for (; leaveIter!=leaving_mts.end() ; ++leaveIter)
		{
			int bay, stage;
			PiVerify(2 == sscanf((*leaveIter)->GetName().c_str(), "leaving_stage%d_bay%d", &stage, &bay));
			PiVerify(bay>0 && stage>0);
			m_ports[bay].m_leaving[stage] = (*leaveIter)->GetTransform();
		}

		assert(!m_ports.empty());
		assert(numDockingStages > 0);
		assert(numUndockStages > 0);

		for (PortMap::const_iterator pIt = m_ports.begin(), pItEnd = m_ports.end(); pIt!=pItEnd; ++pIt)
		{
			if (Uint32(numDockingStages-1) < pIt->second.m_docking.size()) {
				Error(
					"(%s): numDockingStages (%d) vs number of docking stages (" SIZET_FMT ")\n"
					"Must have at least the same number of entries as the number of docking stages "
					"PLUS the docking timeout at the start of the array.",
					modelName.c_str(), (numDockingStages-1), pIt->second.m_docking.size());

			} else if (Uint32(numDockingStages-1) != pIt->second.m_docking.size()) {
				Warning(
					"(%s): numDockingStages (%d) vs number of docking stages (" SIZET_FMT ")\n",
					modelName.c_str(), (numDockingStages-1), pIt->second.m_docking.size());
			}

			if (0!=pIt->second.m_leaving.size() && Uint32(numUndockStages) < pIt->second.m_leaving.size()) {
				Error(
					"(%s): numUndockStages (%d) vs number of leaving stages (" SIZET_FMT ")\n"
					"Must have at least the same number of entries as the number of leaving stages.",
					modelName.c_str(), (numDockingStages-1), pIt->second.m_docking.size());

			} else if(0!=pIt->second.m_leaving.size() && Uint32(numUndockStages) != pIt->second.m_leaving.size()) {
				Warning(
					"(%s): numUndockStages (%d) vs number of leaving stages (" SIZET_FMT ")\n",
					modelName.c_str(), numUndockStages, pIt->second.m_leaving.size());
			}

		}
	}
}
Пример #11
0
void SpaceStationType::OnSetupComplete()
{
	// Since the model contains (almost) all of the docking information we have to extract that
	// and then generate any additional locators and information the station will need from it.

	// First we gather the MatrixTransforms that contain the location and orientation of the docking
	// locators/waypoints. We store some information within the name of these which needs parsing too.

	// Next we build the additional information required for docking ships with SPACE stations
	// on autopilot - this is the only option for docking with SPACE stations currently.
	// This mostly means offsetting from one locator to create the next in the sequence.

	// ground stations have a "special-f*****g-case" 0 stage launch process
	shipLaunchStage = ((SURFACE==dockMethod) ? 0 : 3);

	// gather the tags
	SceneGraph::Model::TVecMT entrance_mts;
	SceneGraph::Model::TVecMT locator_mts;
	SceneGraph::Model::TVecMT exit_mts;
	model->FindTagsByStartOfName("entrance_", entrance_mts);
	model->FindTagsByStartOfName("loc_", locator_mts);
	model->FindTagsByStartOfName("exit_", exit_mts);

	Output("%s has:\n %lu entrances,\n %lu pads,\n %lu exits\n", modelName.c_str(), entrance_mts.size(), locator_mts.size(), exit_mts.size());

	// Add the partially initialised ports
	for (auto apprIter : entrance_mts)
	{
		int portId;
		PiVerify(1 == sscanf(apprIter->GetName().c_str(), "entrance_port%d", &portId));
		PiVerify(portId>0);

		SPort new_port;
		new_port.portId = portId;
		new_port.name = apprIter->GetName();
		if(SURFACE==dockMethod) {
			const vector3f offDir = apprIter->GetTransform().Up().Normalized();
			new_port.m_approach[1] = apprIter->GetTransform();
			new_port.m_approach[1].SetTranslate( apprIter->GetTransform().GetTranslate() + (offDir * 500.0f) );
		} else {
			const vector3f offDir = -apprIter->GetTransform().Back().Normalized();
			new_port.m_approach[1] = apprIter->GetTransform();
			new_port.m_approach[1].SetTranslate( apprIter->GetTransform().GetTranslate() + (offDir * 1500.0f) );
		}
		new_port.m_approach[2] = apprIter->GetTransform();
		m_ports.push_back( new_port );
	}

	int bay=0;
	for (auto locIter : locator_mts)
	{
		int bayStr, portId;
		int minSize, maxSize;
		char padname[8];
		const matrix4x4f &locTransform = locIter->GetTransform();

		++bay;

		// eg:loc_A001_p01_s0_500_b01
		PiVerify(5 == sscanf(locIter->GetName().c_str(), "loc_%4s_p%d_s%d_%d_b%d", &padname[0], &portId, &minSize, &maxSize, &bayStr));
		PiVerify(bay>0 && portId>0);

		// find the port and setup the rest of it's information
		bool bFoundPort = false;
		matrix4x4f approach1;
		matrix4x4f approach2;
		for(auto &rPort : m_ports) {
			if(rPort.portId == portId) {
				rPort.minShipSize = std::min(minSize,rPort.minShipSize);
				rPort.maxShipSize = std::max(maxSize,rPort.maxShipSize);
				rPort.bayIDs.push_back( std::make_pair(bay-1, padname) );
				bFoundPort = true;
				approach1 = rPort.m_approach[1];
				approach2 = rPort.m_approach[2];
				break;
			}
		}
		assert(bFoundPort);

		// now build the docking/leaving waypoints
		if( SURFACE == dockMethod )
		{
			// ground stations don't have leaving waypoints.
			m_portPaths[bay].m_docking[2] = locTransform; // final (docked)
			numDockingStages = 2;
			numUndockStages = 1;
		}
		else
		{
			struct TPointLine
			{
				// for reference: http://paulbourke.net/geometry/pointlineplane/
				static bool ClosestPointOnLine( const vector3f &Point, const vector3f &LineStart, const vector3f &LineEnd, vector3f &Intersection )
				{
					const float LineMag = ( LineStart - LineEnd ).Length();
 
					const float U = ( ( ( Point.x - LineStart.x ) * ( LineEnd.x - LineStart.x ) ) +
									(   ( Point.y - LineStart.y ) * ( LineEnd.y - LineStart.y ) ) +
									(   ( Point.z - LineStart.z ) * ( LineEnd.z - LineStart.z ) ) ) /
									( LineMag * LineMag );
 
					if( U < 0.0f || U > 1.0f )
						return false;   // closest point does not fall within the line segment
 
					Intersection.x = LineStart.x + U * ( LineEnd.x - LineStart.x );
					Intersection.y = LineStart.y + U * ( LineEnd.y - LineStart.y );
					Intersection.z = LineStart.z + U * ( LineEnd.z - LineStart.z );
 
					return true;
				}
			};

			// create the docking locators
			// start
			m_portPaths[bay].m_docking[2] = approach2;
			m_portPaths[bay].m_docking[2].SetRotationOnly( locTransform.GetOrient() );
			// above the pad
			vector3f intersectionPos(0.0f);
			const vector3f approach1Pos = approach1.GetTranslate();
			const vector3f approach2Pos = approach2.GetTranslate();
			{
				const vector3f p0 = locTransform.GetTranslate(); // plane position
				const vector3f l = (approach2Pos - approach1Pos).Normalized(); // ray direction
				const vector3f l0 = approach1Pos + (l*10000.0f);

				if(!TPointLine::ClosestPointOnLine(p0, approach1Pos, l0, intersectionPos)) {
					Output("No point found on line segment");
				} 
			}
			m_portPaths[bay].m_docking[3] = locTransform;
			m_portPaths[bay].m_docking[3].SetTranslate( intersectionPos );
			// final (docked)
			m_portPaths[bay].m_docking[4] = locTransform;
			numDockingStages = 4;

			// leaving locators ...
			matrix4x4f orient = locTransform.GetOrient(), EndOrient;
			if( exit_mts.empty() )
			{
				// leaving locators need to face in the opposite direction
				const matrix4x4f rot = matrix3x3f::Rotate(DEG2RAD(180.0f), orient.Back());
				orient = orient * rot;
				orient.SetTranslate( locTransform.GetTranslate() );
				EndOrient = approach2;
				EndOrient.SetRotationOnly(orient);
			}
			else
			{
				// leaving locators, use whatever orientation they have
				orient.SetTranslate( locTransform.GetTranslate() );
				int exitport = 0;
				for( auto &exitIt : exit_mts ) {
					PiVerify(1 == sscanf( exitIt->GetName().c_str(), "exit_port%d", &exitport ));
					if( exitport == portId )
					{
						EndOrient = exitIt->GetTransform();
						break;
					}
				}
				if( exitport == 0 )
				{
					EndOrient = approach2;
				}
			}

			// create the leaving locators
			m_portPaths[bay].m_leaving[1] = locTransform; // start - maintain the same orientation and position as when docked.
			m_portPaths[bay].m_leaving[2] = orient; // above the pad - reorient...
			m_portPaths[bay].m_leaving[2].SetTranslate( intersectionPos ); //  ...and translate to new position
			m_portPaths[bay].m_leaving[3] = EndOrient; // end (on manual after here)
			numUndockStages = 3;
		}
	}
	
	numDockingPorts = m_portPaths.size();

	// sanity
	assert(!m_portPaths.empty());
	assert(numDockingStages > 0);
	assert(numUndockStages > 0);

	// insanity
	for (PortPathMap::const_iterator pIt = m_portPaths.begin(), pItEnd = m_portPaths.end(); pIt!=pItEnd; ++pIt)
	{
		if (Uint32(numDockingStages-1) < pIt->second.m_docking.size()) {
			Error(
				"(%s): numDockingStages (%d) vs number of docking stages (" SIZET_FMT ")\n"
				"Must have at least the same number of entries as the number of docking stages "
				"PLUS the docking timeout at the start of the array.",
				modelName.c_str(), (numDockingStages-1), pIt->second.m_docking.size());

		} else if (Uint32(numDockingStages-1) != pIt->second.m_docking.size()) {
			Warning(
				"(%s): numDockingStages (%d) vs number of docking stages (" SIZET_FMT ")\n",
				modelName.c_str(), (numDockingStages-1), pIt->second.m_docking.size());
		}

		if (0!=pIt->second.m_leaving.size() && Uint32(numUndockStages) < pIt->second.m_leaving.size()) {
			Error(
				"(%s): numUndockStages (%d) vs number of leaving stages (" SIZET_FMT ")\n"
				"Must have at least the same number of entries as the number of leaving stages.",
				modelName.c_str(), (numDockingStages-1), pIt->second.m_docking.size());

		} else if(0!=pIt->second.m_leaving.size() && Uint32(numUndockStages) != pIt->second.m_leaving.size()) {
			Warning(
				"(%s): numUndockStages (%d) vs number of leaving stages (" SIZET_FMT ")\n",
				modelName.c_str(), numUndockStages, pIt->second.m_leaving.size());
		}

	}
}