Changeset View
Changeset View
Standalone View
Standalone View
source/simulation2/components/CCmpPhysics.cpp
- This file was added.
/* Copyright (C) 2020 Wildfire Games. | |||||
* This file is part of 0 A.D. | |||||
* | |||||
* 0 A.D. 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 2 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* 0 A.D. 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 0 A.D. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#include "precompiled.h" | |||||
#include "simulation2/system/Component.h" | |||||
#include "ICmpPhysics.h" | |||||
#include "simulation2/MessageTypes.h" | |||||
#include "ICmpPosition.h" | |||||
#include "ICmpTerrain.h" | |||||
#include "ICmpWaterManager.h" | |||||
#include "ps/Profile.h" | |||||
/** | |||||
* Fairly basic physics implementation, for units and buildings etc. | |||||
*/ | |||||
class CCmpPhysics : public ICmpPhysics | |||||
{ | |||||
public: | |||||
static void ClassInit(CComponentManager& UNUSED(componentManager)) | |||||
{ | |||||
} | |||||
DEFAULT_COMPONENT_ALLOCATOR(Physics) | |||||
bool m_Active; | |||||
entity_pos_t m_AirFriction; | |||||
entity_pos_t m_GroundFriction; | |||||
entity_pos_t m_Gravity; | |||||
entity_pos_t m_Mass; | |||||
entity_pos_t m_MaxForce; | |||||
entity_pos_t m_MomentOfInertia; | |||||
CVector3D m_Energy; | |||||
static std::string GetSchema() | |||||
{ | |||||
return | |||||
"<element name='Active' a:help='If false, the entity will not react to any physics calls.'>" | |||||
"<data type='boolean'/>" | |||||
"</element>" | |||||
"<element name='AirFriction' a:help='An approximation of the friction with the air to determine flying range. 0 Means no drag, 1 means direct stop.'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>" | |||||
"<element name='Gravity' a:help='The gravity in m/s^2 that is affecting this entity. Should be defined per map IMHO.'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>" | |||||
"<element name='GroundFriction' a:help='An approximation of the friction with the ground. 0 Means no drag, 1 means direct stop.'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>" | |||||
"<element name='Mass' a:help='Mass of the entity, in kg. ~500 for an average horse, ~100 for the humans. Should include armour and such.'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>" | |||||
"<element name='MaxForce' a:help='Maximum of the force an entity can exert, in N. ~7000 for a strong horse, ~1000 for an average well-trained soldier.'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>" | |||||
"<element name='MomentOfInertia' a:help='Angular mass of the entity, in kg*m^-2. Approximated with 1/2 m r^2. Determines the turning speed of an entity.'>" | |||||
"<ref name='nonNegativeDecimal'/>" | |||||
"</element>"; | |||||
} | |||||
virtual void Init(const CParamNode& paramNode) | |||||
{ | |||||
m_Active = paramNode.GetChild("Active").ToBool(); | |||||
m_AirFriction = paramNode.GetChild("AirFriction").ToFixed(); | |||||
m_GroundFriction = paramNode.GetChild("GroundFriction").ToFixed(); | |||||
m_Gravity = paramNode.GetChild("Gravity").ToFixed(); | |||||
m_Mass = paramNode.GetChild("Mass").ToFixed(); | |||||
m_MaxForce = paramNode.GetChild("MaxForce").ToFixed(); | |||||
m_MomentOfInertia = paramNode.GetChild("MomentOfInertia").ToFixed(); | |||||
if (m_Active) | |||||
GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_Update, this, true); | |||||
} | |||||
virtual void Deinit() | |||||
{ | |||||
} | |||||
virtual void Serialize(ISerializer& serialize) | |||||
{ | |||||
serialize.Bool("active", m_Active); | |||||
serialize.NumberFixed_Unbounded("frictionair", m_AirFriction); | |||||
serialize.NumberFixed_Unbounded("gravity", m_Gravity); | |||||
serialize.NumberFixed_Unbounded("frictionground", m_GroundFriction); | |||||
serialize.NumberFixed_Unbounded("mass", m_Mass); | |||||
serialize.NumberFixed_Unbounded("maxforce", m_MaxForce); | |||||
serialize.NumberFixed_Unbounded("momentofinertia", m_MomentOfInertia); | |||||
serialize.NumberFloat_Unbounded("forcex", m_Energy.X); | |||||
serialize.NumberFloat_Unbounded("forcey", m_Energy.Y); | |||||
serialize.NumberFloat_Unbounded("forcez", m_Energy.Z); | |||||
} | |||||
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) | |||||
{ | |||||
Init(paramNode); | |||||
deserialize.Bool("active", m_Active); | |||||
deserialize.NumberFixed_Unbounded("frictionair", m_AirFriction); | |||||
deserialize.NumberFixed_Unbounded("gravity", m_Gravity); | |||||
deserialize.NumberFixed_Unbounded("frictionground", m_GroundFriction); | |||||
deserialize.NumberFixed_Unbounded("mass", m_Mass); | |||||
deserialize.NumberFixed_Unbounded("maxforce", m_MaxForce); | |||||
deserialize.NumberFixed_Unbounded("momentofinertia", m_MomentOfInertia); | |||||
deserialize.NumberFloat_Unbounded("forcex", m_Energy.X); | |||||
deserialize.NumberFloat_Unbounded("forcey", m_Energy.Y); | |||||
deserialize.NumberFloat_Unbounded("forcez", m_Energy.Z); | |||||
} | |||||
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) | |||||
{ | |||||
switch (msg.GetType()) | |||||
{ | |||||
case MT_Update: | |||||
{ | |||||
PROFILE("Physics::Update"); | |||||
if (!m_Active) | |||||
break; | |||||
Update(static_cast<const CMessageUpdate&> (msg).turnLength); | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
virtual void Update(fixed turnLength) | |||||
{ | |||||
// When there is no net energy, there is nothing to do. | |||||
if (m_Energy.X == 0 && m_Energy.Y == 0 && m_Energy.Z == 0) | |||||
return; | |||||
// If there's no position don't do anything. | |||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | |||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | |||||
return; | |||||
CVector3D initialPosition = cmpPosition->GetPosition(); | |||||
// The "10" below is determined by trail and error, it gives enough iterations | |||||
// to describe motion and doesn't seem too slow. | |||||
float dt = turnLength.ToFloat() / 10; | |||||
float weight = m_Mass.ToFloat() * m_Gravity.ToFloat(); | |||||
CVector3D newPosition; | |||||
newPosition = initialPosition + m_Energy * dt; | |||||
CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity()); | |||||
CmpPtr<ICmpWaterManager> cmpWaterMgr(GetSystemEntity()); | |||||
float groundLevel = cmpTerrain->GetExactGroundLevel(newPosition.X, newPosition.Z); | |||||
float waterLevel = cmpWaterMgr->GetExactWaterLevel(newPosition.X, newPosition.Z); | |||||
// Part of the kinetic energy converted to potential energy when ascending. | |||||
// And vice versa but opposite direction when falling down. | |||||
float dh = (fixed::FromFloat((newPosition.Y - initialPosition.Y)).Absolute()).ToFloat(); | |||||
m_Energy.Y -= weight * dh * dt; | |||||
LOGERROR("%s", (fixed::FromFloat(m_Energy.Y)).ToString()); | |||||
// ToDo: Fix entities landing on water. | |||||
// They should just try to stand on the ground actually, | |||||
// unless they are dead, then they should float. So that should | |||||
// be done in CCmpDecay probably. | |||||
// However, when going too deep a unit should die. | |||||
if (newPosition.Y < waterLevel && waterLevel > groundLevel && m_Energy.Y <= 0) | |||||
{ | |||||
Silier: should be checked if that tile is walkable else unit would stuck probably | |||||
cmpPosition->SetFloating(true); | |||||
newPosition.Y = waterLevel; | |||||
Not Done Inline Actionsget unit footprint and compare with water level Silier: get unit footprint and compare with water level | |||||
// Drain all energy when landing on water. | |||||
m_Energy *= 0; | |||||
} | |||||
else if (newPosition.Y > groundLevel) | |||||
{ | |||||
// Simplified Stokes' drag. | |||||
m_Energy -= m_Energy * m_AirFriction.ToFloat() * dt; | |||||
// Gravity pull. | |||||
m_Energy.Y -= weight * dt; | |||||
} | |||||
else if (newPosition.Y <= groundLevel) | |||||
{ | |||||
CVector3D normal = cmpTerrain->CalcExactNormal(newPosition.X, newPosition.Z); | |||||
float length = m_Energy.Length(); | |||||
m_Energy *= 1.0f / length; | |||||
Not Done Inline ActionsWhy absolute velocity, should the drag just be a force that acts in the opposite sense of the velocity vector ? nani: Why absolute velocity, should the drag just be a force that acts in the opposite sense of the… | |||||
Not Done Inline ActionsTrue, but here the acceleration is here a mock for the energy, that must be dissipated by drag. I would love it if someone knowledgeble in physics would become enthousiastic about this and take over this diff xD Freagarach: True, but here the acceleration is here a mock for the energy, that must be dissipated by drag. | |||||
Not Done Inline Actionsnani is right, should not be in absolute, this will speed up fall of entities instead applying slow down Silier: nani is right, should not be in absolute, this will speed up fall of entities instead applying… | |||||
// Reflect the force, losing some energy in the process. | |||||
float dotProduct = m_Energy.Dot(normal); | |||||
float dampen = 1.0f - (m_GroundFriction.ToFloat() - dotProduct); | |||||
if (dampen < 0.0f) | |||||
dampen = 0.0f; | |||||
m_Energy = (normal * -2 * dotProduct + m_Energy) * dampen * length; | |||||
FreagarachAuthorUnsubmitted Not Done Inline ActionsStill no clue where @wraitii used the -2 for :s Freagarach: Still no clue where @wraitii used the `-2` for :s | |||||
newPosition.Y = groundLevel; | |||||
} | |||||
else | |||||
{ | |||||
// Nothing? | |||||
} | |||||
// We should check for collisions. | |||||
cmpPosition->MoveTo(entity_pos_t::FromFloat(newPosition.X), entity_pos_t::FromFloat(newPosition.Z)); | |||||
cmpPosition->SetHeightFixed(entity_pos_t::FromFloat(newPosition.Y)); | |||||
} | |||||
virtual void ApplyForce(float x, float y, float z) | |||||
{ | |||||
// Correct for time force applies? What are nice values? | |||||
// It is ugly to calculate the acceleration and say it is energy. | |||||
m_Energy.X += x / m_Mass.ToFloat(); | |||||
m_Energy.Y += y / m_Mass.ToFloat(); | |||||
m_Energy.Z += z / m_Mass.ToFloat(); | |||||
} | |||||
virtual void SetActive(bool active) | |||||
{ | |||||
if (active != m_Active) | |||||
{ | |||||
GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_Update, this, active); | |||||
m_Active = active; | |||||
} | |||||
} | |||||
}; | |||||
REGISTER_COMPONENT_TYPE(Physics) | |||||
Not Done Inline Actionsearly return would be better Silier: early return would be better |
Wildfire Games · Phabricator
should be checked if that tile is walkable else unit would stuck probably