stk-code_catmod/src/tracks/track_object.cpp

727 lines
25 KiB
C++

//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2013-2015 Joerg Henrichs, Marianne Gagnon
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "tracks/track_object.hpp"
#include "animations/three_d_animation.hpp"
#include "graphics/central_settings.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/lod_node.hpp"
#include "graphics/material.hpp"
#include "graphics/material_manager.hpp"
#include "graphics/render_info.hpp"
#include "graphics/sp/sp_mesh_buffer.hpp"
#include "graphics/sp/sp_mesh_node.hpp"
#include "io/file_manager.hpp"
#include "io/xml_node.hpp"
#include "input/device_manager.hpp"
#include "items/item_manager.hpp"
#include "physics/physical_object.hpp"
#include "race/race_manager.hpp"
#include "scriptengine/script_engine.hpp"
#include "tracks/track.hpp"
#include "tracks/model_definition_loader.hpp"
#include <IAnimatedMeshSceneNode.h>
#include <ISceneManager.h>
/** A track object: any additional object on the track. This object implements
* a graphics-only representation, i.e. there is no physical representation.
* Derived classes can implement a physical representation (see
* physics/physical_object) or animations.
* \param xml_node The xml node from which the initial data is taken. This is
* for now: initial position, initial rotation, name of the
* model, enable/disable status, timer information.
* \param lod_node Lod node (defaults to NULL).
*/
TrackObject::TrackObject(const XMLNode &xml_node, scene::ISceneNode* parent,
ModelDefinitionLoader& model_def_loader,
TrackObject* parent_library)
{
init(xml_node, parent, model_def_loader, parent_library);
} // TrackObject
// ----------------------------------------------------------------------------
/**
* \param is_dynamic Only if interaction == 'movable', i.e. the object is
* affected by physics
* \param physics_settings If interaction != 'ghost'
*/
TrackObject::TrackObject(const core::vector3df& xyz, const core::vector3df& hpr,
const core::vector3df& scale, const char* interaction,
TrackObjectPresentation* presentation,
bool is_dynamic,
const PhysicalObject::Settings* physics_settings)
{
m_init_xyz = xyz;
m_init_hpr = hpr;
m_init_scale = scale;
m_enabled = true;
m_presentation = NULL;
m_animator = NULL;
m_physical_object = NULL;
m_parent_library = NULL;
m_interaction = interaction;
m_presentation = presentation;
m_is_driveable = false;
m_soccer_ball = false;
m_initially_visible = false;
m_type = "";
if (m_interaction != "ghost" && m_interaction != "none" &&
physics_settings )
{
m_physical_object = new PhysicalObject(is_dynamic,
*physics_settings,
this);
}
reset();
} // TrackObject
// ----------------------------------------------------------------------------
/** Initialises the track object based on the specified XML data.
* \param xml_node The XML data.
* \param parent The parent scene node.
* \param model_def_loader Used to load level-of-detail nodes.
*/
void TrackObject::init(const XMLNode &xml_node, scene::ISceneNode* parent,
ModelDefinitionLoader& model_def_loader,
TrackObject* parent_library)
{
m_init_xyz = core::vector3df(0,0,0);
m_init_hpr = core::vector3df(0,0,0);
m_init_scale = core::vector3df(1,1,1);
m_enabled = true;
m_initially_visible = false;
m_presentation = NULL;
m_animator = NULL;
m_parent_library = parent_library;
m_physical_object = NULL;
xml_node.get("id", &m_id );
xml_node.get("model", &m_name );
xml_node.get("xyz", &m_init_xyz );
xml_node.get("hpr", &m_init_hpr );
xml_node.get("scale", &m_init_scale);
xml_node.get("enabled", &m_enabled );
m_interaction = "static";
xml_node.get("interaction", &m_interaction);
xml_node.get("lod_group", &m_lod_group);
m_is_driveable = false;
xml_node.get("driveable", &m_is_driveable);
bool lod_instance = false;
xml_node.get("lod_instance", &lod_instance);
m_soccer_ball = false;
xml_node.get("soccer_ball", &m_soccer_ball);
std::string type;
xml_node.get("type", &type );
m_type = type;
m_initially_visible = true;
xml_node.get("if", &m_visibility_condition);
if (m_visibility_condition == "false")
{
m_initially_visible = false;
}
if (!m_initially_visible)
setEnabled(false);
if (xml_node.getName() == "particle-emitter")
{
m_type = "particle-emitter";
m_presentation = new TrackObjectPresentationParticles(xml_node, parent);
}
else if (xml_node.getName() == "light")
{
m_type = "light";
m_presentation = new TrackObjectPresentationLight(xml_node, parent);
}
else if (xml_node.getName() == "library")
{
xml_node.get("name", &m_name);
m_presentation = new TrackObjectPresentationLibraryNode(this, xml_node, model_def_loader);
if (parent_library != NULL)
{
Track::getCurrentTrack()->addMetaLibrary(parent_library, this);
}
}
else if (type == "sfx-emitter")
{
// FIXME: at this time sound emitters are just disabled in multiplayer
// otherwise the sounds would be constantly heard
if (race_manager->getNumLocalPlayers() < 2)
m_presentation = new TrackObjectPresentationSound(xml_node, parent);
}
else if (type == "action-trigger")
{
std::string action;
xml_node.get("action", &action);
m_name = action; //adds action as name so that it can be found by using getName()
m_presentation = new TrackObjectPresentationActionTrigger(xml_node, parent_library);
}
else if (type == "billboard")
{
m_presentation = new TrackObjectPresentationBillboard(xml_node, parent);
}
else if (type=="cutscene_camera")
{
m_presentation = new TrackObjectPresentationEmpty(xml_node);
}
else
{
// Colorization settings
std::string model_name;
xml_node.get("model", &model_name);
#ifndef SERVER_ONLY
if (CVS->isGLSL())
{
scene::IMesh* mesh = NULL;
// Use the first material in mesh to determine hue
Material* colorized = NULL;
if (model_name.size() > 0)
{
mesh = irr_driver->getMesh(model_name);
if (mesh != NULL)
{
for (u32 j = 0; j < mesh->getMeshBufferCount(); j++)
{
SP::SPMeshBuffer* mb = static_cast<SP::SPMeshBuffer*>
(mesh->getMeshBuffer(j));
std::vector<Material*> mbs = mb->getAllSTKMaterials();
for (Material* m : mbs)
{
if (m->isColorizable() && m->hasRandomHue())
{
colorized = m;
break;
}
}
if (colorized != NULL)
{
break;
}
}
}
}
else
{
std::string group_name = "";
xml_node.get("lod_group", &group_name);
// Try to get the first mesh from lod groups
mesh = model_def_loader.getFirstMeshFor(group_name);
if (mesh != NULL)
{
for (u32 j = 0; j < mesh->getMeshBufferCount(); j++)
{
SP::SPMeshBuffer* mb = static_cast<SP::SPMeshBuffer*>
(mesh->getMeshBuffer(j));
std::vector<Material*> mbs = mb->getAllSTKMaterials();
for (Material* m : mbs)
{
if (m->isColorizable() && m->hasRandomHue())
{
colorized = m;
break;
}
}
if (colorized != NULL)
{
break;
}
}
}
}
// If at least one material is colorizable, add RenderInfo for it
if (colorized != NULL)
{
const float hue = colorized->getRandomHue();
if (hue > 0.0f)
{
m_render_info = std::make_shared<RenderInfo>(hue);
}
}
}
#endif
scene::ISceneNode *glownode = NULL;
bool is_movable = false;
if (lod_instance)
{
m_type = "lod";
TrackObjectPresentationLOD* lod_node =
new TrackObjectPresentationLOD(xml_node, parent, model_def_loader, m_render_info);
m_presentation = lod_node;
LODNode* node = (LODNode*)lod_node->getNode();
if (type == "movable" && parent != NULL)
{
// HACK: unparent movables from their parent library object if any,
// because bullet provides absolute transforms, not transforms relative
// to the parent object
node->updateAbsolutePosition();
core::matrix4 absTransform = node->getAbsoluteTransformation();
node->setParent(irr_driver->getSceneManager()->getRootSceneNode());
node->setPosition(absTransform.getTranslation());
node->setRotation(absTransform.getRotationDegrees());
node->setScale(absTransform.getScale());
}
glownode = node->getAllNodes()[0];
}
else
{
m_type = "mesh";
m_presentation = new TrackObjectPresentationMesh(xml_node,
m_enabled,
parent,
m_render_info);
scene::ISceneNode* node = ((TrackObjectPresentationMesh *)m_presentation)->getNode();
if (type == "movable" && parent != NULL)
{
// HACK: unparent movables from their parent library object if any,
// because bullet provides absolute transforms, not transforms relative
// to the parent object
node->updateAbsolutePosition();
core::matrix4 absTransform = node->getAbsoluteTransformation();
node->setParent(irr_driver->getSceneManager()->getRootSceneNode());
node->setPosition(absTransform.getTranslation());
// Doesn't seem necessary to set rotation here, TODO: not sure why
//node->setRotation(absTransform.getRotationDegrees());
node->setScale(absTransform.getScale());
is_movable = true;
}
glownode = node;
}
std::string render_pass;
xml_node.get("renderpass", &render_pass);
if (m_interaction != "ghost" && m_interaction != "none" &&
render_pass != "skybox" )
{
m_physical_object = PhysicalObject::fromXML(type == "movable",
xml_node,
this);
}
if (parent_library != NULL)
{
if (is_movable)
parent_library->addMovableChild(this);
else
parent_library->addChild(this);
}
video::SColor glow;
if (xml_node.get("glow", &glow) && glownode)
{
float r, g, b;
r = glow.getRed() / 255.0f;
g = glow.getGreen() / 255.0f;
b = glow.getBlue() / 255.0f;
SP::SPMeshNode* spmn = dynamic_cast<SP::SPMeshNode*>(glownode);
if (spmn)
{
spmn->setGlowColor(video::SColorf(r, g, b));
}
}
bool is_in_shadowpass = true;
if (xml_node.get("shadow-pass", &is_in_shadowpass) && glownode)
{
SP::SPMeshNode* spmn = dynamic_cast<SP::SPMeshNode*>(glownode);
if (spmn)
{
spmn->setInShadowPass(is_in_shadowpass);
}
}
bool forcedbloom = false;
if (xml_node.get("forcedbloom", &forcedbloom) && forcedbloom && glownode)
{
float power = 1;
xml_node.get("bloompower", &power);
btClamp(power, 0.5f, 10.0f);
irr_driver->addForcedBloomNode(glownode, power);
}
}
if (type == "animation" || xml_node.hasChildNamed("curve"))
{
m_animator = new ThreeDAnimation(xml_node, this);
}
reset();
if (!m_initially_visible)
setEnabled(false);
if (parent_library != NULL && !parent_library->isEnabled())
setEnabled(false);
} // TrackObject
// ----------------------------------------------------------------------------
void TrackObject::onWorldReady()
{
if (m_visibility_condition == "false")
{
m_initially_visible = false;
}
else if (m_visibility_condition.size() > 0)
{
unsigned char result = -1;
Scripting::ScriptEngine* script_engine =
Scripting::ScriptEngine::getInstance();
std::ostringstream fn_signature;
std::vector<std::string> arguments;
if (m_visibility_condition.find("(") != std::string::npos &&
m_visibility_condition.find(")") != std::string::npos)
{
// There are arguments to pass to the function
// TODO: For the moment we only support string arguments
// TODO: this parsing could be improved
unsigned first = (unsigned)m_visibility_condition.find("(");
unsigned last = (unsigned)m_visibility_condition.find_last_of(")");
std::string fn_name = m_visibility_condition.substr(0, first);
std::string str_arguments = m_visibility_condition.substr(first + 1, last - first - 1);
arguments = StringUtils::split(str_arguments, ',');
fn_signature << "bool " << fn_name << "(";
for (unsigned int i = 0; i < arguments.size(); i++)
{
if (i > 0)
fn_signature << ",";
fn_signature << "string";
}
fn_signature << ",Track::TrackObject@)";
}
else
{
fn_signature << "bool " << m_visibility_condition << "(Track::TrackObject@)";
}
TrackObject* self = this;
script_engine->runFunction(true, fn_signature.str(),
[&](asIScriptContext* ctx)
{
for (unsigned int i = 0; i < arguments.size(); i++)
{
ctx->SetArgObject(i, &arguments[i]);
}
ctx->SetArgObject((int)arguments.size(), self);
},
[&](asIScriptContext* ctx) { result = ctx->GetReturnByte(); });
if (result == 0)
m_initially_visible = false;
}
if (!m_initially_visible)
setEnabled(false);
}
// ----------------------------------------------------------------------------
/** Destructor. Removes the node from the scene graph, and also
* drops the textures of the mesh. Sound buffers are also freed.
*/
TrackObject::~TrackObject()
{
delete m_presentation;
delete m_animator;
delete m_physical_object;
} // ~TrackObject
// ----------------------------------------------------------------------------
/** Initialises an object before a race starts.
*/
void TrackObject::reset()
{
if (m_presentation ) m_presentation->reset();
if (m_animator ) m_animator->reset();
if (m_physical_object) m_physical_object->reset();
} // reset
// ----------------------------------------------------------------------------
/** Enables or disables this object. This affects the visibility, i.e.
* disabled objects will not be displayed anymore.
* \param mode Enable (true) or disable (false) this object.
*/
void TrackObject::setEnabled(bool enabled)
{
m_enabled = enabled;
if (m_presentation != NULL)
m_presentation->setEnable(m_enabled);
if (getType() == "mesh")
{
if (m_physical_object != NULL)
{
if (enabled)
m_physical_object->addBody();
else
m_physical_object->removeBody();
}
}
for (unsigned int i = 0; i < m_movable_children.size(); i++)
{
m_movable_children[i]->setEnabled(enabled);
}
} // setEnable
// ----------------------------------------------------------------------------
void TrackObject::resetEnabled()
{
m_enabled = m_initially_visible;
if (m_presentation != NULL)
m_presentation->setEnable(m_initially_visible);
if (getType() == "mesh")
{
if (m_physical_object != NULL)
{
if (m_initially_visible)
m_physical_object->addBody();
else
m_physical_object->removeBody();
}
}
for (unsigned int i = 0; i < m_movable_children.size(); i++)
{
m_movable_children[i]->resetEnabled();
}
}
// ----------------------------------------------------------------------------
void TrackObject::update(float dt)
{
if (m_presentation) m_presentation->update(dt);
if (m_physical_object) m_physical_object->update(dt);
if (m_animator) m_animator->update(dt);
} // update
// ----------------------------------------------------------------------------
/** Does a raycast against the track object. The object must have a physical
* object.
* \param from/to The from and to position for the raycast.
* \param xyz The position in world where the ray hit.
* \param material The material of the mesh that was hit.
* \param normal The intrapolated normal at that position.
* \param interpolate_normal If true, the returned normal is the interpolated
* based on the three normals of the triangle and the location of the
* hit point (which is more compute intensive, but results in much
* smoother results).
* \return True if a triangle was hit, false otherwise (and no output
* variable will be set.
*/
bool TrackObject::castRay(const btVector3 &from,
const btVector3 &to, btVector3 *hit_point,
const Material **material, btVector3 *normal,
bool interpolate_normal) const
{
if(!m_physical_object)
{
Log::warn("TrackObject", "Can't raycast on non-physical object.");
return false;
}
return m_physical_object->castRay(from, to, hit_point, material, normal,
interpolate_normal);
} // castRay
// ----------------------------------------------------------------------------
void TrackObject::move(const core::vector3df& xyz, const core::vector3df& hpr,
const core::vector3df& scale, bool update_rigid_body,
bool isAbsoluteCoord)
{
if (m_presentation != NULL)
m_presentation->move(xyz, hpr, scale, isAbsoluteCoord);
if (update_rigid_body && m_physical_object != NULL)
{
movePhysicalBodyToGraphicalNode(xyz, hpr);
}
} // move
// ----------------------------------------------------------------------------
void TrackObject::movePhysicalBodyToGraphicalNode(const core::vector3df& xyz, const core::vector3df& hpr)
{
// If we set a bullet position from an irrlicht position, we need to
// get the absolute transform from the presentation object (as set in
// the line before), since xyz etc here are only relative to a
// potential parent scene node.
TrackObjectPresentationSceneNode *tops =
dynamic_cast<TrackObjectPresentationSceneNode*>(m_presentation);
if (tops)
{
const core::matrix4 &m = tops->getNode()
->getAbsoluteTransformation();
m_physical_object->move(m.getTranslation(), m.getRotationDegrees());
}
else
{
m_physical_object->move(xyz, hpr);
}
}
// ----------------------------------------------------------------------------
const core::vector3df& TrackObject::getPosition() const
{
if (m_presentation != NULL)
return m_presentation->getPosition();
else
return m_init_xyz;
} // getPosition
// ----------------------------------------------------------------------------
const core::vector3df TrackObject::getAbsoluteCenterPosition() const
{
if (m_presentation != NULL)
return m_presentation->getAbsoluteCenterPosition();
else
return m_init_xyz;
} // getAbsolutePosition
// ----------------------------------------------------------------------------
const core::vector3df TrackObject::getAbsolutePosition() const
{
if (m_presentation != NULL)
return m_presentation->getAbsolutePosition();
else
return m_init_xyz;
} // getAbsolutePosition
// ----------------------------------------------------------------------------
const core::vector3df& TrackObject::getRotation() const
{
if (m_presentation != NULL)
return m_presentation->getRotation();
else
return m_init_xyz;
} // getRotation
// ----------------------------------------------------------------------------
const core::vector3df& TrackObject::getScale() const
{
if (m_presentation != NULL)
return m_presentation->getScale();
else
return m_init_scale;
} // getScale
// ----------------------------------------------------------------------------
void TrackObject::addMovableChild(TrackObject* child)
{
if (!m_enabled)
child->setEnabled(false);
m_movable_children.push_back(child);
}
// ----------------------------------------------------------------------------
void TrackObject::addChild(TrackObject* child)
{
if (!m_enabled)
child->setEnabled(false);
m_children.push_back(child);
}
// ----------------------------------------------------------------------------
// scripting function
void TrackObject::moveTo(const Scripting::SimpleVec3* pos, bool isAbsoluteCoord)
{
TrackObjectPresentationLibraryNode *libnode =
dynamic_cast<TrackObjectPresentationLibraryNode*>(m_presentation);
if (libnode != NULL)
{
libnode->move(core::vector3df(pos->getX(), pos->getY(), pos->getZ()),
core::vector3df(0.0f, 0.0f, 0.0f), // TODO: preserve rotation
core::vector3df(1.0f, 1.0f, 1.0f), // TODO: preserve scale
isAbsoluteCoord,
true /* moveChildrenPhysicalBodies */);
}
else
{
move(core::vector3df(pos->getX(), pos->getY(), pos->getZ()),
core::vector3df(0.0f, 0.0f, 0.0f), // TODO: preserve rotation
core::vector3df(1.0f, 1.0f, 1.0f), // TODO: preserve scale
true, // updateRigidBody
isAbsoluteCoord);
}
}
// ----------------------------------------------------------------------------
scene::IAnimatedMeshSceneNode* TrackObject::getMesh()
{
if (getPresentation<TrackObjectPresentationLOD>())
{
LODNode* ln = dynamic_cast<LODNode*>
(getPresentation<TrackObjectPresentationLOD>()->getNode());
if (ln && !ln->getAllNodes().empty())
{
scene::IAnimatedMeshSceneNode* an =
dynamic_cast<scene::IAnimatedMeshSceneNode*>
(ln->getFirstNode());
if (an)
{
return an;
}
}
}
else if (getPresentation<TrackObjectPresentationMesh>())
{
scene::IAnimatedMeshSceneNode* an =
dynamic_cast<scene::IAnimatedMeshSceneNode*>
(getPresentation<TrackObjectPresentationMesh>()->getNode());
if (an)
{
return an;
}
}
Log::debug("TrackObject", "No animated mesh");
return NULL;
} // getMesh