pioneer/src/SpaceStationType.cpp

438 lines
15 KiB
C++

// Copyright © 2008-2020 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#include "SpaceStationType.h"
#include "FileSystem.h"
#include "Json.h"
#include "MathUtil.h"
#include "OS.h"
#include "Pi.h"
#include "Ship.h"
#include "StringF.h"
#include "scenegraph/MatrixTransform.h"
#include "scenegraph/Model.h"
#include <algorithm>
// TODO: Fix the horrible control flow that makes this exception type necessary.
struct StationTypeLoadError {};
std::vector<SpaceStationType> SpaceStationType::surfaceTypes;
std::vector<SpaceStationType> SpaceStationType::orbitalTypes;
SpaceStationType::SpaceStationType(const std::string &id_, const std::string &path_) :
id(id_),
model(0),
modelName(""),
angVel(0.f),
dockMethod(SURFACE),
numDockingPorts(0),
numDockingStages(0),
numUndockStages(0),
shipLaunchStage(3),
parkingDistance(0),
parkingGapSize(0)
{
Json data = JsonUtils::LoadJsonDataFile(path_);
if (data.is_null()) {
Output("couldn't read station def '%s'\n", path_.c_str());
throw StationTypeLoadError();
}
modelName = data.value("model", "");
const std::string type = data.value("type", "");
if (type == "surface")
dockMethod = SURFACE;
else if (type == "orbital")
dockMethod = ORBITAL;
else {
Output("couldn't parse station def '%s': unknown type '%s'\n", path_.c_str(), type.c_str());
throw StationTypeLoadError();
}
angVel = data.value("angular_velocity", 0.0f);
parkingDistance = data.value("parking_distance", 0.0f);
parkingGapSize = data.value("parking_gap_size", 0.0f);
padOffset = data.value("pad_offset", 150.f);
model = Pi::FindModel(modelName, /* allowPlaceholder = */ false);
if (!model) {
Output("couldn't initialize station type '%s' because the corresponding model ('%s') could not be found.\n", path_.c_str(), modelName.c_str());
throw StationTypeLoadError();
}
OnSetupComplete();
}
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-fucking-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(0.0);
matrix4x4f approach2(0.0);
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());
}
}
}
const SpaceStationType::SPort *SpaceStationType::FindPortByBay(const int zeroBaseBayID) const
{
for (TPorts::const_iterator bayIter = m_ports.begin(), grpEnd = m_ports.end(); bayIter != grpEnd; ++bayIter) {
for (auto idIter : (*bayIter).bayIDs) {
if (idIter.first == zeroBaseBayID) {
return &(*bayIter);
}
}
}
// is it safer to return that the bay is locked?
return 0;
}
SpaceStationType::SPort *SpaceStationType::GetPortByBay(const int zeroBaseBayID)
{
for (TPorts::iterator bayIter = m_ports.begin(), grpEnd = m_ports.end(); bayIter != grpEnd; ++bayIter) {
for (auto idIter : (*bayIter).bayIDs) {
if (idIter.first == zeroBaseBayID) {
return &(*bayIter);
}
}
}
// is it safer to return that the bay is locked?
return 0;
}
bool SpaceStationType::GetShipApproachWaypoints(const unsigned int port, const int stage, positionOrient_t &outPosOrient) const
{
bool gotOrient = false;
const SPort *pPort = FindPortByBay(port);
if (pPort && stage > 0) {
TMapBayIDMat::const_iterator stageDataIt = pPort->m_approach.find(stage);
if (stageDataIt != pPort->m_approach.end()) {
const matrix4x4f &mt = pPort->m_approach.at(stage);
outPosOrient.pos = vector3d(mt.GetTranslate());
outPosOrient.xaxis = vector3d(mt.GetOrient().VectorX());
outPosOrient.yaxis = vector3d(mt.GetOrient().VectorY());
outPosOrient.zaxis = vector3d(mt.GetOrient().VectorZ());
outPosOrient.xaxis = outPosOrient.xaxis.Normalized();
outPosOrient.yaxis = outPosOrient.yaxis.Normalized();
outPosOrient.zaxis = outPosOrient.zaxis.Normalized();
gotOrient = true;
}
}
return gotOrient;
}
double SpaceStationType::GetDockAnimStageDuration(const int stage) const
{
return (stage == 0) ? 300.0 : ((SURFACE == dockMethod) ? 0.0 : 3.0);
}
double SpaceStationType::GetUndockAnimStageDuration(const int stage) const
{
return ((SURFACE == dockMethod) ? 0.0 : 5.0);
}
static bool GetPosOrient(const SpaceStationType::TMapBayIDMat &bayMap, const int stage, const double t, const vector3d &from,
SpaceStationType::positionOrient_t &outPosOrient)
{
bool gotOrient = false;
vector3d toPos;
const SpaceStationType::TMapBayIDMat::const_iterator stageDataIt = bayMap.find(stage);
const bool bHasStageData = (stageDataIt != bayMap.end());
assert(bHasStageData);
if (bHasStageData) {
const matrix4x4f &mt = stageDataIt->second;
outPosOrient.xaxis = vector3d(mt.GetOrient().VectorX()).Normalized();
outPosOrient.yaxis = vector3d(mt.GetOrient().VectorY()).Normalized();
outPosOrient.zaxis = vector3d(mt.GetOrient().VectorZ()).Normalized();
toPos = vector3d(mt.GetTranslate());
gotOrient = true;
}
if (gotOrient) {
vector3d pos = MathUtil::mix<vector3d, double>(from, toPos, t);
outPosOrient.pos = pos;
}
return gotOrient;
}
/* when ship is on rails it returns true and fills outPosOrient.
* when ship has been released (or docked) it returns false.
* Note station animations may continue for any number of stages after
* ship has been released and is under player control again */
bool SpaceStationType::GetDockAnimPositionOrient(const unsigned int port, int stage, double t, const vector3d &from, positionOrient_t &outPosOrient, const Ship *ship) const
{
assert(ship);
if (stage < -shipLaunchStage) {
stage = -shipLaunchStage;
t = 1.0;
}
if (stage > numDockingStages || !stage) {
stage = numDockingStages;
t = 1.0;
}
// note case for stageless launch (shipLaunchStage==0)
bool gotOrient = false;
assert(port <= m_portPaths.size());
const PortPath &rPortPath = m_portPaths.at(port + 1);
if (stage < 0) {
const int leavingStage = (-1 * stage);
gotOrient = GetPosOrient(rPortPath.m_leaving, leavingStage, t, from, outPosOrient);
const vector3d up = outPosOrient.yaxis.Normalized() * ship->GetLandingPosOffset();
outPosOrient.pos = outPosOrient.pos - up;
} else if (stage > 0) {
gotOrient = GetPosOrient(rPortPath.m_docking, stage, t, from, outPosOrient);
const vector3d up = outPosOrient.yaxis.Normalized() * ship->GetLandingPosOffset();
outPosOrient.pos = outPosOrient.pos - up;
}
return gotOrient;
}
/*static*/
void SpaceStationType::Init()
{
PROFILE_SCOPED()
static bool isInitted = false;
if (isInitted)
return;
isInitted = true;
// load all station definitions
namespace fs = FileSystem;
for (fs::FileEnumerator files(fs::gameDataFiles, "stations", 0); !files.Finished(); files.Next()) {
const fs::FileInfo &info = files.Current();
if (ends_with_ci(info.GetPath(), ".json")) {
const std::string id(info.GetName().substr(0, info.GetName().size() - 5));
try {
SpaceStationType st = SpaceStationType(id, info.GetPath());
switch (st.dockMethod) {
case SURFACE: surfaceTypes.push_back(st); break;
case ORBITAL: orbitalTypes.push_back(st); break;
}
} catch (StationTypeLoadError) {
// TODO: Actual error handling would be nice.
Error("Error while loading Space Station data (check stdout/output.txt).\n");
}
}
}
}
/*static*/
const SpaceStationType *SpaceStationType::RandomStationType(Random &random, const bool bIsGround)
{
if (bIsGround) {
return &surfaceTypes[random.Int32(SpaceStationType::surfaceTypes.size())];
}
return &orbitalTypes[random.Int32(SpaceStationType::orbitalTypes.size())];
}
/*static*/
const SpaceStationType *SpaceStationType::FindByName(const std::string &name)
{
for (auto &sst : surfaceTypes)
if (sst.id == name)
return &sst;
for (auto &sst : orbitalTypes)
if (sst.id == name)
return &sst;
return nullptr;
}