389 lines
11 KiB
C++
389 lines
11 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 "DynamicBody.h"
|
|
|
|
#include "FixedGuns.h"
|
|
#include "Frame.h"
|
|
#include "GameSaveError.h"
|
|
#include "Json.h"
|
|
#include "Planet.h"
|
|
#include "Space.h"
|
|
#include "collider/CollisionContact.h"
|
|
#include "ship/Propulsion.h"
|
|
|
|
static const float KINETIC_ENERGY_MULT = 0.00001f;
|
|
const double DynamicBody::DEFAULT_DRAG_COEFF = 0.1; // 'smooth sphere'
|
|
|
|
DynamicBody::DynamicBody() :
|
|
ModelBody(),
|
|
m_propulsion(nullptr),
|
|
m_fixedGuns(nullptr)
|
|
{
|
|
m_dragCoeff = DEFAULT_DRAG_COEFF;
|
|
m_flags = Body::FLAG_CAN_MOVE_FRAME;
|
|
m_oldPos = GetPosition();
|
|
m_oldAngDisplacement = vector3d(0.0);
|
|
m_force = vector3d(0.0);
|
|
m_torque = vector3d(0.0);
|
|
m_vel = vector3d(0.0);
|
|
m_angVel = vector3d(0.0);
|
|
m_mass = 1;
|
|
m_angInertia = 1;
|
|
m_massRadius = 1;
|
|
m_isMoving = true;
|
|
m_atmosForce = vector3d(0.0);
|
|
m_gravityForce = vector3d(0.0);
|
|
m_externalForce = vector3d(0.0); // do external forces calc instead?
|
|
m_lastForce = vector3d(0.0);
|
|
m_lastTorque = vector3d(0.0);
|
|
m_aiMessage = AIError::AIERROR_NONE;
|
|
m_decelerating = false;
|
|
for (int i = 0; i < Feature::MAX_FEATURE; i++)
|
|
m_features[i] = false;
|
|
}
|
|
|
|
DynamicBody::DynamicBody(const Json &jsonObj, Space *space) :
|
|
ModelBody(jsonObj, space),
|
|
m_dragCoeff(DEFAULT_DRAG_COEFF),
|
|
m_externalForce(vector3d(0.0)),
|
|
m_atmosForce(vector3d(0.0)),
|
|
m_gravityForce(vector3d(0.0)),
|
|
m_lastForce(vector3d(0.0)),
|
|
m_lastTorque(vector3d(0.0)),
|
|
m_propulsion(nullptr),
|
|
m_fixedGuns(nullptr)
|
|
{
|
|
m_flags = Body::FLAG_CAN_MOVE_FRAME;
|
|
m_oldPos = GetPosition();
|
|
m_oldAngDisplacement = vector3d(0.0);
|
|
|
|
try {
|
|
Json dynamicBodyObj = jsonObj["dynamic_body"];
|
|
|
|
m_force = dynamicBodyObj["force"];
|
|
m_torque = dynamicBodyObj["torque"];
|
|
m_vel = dynamicBodyObj["vel"];
|
|
m_angVel = dynamicBodyObj["ang_vel"];
|
|
m_mass = dynamicBodyObj["mass"];
|
|
m_massRadius = dynamicBodyObj["mass_radius"];
|
|
m_angInertia = dynamicBodyObj["ang_inertia"];
|
|
m_isMoving = dynamicBodyObj["is_moving"];
|
|
} catch (Json::type_error &) {
|
|
throw SavedGameCorruptException();
|
|
}
|
|
|
|
m_aiMessage = AIError::AIERROR_NONE;
|
|
m_decelerating = false;
|
|
for (int i = 0; i < Feature::MAX_FEATURE; i++)
|
|
m_features[i] = false;
|
|
}
|
|
|
|
void DynamicBody::SaveToJson(Json &jsonObj, Space *space)
|
|
{
|
|
ModelBody::SaveToJson(jsonObj, space);
|
|
|
|
Json dynamicBodyObj = Json::object(); // Create JSON object to contain dynamic body data.
|
|
|
|
dynamicBodyObj["force"] = m_force;
|
|
dynamicBodyObj["torque"] = m_torque;
|
|
dynamicBodyObj["vel"] = m_vel;
|
|
dynamicBodyObj["ang_vel"] = m_angVel;
|
|
dynamicBodyObj["mass"] = m_mass;
|
|
dynamicBodyObj["mass_radius"] = m_massRadius;
|
|
dynamicBodyObj["ang_inertia"] = m_angInertia;
|
|
dynamicBodyObj["is_moving"] = m_isMoving;
|
|
|
|
jsonObj["dynamic_body"] = dynamicBodyObj; // Add dynamic body object to supplied object.
|
|
}
|
|
|
|
void DynamicBody::GetCurrentAtmosphericState(double &pressure, double &density) const
|
|
{
|
|
Frame *f = Frame::GetFrame(GetFrame());
|
|
Body *body = f->GetBody();
|
|
if (!body || !f->IsRotFrame() || !body->IsType(ObjectType::PLANET)) {
|
|
pressure = density = 0;
|
|
return;
|
|
}
|
|
Planet *planet = static_cast<Planet *>(body);
|
|
planet->GetAtmosphericState(GetPosition().Length(), &pressure, &density);
|
|
}
|
|
|
|
void DynamicBody::PostLoadFixup(Space *space)
|
|
{
|
|
Body::PostLoadFixup(space);
|
|
m_oldPos = GetPosition();
|
|
// CalcExternalForce(); // too dangerous
|
|
}
|
|
|
|
DynamicBody::~DynamicBody()
|
|
{
|
|
m_propulsion.Reset();
|
|
m_fixedGuns.Reset();
|
|
}
|
|
|
|
void DynamicBody::AddFeature(Feature f)
|
|
{
|
|
m_features[f] = true;
|
|
if (f == Feature::PROPULSION && m_propulsion == nullptr) {
|
|
m_propulsion.Reset(new Propulsion());
|
|
} else if (f == Feature::FIXED_GUNS && m_fixedGuns == nullptr) {
|
|
m_fixedGuns.Reset(new FixedGuns());
|
|
}
|
|
}
|
|
|
|
void DynamicBody::SetForce(const vector3d &f)
|
|
{
|
|
m_force = f;
|
|
}
|
|
|
|
void DynamicBody::AddForce(const vector3d &f)
|
|
{
|
|
m_force += f;
|
|
}
|
|
|
|
void DynamicBody::AddTorque(const vector3d &t)
|
|
{
|
|
m_torque += t;
|
|
}
|
|
|
|
void DynamicBody::AddRelForce(const vector3d &f)
|
|
{
|
|
m_force += GetOrient() * f;
|
|
}
|
|
|
|
void DynamicBody::AddRelTorque(const vector3d &t)
|
|
{
|
|
m_torque += GetOrient() * t;
|
|
}
|
|
|
|
const Propulsion *DynamicBody::GetPropulsion() const
|
|
{
|
|
assert(m_propulsion != nullptr);
|
|
return m_propulsion.Get();
|
|
}
|
|
|
|
Propulsion *DynamicBody::GetPropulsion()
|
|
{
|
|
assert(m_propulsion != nullptr);
|
|
return m_propulsion.Get();
|
|
}
|
|
|
|
const FixedGuns *DynamicBody::GetFixedGuns() const
|
|
{
|
|
assert(m_fixedGuns != nullptr);
|
|
return m_fixedGuns.Get();
|
|
}
|
|
|
|
FixedGuns *DynamicBody::GetFixedGuns()
|
|
{
|
|
assert(m_fixedGuns != nullptr);
|
|
return m_fixedGuns.Get();
|
|
}
|
|
|
|
void DynamicBody::SetTorque(const vector3d &t)
|
|
{
|
|
m_torque = t;
|
|
}
|
|
|
|
void DynamicBody::SetMass(double mass)
|
|
{
|
|
m_mass = mass;
|
|
// This is solid sphere mass distribution, my friend
|
|
m_angInertia = (2 / 5.0) * m_mass * m_massRadius * m_massRadius;
|
|
}
|
|
|
|
void DynamicBody::SetFrame(FrameId fId)
|
|
{
|
|
ModelBody::SetFrame(fId);
|
|
// external forces will be wrong after frame transition
|
|
m_externalForce = m_gravityForce = m_atmosForce = vector3d(0.0);
|
|
}
|
|
|
|
double DynamicBody::CalcAtmosphericDrag(double velSqr, double area, double coeff) const
|
|
{
|
|
double pressure, density;
|
|
GetCurrentAtmosphericState(pressure, density);
|
|
|
|
// Simplified calculation of atmospheric drag/lift force.
|
|
return density > 0 ? 0.5 * density * velSqr * area * coeff : 0;
|
|
}
|
|
|
|
vector3d DynamicBody::CalcAtmosphericForce() const
|
|
{
|
|
vector3d dragDir = -m_vel.NormalizedSafe();
|
|
|
|
// We assume the object is a perfect sphere in the size of the clip radius.
|
|
// Most things are /not/ using the default DynamicBody code, but this is still better than before.
|
|
return CalcAtmosphericDrag(m_vel.LengthSqr(), GetClipRadius() * GetClipRadius() * M_PI, m_dragCoeff) * dragDir;
|
|
}
|
|
|
|
void DynamicBody::CalcExternalForce()
|
|
{
|
|
// gravity
|
|
Frame *f = Frame::GetFrame(GetFrame());
|
|
if (!f) return; // no external force if not in a frame
|
|
Body *body = f->GetBody();
|
|
if (body && !body->IsType(ObjectType::SPACESTATION)) { // they ought to have mass though...
|
|
vector3d b1b2 = GetPosition();
|
|
double m1m2 = GetMass() * body->GetMass();
|
|
double invrsqr = 1.0 / b1b2.LengthSqr();
|
|
double force = G * m1m2 * invrsqr;
|
|
m_externalForce = -b1b2 * sqrt(invrsqr) * force;
|
|
} else
|
|
m_externalForce = vector3d(0.0);
|
|
m_gravityForce = m_externalForce;
|
|
|
|
// atmospheric drag
|
|
if (body && f->IsRotFrame() && body->IsType(ObjectType::PLANET)) {
|
|
vector3d fAtmoForce = CalcAtmosphericForce();
|
|
|
|
// make this a bit less daft at high time accel
|
|
// only allow atmosForce to increase by .1g per frame
|
|
// TODO: clamp fAtmoForce instead.
|
|
vector3d f1g = m_atmosForce + fAtmoForce.NormalizedSafe() * GetMass();
|
|
if (fAtmoForce.LengthSqr() > f1g.LengthSqr())
|
|
m_atmosForce = f1g;
|
|
else
|
|
m_atmosForce = fAtmoForce;
|
|
|
|
m_externalForce += m_atmosForce;
|
|
} else
|
|
m_atmosForce = vector3d(0.0);
|
|
|
|
// centrifugal and coriolis forces for rotating frames
|
|
if (f->IsRotFrame()) {
|
|
vector3d angRot(0, f->GetAngSpeed(), 0);
|
|
m_externalForce -= m_mass * angRot.Cross(angRot.Cross(GetPosition())); // centrifugal
|
|
m_externalForce -= 2 * m_mass * angRot.Cross(GetVelocity()); // coriolis
|
|
}
|
|
}
|
|
|
|
void DynamicBody::TimeStepUpdate(const float timeStep)
|
|
{
|
|
m_oldPos = GetPosition();
|
|
if (m_isMoving) {
|
|
m_force += m_externalForce;
|
|
|
|
m_vel += double(timeStep) * m_force * (1.0 / m_mass);
|
|
m_angVel += double(timeStep) * m_torque * (1.0 / m_angInertia);
|
|
|
|
double len = m_angVel.Length();
|
|
if (len > 1e-16) {
|
|
vector3d axis = m_angVel * (1.0 / len);
|
|
matrix3x3d r = matrix3x3d::Rotate(len * timeStep, axis);
|
|
SetOrient(r * GetOrient());
|
|
}
|
|
m_oldAngDisplacement = m_angVel * timeStep;
|
|
|
|
SetPosition(GetPosition() + m_vel * double(timeStep));
|
|
|
|
//if (this->IsType(ObjectType::PLAYER))
|
|
//Output("pos = %.1f,%.1f,%.1f, vel = %.1f,%.1f,%.1f, force = %.1f,%.1f,%.1f, external = %.1f,%.1f,%.1f\n",
|
|
// pos.x, pos.y, pos.z, m_vel.x, m_vel.y, m_vel.z, m_force.x, m_force.y, m_force.z,
|
|
// m_externalForce.x, m_externalForce.y, m_externalForce.z);
|
|
|
|
m_lastForce = m_force;
|
|
m_lastTorque = m_torque;
|
|
m_force = vector3d(0.0);
|
|
m_torque = vector3d(0.0);
|
|
CalcExternalForce(); // regenerate for new pos/vel
|
|
} else {
|
|
m_oldAngDisplacement = vector3d(0.0);
|
|
}
|
|
|
|
ModelBody::TimeStepUpdate(timeStep);
|
|
}
|
|
|
|
void DynamicBody::UpdateInterpTransform(double alpha)
|
|
{
|
|
m_interpPos = alpha * GetPosition() + (1.0 - alpha) * m_oldPos;
|
|
|
|
double len = m_oldAngDisplacement.Length() * (1.0 - alpha);
|
|
if (len > 1e-16) {
|
|
vector3d axis = m_oldAngDisplacement.Normalized();
|
|
matrix3x3d rot = matrix3x3d::Rotate(-len, axis); // rotate backwards
|
|
m_interpOrient = rot * GetOrient();
|
|
} else
|
|
m_interpOrient = GetOrient();
|
|
}
|
|
|
|
void DynamicBody::SetMassDistributionFromModel()
|
|
{
|
|
CollMesh *m = GetCollMesh();
|
|
// XXX totally arbitrarily pick to distribute mass over a half
|
|
// bounding sphere area
|
|
m_massRadius = m->GetRadius() * 0.5f;
|
|
SetMass(m_mass);
|
|
}
|
|
|
|
vector3d DynamicBody::GetAngularMomentum() const
|
|
{
|
|
return m_angInertia * m_angVel;
|
|
}
|
|
|
|
vector3d DynamicBody::GetVelocity() const
|
|
{
|
|
return m_vel;
|
|
}
|
|
|
|
void DynamicBody::SetVelocity(const vector3d &v)
|
|
{
|
|
m_vel = v;
|
|
}
|
|
|
|
vector3d DynamicBody::GetAngVelocity() const
|
|
{
|
|
return m_angVel;
|
|
}
|
|
|
|
void DynamicBody::SetAngVelocity(const vector3d &v)
|
|
{
|
|
m_angVel = v;
|
|
}
|
|
|
|
bool DynamicBody::OnCollision(Body *o, Uint32 flags, double relVel)
|
|
{
|
|
// don't bother doing collision damage from a missile that will now explode, or may have already
|
|
// also avoids an occasional race condition where destruction event of this could be queued twice
|
|
// returning true to ensure that the missile can react to the collision
|
|
if (o->IsType(ObjectType::MISSILE)) return true;
|
|
|
|
double kineticEnergy = 0;
|
|
if (o->IsType(ObjectType::DYNAMICBODY)) {
|
|
kineticEnergy = KINETIC_ENERGY_MULT * static_cast<DynamicBody *>(o)->GetMass() * relVel * relVel;
|
|
} else {
|
|
kineticEnergy = KINETIC_ENERGY_MULT * m_mass * relVel * relVel;
|
|
}
|
|
|
|
// damage (kineticEnergy is being passed as a damage value) is measured in kilograms
|
|
// ignore damage less than a gram except for cargo, which is very fragile.
|
|
CollisionContact dummy;
|
|
if (this->IsType(ObjectType::CARGOBODY)) {
|
|
OnDamage(o, float(kineticEnergy), dummy);
|
|
} else if (kineticEnergy > 1e-3) {
|
|
OnDamage(o, float(kineticEnergy), dummy);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// return parameters for orbit of any body, gives both elliptic and hyperbolic trajectories
|
|
Orbit DynamicBody::ComputeOrbit() const
|
|
{
|
|
auto f = Frame::GetFrame(GetFrame());
|
|
// if we are in a rotating frame, then dynamic body currently under the
|
|
// influence of a rotational frame, therefore getting the orbital parameters
|
|
// is not appropriate, return the orbit as a fixed point
|
|
if (f->IsRotFrame()) return Orbit::ForStaticBody(GetPosition());
|
|
FrameId nrFrameId = f->GetId();
|
|
const Frame *nrFrame = Frame::GetFrame(nrFrameId);
|
|
const double mass = nrFrame->GetSystemBody()->GetMass();
|
|
|
|
// current velocity and position with respect to non-rotating frame
|
|
const vector3d vel = GetVelocityRelTo(nrFrameId);
|
|
const vector3d pos = GetPositionRelTo(nrFrameId);
|
|
|
|
return Orbit::FromBodyState(pos, vel, mass);
|
|
}
|