pioneer/src/Shields.cpp

345 lines
9.7 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 "Shields.h"
#include "GameSaveError.h"
#include "JsonUtils.h"
#include "Ship.h"
#include "graphics/RenderState.h"
#include "graphics/TextureBuilder.h"
#include "scenegraph/CollisionGeometry.h"
#include "scenegraph/FindNodeVisitor.h"
#include "scenegraph/SceneGraph.h"
#include <sstream>
namespace {
static RefCountedPtr<Graphics::Material> s_matShield;
static ShieldRenderParameters s_renderParams;
static const std::string s_shieldGroupName("Shields");
static const std::string s_matrixTransformName("_accMtx4");
static RefCountedPtr<Graphics::Material> GetGlobalShieldMaterial()
{
return s_matShield;
}
} // namespace
//used to find the accumulated transform of a MatrixTransform
class MatrixAccumVisitor : public SceneGraph::NodeVisitor {
public:
MatrixAccumVisitor(const std::string &name_) :
outMat(matrix4x4f::Identity()),
m_accumMat(matrix4x4f::Identity()),
m_name(name_)
{
}
virtual void ApplyMatrixTransform(SceneGraph::MatrixTransform &mt) override
{
if (mt.GetName() == m_name) {
outMat = m_accumMat * mt.GetTransform();
} else {
const matrix4x4f prevAcc = m_accumMat;
m_accumMat = m_accumMat * mt.GetTransform();
mt.Traverse(*this);
m_accumMat = prevAcc;
}
}
matrix4x4f outMat;
private:
matrix4x4f m_accumMat;
std::string m_name;
};
typedef std::vector<Shields::Shield>::iterator ShieldIterator;
//static
bool Shields::s_initialised = false;
Shields::Shield::Shield(const Color3ub &_colour, const matrix4x4f &matrix, SceneGraph::StaticGeometry *_sg) :
m_colour(_colour),
m_matrix(matrix),
m_mesh(_sg)
{}
Shields::Hits::Hits(const vector3d &_pos, const Uint32 _start, const Uint32 _end) :
pos(_pos),
start(_start),
end(_end)
{}
void Shields::Init(Graphics::Renderer *renderer)
{
PROFILE_SCOPED()
assert(!s_initialised);
// create our global shield material
Graphics::MaterialDescriptor desc;
desc.textures = 0;
desc.lighting = true;
desc.alphaTest = false;
desc.effect = Graphics::EffectType::EFFECT_SHIELD;
s_matShield.Reset(renderer->CreateMaterial(desc));
s_matShield->diffuse = Color(1.0f, 1.0f, 1.0f, 1.0f);
s_initialised = true;
}
void Shields::ReparentShieldNodes(SceneGraph::Model *model)
{
assert(s_initialised);
Graphics::Renderer *renderer = model->GetRenderer();
using SceneGraph::Group;
using SceneGraph::MatrixTransform;
using SceneGraph::Node;
using SceneGraph::StaticGeometry;
//This will find all matrix transforms meant for navlights.
SceneGraph::FindNodeVisitor shieldFinder(SceneGraph::FindNodeVisitor::MATCH_NAME_ENDSWITH, "_shield");
model->GetRoot()->Accept(shieldFinder);
const std::vector<Node *> &results = shieldFinder.GetResults();
//Move shield geometry to same level as the LODs
for (unsigned int i = 0; i < results.size(); i++) {
MatrixTransform *mt = dynamic_cast<MatrixTransform *>(results.at(i));
assert(mt);
const Uint32 NumChildren = mt->GetNumChildren();
if (NumChildren > 0) {
// Group to contain all of the shields we might find
Group *shieldGroup = new Group(renderer);
shieldGroup->SetName(s_shieldGroupName);
// go through all of this MatrixTransforms children to extract all of the shield meshes
for (Uint32 iChild = 0; iChild < NumChildren; ++iChild) {
Node *node = mt->GetChildAt(iChild);
assert(node);
if (node) {
RefCountedPtr<StaticGeometry> sg(dynamic_cast<StaticGeometry *>(node));
assert(sg.Valid());
sg->SetNodeMask(SceneGraph::NODE_TRANSPARENT);
// We can early-out if we've already processed this models scenegraph.
if (Graphics::BLEND_ALPHA == sg->m_blendMode) {
assert(false);
}
// force the blend mode
sg->m_blendMode = Graphics::BLEND_ALPHA;
Graphics::RenderStateDesc rsd;
rsd.blendMode = Graphics::BLEND_ALPHA;
rsd.depthWrite = false;
sg->SetRenderState(renderer->CreateRenderState(rsd));
for (Uint32 iMesh = 0; iMesh < sg->GetNumMeshes(); ++iMesh) {
StaticGeometry::Mesh &rMesh = sg->GetMeshAt(iMesh);
rMesh.material = GetGlobalShieldMaterial();
}
// find the accumulated transform from the root to our node
MatrixAccumVisitor mav(mt->GetName());
model->GetRoot()->Accept(mav);
// set our nodes transformation to be the accumulated transform
MatrixTransform *sg_transform_parent = new MatrixTransform(renderer, mav.outMat);
std::stringstream nodeStream;
nodeStream << iChild << s_matrixTransformName;
sg_transform_parent->SetName(nodeStream.str());
sg_transform_parent->AddChild(sg.Get());
// dettach node from current location in the scenegraph...
mt->RemoveChild(node);
// attach new transform node which parents the our shields mesh to the shield group.
shieldGroup->AddChild(sg_transform_parent);
}
}
model->GetRoot()->AddChild(shieldGroup);
}
}
}
void Shields::Uninit()
{
assert(s_initialised);
s_initialised = false;
}
Shields::Shields(SceneGraph::Model *model) :
m_enabled(false)
{
assert(s_initialised);
using SceneGraph::CollisionGeometry;
using SceneGraph::MatrixTransform;
using SceneGraph::Node;
using SceneGraph::StaticGeometry;
//This will find all matrix transforms meant for shields.
SceneGraph::FindNodeVisitor shieldFinder(SceneGraph::FindNodeVisitor::MATCH_NAME_ENDSWITH, s_matrixTransformName);
model->GetRoot()->Accept(shieldFinder);
const std::vector<Node *> &results = shieldFinder.GetResults();
//Store pointer to the shields for later.
for (unsigned int i = 0; i < results.size(); i++) {
MatrixTransform *mt = dynamic_cast<MatrixTransform *>(results.at(i));
assert(mt);
for (Uint32 iChild = 0; iChild < mt->GetNumChildren(); ++iChild) {
Node *node = mt->GetChildAt(iChild);
if (node) {
RefCountedPtr<StaticGeometry> sg(dynamic_cast<StaticGeometry *>(node));
assert(sg.Valid());
sg->SetNodeMask(SceneGraph::NODE_TRANSPARENT);
Graphics::RenderStateDesc rsd;
rsd.blendMode = Graphics::BLEND_ALPHA;
rsd.depthWrite = false;
sg->SetRenderState(sg->GetRenderer()->CreateRenderState(rsd));
// set the material
for (Uint32 iMesh = 0; iMesh < sg->GetNumMeshes(); ++iMesh) {
StaticGeometry::Mesh &rMesh = sg->GetMeshAt(iMesh);
rMesh.material = GetGlobalShieldMaterial();
}
m_shields.push_back(Shield(Color3ub(255), mt->GetTransform(), sg.Get()));
}
}
}
}
Shields::~Shields()
{
}
void Shields::SaveToJson(Json &jsonObj)
{
Json shieldsObj({}); // Create JSON object to contain shields data.
shieldsObj["enabled"] = m_enabled;
shieldsObj["num_shields"] = m_shields.size();
Json shieldArray = Json::array(); // Create JSON array to contain shield data.
for (ShieldIterator it = m_shields.begin(); it != m_shields.end(); ++it) {
Json shieldArrayEl({}); // Create JSON object to contain shield.
shieldArrayEl["color"] = it->m_colour;
shieldArrayEl["mesh_name"] = it->m_mesh->GetName();
shieldArray.push_back(shieldArrayEl); // Append shield object to array.
}
shieldsObj["shield_array"] = shieldArray; // Add shield array to shields object.
jsonObj["shields"] = shieldsObj; // Add shields object to supplied object.
}
void Shields::LoadFromJson(const Json &jsonObj)
{
try {
Json shieldsObj = jsonObj["shields"];
m_enabled = shieldsObj["enabled"];
assert(shieldsObj["num_shields"].get<unsigned int>() == m_shields.size());
Json shieldArray = shieldsObj["shield_array"].get<Json::array_t>();
for (unsigned int i = 0; i < shieldArray.size(); ++i) {
Json shieldArrayEl = shieldArray[i];
for (ShieldIterator it = m_shields.begin(); it != m_shields.end(); ++it) {
if (shieldArrayEl["mesh_name"] == it->m_mesh->GetName()) {
it->m_colour = shieldArrayEl["color"];
break;
}
}
}
} catch (Json::type_error &) {
throw SavedGameCorruptException();
}
}
void Shields::Update(const float coolDown, const float shieldStrength)
{
// update hits on the shields
const Uint32 tickTime = SDL_GetTicks();
{
HitIterator it = m_hits.begin();
while (it != m_hits.end()) {
if (tickTime > it->end) {
it = m_hits.erase(it);
} else {
++it;
}
}
}
if (!m_enabled) {
for (ShieldIterator it = m_shields.begin(); it != m_shields.end(); ++it) {
it->m_mesh->SetNodeMask(0x0);
}
return;
}
// setup the render params
if (shieldStrength > 0.0f) {
s_renderParams.strength = shieldStrength;
s_renderParams.coolDown = coolDown;
Uint32 numHits = m_hits.size();
for (Uint32 i = 0; i < numHits && i < ShieldRenderParameters::MAX_SHIELD_HITS; ++i) {
const Hits &hit = m_hits[i];
s_renderParams.hitPos[i] = vector3f(hit.pos.x, hit.pos.y, hit.pos.z);
//Calculate the impact's radius dependant on time
Uint32 dif1 = hit.end - hit.start;
Uint32 dif2 = tickTime - hit.start;
//Range from start (0.0) to end (1.0)
float dif = float(dif2 / (dif1 * 1.0f));
s_renderParams.radii[i] = dif;
}
s_renderParams.numHits = m_hits.size();
}
// update the shield visibility
for (ShieldIterator it = m_shields.begin(); it != m_shields.end(); ++it) {
if (shieldStrength > 0.0f) {
it->m_mesh->SetNodeMask(SceneGraph::NODE_TRANSPARENT);
GetGlobalShieldMaterial()->specialParameter0 = &s_renderParams;
} else {
it->m_mesh->SetNodeMask(0x0);
}
}
}
void Shields::SetColor(const Color3ub &inCol)
{
for (ShieldIterator it = m_shields.begin(); it != m_shields.end(); ++it) {
it->m_colour = inCol;
}
}
void Shields::AddHit(const vector3d &hitPos)
{
Uint32 tickTime = SDL_GetTicks();
m_hits.push_back(Hits(hitPos, tickTime, tickTime + 1000));
}
SceneGraph::StaticGeometry *Shields::GetFirstShieldMesh()
{
for (ShieldIterator it = m_shields.begin(); it != m_shields.end(); ++it) {
if (it->m_mesh) {
return it->m_mesh.Get();
}
}
return nullptr;
}