mirror of
https://github.com/rwengine/openrw.git
synced 2024-11-25 03:42:48 +01:00
add ProjectileObject with support for grenades
This commit is contained in:
parent
a8c2312b87
commit
f8f17db68f
@ -6,6 +6,7 @@
|
||||
#include <loaders/LoaderIDE.hpp>
|
||||
#include <loaders/LoaderIPL.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <memory>
|
||||
|
||||
class CharacterController;
|
||||
@ -20,8 +21,12 @@ class GameWorld;
|
||||
* Contains handle to the world, and other useful properties like water level
|
||||
* tracking used to make tunnels work.
|
||||
*/
|
||||
struct GameObject
|
||||
class GameObject
|
||||
{
|
||||
glm::vec3 _lastPosition;
|
||||
glm::quat _lastRotation;
|
||||
|
||||
public:
|
||||
glm::vec3 position;
|
||||
glm::quat rotation;
|
||||
|
||||
@ -44,7 +49,7 @@ struct GameObject
|
||||
float _lastHeight;
|
||||
|
||||
GameObject(GameWorld* engine, const glm::vec3& pos, const glm::quat& rot, ModelHandle* model)
|
||||
: position(pos), rotation(rot), model(model), engine(engine), animator(nullptr), mHealth(0.f),
|
||||
: _lastPosition(pos), _lastRotation(rot), position(pos), rotation(rot), model(model), engine(engine), animator(nullptr), mHealth(0.f),
|
||||
_inWater(false), _lastHeight(std::numeric_limits<float>::max())
|
||||
{}
|
||||
|
||||
@ -69,8 +74,9 @@ struct GameObject
|
||||
virtual Type type() { return Unknown; }
|
||||
|
||||
virtual void setPosition(const glm::vec3& pos);
|
||||
|
||||
virtual glm::vec3 getPosition() const;
|
||||
|
||||
virtual glm::vec3 getPosition() const { return position; }
|
||||
const glm::vec3& getLastPosition() const { return _lastPosition; }
|
||||
|
||||
virtual glm::quat getRotation() const;
|
||||
|
||||
@ -117,6 +123,24 @@ struct GameObject
|
||||
virtual bool isInWater() const { return _inWater; }
|
||||
|
||||
virtual void tick(float dt) = 0;
|
||||
|
||||
/**
|
||||
* @brief Function used to modify the last transform
|
||||
* @param newPos
|
||||
*/
|
||||
void _updateLastTransform()
|
||||
{
|
||||
_lastPosition = getPosition();
|
||||
_lastRotation = getRotation();
|
||||
}
|
||||
|
||||
glm::mat4 getTimeAdjustedTransform(float alpha) const {
|
||||
glm::mat4 t;
|
||||
t = glm::translate(t, glm::mix(_lastPosition, getPosition(), alpha));
|
||||
t = t * glm::mat4_cast(glm::slerp(_lastRotation, getRotation(), alpha));
|
||||
return t;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // __GAMEOBJECTS_HPP__
|
||||
|
52
rwengine/include/objects/ProjectileObject.hpp
Normal file
52
rwengine/include/objects/ProjectileObject.hpp
Normal file
@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#ifndef _PROJECTILEOBJECT_HPP_
|
||||
#define _PROJECTILEOBJECT_HPP_
|
||||
#include <engine/GameObject.hpp>
|
||||
#include <bullet/btBulletDynamicsCommon.h>
|
||||
|
||||
/**
|
||||
* @brief Implements weapon projectile (e.g. molotovs, RPGs etc.)
|
||||
*/
|
||||
class ProjectileObject : public GameObject
|
||||
{
|
||||
public:
|
||||
|
||||
enum ProjectileType {
|
||||
Grenade,
|
||||
Molotov,
|
||||
RPG,
|
||||
};
|
||||
|
||||
struct ProjectileInfo {
|
||||
ProjectileType type;
|
||||
glm::vec3 direction;
|
||||
float velocity;
|
||||
|
||||
/** Time to dentonation or removal */
|
||||
float time;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
ProjectileInfo _info;
|
||||
|
||||
btSphereShape* _shape;
|
||||
union {
|
||||
btRigidBody* _body;
|
||||
};
|
||||
|
||||
void explode();
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief ProjectileObject constructor
|
||||
*/
|
||||
ProjectileObject(GameWorld* world, const glm::vec3& position, const ProjectileInfo& info);
|
||||
|
||||
~ProjectileObject();
|
||||
|
||||
void tick(float dt);
|
||||
};
|
||||
|
||||
#endif
|
@ -23,8 +23,14 @@ void Animator::reset()
|
||||
if( it == getAnimation()->bones.end() ) continue;
|
||||
|
||||
auto A = getKeyframeAt(f, 0.f);
|
||||
|
||||
_frameInstances.insert( { f, { it->second, A, A } } );
|
||||
auto kfit = _frameInstances.find( f );
|
||||
if( kfit == _frameInstances.end() ) {
|
||||
_frameInstances.insert( { f, { it->second, A, A } } );
|
||||
}
|
||||
else {
|
||||
kfit->second.first = kfit->second.second;
|
||||
kfit->second.second = A;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -39,7 +45,7 @@ void Animator::setAnimation(Animation *animation, bool repeat)
|
||||
queueAnimation(animation);
|
||||
this->repeat = repeat;
|
||||
|
||||
_frameInstances.clear();
|
||||
//_frameInstances.clear();
|
||||
|
||||
reset();
|
||||
}
|
||||
@ -218,8 +224,8 @@ glm::mat4 Animator::getFrameMatrix(ModelFrame *frame, float alpha, bool ignoreRo
|
||||
{
|
||||
auto it = _frameInstances.find( frame );
|
||||
if( it != _frameInstances.end() && it->second.bone ) {
|
||||
const AnimationKeyframe& F = it->second.first;
|
||||
const AnimationKeyframe& S = it->second.second;
|
||||
const AnimationKeyframe& S = it->second.first;
|
||||
const AnimationKeyframe& F = it->second.second;
|
||||
|
||||
AnimationKeyframe kf {
|
||||
glm::slerp(F.rotation, S.rotation, alpha),
|
||||
|
@ -4,12 +4,7 @@
|
||||
|
||||
void GameObject::setPosition(const glm::vec3& pos)
|
||||
{
|
||||
position = pos;
|
||||
}
|
||||
|
||||
glm::vec3 GameObject::getPosition() const
|
||||
{
|
||||
return position;
|
||||
_lastPosition = position = pos;
|
||||
}
|
||||
|
||||
glm::quat GameObject::getRotation() const
|
||||
|
@ -298,7 +298,7 @@ void CharacterObject::setPosition(const glm::vec3& pos)
|
||||
{
|
||||
btTransform& tf = physCharacter->getGhostObject()->getWorldTransform();
|
||||
tf.setOrigin(btVector3(pos.x, pos.y, pos.z));
|
||||
position = pos;
|
||||
GameObject::setPosition(pos);
|
||||
}
|
||||
|
||||
glm::vec3 CharacterObject::getPosition() const
|
||||
|
80
rwengine/src/objects/ProjectileObject.cpp
Normal file
80
rwengine/src/objects/ProjectileObject.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
#include <objects/ProjectileObject.hpp>
|
||||
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
|
||||
#include <engine/GameWorld.hpp>
|
||||
#include <data/WeaponData.hpp>
|
||||
|
||||
void ProjectileObject::explode()
|
||||
{
|
||||
/// @todo accelerate this with bullet instead of doing this stupid loop.
|
||||
for(auto& o : engine->objects) {
|
||||
if( o == this ) continue;
|
||||
switch( o->type() ) {
|
||||
case GameObject::Instance:
|
||||
case GameObject::Vehicle:
|
||||
case GameObject::Character:
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
float d = glm::distance(getPosition(), o->getPosition());
|
||||
if( d > 10.f ) continue;
|
||||
|
||||
o->takeDamage({
|
||||
getPosition(),
|
||||
getPosition(),
|
||||
10.f / glm::max(d, 1.f),
|
||||
DamageInfo::Explosion,
|
||||
0.f
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ProjectileObject::ProjectileObject(GameWorld *world, const glm::vec3 &position, const ProjectileObject::ProjectileInfo &info)
|
||||
: GameObject(world, position, glm::quat(), nullptr), _info(info),
|
||||
_body(nullptr)
|
||||
{
|
||||
_shape = new btSphereShape(0.25f);
|
||||
btVector3 inertia(0.f, 0.f, 0.f);
|
||||
_shape->calculateLocalInertia(1.f, inertia);
|
||||
btRigidBody::btRigidBodyConstructionInfo riginfo(1.f, nullptr, _shape, inertia);
|
||||
|
||||
riginfo.m_startWorldTransform = btTransform(btQuaternion(), btVector3(position.x, position.y, position.z));
|
||||
riginfo.m_mass = 1.f;
|
||||
|
||||
_body = new btRigidBody(riginfo);
|
||||
_body->setUserPointer(this);
|
||||
_body->setLinearVelocity(btVector3(_info.direction.x, _info.direction.y, _info.direction.z) * _info.velocity);
|
||||
engine->dynamicsWorld->addRigidBody(_body);
|
||||
|
||||
if( _info.type == RPG ) {
|
||||
// RPGs aren't affected by gravity
|
||||
_body->setGravity( { 0.f, 0.f, 0.f } );
|
||||
}
|
||||
}
|
||||
|
||||
ProjectileObject::~ProjectileObject()
|
||||
{
|
||||
if( _body ) {
|
||||
engine->dynamicsWorld->removeCollisionObject(_body);
|
||||
delete _body;
|
||||
}
|
||||
if( _shape ) {
|
||||
delete _shape;
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectileObject::tick(float dt)
|
||||
{
|
||||
auto& bttr = _body->getWorldTransform();
|
||||
position = { bttr.getOrigin().x(), bttr.getOrigin().y(), bttr.getOrigin().z() };
|
||||
|
||||
_info.time -= dt;
|
||||
|
||||
/// @todo implement contact callbacks or GhostObject for detecting overlaps for molotovs and RPGs.
|
||||
|
||||
if( _info.time <= 0.f ) {
|
||||
explode();
|
||||
/// @todo request the world delete us
|
||||
}
|
||||
}
|
@ -414,10 +414,7 @@ void GameRenderer::renderWorld(float alpha)
|
||||
|
||||
void GameRenderer::renderPedestrian(CharacterObject *pedestrian)
|
||||
{
|
||||
glm::mat4 matrixModel;
|
||||
|
||||
matrixModel = glm::translate(matrixModel, pedestrian->getPosition());
|
||||
matrixModel = matrixModel * glm::mat4_cast(pedestrian->getRotation());
|
||||
glm::mat4 matrixModel = pedestrian->getTimeAdjustedTransform( _renderAlpha );
|
||||
|
||||
if(!pedestrian->model->model) return;
|
||||
|
||||
@ -428,7 +425,7 @@ void GameRenderer::renderPedestrian(CharacterObject *pedestrian)
|
||||
glm::mat4 localMatrix;
|
||||
if( handFrame ) {
|
||||
while( handFrame->getParent() ) {
|
||||
localMatrix = pedestrian->animator->getFrameMatrix(handFrame) * localMatrix;
|
||||
localMatrix = pedestrian->animator->getFrameMatrix(handFrame, _renderAlpha) * localMatrix;
|
||||
handFrame = handFrame->getParent();
|
||||
}
|
||||
}
|
||||
@ -443,9 +440,7 @@ void GameRenderer::renderVehicle(VehicleObject *vehicle)
|
||||
std::cout << "model " << vehicle->vehicle->modelName << " not loaded (" << engine->gameData.models.size() << " models loaded)" << std::endl;
|
||||
}
|
||||
|
||||
glm::mat4 matrixModel;
|
||||
matrixModel = glm::translate(matrixModel, vehicle->getPosition());
|
||||
matrixModel = matrixModel * glm::mat4_cast(vehicle->getRotation());
|
||||
glm::mat4 matrixModel = vehicle->getTimeAdjustedTransform( _renderAlpha );
|
||||
|
||||
renderModel(vehicle->model->model, matrixModel, vehicle);
|
||||
|
||||
@ -454,16 +449,40 @@ void GameRenderer::renderVehicle(VehicleObject *vehicle)
|
||||
auto woi = engine->objectTypes.find(vehicle->vehicle->wheelModelID);
|
||||
if(woi != engine->objectTypes.end()) {
|
||||
Model* wheelModel = engine->gameData.models["wheels"]->model;
|
||||
auto& wi = vehicle->physVehicle->getWheelInfo(w);
|
||||
if( wheelModel ) {
|
||||
// Tell bullet to update the matrix for this wheel.
|
||||
// Construct our own matrix so we can use the local transform
|
||||
vehicle->physVehicle->updateWheelTransform(w, false);
|
||||
glm::mat4 wheel_tf;
|
||||
vehicle->physVehicle->getWheelTransformWS(w).getOpenGLMatrix(glm::value_ptr(wheel_tf));
|
||||
wheel_tf = glm::scale(wheel_tf, glm::vec3(vehicle->vehicle->wheelScale));
|
||||
if(vehicle->physVehicle->getWheelInfo(w).m_chassisConnectionPointCS.x() < 0.f) {
|
||||
wheel_tf = glm::scale(wheel_tf, glm::vec3(-1.f, 1.f, 1.f));
|
||||
/// @todo migrate this into Vehicle physics tick so we can interpolate old -> new
|
||||
|
||||
glm::mat4 wheelM ( matrixModel );
|
||||
|
||||
auto up = -wi.m_wheelDirectionCS;
|
||||
auto right = wi.m_wheelAxleCS;
|
||||
auto fwd = up.cross(right);
|
||||
btQuaternion steerQ(up, wi.m_steering);
|
||||
btQuaternion rollQ(right, -wi.m_rotation);
|
||||
|
||||
btMatrix3x3 basis(
|
||||
right[0], fwd[0], up[0],
|
||||
right[1], fwd[1], up[1],
|
||||
right[2], fwd[2], up[2]
|
||||
);
|
||||
|
||||
|
||||
btTransform t;
|
||||
t.setBasis(btMatrix3x3(steerQ) * btMatrix3x3(rollQ) * basis);
|
||||
t.setOrigin(wi.m_chassisConnectionPointCS + wi.m_wheelDirectionCS * wi.m_raycastInfo.m_suspensionLength);
|
||||
|
||||
t.getOpenGLMatrix(glm::value_ptr(wheelM));
|
||||
wheelM = matrixModel * wheelM;
|
||||
|
||||
wheelM = glm::scale(wheelM, glm::vec3(vehicle->vehicle->wheelScale));
|
||||
if(wi.m_chassisConnectionPointCS.x() < 0.f) {
|
||||
wheelM = glm::scale(wheelM, glm::vec3(-1.f, 1.f, 1.f));
|
||||
}
|
||||
renderWheel(wheelModel, wheel_tf, woi->second->modelName);
|
||||
|
||||
renderWheel(wheelModel, wheelM, woi->second->modelName);
|
||||
}
|
||||
else {
|
||||
std::cout << "Wheel model " << woi->second->modelName << " not loaded" << std::endl;
|
||||
|
@ -40,11 +40,13 @@ int debugMode = 0;
|
||||
|
||||
sf::Font font;
|
||||
|
||||
glm::vec3 viewPosition { -260.f, -151.5f, 9.f };
|
||||
glm::vec2 viewAngles { -0.3f, 0.05f };
|
||||
glm::vec3 viewPosition { -260.f, -151.5f, 9.f }, lastViewPosition;
|
||||
glm::vec2 viewAngles { -0.3f, 0.05f }, lastViewAngles;
|
||||
|
||||
void setViewParameters(const glm::vec3 ¢er, const glm::vec2 &angles)
|
||||
{
|
||||
lastViewPosition = viewPosition;
|
||||
lastViewAngles = viewAngles;
|
||||
viewPosition = center;
|
||||
viewAngles = angles;
|
||||
}
|
||||
@ -205,6 +207,8 @@ void init(std::string gtapath, bool loadWorld)
|
||||
debugDrawer->setDebugMode(btIDebugDraw::DBG_DrawWireframe);
|
||||
gta->dynamicsWorld->setDebugDrawer(debugDrawer);
|
||||
|
||||
setViewParameters( { -260.f, -151.5f, 9.f }, { -0.3f, 0.05f } );
|
||||
|
||||
std::cout << "Loaded "
|
||||
<< gta->gameData.models.size() << " models, "
|
||||
<< gta->gameData.textures.size() << " textures" << std::endl;
|
||||
@ -213,20 +217,10 @@ void init(std::string gtapath, bool loadWorld)
|
||||
void update(float dt)
|
||||
{
|
||||
if (inFocus) {
|
||||
float qpi = glm::half_pi<float>();
|
||||
|
||||
glm::mat4 view;
|
||||
view = glm::translate(view, viewPosition);
|
||||
view = glm::rotate(view, viewAngles.x, glm::vec3(0, 0, 1));
|
||||
view = glm::rotate(view, viewAngles.y - qpi, glm::vec3(1, 0, 0));
|
||||
view = glm::inverse(view);
|
||||
|
||||
gta->gameTime += dt;
|
||||
|
||||
gta->renderer.camera.worldPos = viewPosition;
|
||||
gta->renderer.camera.frustum.view = view;
|
||||
|
||||
for( GameObject* object : gta->objects ) {
|
||||
object->_updateLastTransform();
|
||||
object->tick(dt);
|
||||
}
|
||||
|
||||
@ -238,6 +232,18 @@ void render(float alpha)
|
||||
{
|
||||
gta->_work->update();
|
||||
|
||||
float qpi = glm::half_pi<float>();
|
||||
|
||||
glm::mat4 view;
|
||||
view = glm::translate(view, glm::mix(lastViewPosition, viewPosition, alpha));
|
||||
auto va = glm::mix(lastViewAngles, viewAngles, alpha);
|
||||
view = glm::rotate(view, va.x, glm::vec3(0, 0, 1));
|
||||
view = glm::rotate(view, va.y - qpi, glm::vec3(1, 0, 0));
|
||||
view = glm::inverse(view);
|
||||
|
||||
gta->renderer.camera.worldPos = viewPosition;
|
||||
gta->renderer.camera.frustum.view = view;
|
||||
|
||||
// Update aspect ratio..
|
||||
gta->renderer.camera.frustum.aspectRatio = window.getSize().x / (float) window.getSize().y;
|
||||
|
||||
@ -376,6 +382,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
float accum = 0.f;
|
||||
float ts = 1.f / 60.f;
|
||||
float timescale = 1.f;
|
||||
|
||||
// Loop until the window is closed or we run out of state.
|
||||
while (window.isOpen() && StateManager::get().states.size()) {
|
||||
@ -388,7 +395,7 @@ int main(int argc, char *argv[])
|
||||
StateManager::get().states.back()->handleEvent(event);
|
||||
}
|
||||
|
||||
accum += clock.restart().asSeconds();
|
||||
accum += clock.restart().asSeconds() * timescale;
|
||||
|
||||
while ( accum >= ts ) {
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "test_globals.hpp"
|
||||
#include <objects/CharacterObject.hpp>
|
||||
#include <data/WeaponData.hpp>
|
||||
#include <objects/ProjectileObject.hpp>
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(WeaponTests)
|
||||
|
||||
@ -24,4 +25,94 @@ BOOST_AUTO_TEST_CASE(TestWeaponScan)
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(TestProjectile)
|
||||
{
|
||||
{
|
||||
auto character = Global::get().e->createPedestrian(1, {25.f, 0.f, 0.f});
|
||||
BOOST_REQUIRE( character != nullptr );
|
||||
|
||||
auto projectile = new ProjectileObject(Global::get().e, {25.f, 0.f, 10.f},
|
||||
{
|
||||
ProjectileObject::Grenade,
|
||||
{0.f, 0.f, -1.f},
|
||||
2.0f,
|
||||
0.5f
|
||||
});
|
||||
|
||||
Global::get().e->objects.insert( projectile );
|
||||
|
||||
BOOST_CHECK( character->mHealth == 100.f );
|
||||
|
||||
for(float t = 0.f; t <= 1.f; t += 0.016f) {
|
||||
Global::get().e->dynamicsWorld->stepSimulation(0.016f, 0, 0);
|
||||
projectile->tick(0.016f);
|
||||
}
|
||||
|
||||
BOOST_CHECK( projectile->getPosition().z < 10.f );
|
||||
|
||||
// Grenade should have dentonated by this point
|
||||
BOOST_CHECK( character->mHealth < 100.f );
|
||||
|
||||
Global::get().e->destroyObject(projectile);
|
||||
Global::get().e->destroyObject(character);
|
||||
}
|
||||
|
||||
{
|
||||
auto character = Global::get().e->createPedestrian(1, {25.f, 0.f, 0.f});
|
||||
BOOST_REQUIRE( character != nullptr );
|
||||
|
||||
auto projectile = new ProjectileObject(Global::get().e, {25.f, 0.f, 10.f},
|
||||
{
|
||||
ProjectileObject::Molotov,
|
||||
{0.f, 0.f, -1.f},
|
||||
2.0f,
|
||||
1.5f
|
||||
});
|
||||
|
||||
Global::get().e->objects.insert( projectile );
|
||||
|
||||
BOOST_CHECK( character->mHealth == 100.f );
|
||||
|
||||
for(float t = 0.f; t <= 1.f; t += 0.016f) {
|
||||
Global::get().e->dynamicsWorld->stepSimulation(0.016f, 0, 0);
|
||||
projectile->tick(0.016f);
|
||||
}
|
||||
|
||||
BOOST_CHECK( projectile->getPosition().z < 10.f );
|
||||
|
||||
BOOST_CHECK( character->mHealth < 100.f );
|
||||
|
||||
Global::get().e->destroyObject(projectile);
|
||||
Global::get().e->destroyObject(character);
|
||||
}
|
||||
{
|
||||
auto character = Global::get().e->createPedestrian(1, {25.f, 0.f, 0.f});
|
||||
BOOST_REQUIRE( character != nullptr );
|
||||
|
||||
auto projectile = new ProjectileObject(Global::get().e, {25.f, 0.f, 10.f},
|
||||
{
|
||||
ProjectileObject::RPG,
|
||||
{0.f, 0.f, -1.f},
|
||||
2.0f,
|
||||
1.5f
|
||||
});
|
||||
|
||||
Global::get().e->objects.insert( projectile );
|
||||
|
||||
BOOST_CHECK( character->mHealth == 100.f );
|
||||
|
||||
for(float t = 0.f; t <= 1.f; t += 0.016f) {
|
||||
Global::get().e->dynamicsWorld->stepSimulation(0.016f, 0, 0);
|
||||
projectile->tick(0.016f);
|
||||
}
|
||||
|
||||
BOOST_CHECK( projectile->getPosition().z < 10.f );
|
||||
|
||||
BOOST_CHECK( character->mHealth < 100.f );
|
||||
|
||||
Global::get().e->destroyObject(projectile);
|
||||
Global::get().e->destroyObject(character);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
Loading…
Reference in New Issue
Block a user