EMI/GRIM: Implement collision handling for EMI

- for solving the Monkey Kombat puzzle it is necessary that a kombat is
  triggered when Guybrush runs into one of the monkeys on the map
- implement SetActorCollisionMode and SetActorCollisionScale
- add EMI-specific calculations for retrieving the sphere data for the
  actors (and some minor refactoring for better readability)

The following changes may alter the behaviour in GRIM, too:
- in Actor::collisionHandlerCallback(), call the collision handler for
  both affected objects (that is necessary since only one actor may have
  a collision handler associated in lua but it may happen that the
  character with the handler is standing still and the other one is run
  into him)
- Actor::handleCollisionTo() did only update a given position to ensure
  that there is no collision - so it is necessary to actually check for
  collisions and execute the lua callback if necessary (via
  Actor::handleCollisionWith())
This commit is contained in:
Christian Krause 2013-12-22 03:22:09 +01:00
parent d8d9614baa
commit ce9239412d
3 changed files with 98 additions and 35 deletions

View File

@ -41,6 +41,7 @@
#include "engines/grim/emi/costumeemi.h"
#include "engines/grim/emi/skeleton.h"
#include "engines/grim/emi/costume/emiskel_component.h"
#include "engines/grim/emi/modelemi.h"
#include "common/foreach.h"
@ -704,11 +705,11 @@ void Actor::moveTo(const Math::Vector3d &pos) {
mode = CollisionSphere;
}
Math::Vector3d v = pos - _pos;
Math::Vector3d moveVec = pos - _pos;
foreach (Actor *a, g_grim->getActiveActors()) {
handleCollisionWith(a, mode, &v);
handleCollisionWith(a, mode, &moveVec);
}
_pos += v;
_pos += moveVec;
}
void Actor::walkForward() {
@ -1787,9 +1788,11 @@ Math::Vector3d Actor::handleCollisionTo(const Math::Vector3d &from, const Math::
}
Math::Vector3d p = pos;
Math::Vector3d moveVec = pos - _pos;
foreach (Actor *a, Actor::getPool()) {
if (a != this && a->isInSet(_setName) && a->isVisible()) {
p = a->getTangentPos(from, p);
handleCollisionWith(a, _collisionMode, &moveVec);
}
}
return p;
@ -1800,10 +1803,10 @@ Math::Vector3d Actor::getTangentPos(const Math::Vector3d &pos, const Math::Vecto
return dest;
}
Model *model = getCurrentCostume()->getModel();
Math::Vector3d p = _pos + model->_insertOffset;
float size = model->_radius * _collisionScale;
Math::Vector3d p;
float size;
if (!getSphereInfo(false, size, p))
return dest;
Math::Vector2d p1(pos.x(), pos.y());
Math::Vector2d p2(dest.x(), dest.y());
Math::Segment2d segment(p1, p2);
@ -1830,6 +1833,48 @@ Math::Vector3d Actor::getTangentPos(const Math::Vector3d &pos, const Math::Vecto
return dest;
}
void Actor::getBBoxInfo(Math::Vector3d &bboxPos, Math::Vector3d &bboxSize) const {
if (g_grim->getGameType() == GType_MONKEY4) {
bboxPos = Math::Vector3d(0, 0, 0);
bboxSize = Math::Vector3d(0, 0, 0);
} else {
Model *model = getCurrentCostume()->getModel();
bboxPos = model->_bboxPos;
bboxSize = model->_bboxSize;
}
}
bool Actor::getSphereInfo(bool adjustZ, float &size, Math::Vector3d &p) const {
if (g_grim->getGameType() == GType_MONKEY4) {
EMICostume *costume = static_cast<EMICostume *>(getCurrentCostume());
if (!costume) {
warning("Actor::getSphereInfo: actor \"%s\" has no costume", getName().c_str());
return false;
}
EMIChore *chore = costume->_wearChore;
if (!chore) {
warning("Actor::getSphereInfo: actor \"%s\" has no chore", getName().c_str());
return false;
}
EMIModel *model = chore->getMesh()->_obj;
assert(model);
p = _pos + *(model->_center);
// pre-scale factor of 0.8 was guessed by comparing with the original game
size = model->_radius * _collisionScale * 0.8f;
} else {
Model *model = getCurrentCostume()->getModel();
assert(model);
p = _pos + model->_insertOffset;
// center the sphere on the model center.
if (adjustZ) {
p.z() += model->_bboxSize.z() / 2.f;
}
size = model->_radius * _collisionScale;
}
return true;
}
bool Actor::handleCollisionWith(Actor *actor, CollisionMode mode, Math::Vector3d *vec) const {
if (actor->_collisionMode == CollisionOff || actor == this) {
return false;
@ -1839,25 +1884,21 @@ bool Actor::handleCollisionWith(Actor *actor, CollisionMode mode, Math::Vector3d
return false;
}
Model *model1 = getCurrentCostume()->getModel();
Model *model2 = actor->getCurrentCostume()->getModel();
Math::Vector3d p1 = _pos + model1->_insertOffset;
Math::Vector3d p2 = actor->_pos + model2->_insertOffset;
float size1 = model1->_radius * _collisionScale;
float size2 = model2->_radius * actor->_collisionScale;
Math::Vector3d p1, p2;
float size1, size2;
// you may ask: why center the sphere of the first actor only (by setting adjustZ to true)?
// because it seems the original does so.
// if you change this code test this places: the rocks in lb and bv (both when booting directly in the
// set and when coming in from another one) and the poles in xb.
if (!this->getSphereInfo(true, size1, p1) ||
!actor->getSphereInfo(false, size2, p2)) {
return false;
}
CollisionMode mode1 = mode;
CollisionMode mode2 = actor->_collisionMode;
if (mode1 == CollisionSphere && mode2 == CollisionSphere) {
// center the sphere on the model center.
p1.z() += model1->_bboxSize.z() / 2.f;
// you may ask: why center the sphere of the first actor only? because it seems the original does so.
// if you change this code test this places: the rocks in lb and bv (both when booting directly in the
// set and when coming in from another one) and the poles in xb.
Math::Vector3d pos = p1 + *vec;
float distance = (pos - p2).getMagnitude();
if (distance < size1 + size2) {
@ -1875,6 +1916,13 @@ bool Actor::handleCollisionWith(Actor *actor, CollisionMode mode, Math::Vector3d
warning("Collision between box and box not implemented!");
return false;
} else {
Math::Vector3d bboxSize1, bboxSize2;
Math::Vector3d bboxPos1, bboxPos2;
// get bboxSize and bboxPos for the current and the colliding actor
this->getBBoxInfo(bboxPos1, bboxSize1);
actor->getBBoxInfo(bboxPos2, bboxSize2);
Math::Rect2d rect;
Math::Vector3d bboxPos;
@ -1889,8 +1937,8 @@ bool Actor::handleCollisionWith(Actor *actor, CollisionMode mode, Math::Vector3d
if (mode1 == CollisionBox) {
pos = p1 + *vec;
bboxPos = pos + model1->_bboxPos;
size = model1->_bboxSize;
bboxPos = pos + bboxPos1;
size = bboxSize1;
scale = _collisionScale;
yaw = _yaw;
@ -1900,8 +1948,8 @@ bool Actor::handleCollisionWith(Actor *actor, CollisionMode mode, Math::Vector3d
radius = size2;
} else {
pos = p2;
bboxPos = p2 + model2->_bboxPos;
size = model2->_bboxSize;
bboxPos = p2 + bboxPos2;
size = bboxSize2;
scale = actor->_collisionScale;
yaw = actor->_yaw;
@ -2004,6 +2052,11 @@ void Actor::collisionHandlerCallback(Actor *other) const {
objects.add(other);
LuaBase::instance()->callback("collisionHandler", objects);
LuaObjects objects2;
objects2.add(other);
objects2.add(this);
LuaBase::instance()->callback("collisionHandler", objects2);
}
Math::Vector3d Actor::getWorldPos() const {

View File

@ -552,6 +552,9 @@ private:
Math::Vector3d getSimplePuckVector() const;
void calculateOrientation(const Math::Vector3d &pos, Math::Angle *pitch, Math::Angle *yaw, Math::Angle *roll);
void getBBoxInfo(Math::Vector3d &bboxPos, Math::Vector3d &bboxSize) const;
bool getSphereInfo(bool adjustZ, float &size, Math::Vector3d &pos) const;
Common::String _name;
Common::String _setName; // The actual current set

View File

@ -798,14 +798,23 @@ void Lua_V2::SetActorCollisionMode() {
Actor *actor = getactor(actorObj);
assert(actor);
int mode = (int)lua_getnumber(modeObj);
// From _actors.lua
// COLLISION_OFF = 0
// COLLISION_BOX = 1
// COLLISION_SPHERE = 2
// FIXME: set collision mode
//actor->func(mode);
warning("Lua_V2::SetActorCollisionMode: implement opcode. Mode %d", mode);
Actor::CollisionMode m;
switch (mode) {
case Actor::CollisionOff:
m = Actor::CollisionOff;
break;
case Actor::CollisionBox:
m = Actor::CollisionBox;
break;
case Actor::CollisionSphere:
m = Actor::CollisionSphere;
break;
default:
warning("Lua_V2::SetActorCollisionMode(): wrong collisionmode: %d, using default 0", mode);
m = Actor::CollisionOff;
}
actor->setCollisionMode(m);
}
void Lua_V2::SetActorCollisionScale() {
@ -819,9 +828,7 @@ void Lua_V2::SetActorCollisionScale() {
assert(actor);
float scale = lua_getnumber(scaleObj);
// FIXME: set collision scale
//actor->func(scale);
warning("Lua_V2::SetActorCollisionScale: implement opcode. Scale %f", scale);
actor->setCollisionScale(scale);
}
void Lua_V2::GetActorPuckVector() {