pioneer/src/HyperspaceCloud.cpp

194 lines
5.8 KiB
C++

// Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#include "HyperspaceCloud.h"
#include "Game.h"
#include "GameSaveError.h"
#include "Json.h"
#include "Lang.h"
#include "Pi.h"
#include "Player.h"
#include "Ship.h"
#include "Space.h"
#include "graphics/RenderState.h"
#include "graphics/Renderer.h"
#include "perlin.h"
using namespace Graphics;
/** How long does a hyperspace cloud last for? 2 Days? */
#define HYPERCLOUD_DURATION (60.0 * 60.0 * 24.0 * 2.0)
HyperspaceCloud::HyperspaceCloud(Ship *s, double dueDate, bool isArrival) :
m_isBeingKilled(false)
{
m_flags = Body::FLAG_CAN_MOVE_FRAME |
Body::FLAG_LABEL_HIDDEN;
m_ship = s;
SetPhysRadius(0.0);
SetClipRadius(1200.0);
m_vel = (s ? s->GetVelocity() : vector3d(0.0));
m_birthdate = Pi::game->GetTime();
m_due = dueDate;
SetIsArrival(isArrival);
InitGraphics();
}
HyperspaceCloud::HyperspaceCloud(const Json &jsonObj, Space *space) :
Body(jsonObj, space),
m_isBeingKilled(false)
{
try {
Json hyperspaceCloudObj = jsonObj["hyperspace_cloud"];
m_vel = hyperspaceCloudObj["vel"];
m_birthdate = hyperspaceCloudObj["birth_date"];
m_due = hyperspaceCloudObj["due"];
m_isArrival = hyperspaceCloudObj["is_arrival"];
m_ship = nullptr;
if (hyperspaceCloudObj["ship"].is_object()) {
Json shipObj = hyperspaceCloudObj["ship"];
m_ship = static_cast<Ship *>(Body::FromJson(shipObj, space));
}
InitGraphics();
} catch (Json::type_error &) {
throw SavedGameCorruptException();
}
}
HyperspaceCloud::~HyperspaceCloud()
{
if (m_ship) delete m_ship;
}
void HyperspaceCloud::SetIsArrival(bool isArrival)
{
m_isArrival = isArrival;
SetLabel(isArrival ? Lang::HYPERSPACE_ARRIVAL_CLOUD : Lang::HYPERSPACE_DEPARTURE_CLOUD);
}
void HyperspaceCloud::SaveToJson(Json &jsonObj, Space *space)
{
Body::SaveToJson(jsonObj, space);
Json hyperspaceCloudObj = Json::object(); // Create JSON object to contain hyperspace cloud data.
hyperspaceCloudObj["vel"] = m_vel;
hyperspaceCloudObj["birth_date"] = m_birthdate;
hyperspaceCloudObj["due"] = m_due;
hyperspaceCloudObj["is_arrival"] = m_isArrival;
if (m_ship) {
Json shipObj = Json::object(); // Create JSON object to contain ship data.
m_ship->ToJson(shipObj, space);
hyperspaceCloudObj["ship"] = shipObj; // Add ship object to hyperpace cloud object.
}
jsonObj["hyperspace_cloud"] = hyperspaceCloudObj; // Add hyperspace cloud object to supplied object.
}
void HyperspaceCloud::PostLoadFixup(Space *space)
{
Body::PostLoadFixup(space);
if (m_ship) m_ship->PostLoadFixup(space);
}
void HyperspaceCloud::TimeStepUpdate(const float timeStep)
{
if (m_isBeingKilled)
return;
SetPosition(GetPosition() + m_vel * timeStep);
if (m_isArrival && m_ship && (m_due < Pi::game->GetTime())) {
// spawn ship
// XXX some overlap with Space::DoHyperspaceTo(). should probably all
// be moved into EvictShip()
m_ship->SetPosition(GetPosition());
m_ship->SetVelocity(m_vel);
m_ship->SetOrient(matrix3x3d::Identity());
m_ship->SetFrame(GetFrame());
Pi::game->GetSpace()->AddBody(m_ship);
if (Pi::player->GetNavTarget() == this && !Pi::player->GetCombatTarget())
Pi::player->SetCombatTarget(m_ship, Pi::player->GetSetSpeedTarget() == this);
m_ship->EnterSystem();
m_ship = nullptr;
}
// cloud expiration
if (m_birthdate + HYPERCLOUD_DURATION <= Pi::game->GetTime()) {
Pi::game->RemoveHyperspaceCloud(this);
Pi::game->GetSpace()->KillBody(this);
m_isBeingKilled = true;
}
}
Ship *HyperspaceCloud::EvictShip()
{
Ship *s = m_ship;
m_ship = nullptr;
return s;
}
static void make_circle_thing(VertexArray &va, float radius, const Color &colCenter, const Color &colEdge)
{
va.Add(vector3f(0.f, 0.f, 0.f), colCenter);
for (float ang = 0; ang < float(M_PI) * 2.f; ang += 0.1f) {
va.Add(vector3f(radius * sin(ang), radius * cos(ang), 0.0f), colEdge);
}
va.Add(vector3f(0.f, radius, 0.f), colEdge);
}
void HyperspaceCloud::UpdateInterpTransform(double alpha)
{
m_interpOrient = matrix3x3d::Identity();
const vector3d oldPos = GetPosition() - m_vel * Pi::game->GetTimeStep();
m_interpPos = alpha * GetPosition() + (1.0 - alpha) * oldPos;
}
void HyperspaceCloud::Render(Renderer *renderer, const Camera *camera, const vector3d &viewCoords, const matrix4x4d &viewTransform)
{
if (m_isBeingKilled)
return;
matrix4x4d trans = matrix4x4d::Identity();
trans.Translate(float(viewCoords.x), float(viewCoords.y), float(viewCoords.z));
// face the camera dammit
vector3d zaxis = viewCoords.NormalizedSafe();
vector3d xaxis = vector3d(0, 1, 0).Cross(zaxis).Normalized();
vector3d yaxis = zaxis.Cross(xaxis);
matrix4x4d rot = matrix4x4d::MakeRotMatrix(xaxis, yaxis, zaxis).Inverse();
renderer->SetTransform(matrix4x4f(trans * rot));
// precise to the rendered frame (better than PHYSICS_HZ granularity)
const double preciseTime = Pi::game->GetTime() + Pi::GetGameTickAlpha() * Pi::game->GetTimeStep();
// Flickering gradient circle, departure clouds are red and arrival clouds blue
// XXX could just alter the scale instead of recreating the model
const float radius = 1000.0f + 200.0f * float(noise(vector3d(10.0 * preciseTime, 0, 0)));
m_graphic.vertices->Clear();
Color outerColor = m_isArrival ? Color::BLUE : Color::RED;
outerColor.a = 0;
make_circle_thing(*m_graphic.vertices.get(), radius, Color::WHITE, outerColor);
renderer->DrawTriangles(m_graphic.vertices.get(), m_graphic.renderState, m_graphic.material.get(), TRIANGLE_FAN);
}
void HyperspaceCloud::InitGraphics()
{
m_graphic.vertices.reset(new Graphics::VertexArray(ATTRIB_POSITION | ATTRIB_DIFFUSE));
Graphics::MaterialDescriptor desc;
desc.vertexColors = true;
m_graphic.material.reset(Pi::renderer->CreateMaterial(desc));
Graphics::RenderStateDesc rsd;
rsd.blendMode = BLEND_ALPHA_ONE;
rsd.depthWrite = false;
m_graphic.renderState = Pi::renderer->CreateRenderState(rsd);
}