Use real bounding box tests for GetCollidedObjects (#1343)

* Initial commit

* Use DX bboxes for item collision tests

* Update collide_item.cpp

* Update collide_item.cpp

* Update collide_item.cpp

* Code cleanup

* More code cleanup and optimizations

* Remove obsolete radius argument for GetCollidedObjects, mode code cleanups

* Fix ITEM_INVISIBLE typos

* Restore custom radius where needed

* Restore custom radius for polerope

* Function cleanup

* Simplify function

* Remove CollidedItems and CollidedMeshes globals

* Use custom radius for pickups and explosion

* Add changelog, shuffle function order

* Simplify

* fix syntax error

* Rename local variable

* Update collide_item.cpp

---------

Co-authored-by: Sezz <sezzary@outlook.com>
Co-authored-by: Jakub <kubabilinski03@gmail.com>
This commit is contained in:
Lwmte 2024-04-12 16:34:30 +03:00 committed by GitHub
parent 35bf1470c9
commit 266f1b5b6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 482 additions and 511 deletions

View file

@ -28,6 +28,7 @@ Lua API changes:
* Added Flow.GetFlipMapStatus() function to get current flipmap status.
* Added Moveable:GetMeshCount() function to get number of moveable meshes.
* Added Static:GetHP() and Static:SetHP() functions to change shatterable static mesh hit points.
* Fixed Moveable:SetOnCollidedWithObject() callback.
Version 1.3
===========

View file

@ -145,6 +145,14 @@ associated getters and setters.</td>
<td class="summary">Set the name of the function to be called when the moveable is shot by Lara.</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetOnCollidedWithObject">Moveable:SetOnCollidedWithObject(func)</a></td>
<td class="summary">Set the function to be called when this moveable collides with another moveable</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetOnCollidedWithRoom">Moveable:SetOnCollidedWithRoom(func)</a></td>
<td class="summary">Set the function called when this moveable collides with room geometry (e.g.</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetOnKilled">Moveable:SetOnKilled(callback)</a></td>
<td class="summary">Set the name of the function to be called when the moveable is destroyed/killed
Note that enemy death often occurs at the end of an animation, and not at the exact moment
@ -274,14 +282,6 @@ associated getters and setters.</td>
<td class="summary">Borrow animation from an object</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetOnCollidedWithObject">Moveable:SetOnCollidedWithObject(func)</a></td>
<td class="summary">Set the function to be called when this moveable collides with another moveable</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetOnCollidedWithRoom">Moveable:SetOnCollidedWithRoom(func)</a></td>
<td class="summary">Set the function called when this moveable collides with room geometry (e.g.</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:GetPosition">Moveable:GetPosition()</a></td>
<td class="summary">Get the object's position</td>
</tr>
@ -641,6 +641,64 @@ most can just be ignored (see usage).
</dd>
<dt>
<a name = "Moveable:SetOnCollidedWithObject"></a>
<strong>Moveable:SetOnCollidedWithObject(func)</strong>
</dt>
<dd>
Set the function to be called when this moveable collides with another moveable
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">func</span>
<span class="types"><span class="type">function</span></span>
callback function to be called (must be in LevelFuncs hierarchy). This function can take two arguments; these will store the two <a href="../2 classes/Objects.Moveable.html#Moveable">Moveable</a>s taking part in the collision.
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example">LevelFuncs.objCollided = <span class="keyword">function</span>(obj1, obj2)
<span class="global">print</span>(obj1:GetName() .. <span class="string">" collided with "</span> .. obj2:GetName())
<span class="keyword">end</span>
baddy:SetOnCollidedWithObject(LevelFuncs.objCollided)</pre>
</ul>
</dd>
<dt>
<a name = "Moveable:SetOnCollidedWithRoom"></a>
<strong>Moveable:SetOnCollidedWithRoom(func)</strong>
</dt>
<dd>
Set the function called when this moveable collides with room geometry (e.g. a wall or floor). This function can take an argument that holds the <a href="../2 classes/Objects.Moveable.html#Moveable">Moveable</a> that collided with geometry.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">func</span>
<span class="types"><span class="type">function</span></span>
callback function to be called (must be in LevelFuncs hierarchy)
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example">LevelFuncs.roomCollided = <span class="keyword">function</span>(obj)
<span class="global">print</span>(obj:GetName() .. <span class="string">" collided with room geometry"</span>)
<span class="keyword">end</span>
baddy:SetOnCollidedWithRoom(LevelFuncs.roomCollided)</pre>
</ul>
</dd>
<dt>
<a name = "Moveable:SetOnKilled"></a>
@ -1372,64 +1430,6 @@ shiva:SetObjectID(TEN.Objects.ObjID.BIGMEDI_ITEM)</pre>
</dd>
<dt>
<a name = "Moveable:SetOnCollidedWithObject"></a>
<strong>Moveable:SetOnCollidedWithObject(func)</strong>
</dt>
<dd>
Set the function to be called when this moveable collides with another moveable
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">func</span>
<span class="types"><span class="type">function</span></span>
callback function to be called (must be in LevelFuncs hierarchy). This function can take two arguments; these will store the two <a href="../2 classes/Objects.Moveable.html#Moveable">Moveable</a>s taking part in the collision.
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example">LevelFuncs.objCollided = <span class="keyword">function</span>(obj1, obj2)
<span class="global">print</span>(obj1:GetName() .. <span class="string">" collided with "</span> .. obj2:GetName())
<span class="keyword">end</span>
baddy:SetOnCollidedWithObject(LevelFuncs.objCollided)</pre>
</ul>
</dd>
<dt>
<a name = "Moveable:SetOnCollidedWithRoom"></a>
<strong>Moveable:SetOnCollidedWithRoom(func)</strong>
</dt>
<dd>
Set the function called when this moveable collides with room geometry (e.g. a wall or floor). This function can take an argument that holds the <a href="../2 classes/Objects.Moveable.html#Moveable">Moveable</a> that collided with geometry.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">func</span>
<span class="types"><span class="type">function</span></span>
callback function to be called (must be in LevelFuncs hierarchy)
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example">LevelFuncs.roomCollided = <span class="keyword">function</span>(obj)
<span class="global">print</span>(obj:GetName() .. <span class="string">" collided with room geometry"</span>)
<span class="keyword">end</span>
baddy:SetOnCollidedWithRoom(LevelFuncs.roomCollided)</pre>
</ul>
</dd>
<dt>
<a name = "Moveable:GetPosition"></a>

View file

@ -2111,6 +2111,35 @@ most can just be ignored (see usage).</description>
</parameters>
</function>
<function>
<module>Objects.Moveable</module>
<caller>Moveable</caller>
<name>SetOnCollidedWithObject</name>
<summary>Set the function to be called when this moveable collides with another moveable</summary>
<parameters>
<parameter>
<name>func</name>
<type>function</type>
<description>callback function to be called (must be in LevelFuncs hierarchy). This function can take two arguments; these will store the two @{Moveable}s taking part in the collision.</description>
</parameter>
</parameters>
</function>
<function>
<module>Objects.Moveable</module>
<caller>Moveable</caller>
<name>SetOnCollidedWithRoom</name>
<summary>Set the function called when this moveable collides with room geometry (e.g.</summary>
<description>a wall or floor). This function can take an argument that holds the @{Moveable} that collided with geometry.</description>
<parameters>
<parameter>
<name>func</name>
<type>function</type>
<description>callback function to be called (must be in LevelFuncs hierarchy)</description>
</parameter>
</parameters>
</function>
<function>
<module>Objects.Moveable</module>
<caller>Moveable</caller>
@ -2595,35 +2624,6 @@ most can just be ignored (see usage).</description>
</parameters>
</function>
<function>
<module>Objects.Moveable</module>
<caller>Moveable</caller>
<name>SetOnCollidedWithObject</name>
<summary>Set the function to be called when this moveable collides with another moveable</summary>
<parameters>
<parameter>
<name>func</name>
<type>function</type>
<description>callback function to be called (must be in LevelFuncs hierarchy). This function can take two arguments; these will store the two @{Moveable}s taking part in the collision.</description>
</parameter>
</parameters>
</function>
<function>
<module>Objects.Moveable</module>
<caller>Moveable</caller>
<name>SetOnCollidedWithRoom</name>
<summary>Set the function called when this moveable collides with room geometry (e.g.</summary>
<description>a wall or floor). This function can take an argument that holds the @{Moveable} that collided with geometry.</description>
<parameters>
<parameter>
<name>func</name>
<type>function</type>
<description>callback function to be called (must be in LevelFuncs hierarchy)</description>
</parameter>
</parameters>
</function>
<function>
<module>Objects.Moveable</module>
<caller>Moveable</caller>

View file

@ -753,8 +753,8 @@ bool TestLaraObjectCollision(ItemInfo* item, short headingAngle, int forward, in
item->Pose.Position.y += down;
item->Pose.Position.z += phd_cos(item->Pose.Orientation.y + headingAngle) * forward + phd_sin(headingAngle + ANGLE(90.0f) * sideSign) * abs(right);
bool result = GetCollidedObjects(item, LARA_RADIUS, true, CollidedItems, CollidedMeshes, 0);
bool isCollided = !GetCollidedObjects(*item, true, false).IsEmpty();
item->Pose = prevPose;
return result;
return isCollided;
}

View file

@ -324,10 +324,10 @@ void CreateFlare(ItemInfo& laraItem, GAME_OBJECT_ID objectID, bool isThrown)
flareItem.Pose.Position = pos;
int floorHeight = GetCollision(pos.x, pos.y, pos.z, laraItem.RoomNumber).Position.Floor;
auto hasCollided = GetCollidedObjects(&flareItem, 0, true, CollidedItems, CollidedMeshes, true);
auto isCollided = !GetCollidedObjects(flareItem, true, true).IsEmpty();
bool hasLanded = false;
if (floorHeight < pos.y || hasCollided)
if (floorHeight < pos.y || isCollided)
{
hasLanded = true;
flareItem.Pose.Position.x = laraItem.Pose.Position.x + 320 * phd_sin(flareItem.Pose.Orientation.y);

View file

@ -54,9 +54,9 @@ void InitializeLara(bool restore)
ZeroMemory(&Lara, sizeof(LaraInfo));
LaraItem->Data = &Lara;
Lara.Context = PlayerContext(*LaraItem, LaraCollision);
LaraItem->Collidable = false;
Lara.Context = PlayerContext(*LaraItem, LaraCollision);
Lara.Status.Air = LARA_AIR_MAX;
Lara.Status.Exposure = LARA_EXPOSURE_MAX;

View file

@ -1554,42 +1554,34 @@ void HandleProjectile(ItemInfo& projectile, ItemInfo& emitter, const Vector3i& p
}
// Found possible collided items and statics.
GetCollidedObjects(&projectile, radius, true, &CollidedItems[0], &CollidedMeshes[0], false);
// If no collided items and meshes are found, exit the loop.
if (!CollidedItems[0] && !CollidedMeshes[0])
auto collObjects = GetCollidedObjects(projectile, true, false, radius);
if (collObjects.IsEmpty())
break;
for (int i = 0; i < MAX_COLLIDED_OBJECTS; i++)
// Run through statics.
for (auto* staticPtr : collObjects.StaticPtrs)
{
auto* meshPtr = CollidedMeshes[i];
if (!meshPtr)
break;
hasHit = hasHitNotByEmitter = doShatter = true;
doExplosion = isExplosive;
if (StaticObjects[meshPtr->staticNumber].shatterType == ShatterType::None)
if (StaticObjects[staticPtr->staticNumber].shatterType == ShatterType::None)
continue;
meshPtr->HitPoints -= damage;
if (meshPtr->HitPoints <= 0)
ShatterObject(nullptr, meshPtr, -128, projectile.RoomNumber, 0);
staticPtr->HitPoints -= damage;
if (staticPtr->HitPoints <= 0)
ShatterObject(nullptr, staticPtr, -128, projectile.RoomNumber, 0);
if (!isExplosive)
continue;
TriggerExplosionSparks(meshPtr->pos.Position.x, meshPtr->pos.Position.y, meshPtr->pos.Position.z, 3, -2, 0, projectile.RoomNumber);
auto pose = Pose(meshPtr->pos.Position.x, meshPtr->pos.Position.y - 128, meshPtr->pos.Position.z, 0, meshPtr->pos.Orientation.y, 0);
TriggerExplosionSparks(staticPtr->pos.Position.x, staticPtr->pos.Position.y, staticPtr->pos.Position.z, 3, -2, 0, projectile.RoomNumber);
auto pose = Pose(staticPtr->pos.Position.x, staticPtr->pos.Position.y - 128, staticPtr->pos.Position.z, 0, staticPtr->pos.Orientation.y, 0);
TriggerShockwave(&pose, 40, 176, 64, 0, 96, 128, 16, EulerAngles::Identity, 0, true, false, false, (int)ShockwaveStyle::Normal);
}
for (int i = 0; i < MAX_COLLIDED_OBJECTS; i++)
// Run through items.
for (auto* itemPtr : collObjects.ItemPtrs)
{
auto* itemPtr = CollidedItems[i];
if (itemPtr == nullptr)
break;
#
// Object was already affected by collision, skip it.
if (std::find(affectedObjects.begin(), affectedObjects.end(), itemPtr->Index) != affectedObjects.end())
continue;
@ -1652,7 +1644,7 @@ void HandleProjectile(ItemInfo& projectile, ItemInfo& emitter, const Vector3i& p
SmashObject(itemPtr->Index);
KillItem(itemPtr->Index);
}
else if (currentObject.collision && !(itemPtr->Status & ITEM_INVISIBLE))
else if (currentObject.collision && itemPtr->Status != ITEM_INVISIBLE)
{
doShatter = hasHit = true;
doExplosion = isExplosive;

View file

@ -1784,8 +1784,8 @@ bool TestLaraPoleCollision(ItemInfo* item, CollisionInfo* coll, bool goingUp, fl
bool atLeastOnePoleCollided = false;
if (GetCollidedObjects(item, BLOCK(1), true, CollidedItems, nullptr, false) &&
CollidedItems[0] != nullptr)
auto collObjects = GetCollidedObjects(*item, true, false, BLOCK(1), ObjectCollectionMode::Items);
if (!collObjects.IsEmpty())
{
auto laraBox = GameBoundingBox(item).ToBoundingOrientedBox(item->Pose);
@ -1803,16 +1803,12 @@ bool TestLaraPoleCollision(ItemInfo* item, CollisionInfo* coll, bool goingUp, fl
//g_Renderer.AddDebugSphere(sphere.Center, 16.0f, Vector4(1, 0, 0, 1), RendererDebugPage::CollisionStats);
int i = 0;
while (CollidedItems[i] != nullptr)
for (const auto* itemPtr : collObjects.ItemPtrs)
{
auto*& object = CollidedItems[i];
i++;
if (object->ObjectNumber != ID_POLEROPE)
if (itemPtr->ObjectNumber != ID_POLEROPE)
continue;
auto poleBox = GameBoundingBox(object).ToBoundingOrientedBox(object->Pose);
auto poleBox = GameBoundingBox(itemPtr).ToBoundingOrientedBox(itemPtr->Pose);
poleBox.Extents = poleBox.Extents + Vector3(coll->Setup.Radius, 0.0f, coll->Setup.Radius);
//g_Renderer.AddDebugBox(poleBox, Vector4(0, 0, 1, 1), RendererDebugPage::CollisionStats);

View file

@ -23,12 +23,11 @@
using namespace TEN::Math;
using namespace TEN::Renderer;
GameBoundingBox GlobalCollisionBounds;
ItemInfo* CollidedItems[MAX_COLLIDED_OBJECTS];
MESH_INFO* CollidedMeshes[MAX_COLLIDED_OBJECTS];
constexpr auto ANIMATED_ALIGNMENT_FRAME_COUNT_THRESHOLD = 6;
// Globals
GameBoundingBox GlobalCollisionBounds;
void GenericSphereBoxCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
{
auto* item = &g_Level.Items[itemNumber];
@ -100,174 +99,162 @@ void GenericSphereBoxCollision(short itemNumber, ItemInfo* laraItem, CollisionIn
}
}
bool GetCollidedObjects(ItemInfo* collidingItem, int radius, bool onlyVisible, ItemInfo** collidedItems, MESH_INFO** collidedMeshes, bool ignoreLara)
CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible, bool ignorePlayer, float customRadius, ObjectCollectionMode mode)
{
short numItems = 0;
short numMeshes = 0;
constexpr auto EXTENTS_LENGTH_MIN = 2.0f;
constexpr auto ROUGH_BOX_HEIGHT_MIN = BLOCK(1 / 8.0f);
// Collect all the rooms where to check
for (auto i : g_Level.Rooms[collidingItem->RoomNumber].neighbors)
auto collObjects = CollidedObjectData{};
int itemCount = 0;
int staticCount = 0;
// Establish parameters of colliding item.
const auto& collidingBounds = GetBestFrame(collidingItem).BoundingBox;
auto collidingExtents = collidingBounds.GetExtents();
auto collidingSphere = BoundingSphere(collidingBounds.GetCenter() + collidingItem.Pose.Position.ToVector3(), collidingExtents.Length());
auto collidingCircle = Vector3(collidingSphere.Center.x, collidingSphere.Center.z, (customRadius > 0.0f) ? customRadius : std::hypot(collidingExtents.x, collidingExtents.z));
// Quickly discard collision if colliding item bounds are below tolerance threshold.
if (collidingSphere.Radius <= EXTENTS_LENGTH_MIN)
return collObjects;
// Run through neighboring rooms.
const auto& room = g_Level.Rooms[collidingItem.RoomNumber];
for (int roomNumber : room.neighbors)
{
if (!g_Level.Rooms[i].Active())
auto& neighborRoom = g_Level.Rooms[roomNumber];
if (!neighborRoom.Active())
continue;
auto* room = &g_Level.Rooms[i];
if (collidedMeshes)
// Collect items.
if (mode == ObjectCollectionMode::All ||
mode == ObjectCollectionMode::Items)
{
for (int j = 0; j < room->mesh.size(); j++)
{
auto* mesh = &room->mesh[j];
const auto& bBox = GetBoundsAccurate(*mesh, false);
if (!(mesh->flags & StaticMeshFlags::SM_VISIBLE))
continue;
if ((collidingItem->Pose.Position.y + radius + CLICK(0.5f)) < (mesh->pos.Position.y + bBox.Y1))
continue;
if (collidingItem->Pose.Position.y > (mesh->pos.Position.y + bBox.Y2))
continue;
float sinY = phd_sin(mesh->pos.Orientation.y);
float cosY = phd_cos(mesh->pos.Orientation.y);
float rx = ((collidingItem->Pose.Position.x - mesh->pos.Position.x) * cosY) - ((collidingItem->Pose.Position.z - mesh->pos.Position.z) * sinY);
float rz = ((collidingItem->Pose.Position.z - mesh->pos.Position.z) * cosY) + ((collidingItem->Pose.Position.x - mesh->pos.Position.x) * sinY);
if ((radius + rx + CLICK(0.5f) < bBox.X1) || (rx - radius - CLICK(0.5f) > bBox.X2))
continue;
if ((radius + rz + CLICK(0.5f) < bBox.Z1) || (rz - radius - CLICK(0.5f) > bBox.Z2))
continue;
collidedMeshes[numMeshes++] = mesh;
if (!radius)
{
collidedItems[0] = nullptr;
return true;
}
}
collidedMeshes[numMeshes] = nullptr;
}
if (collidedItems)
{
int itemNumber = room->itemNumber;
int itemNumber = neighborRoom.itemNumber;
if (itemNumber != NO_VALUE)
{
do
{
auto* item = &g_Level.Items[itemNumber];
auto& item = g_Level.Items[itemNumber];
const auto& object = Objects[item.ObjectNumber];
if (item == collidingItem ||
(ignoreLara && item->ObjectNumber == ID_LARA) ||
(onlyVisible && item->Status == ITEM_INVISIBLE) ||
item->Flags & IFLAG_KILLED ||
item->MeshBits == NO_JOINT_BITS ||
(Objects[item->ObjectNumber].drawRoutine == nullptr && item->ObjectNumber != ID_LARA) ||
(Objects[item->ObjectNumber].collision == nullptr && item->ObjectNumber != ID_LARA))
itemNumber = item.NextItem;
// Ignore player (if applicable).
if (ignorePlayer && item.IsLara())
continue;
// Ignore invisible item (if applicable).
if (onlyVisible && item.Status == ITEM_INVISIBLE)
continue;
// Ignore items not feasible for collision.
if (item.Index == collidingItem.Index ||
item.Flags & IFLAG_KILLED || item.MeshBits == NO_JOINT_BITS ||
(object.drawRoutine == nullptr && !item.IsLara()) ||
(object.collision == nullptr && !item.IsLara()))
{
itemNumber = item->NextItem;
continue;
}
// TODO: This is awful and we need a better system.
if (item->ObjectNumber == ID_UPV && item->HitPoints == 1)
// HACK: Ignore UPV and big gun.
if ((item.ObjectNumber == ID_UPV || item.ObjectNumber == ID_BIGGUN) && item.HitPoints == 1)
continue;
// Test rough distance to discard objects more than 6 blocks away.
float dist = Vector3i::Distance(item.Pose.Position, collidingItem.Pose.Position);
if (dist > COLLISION_CHECK_DISTANCE)
continue;
const auto& bounds = GetBestFrame(item).BoundingBox;
auto extents = bounds.GetExtents();
// If item bounding box extents is below tolerance threshold, discard object.
if (extents.Length() <= EXTENTS_LENGTH_MIN)
continue;
// Test rough vertical distance to discard objects not intersecting vertically.
if (((collidingItem.Pose.Position.y + collidingBounds.Y1) - ROUGH_BOX_HEIGHT_MIN) >
((item.Pose.Position.y + bounds.Y2) + ROUGH_BOX_HEIGHT_MIN))
{
itemNumber = item->NextItem;
continue;
}
if (item->ObjectNumber == ID_BIGGUN && item->HitPoints == 1)
if (((collidingItem.Pose.Position.y + collidingBounds.Y2) + ROUGH_BOX_HEIGHT_MIN) <
((item.Pose.Position.y + bounds.Y1) - ROUGH_BOX_HEIGHT_MIN))
{
itemNumber = item->NextItem;
continue;
}
int dx = collidingItem->Pose.Position.x - item->Pose.Position.x;
int dy = collidingItem->Pose.Position.y - item->Pose.Position.y;
int dz = collidingItem->Pose.Position.z - item->Pose.Position.z;
// Test rough circle intersection to discard objects not intersecting horizontally.
auto circle = Vector3(item.Pose.Position.x, item.Pose.Position.z, std::hypot(extents.x, extents.z));
if (!Geometry::CircleIntersects(circle, collidingCircle))
continue;
auto bounds = GetBestFrame(*item).BoundingBox;
auto box0 = bounds.ToBoundingOrientedBox(item.Pose);
auto box1 = collidingBounds.ToBoundingOrientedBox(collidingItem.Pose);
if (dx >= -BLOCK(2) && dx <= BLOCK(2) &&
dy >= -BLOCK(2) && dy <= BLOCK(2) &&
dz >= -BLOCK(2) && dz <= BLOCK(2) &&
(collidingItem->Pose.Position.y + radius + CLICK(0.5f)) >= (item->Pose.Position.y + bounds.Y1) &&
(collidingItem->Pose.Position.y - radius - CLICK(0.5f)) <= (item->Pose.Position.y + bounds.Y2))
{
float sinY = phd_sin(item->Pose.Orientation.y);
float cosY = phd_cos(item->Pose.Orientation.y);
// Override extents if specified.
if (customRadius > 0.0f)
box1.Extents = Vector3(customRadius);
int rx = (dx * cosY) - (dz * sinY);
int rz = (dz * cosY) + (dx * sinY);
// TODO: Modify asset to avoid hardcoded bounds change. -- Sezz 2023.04.30
if (item->ObjectNumber == ID_TURN_SWITCH)
{
bounds.X1 = -CLICK(1);
bounds.X2 = CLICK(1);
bounds.Z1 = -CLICK(1);
bounds.Z1 = CLICK(1);
}
if ((radius + rx + CLICK(0.5f)) >= bounds.X1 &&
(rx - radius - CLICK(0.5f)) <= bounds.X2)
{
if ((radius + rz + CLICK(0.5f)) >= bounds.Z1 &&
(rz - radius - CLICK(0.5f)) <= bounds.Z2)
{
collidedItems[numItems++] = item;
}
}
else
{
if ((collidingItem->Pose.Position.y + radius + CLICK(0.5f)) >= (item->Pose.Position.y + bounds.Y1) &&
(collidingItem->Pose.Position.y - radius - CLICK(0.5f)) <= (item->Pose.Position.y + bounds.Y2))
{
float sinY = phd_sin(item->Pose.Orientation.y);
float cosY = phd_cos(item->Pose.Orientation.y);
int rx = (dx * cosY) - (dz * sinY);
int rz = (dz * cosY) + (dx * sinY);
// TODO: Modify asset to avoid hardcoded bounds change. -- Sezz 2023.04.30
if (item->ObjectNumber == ID_TURN_SWITCH)
{
bounds.X1 = -CLICK(1);
bounds.X2 = CLICK(1);
bounds.Z1 = -CLICK(1);
bounds.Z1 = CLICK(1);
}
if ((radius + rx + CLICK(0.5f)) >= bounds.X1 &&
(rx - radius - CLICK(0.5f)) <= bounds.X2)
{
if ((radius + rz + CLICK(0.5f)) >= bounds.Z1 &&
(rz - radius - CLICK(0.5f)) <= bounds.Z2)
{
collidedItems[numItems++] = item;
if (!radius)
return true;
}
}
}
}
}
itemNumber = item->NextItem;
// Test accurate box intersection.
if (box0.Intersects(box1))
collObjects.ItemPtrs.push_back(&item);
}
while (itemNumber != NO_VALUE);
}
}
collidedItems[numItems] = nullptr;
// Collect statics.
if (mode == ObjectCollectionMode::All ||
mode == ObjectCollectionMode::Statics)
{
for (auto& staticObj : neighborRoom.mesh)
{
// Discard invisible statics.
if (!(staticObj.flags & StaticMeshFlags::SM_VISIBLE))
continue;
// Test rough distance to discard statics beyond collision check threshold.
float dist = Vector3i::Distance(staticObj.pos.Position, collidingItem.Pose.Position);
if (dist > COLLISION_CHECK_DISTANCE)
continue;
const auto& bounds = GetBoundsAccurate(staticObj, false);
// Test rough vertical distance to discard statics not intersecting vertically.
if (((collidingItem.Pose.Position.y + collidingBounds.Y1) - ROUGH_BOX_HEIGHT_MIN) >
((staticObj.pos.Position.y + bounds.Y2) + ROUGH_BOX_HEIGHT_MIN))
{
continue;
}
if (((collidingItem.Pose.Position.y + collidingBounds.Y2) + ROUGH_BOX_HEIGHT_MIN) <
((staticObj.pos.Position.y + bounds.Y1) - ROUGH_BOX_HEIGHT_MIN))
{
continue;
}
// Test rough circle intersection to discard statics not intersecting horizontally.
auto circle = Vector3(staticObj.pos.Position.x, staticObj.pos.Position.z, (bounds.GetExtents() * Vector3(1.0f, 0.0f, 1.0f)).Length());
if (!Geometry::CircleIntersects(circle, collidingCircle))
continue;
auto box0 = bounds.ToBoundingOrientedBox(staticObj.pos.Position);
auto box1 = collidingBounds.ToBoundingOrientedBox(collidingItem.Pose);
// Override extents if specified.
if (customRadius > 0.0f)
box1.Extents = Vector3(customRadius);
// Test accurate box intersection.
if (box0.Intersects(box1))
collObjects.StaticPtrs.push_back(&staticObj);
}
}
}
return (numItems || numMeshes);
return collObjects;
}
bool TestWithGlobalCollisionBounds(ItemInfo* item, ItemInfo* laraItem, CollisionInfo* coll)

View file

@ -7,14 +7,17 @@ struct CollisionResult;
struct ItemInfo;
struct MESH_INFO;
constexpr auto MAX_COLLIDED_OBJECTS = 1024;
constexpr auto ITEM_RADIUS_YMAX = BLOCK(3);
constexpr auto ITEM_RADIUS_YMAX = BLOCK(3);
constexpr auto VEHICLE_COLLISION_TERMINAL_VELOCITY = 30.0f;
extern GameBoundingBox GlobalCollisionBounds;
extern ItemInfo* CollidedItems[MAX_COLLIDED_OBJECTS];
extern MESH_INFO* CollidedMeshes[MAX_COLLIDED_OBJECTS];
enum class ObjectCollectionMode
{
All,
Items,
Statics
};
struct ObjectCollisionBounds
{
@ -22,8 +25,16 @@ struct ObjectCollisionBounds
std::pair<EulerAngles, EulerAngles> OrientConstraint = {};
};
struct CollidedObjectData
{
std::vector<ItemInfo*> ItemPtrs = {};
std::vector<MESH_INFO*> StaticPtrs = {};
bool IsEmpty() const { return (ItemPtrs.empty() && StaticPtrs.empty()); };
};
void GenericSphereBoxCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);
bool GetCollidedObjects(ItemInfo* collidingItem, int radius, bool onlyVisible, ItemInfo** collidedItems, MESH_INFO** collidedMeshes, bool ignoreLara);
CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible, bool ignorePlayer, float customRadius = 0.0f, ObjectCollectionMode mode = ObjectCollectionMode::All);
bool TestWithGlobalCollisionBounds(ItemInfo* item, ItemInfo* laraItem, CollisionInfo* coll);
void TestForObjectOnLedge(ItemInfo* item, CollisionInfo* coll);

View file

@ -229,42 +229,35 @@ static void HideOrDisablePickup(ItemInfo& pickupItem)
void CollectMultiplePickups(int itemNumber)
{
auto* firstItem = &g_Level.Items[itemNumber];
GetCollidedObjects(firstItem, LARA_RADIUS, true, CollidedItems, CollidedMeshes, true);
for (int i = 0; i < MAX_COLLIDED_OBJECTS; i++)
auto& firstItem = g_Level.Items[itemNumber];
auto collObjects = GetCollidedObjects(firstItem, true, true, LARA_RADIUS, ObjectCollectionMode::Items);
collObjects.ItemPtrs.push_back(&firstItem);
for (auto* itemPtr : collObjects.ItemPtrs)
{
auto* currentItem = CollidedItems[i];
if (currentItem == nullptr)
currentItem = firstItem;
if (!Objects[currentItem->ObjectNumber].isPickup)
if (!Objects[itemPtr->ObjectNumber].isPickup)
continue;
// HACK: Exclude flares and torches from pickup batches.
if ((currentItem->ObjectNumber == ID_FLARE_ITEM && currentItem->Active) ||
currentItem->ObjectNumber == ID_BURNING_TORCH_ITEM)
if ((itemPtr->ObjectNumber == ID_FLARE_ITEM && itemPtr->Active) ||
itemPtr->ObjectNumber == ID_BURNING_TORCH_ITEM)
{
continue;
continue;
}
PickedUpObject(currentItem->ObjectNumber);
g_Hud.PickupSummary.AddDisplayPickup(currentItem->ObjectNumber, currentItem->Pose.Position.ToVector3());
PickedUpObject(itemPtr->ObjectNumber);
g_Hud.PickupSummary.AddDisplayPickup(itemPtr->ObjectNumber, itemPtr->Pose.Position.ToVector3());
if (currentItem->TriggerFlags & (1 << 8))
if (itemPtr->TriggerFlags & (1 << 8))
{
for (int i = 0; i < g_Level.NumItems; i++)
{
if (g_Level.Items[i].ObjectNumber == currentItem->ObjectNumber)
if (g_Level.Items[i].ObjectNumber == itemPtr->ObjectNumber)
KillItem(i);
}
}
HideOrDisablePickup(*currentItem);
if (currentItem == firstItem)
break;
HideOrDisablePickup(*itemPtr);
}
}
@ -846,8 +839,7 @@ void DropPickups(ItemInfo* item)
origin.y = yPos; // Initialize drop origin Y point as floor height at centerpoint, in case all corner tests fail.
// Also collect objects which are around.
bool collidedWithObjects = GetCollidedObjects(item, extents.Length(), true, CollidedItems, CollidedMeshes, true);
auto collObjects = GetCollidedObjects(*item, true, true);
short startAngle = ANGLE(Random::GenerateInt(0, 3) * 90); // Randomize start corner.
@ -892,26 +884,22 @@ void DropPickups(ItemInfo* item)
// Iterate through all found items and statics around, and determine if dummy sphere
// intersects any of those. If so, try other corner.
for (int i = 0; i < MAX_COLLIDED_OBJECTS; i++)
for (const auto* itemPtr : collObjects.ItemPtrs)
{
auto* currentItem = CollidedItems[i];
if (!currentItem)
break;
if (GameBoundingBox(currentItem).ToBoundingOrientedBox(currentItem->Pose).Intersects(sphere))
auto box = GameBoundingBox(itemPtr).ToBoundingOrientedBox(itemPtr->Pose);
if (box.Intersects(sphere))
{
collidedWithObject = true;
break;
}
}
for (int i = 0; i < MAX_COLLIDED_OBJECTS; i++)
for (auto* staticPtr : collObjects.StaticPtrs)
{
auto* currentMesh = CollidedMeshes[i];
if (!currentMesh)
break;
auto& object = StaticObjects[staticPtr->staticNumber];
if (StaticObjects[currentMesh->staticNumber].collisionBox.ToBoundingOrientedBox(currentMesh->pos).Intersects(sphere))
auto box = object.collisionBox.ToBoundingOrientedBox(staticPtr->pos);
if (box.Intersects(sphere))
{
collidedWithObject = true;
break;

View file

@ -384,4 +384,9 @@ namespace TEN::Math::Geometry
return (distSqr <= radiusSqr);
}
bool CircleIntersects(const Vector3& circle0, const Vector3& circle1)
{
return (sqrt(SQUARE(circle1.x - circle0.x) + SQUARE(circle1.y - circle0.y)) <= (circle0.z + circle1.z));
}
}

View file

@ -54,4 +54,7 @@ namespace TEN::Math::Geometry
bool IsPointInBox(const Vector3& point, const BoundingBox& box);
bool IsPointInBox(const Vector3& point, const BoundingOrientedBox& box);
bool IsPointInSphere(const Vector3& point, const BoundingSphere& sphere);
// Intersection inquirers
bool CircleIntersects(const Vector3& circle0, const Vector3& circle1);
}

View file

@ -1,4 +1,6 @@
#include "framework.h"
#include <cmath>
#include "Math/Math.h"
namespace TEN::Math

View file

@ -62,24 +62,19 @@ namespace TEN::Entities::Effects
void BurnNearbyItems(ItemInfo* item, int radius)
{
GetCollidedObjects(item, radius, true, &CollidedItems[0], &CollidedMeshes[0], false);
for (int i = 0; i < MAX_COLLIDED_OBJECTS; i++)
auto collObjects = GetCollidedObjects(*item, true, false, radius, ObjectCollectionMode::Items);
for (auto* itemPtr : collObjects.ItemPtrs)
{
auto* currentItem = CollidedItems[i];
if (!currentItem)
break;
if (TestEnvironment(ENV_FLAG_WATER, currentItem->RoomNumber))
if (TestEnvironment(ENV_FLAG_WATER, itemPtr->RoomNumber))
continue;
if ((!currentItem->IsCreature() && !currentItem->IsLara()) || currentItem->HitPoints <= 0)
if ((!itemPtr->IsCreature() && !itemPtr->IsLara()) || itemPtr->HitPoints <= 0)
continue;
if (currentItem->IsLara() && GetLaraInfo(item)->Control.WaterStatus == WaterStatus::FlyCheat)
if (itemPtr->IsLara() && GetLaraInfo(item)->Control.WaterStatus == WaterStatus::FlyCheat)
continue;
ItemBurn(currentItem, currentItem->IsLara() ? -1 : FLAME_ITEM_BURN_TIMEOUT);
ItemBurn(itemPtr, itemPtr->IsLara() ? -1 : FLAME_ITEM_BURN_TIMEOUT);
}
}

View file

@ -142,8 +142,6 @@ void ElectricityWiresControl(short itemNumber)
SoundEffect(SFX_TR5_ELECTRIC_WIRES, &item->Pose);
GetCollidedObjects(item, BLOCK(4), true, CollidedItems, nullptr, 0) && CollidedItems[0];
auto* object = &Objects[item->ObjectNumber];
auto cableBox = GameBoundingBox(item).ToBoundingOrientedBox(item->Pose);
@ -179,21 +177,18 @@ void ElectricityWiresControl(short itemNumber)
if (GetRandomControl() & 1)
return;
int k = 0;
while (CollidedItems[k] != nullptr)
auto collObjects = GetCollidedObjects(*item, true, false, BLOCK(2), ObjectCollectionMode::Items);
for (auto* itemPtr : collObjects.ItemPtrs)
{
auto* collItem = CollidedItems[k];
auto* collObj = &Objects[collItem->ObjectNumber];
const auto& object = Objects[itemPtr->ObjectNumber];
k++;
if (collItem->ObjectNumber != ID_LARA && !collObj->intelligent)
if (itemPtr->ObjectNumber != ID_LARA && !object.intelligent)
continue;
bool isWaterNearby = false;
auto npcBox = GameBoundingBox(collItem).ToBoundingOrientedBox(collItem->Pose);
auto npcBox = GameBoundingBox(itemPtr).ToBoundingOrientedBox(itemPtr->Pose);
for (int i = 0; i < object->nmeshes; i++)
for (int i = 0; i < object.nmeshes; i++)
{
auto pos = GetJointPosition(item, i, Vector3i(0, 0, CLICK(1)));
short roomNumber = item->RoomNumber;
@ -217,11 +212,11 @@ void ElectricityWiresControl(short itemNumber)
if (pos.y < cableBottomPlane)
continue;
for (int j = 0; j < collObj->nmeshes; j++)
for (int j = 0; j < object.nmeshes; j++)
{
auto collPos = GetJointPosition(collItem, j);
auto collPos = GetJointPosition(itemPtr, j);
auto collJointRoom = GetCollision(collPos.x, collPos.y, collPos.z, collItem->RoomNumber).RoomNumber;
auto collJointRoom = GetCollision(collPos.x, collPos.y, collPos.z, itemPtr->RoomNumber).RoomNumber;
if (!isWaterNearby && isTouchingWater && roomNumber == collJointRoom)
isWaterNearby = true;
@ -233,29 +228,35 @@ void ElectricityWiresControl(short itemNumber)
{
if (!isWaterNearby)
{
if (collItem->Effect.Type != EffectType::Smoke)
if (itemPtr->Effect.Type != EffectType::Smoke)
{
ItemBlueElectricBurn(collItem, 2 *FPS);
ItemBlueElectricBurn(itemPtr, 2 *FPS);
}
else
ItemSmoke(collItem, -1);
{
ItemSmoke(itemPtr, -1);
}
}
if (instantKill)
DoDamage(collItem, INT_MAX);
{
DoDamage(itemPtr, INT_MAX);
}
else
DoDamage(collItem, 8);
{
DoDamage(itemPtr, 8);
}
for (int j = 0; j < collObj->nmeshes; j++)
for (int j = 0; j < object.nmeshes; j++)
{
if ((GetRandomControl() & 127) < 16)
TriggerElectricitySparks(collItem, j, false);
TriggerElectricitySparks(itemPtr, j, false);
}
TriggerDynamicLight(
collItem->Pose.Position.x,
collItem->Pose.Position.y,
collItem->Pose.Position.z,
itemPtr->Pose.Position.x,
itemPtr->Pose.Position.y,
itemPtr->Pose.Position.z,
5,
0,
(GetRandomControl() & 0x3F) + 0x2F,

View file

@ -116,28 +116,24 @@ namespace TEN::Entities::Generic
// Test object collision.
auto prevPos = pushableItem.Pose.Position;
pushableItem.Pose.Position = targetPos;
GetCollidedObjects(&pushableItem, BLOCK(0.25f), true, &CollidedItems[0], &CollidedMeshes[0], true);
auto collObjects = GetCollidedObjects(pushableItem, true, true);
pushableItem.Pose.Position = prevPos;
if (CollidedMeshes[0])
if (!collObjects.StaticPtrs.empty())
return false;
for (int i = 0; i < MAX_COLLIDED_OBJECTS; i++)
for (const auto* itemPtr : collObjects.ItemPtrs)
{
if (CollidedItems[i] == nullptr)
break;
const auto& item = *CollidedItems[i];
const auto& object = Objects[item.ObjectNumber];
const auto& object = Objects[itemPtr->ObjectNumber];
if (object.isPickup)
continue;
if (!item.IsBridge())
if (!itemPtr->IsBridge())
return false;
const auto& bridge = GetBridgeObject(item);
if (!bridge.GetFloorHeight(item, item.Pose.Position).has_value())
const auto& bridge = GetBridgeObject(*itemPtr);
if (!bridge.GetFloorHeight(*itemPtr, itemPtr->Pose.Position).has_value())
return false;
}
@ -191,34 +187,30 @@ namespace TEN::Entities::Generic
// Collide with objects.
auto prevPos = LaraItem->Pose.Position;
LaraItem->Pose.Position = pointColl.Coordinates;
GetCollidedObjects(LaraItem, LARA_RADIUS, true, &CollidedItems[0], &CollidedMeshes[0], true);
auto collObjects = GetCollidedObjects(*LaraItem, true, true);
LaraItem->Pose.Position = prevPos;
if (CollidedMeshes[0])
if (!collObjects.StaticPtrs.empty())
return false;
for (int i = 0; i < MAX_COLLIDED_OBJECTS; i++)
for (const auto* itemPtr : collObjects.ItemPtrs)
{
if (CollidedItems[i] == nullptr)
break;
const auto& object = Objects[itemPtr->ObjectNumber];
const auto& item = *CollidedItems[i];
const auto& object = Objects[item.ObjectNumber];
if (&item == &pushableItem)
if (itemPtr->Index == pushableItem.Index)
continue;
if (object.isPickup)
continue;
if (!item.IsBridge())
if (!itemPtr->IsBridge())
{
return false;
}
else
{
const auto& bridge = GetBridgeObject(item);
if (!bridge.GetFloorHeight(item, item.Pose.Position).has_value())
const auto& bridge = GetBridgeObject(*itemPtr);
if (!bridge.GetFloorHeight(*itemPtr, itemPtr->Pose.Position).has_value())
return false;
}
}

View file

@ -231,14 +231,13 @@ namespace TEN::Entities::Generic
item->Pose.Orientation.z = 0;
}
auto velocity = Vector3i(
auto vel = Vector3i(
item->Animation.Velocity.z * phd_sin(item->Pose.Orientation.y),
item->Animation.Velocity.y,
item->Animation.Velocity.z * phd_cos(item->Pose.Orientation.y)
);
item->Animation.Velocity.z * phd_cos(item->Pose.Orientation.y));
auto prevPos = item->Pose.Position;
item->Pose.Position += Vector3i(velocity.x, 0, velocity.z);
item->Pose.Position += Vector3i(vel.x, 0, vel.z);
if (TestEnvironment(ENV_FLAG_WATER, item) ||
TestEnvironment(ENV_FLAG_SWAMP, item))
@ -250,26 +249,31 @@ namespace TEN::Entities::Generic
item->ItemFlags[3] = 0;
}
else
{
item->Animation.Velocity.y += 6;
}
item->Pose.Position.y += item->Animation.Velocity.y;
DoProjectileDynamics(itemNumber, prevPos.x, prevPos.y, prevPos.z, velocity.x, velocity.y, velocity.z);
DoProjectileDynamics(itemNumber, prevPos.x, prevPos.y, prevPos.z, vel.x, vel.y, vel.z);
// Collide with entities.
if (GetCollidedObjects(item, 0, true, CollidedItems, CollidedMeshes, true))
auto collObjects = GetCollidedObjects(*item, true, true);
if (!collObjects.IsEmpty())
{
LaraCollision.Setup.EnableObjectPush = true;
if (CollidedItems[0])
if (!collObjects.ItemPtrs.empty())
{
if (!Objects[CollidedItems[0]->ObjectNumber].intelligent &&
CollidedItems[0]->ObjectNumber != ID_LARA)
const auto& object = Objects[collObjects.ItemPtrs.front()->ObjectNumber];
if (!object.intelligent &&
!collObjects.ItemPtrs.front()->IsLara())
{
ObjectCollision(CollidedItems[0]->Index, item, &LaraCollision);
ObjectCollision(collObjects.ItemPtrs.front()->Index, item, &LaraCollision);
}
}
else if (CollidedMeshes[0])
else if (!collObjects.StaticPtrs.empty())
{
ItemPushStatic(item, *CollidedMeshes[0], &LaraCollision);
ItemPushStatic(item, *collObjects.StaticPtrs.front(), &LaraCollision);
}
item->Animation.Velocity.z = -int(item->Animation.Velocity.z / 1.5f);

View file

@ -112,22 +112,25 @@ namespace TEN::Entities::Creatures::TR1
{
if (!CreatureActive(itemNumber))
return;
auto& item = g_Level.Items[itemNumber];
if (item.ItemFlags[0] == NO_VALUE)
{
TENLog("Failed to do the skateboard kid control (itemNumber: " + std::to_string(itemNumber) + "), the skateboard itemNumber is missing, probably failed to be created !");
return;
}
auto& creature = *GetCreatureInfo(&item);
auto& skateItem = g_Level.Items[item.ItemFlags[0]];
short headingAngle = 0;
auto extraHeadRot = EulerAngles::Identity;
auto extraTorsoRot = EulerAngles::Identity;
if (skateItem.Status & ITEM_INVISIBLE)
if (skateItem.Status == ITEM_INVISIBLE)
{
skateItem.Active = true;
skateItem.Status &= ~(ITEM_INVISIBLE);
skateItem.Status = ITEM_ACTIVE;
}
for (auto& flash : creature.MuzzleFlash)

View file

@ -107,18 +107,18 @@ namespace TEN::Entities::Traps
break;
}
if (GetCollidedObjects(&item, CLICK(1), true, CollidedItems, CollidedMeshes, true))
auto collObjects = GetCollidedObjects(item, true, true);
if (!collObjects.IsEmpty())
{
int lp = 0;
while (CollidedItems[lp] != nullptr)
for (auto* itemPtr : collObjects.ItemPtrs)
{
if (Objects[CollidedItems[lp]->ObjectNumber].intelligent)
{
CollidedItems[lp]->HitPoints = 0;
ItemElectricBurn(CollidedItems[lp], 120);
}
const auto& object = Objects[itemPtr->ObjectNumber];
lp++;
if (object.intelligent)
{
itemPtr->HitPoints = 0;
ItemElectricBurn(itemPtr, 120);
}
}
}

View file

@ -1008,7 +1008,9 @@ namespace TEN::Entities::Vehicles
auto* item = &g_Level.Items[itemNum];
short nextItem = item->NextItem;
if (item->Collidable && item->Status != ITEM_INVISIBLE)
if (item->Collidable &&
item->Status != ITEM_INVISIBLE &&
item != laraItem && item != kayakItem)
{
auto* object = &Objects[item->ObjectNumber];

View file

@ -50,18 +50,20 @@ namespace TEN::Entities::Traps
auto pointColl0 = GetCollision(&item, item.Pose.Orientation.y, (forwardVel >= 0) ? bounds.Z2 : bounds.Z1, bounds.Y2);
auto pointColl1 = GetCollision(&item, item.Pose.Orientation.y, (forwardVel >= 0) ? bounds.Z2 : bounds.Z1, bounds.Y2, (bounds.X2 - bounds.X1) / 2);
if (GetCollidedObjects(&item, CLICK(1), true, CollidedItems, CollidedMeshes, true))
auto collObjects = GetCollidedObjects(item, true, true);
if (!collObjects.IsEmpty())
{
int collidedItemNumber = 0;
while (CollidedItems[collidedItemNumber] != nullptr)
for (auto* itemPtr : collObjects.ItemPtrs)
{
if (Objects[CollidedItems[collidedItemNumber]->ObjectNumber].intelligent)
const auto& object = Objects[itemPtr->ObjectNumber];
if (object.intelligent)
{
CollidedItems[collidedItemNumber]->HitPoints = 0;
itemPtr->HitPoints = 0;
}
else if (CollidedItems[collidedItemNumber]->ObjectNumber == ID_SPIKY_WALL && !item.ItemFlags[1])
else if (itemPtr->ObjectNumber == ID_SPIKY_WALL && !item.ItemFlags[1])
{
CollidedItems[collidedItemNumber]->TriggerFlags = 0;
itemPtr->TriggerFlags = 0;
item.TriggerFlags = 0;
item.Status = ITEM_DEACTIVATED;
@ -69,8 +71,6 @@ namespace TEN::Entities::Traps
StopSoundEffect(SFX_TR4_ROLLING_BALL);
}
collidedItemNumber++;
}
}

View file

@ -40,7 +40,7 @@ void SmashObject(short itemNumber)
SoundEffect(SFX_TR5_SMASH_GLASS, &item->Pose);
item->Collidable = 0;
item->Collidable = false;
item->MeshBits = 0xFFFE;
ExplodingDeath(itemNumber, BODY_DO_EXPLOSION | BODY_NO_BOUNCE);

View file

@ -129,49 +129,43 @@ void ExplosionControl(short itemNumber)
}
}
GetCollidedObjects(item, 2048, true, CollidedItems, CollidedMeshes, 1);
if (CollidedItems[0] || CollidedMeshes[0])
auto collObjects = GetCollidedObjects(*item, true, true, BLOCK(2), ObjectCollectionMode::All);
if (!collObjects.IsEmpty())
{
int i = 0;
while (CollidedItems[i])
for (auto* itemPtr : collObjects.ItemPtrs)
{
if (CollidedItems[i]->ObjectNumber >= ID_SMASH_OBJECT1 && CollidedItems[i]->ObjectNumber <= ID_SMASH_OBJECT16)
if (itemPtr->ObjectNumber >= ID_SMASH_OBJECT1 && itemPtr->ObjectNumber <= ID_SMASH_OBJECT16)
{
TriggerExplosionSparks(CollidedItems[i]->Pose.Position.x, CollidedItems[i]->Pose.Position.y, CollidedItems[i]->Pose.Position.z, 3, -2, 0, CollidedItems[i]->RoomNumber);
CollidedItems[i]->Pose.Position.y -= 128;
TriggerShockwave(&CollidedItems[i]->Pose, 48, 304, 96, 128, 96, 0, 24, EulerAngles::Identity, 0, true, false, false, (int)ShockwaveStyle::Normal);
CollidedItems[i]->Pose.Position.y += 128;
ExplodeItemNode(CollidedItems[i], 0, 0, 80);
SmashObject(CollidedItems[i]->Index);
KillItem(CollidedItems[i]->Index);
TriggerExplosionSparks(itemPtr->Pose.Position.x, itemPtr->Pose.Position.y, itemPtr->Pose.Position.z, 3, -2, 0, itemPtr->RoomNumber);
itemPtr->Pose.Position.y -= 128;
TriggerShockwave(&itemPtr->Pose, 48, 304, 96, 128, 96, 0, 24, EulerAngles::Identity, 0, true, false, false, (int)ShockwaveStyle::Normal);
itemPtr->Pose.Position.y += 128;
ExplodeItemNode(itemPtr, 0, 0, 80);
SmashObject(itemPtr->Index);
KillItem(itemPtr->Index);
}
else if (CollidedItems[i]->ObjectNumber != ID_SWITCH_TYPE7 && CollidedItems[i]->ObjectNumber != ID_SWITCH_TYPE8)
else if (itemPtr->ObjectNumber != ID_SWITCH_TYPE7 && itemPtr->ObjectNumber != ID_SWITCH_TYPE8)
{
if (Objects[CollidedItems[i]->ObjectNumber].intelligent)
DoExplosiveDamage(*LaraItem, *CollidedItems[i], *item, Weapons[(int)LaraWeaponType::GrenadeLauncher].ExplosiveDamage);
if (Objects[itemPtr->ObjectNumber].intelligent)
DoExplosiveDamage(*LaraItem, *itemPtr, *item, Weapons[(int)LaraWeaponType::GrenadeLauncher].ExplosiveDamage);
}
else
{
/* @FIXME This calls CrossbowHitSwitchType78() */
// @FIXME: This calls CrossbowHitSwitchType78()
}
++i;
}
i = 0;
while (CollidedMeshes[i])
for (auto* staticPtr : collObjects.StaticPtrs)
{
if (StaticObjects[CollidedMeshes[i]->staticNumber].shatterType != ShatterType::None)
if (StaticObjects[staticPtr->staticNumber].shatterType != ShatterType::None)
{
TriggerExplosionSparks(CollidedMeshes[i]->pos.Position.x, CollidedMeshes[i]->pos.Position.y, CollidedMeshes[i]->pos.Position.z, 3, -2, 0, item->RoomNumber);
CollidedMeshes[i]->pos.Position.y -= 128;
TriggerShockwave(&CollidedMeshes[i]->pos, 40, 176, 64, 128, 96, 0, 16, EulerAngles::Identity, 0, true, false, false, (int)ShockwaveStyle::Normal);
CollidedMeshes[i]->pos.Position.y += 128;
SoundEffect(GetShatterSound(CollidedMeshes[i]->staticNumber), &CollidedMeshes[i]->pos);
ShatterObject(NULL, CollidedMeshes[i], -128, item->RoomNumber, 0);
TriggerExplosionSparks(staticPtr->pos.Position.x, staticPtr->pos.Position.y, staticPtr->pos.Position.z, 3, -2, 0, item->RoomNumber);
staticPtr->pos.Position.y -= 128;
TriggerShockwave(&staticPtr->pos, 40, 176, 64, 128, 96, 0, 16, EulerAngles::Identity, 0, true, false, false, (int)ShockwaveStyle::Normal);
staticPtr->pos.Position.y += 128;
SoundEffect(GetShatterSound(staticPtr->staticNumber), &staticPtr->pos);
ShatterObject(nullptr, staticPtr, -128, item->RoomNumber, 0);
}
++i;
}
AlertNearbyGuards(item);

View file

@ -165,62 +165,42 @@ void Moveable::Register(sol::state& state, sol::table& parent)
ScriptReserved_SetVisible, &Moveable::SetVisible,
/// Explode item. This also kills and disables item.
// @function Moveable:Explode
/// Explode item. This also kills and disables item.
// @function Moveable:Explode
ScriptReserved_Explode, &Moveable::Explode,
/// Shatter item. This also kills and disables item.
// @function Moveable:Shatter
/// Shatter item. This also kills and disables item.
// @function Moveable:Shatter
ScriptReserved_Shatter, &Moveable::Shatter,
/// Set effect to moveable
// @function Moveable:SetEffect
// @tparam Effects.EffectID effect Type of effect to assign.
// @tparam float timeout time (in seconds) after which effect turns off (optional).
/// Set effect to moveable
// @function Moveable:SetEffect
// @tparam Effects.EffectID effect Type of effect to assign.
// @tparam float timeout time (in seconds) after which effect turns off (optional).
ScriptReserved_SetEffect, &Moveable::SetEffect,
/// Set custom colored burn effect to moveable
// @function Moveable:SetCustomEffect
// @tparam Color Color1 color the primary color of the effect (also used for lighting).
// @tparam Color Color2 color the secondary color of the effect.
// @tparam float timeout time (in seconds) after which effect turns off (optional).
/// Set custom colored burn effect to moveable
// @function Moveable:SetCustomEffect
// @tparam Color Color1 color the primary color of the effect (also used for lighting).
// @tparam Color Color2 color the secondary color of the effect.
// @tparam float timeout time (in seconds) after which effect turns off (optional).
ScriptReserved_SetCustomEffect, &Moveable::SetCustomEffect,
/// Get current moveable effect
// @function Moveable:GetEffect
// @treturn Effects.EffectID effect type currently assigned to moveable.
/// Get current moveable effect
// @function Moveable:GetEffect
// @treturn Effects.EffectID effect type currently assigned to moveable.
ScriptReserved_GetEffect, &Moveable::GetEffect,
/// Get the moveable's status.
// @function Moveable:GetStatus()
// @treturn Objects.MoveableStatus The moveable's status.
/// Get the moveable's status.
// @function Moveable:GetStatus()
// @treturn Objects.MoveableStatus The moveable's status.
ScriptReserved_GetStatus, &Moveable::GetStatus,
/// Set the moveable's status.
// @function Moveable:SetStatus()
// @tparam Objects.MoveableStatus status The new status of the moveable.
/// Set the moveable's status.
// @function Moveable:SetStatus()
// @tparam Objects.MoveableStatus status The new status of the moveable.
ScriptReserved_SetStatus, &Moveable::SetStatus,
/// Set the name of the function to be called when the moveable is shot by Lara.
// Note that this will be triggered twice when shot with both pistols at once.
// @function Moveable:SetOnHit
// @tparam function callback function in LevelFuncs hierarchy to call when moveable is shot
ScriptReserved_SetOnHit, &Moveable::SetOnHit,
ScriptReserved_SetOnCollidedWithObject, &Moveable::SetOnCollidedWithObject,
ScriptReserved_SetOnCollidedWithRoom, &Moveable::SetOnCollidedWithRoom,
/// Set the name of the function to be called when the moveable is destroyed/killed
// Note that enemy death often occurs at the end of an animation, and not at the exact moment
// the enemy's HP becomes zero.
// @function Moveable:SetOnKilled
// @tparam function callback function in LevelFuncs hierarchy to call when enemy is killed
// @usage
// LevelFuncs.baddyKilled = function(theBaddy) print("You killed a baddy!") end
// baddy:SetOnKilled(LevelFuncs.baddyKilled)
ScriptReserved_SetOnKilled, &Moveable::SetOnKilled,
/// Retrieve the object ID
// @function Moveable:GetObjectID
// @treturn int a number representing the ID of the object
@ -433,7 +413,43 @@ ScriptReserved_GetSlotHP, & Moveable::GetSlotHP,
// @tparam Objects.ObjID ObjectID to take animation and stateID from,
// @tparam int animNumber animation from object
// @tparam int stateID state from object
ScriptReserved_AnimFromObject, &Moveable::AnimFromObject);
ScriptReserved_AnimFromObject, &Moveable::AnimFromObject,
/// Set the name of the function to be called when the moveable is shot by Lara.
// Note that this will be triggered twice when shot with both pistols at once.
// @function Moveable:SetOnHit
// @tparam function callback function in LevelFuncs hierarchy to call when moveable is shot
ScriptReserved_SetOnHit, &Moveable::SetOnHit,
/// Set the function to be called when this moveable collides with another moveable
// @function Moveable:SetOnCollidedWithObject
// @tparam function func callback function to be called (must be in LevelFuncs hierarchy). This function can take two arguments; these will store the two @{Moveable}s taking part in the collision.
// @usage
// LevelFuncs.objCollided = function(obj1, obj2)
// print(obj1:GetName() .. " collided with " .. obj2:GetName())
// end
// baddy:SetOnCollidedWithObject(LevelFuncs.objCollided)
ScriptReserved_SetOnCollidedWithObject, &Moveable::SetOnCollidedWithObject,
/// Set the function called when this moveable collides with room geometry (e.g. a wall or floor). This function can take an argument that holds the @{Moveable} that collided with geometry.
// @function Moveable:SetOnCollidedWithRoom
// @tparam function func callback function to be called (must be in LevelFuncs hierarchy)
// @usage
// LevelFuncs.roomCollided = function(obj)
// print(obj:GetName() .. " collided with room geometry")
// end
// baddy:SetOnCollidedWithRoom(LevelFuncs.roomCollided)
ScriptReserved_SetOnCollidedWithRoom, &Moveable::SetOnCollidedWithRoom,
/// Set the name of the function to be called when the moveable is destroyed/killed
// Note that enemy death often occurs at the end of an animation, and not at the exact moment
// the enemy's HP becomes zero.
// @function Moveable:SetOnKilled
// @tparam function callback function in LevelFuncs hierarchy to call when enemy is killed
// @usage
// LevelFuncs.baddyKilled = function(theBaddy) print("You killed a baddy!") end
// baddy:SetOnKilled(LevelFuncs.baddyKilled)
ScriptReserved_SetOnKilled, &Moveable::SetOnKilled);
}
void Moveable::Init()
@ -502,27 +518,11 @@ void Moveable::SetOnKilled(const TypeOrNil<LevelFunc>& cb)
SetLevelFuncCallback(cb, ScriptReserved_SetOnKilled, *this, m_item->Callbacks.OnKilled);
}
/// Set the function to be called when this moveable collides with another moveable
// @function Moveable:SetOnCollidedWithObject
// @tparam function func callback function to be called (must be in LevelFuncs hierarchy). This function can take two arguments; these will store the two @{Moveable}s taking part in the collision.
// @usage
// LevelFuncs.objCollided = function(obj1, obj2)
// print(obj1:GetName() .. " collided with " .. obj2:GetName())
// end
// baddy:SetOnCollidedWithObject(LevelFuncs.objCollided)
void Moveable::SetOnCollidedWithObject(const TypeOrNil<LevelFunc>& cb)
{
SetLevelFuncCallback(cb, ScriptReserved_SetOnCollidedWithObject, *this, m_item->Callbacks.OnObjectCollided);
}
/// Set the function called when this moveable collides with room geometry (e.g. a wall or floor). This function can take an argument that holds the @{Moveable} that collided with geometry.
// @function Moveable:SetOnCollidedWithRoom
// @tparam function func callback function to be called (must be in LevelFuncs hierarchy)
// @usage
// LevelFuncs.roomCollided = function(obj)
// print(obj:GetName() .. " collided with room geometry")
// end
// baddy:SetOnCollidedWithRoom(LevelFuncs.roomCollided)
void Moveable::SetOnCollidedWithRoom(const TypeOrNil<LevelFunc>& cb)
{
SetLevelFuncCallback(cb, ScriptReserved_SetOnCollidedWithRoom, *this, m_item->Callbacks.OnRoomCollided);

View file

@ -172,32 +172,27 @@ ObjectsHandler::ObjectsHandler(sol::state* lua, sol::table& parent) :
void ObjectsHandler::TestCollidingObjects()
{
// Remove any items which can't collide.
for (const auto id : m_collidingItemsToRemove)
m_collidingItems.erase(id);
// Remove items which can't collide.
for (int itemNumber : m_collidingItemsToRemove)
m_collidingItems.erase(itemNumber);
m_collidingItemsToRemove.clear();
for (const auto idOne : m_collidingItems)
for (int itemNumber0 : m_collidingItems)
{
auto item = &g_Level.Items[idOne];
if (!item->Callbacks.OnObjectCollided.empty())
auto& item = g_Level.Items[itemNumber0];
if (!item.Callbacks.OnObjectCollided.empty())
{
// Test against other moveables.
GetCollidedObjects(item, 0, true, CollidedItems, nullptr, false);
size_t i = 0;
while (CollidedItems[i])
{
short idTwo = CollidedItems[i] - &g_Level.Items[0];
g_GameScript->ExecuteFunction(item->Callbacks.OnObjectCollided, idOne, idTwo);
++i;
}
auto collObjects = GetCollidedObjects(item, true, false);
for (const auto& collidedItemPtr : collObjects.ItemPtrs)
g_GameScript->ExecuteFunction(item.Callbacks.OnObjectCollided, itemNumber0, collidedItemPtr->Index);
}
if (!item->Callbacks.OnRoomCollided.empty())
if (!item.Callbacks.OnRoomCollided.empty())
{
// Test against room geometry.
if (TestItemRoomCollisionAABB(item))
g_GameScript->ExecuteFunction(item->Callbacks.OnRoomCollided, idOne);
if (TestItemRoomCollisionAABB(&item))
g_GameScript->ExecuteFunction(item.Callbacks.OnRoomCollided, itemNumber0);
}
}
}
@ -210,10 +205,10 @@ void ObjectsHandler::AssignLara()
bool ObjectsHandler::NotifyKilled(ItemInfo* key)
{
auto it = moveables.find(key);
if (std::end(moveables) != it)
if (it != std::end(moveables))
{
for (auto& m : moveables[key])
m->Invalidate();
for (auto* movPtr : moveables[key])
movPtr->Invalidate();
return true;
}