mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 05:38:56 +00:00
571 lines
22 KiB
C++
571 lines
22 KiB
C++
/* ResidualVM - A 3D game interpreter
|
|
*
|
|
* ResidualVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the AUTHORS
|
|
* file distributed with this source distribution.
|
|
*
|
|
* Additional copyright for this file:
|
|
* Copyright (C) 1999-2000 Revolution Software Ltd.
|
|
* This code is based on source code created by Revolution Software,
|
|
* used with permission.
|
|
*
|
|
* This program 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.
|
|
*
|
|
* This program 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 this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "engines/icb/common/px_rcutypes.h"
|
|
|
|
#include "debug.h"
|
|
|
|
#include "line_of_sight.h"
|
|
#include "global_objects.h"
|
|
#include "mission.h"
|
|
#include "tracer.h"
|
|
#include "event_manager.h"
|
|
#include "res_man.h"
|
|
|
|
namespace ICB {
|
|
|
|
int32 john_number_traces = 0;
|
|
int32 john_total_traces = 0;
|
|
|
|
void _line_of_sight::Initialise() {
|
|
uint32 i;
|
|
_floor_world *pFloorWorld;
|
|
_linked_data_file *pyBarriers;
|
|
uint32 oFileName_hash = NULL_HASH;
|
|
|
|
// Set the number of subscribers processed per cycle back to its starting value.
|
|
m_nSubsPerCycle = LOS_DEFAULT_SUBSCRIBERS_PER_CYCLE;
|
|
|
|
// This is where in the list of subscribers processing will begin next cycle.
|
|
m_nFirstSubscriber = 0;
|
|
|
|
// No subscribers to start with.
|
|
m_nTotalCurrentSubscribers = 0;
|
|
|
|
// Now open the list of barriers and set a pointer to them.
|
|
#if defined(_PSX)
|
|
char *oFileName = PX_FILENAME_LINEOFSIGHT;
|
|
|
|
#else
|
|
char oFileName[ENGINE_STRING_LEN];
|
|
|
|
// When clustered the session files have the base stripped
|
|
strcpy(oFileName, PX_FILENAME_LINEOFSIGHT);
|
|
#endif
|
|
|
|
uint32 cluster_hash = MS->Fetch_session_cluster_hash();
|
|
m_pyLOSData = (_linked_data_file *)private_session_resman->Res_open(oFileName, oFileName_hash, MS->Fetch_session_cluster(), cluster_hash);
|
|
|
|
Zdebug("private_session_resman opened %s", (const char *)oFileName);
|
|
|
|
// Check file version is correct.
|
|
if (m_pyLOSData->GetHeaderVersion() != VERSION_PXWGLINEOFSIGHT)
|
|
Fatal_error(".pxwglineofsight version check failed (file has %d, engine has %d)", m_pyLOSData->GetHeaderVersion(), VERSION_PXWGLINEOFSIGHT);
|
|
|
|
// The tracer object can be initialised now we have the barrier map.
|
|
g_oTracer.SetUpParameters(m_pyLOSData);
|
|
|
|
// Set the number of objects from the list that should already have been created in the session.
|
|
m_nNumObjects = MS->total_objects;
|
|
|
|
// Clear the tables.
|
|
memset((unsigned char *)m_pnTable, 0, (size_t)(LOS_2D_SIZE_PACKED * sizeof(uint8)));
|
|
memset((unsigned char *)m_pnSubscribers, 0, (size_t)(LOS_2D_SIZE_PACKED * sizeof(uint8)));
|
|
memset((unsigned char *)m_pbSuspended, 0, (size_t)(LOS_1D_SIZE * sizeof(bool8)));
|
|
memset((unsigned char *)m_pbCanSeeInDark, 0, LOS_1D_SIZE * sizeof(bool8));
|
|
memset((unsigned char *)m_pbIgnoreShadows, 0, LOS_1D_SIZE * sizeof(bool8));
|
|
|
|
// Field-of-view for megas defaults to 180. Note that this array is not accessed for non-megas, so it doesn't
|
|
// matter that their entries get set to 180 too. Also, by default you are not subscribed to be looking for anything!
|
|
for (i = 0; i < m_nNumObjects; ++i) {
|
|
m_pnFieldOfView[i] = LOS_DEFAULT_MEGA_FIELD_OF_VIEW;
|
|
m_pfHeightOfView[i] = LOS_DEFAULT_OBJECT_HEIGHT_OF_VIEW;
|
|
m_pnSeeingDistance[i] = LOS_DEFAULT_OBJECT_SEEING_DISTANCE;
|
|
m_pnSubscribeNum[i] = 0;
|
|
}
|
|
|
|
// Get the pointer to the raw barriers, make sure it is valid and set it in the tracer.
|
|
pyBarriers = MS->session_barriers->Get_barrier_pointer();
|
|
|
|
if (!pyBarriers)
|
|
Fatal_error("Barriers pointer NULL in _line_of_sight::Initialise()");
|
|
|
|
g_oTracer.SetBarrierPointer(pyBarriers);
|
|
|
|
// The tracer also needs access to the routing data to get the floor rectangles.
|
|
pFloorWorld = MS->floor_def;
|
|
g_oTracer.SetFloorsPointer(pFloorWorld);
|
|
|
|
// These probably don't need setting, but we'll do it for completeness.
|
|
m_oImpactPoint.Set(REAL_ZERO, REAL_ZERO, REAL_ZERO);
|
|
m_eImpactType = NO_IMPACT;
|
|
}
|
|
|
|
void _line_of_sight::DutyCycle() {
|
|
// If there's 0 or 1 objects, there can be no events generated between them.
|
|
if (m_nNumObjects < 2)
|
|
return;
|
|
|
|
// Now I need to do the what-sees-what calculations fresh for the new array.
|
|
// Don't do anything if the line-of-sight engine is turned off.
|
|
if (m_bSwitchedOn)
|
|
WhatSeesWhat();
|
|
}
|
|
|
|
void _line_of_sight::Subscribe(uint32 nObserverID, uint32 nTargetID) {
|
|
// Stop an object from subscribing to itself.
|
|
if (nObserverID == nTargetID)
|
|
return;
|
|
|
|
// Nothing to do if this subscription is in place already.
|
|
if (!GetPackedBit(m_pnSubscribers, nObserverID, nTargetID)) {
|
|
// Simply set the corresponding entry in the subscriber's table to true.
|
|
SetPackedBit(m_pnSubscribers, nObserverID, nTargetID, TRUE8);
|
|
|
|
// Keep track of total number of subscribers.
|
|
++m_nTotalCurrentSubscribers;
|
|
|
|
// This keeps track of how many objects the current object is subscribed to look for. Like a
|
|
// reference count, the object no longer requires LOS processing if this hits 0.
|
|
m_pnSubscribeNum[nObserverID]++;
|
|
}
|
|
}
|
|
|
|
void _line_of_sight::UnSubscribe(uint32 nObserverID, uint32 nTargetID) {
|
|
// Nothing to do if there is no subscription to remove.
|
|
if (GetPackedBit(m_pnSubscribers, nObserverID, nTargetID)) {
|
|
// Simply set the corresponding entry in the subscriber's table to false.
|
|
SetPackedBit(m_pnSubscribers, nObserverID, nTargetID, FALSE8);
|
|
--m_nTotalCurrentSubscribers;
|
|
|
|
// Undo the number of subscriptions for this observerID
|
|
m_pnSubscribeNum[nObserverID]--;
|
|
|
|
// Reset the truth table entry to 0 to make sure A can't see B anymore.
|
|
SetPackedBit(m_pnTable, nObserverID, nTargetID, FALSE8);
|
|
}
|
|
}
|
|
|
|
bool8 _line_of_sight::ObjectToObject(uint32 nObserverID, uint32 nTargetID, _barrier_ray_type eRayType, bool8 bCanSeeUs, ActorEyeMode eEyeMode, bool8 bOverrideHeightLimit) {
|
|
_logic *pObserver;
|
|
_logic *pTarget;
|
|
bool8 bObserverIsActor, bTargetIsActor;
|
|
PXreal fObserverX, fObserverY, fObserverZ;
|
|
PXfloat fObserverDirection;
|
|
PXreal fTargetX, fTargetY, fTargetZ;
|
|
uint32 nFieldOfView;
|
|
px3DRealPoint oFrom, oTo;
|
|
bool8 nRetVal;
|
|
|
|
// If the line-of-sight engine is turned off, this function always returns false.
|
|
if (!m_bSwitchedOn)
|
|
return (FALSE8);
|
|
|
|
// Yes, this observer wants to know about line-of-sight for this target.
|
|
pObserver = MS->logic_structs[nObserverID];
|
|
pTarget = MS->logic_structs[nTargetID];
|
|
|
|
// Check if the observer is an actor (with 180-degree vision) or an object (with
|
|
// 360-degree 'vision'). Also need to set type for target, though field-of-view doesn't
|
|
// matter.
|
|
bObserverIsActor = (pObserver->image_type == VOXEL) ? TRUE8 : FALSE8;
|
|
bTargetIsActor = (pTarget->image_type == VOXEL) ? TRUE8 : FALSE8;
|
|
|
|
// The following shadow check only gets done if shadows are enabled.
|
|
if (m_bHandleShadows) {
|
|
// If the observer is unable to see in the dark and the target is in shade and we
|
|
// are not ignoring shadows for that target then observer cannot see target.
|
|
if (!m_pbCanSeeInDark[nObserverID] && pTarget->mega->in_shade && !m_pbIgnoreShadows[nTargetID])
|
|
return (FALSE8);
|
|
}
|
|
|
|
// Set observer's position.
|
|
if (bObserverIsActor) {
|
|
fObserverX = pObserver->mega->actor_xyz.x;
|
|
fObserverZ = pObserver->mega->actor_xyz.z;
|
|
fObserverDirection = pObserver->pan;
|
|
|
|
switch (eEyeMode) {
|
|
case USE_OBJECT_VALUE:
|
|
if (pObserver->mega->Is_crouched())
|
|
fObserverY = pObserver->mega->actor_xyz.y + ACTOR_CROUCHED_HEIGHT;
|
|
else
|
|
fObserverY = pObserver->mega->actor_xyz.y + ACTOR_EYE_HEIGHT;
|
|
break;
|
|
|
|
case FORCE_EYE_HEIGHT:
|
|
fObserverY = pObserver->mega->actor_xyz.y + ACTOR_EYE_HEIGHT;
|
|
break;
|
|
|
|
case FORCE_CROUCHED_HEIGHT:
|
|
fObserverY = pObserver->mega->actor_xyz.y + ACTOR_CROUCHED_HEIGHT;
|
|
break;
|
|
|
|
default:
|
|
fObserverY = REAL_ZERO; // Stops VC4 warning.
|
|
Fatal_error("Illegal value %d for eEyeMode passed into _line_of_sight::ObjectToObject()", eEyeMode);
|
|
};
|
|
} else {
|
|
fObserverX = pObserver->prop_xyz.x;
|
|
fObserverY = pObserver->prop_xyz.y;
|
|
fObserverZ = pObserver->prop_xyz.z;
|
|
fObserverDirection = FLOAT_ZERO; // Do this for VC4 compiler.
|
|
}
|
|
|
|
// Set target's position.
|
|
if (bTargetIsActor) {
|
|
fTargetX = pTarget->mega->actor_xyz.x;
|
|
fTargetZ = pTarget->mega->actor_xyz.z;
|
|
|
|
if (pTarget->mega->Is_crouched())
|
|
fTargetY = pTarget->mega->actor_xyz.y + ACTOR_CROUCHED_HEIGHT;
|
|
else
|
|
fTargetY = pTarget->mega->actor_xyz.y + ACTOR_EYE_HEIGHT;
|
|
} else {
|
|
fTargetX = pTarget->prop_xyz.x;
|
|
fTargetY = pTarget->prop_xyz.y;
|
|
fTargetZ = pTarget->prop_xyz.z;
|
|
}
|
|
|
|
// Here we check the height of the two objects against each other. If this test fails, we don't need to do
|
|
// any of the heavier calculations. Note that this test can can now be overriden by the caller.
|
|
if (!bOverrideHeightLimit) {
|
|
if ((fTargetY > fObserverY + m_pfHeightOfView[nObserverID]) || (fTargetY < fObserverY - m_pfHeightOfView[nObserverID])) {
|
|
// Target cannot be in view because it is not in the right height range.
|
|
m_eImpactType = NO_IMPACT;
|
|
m_oImpactPoint = oFrom;
|
|
return (FALSE8);
|
|
}
|
|
}
|
|
|
|
// Have a maximum range that objects can see
|
|
PXreal dx = fTargetX - fObserverX;
|
|
PXreal dy = fTargetY - fObserverY;
|
|
PXreal dz = fTargetZ - fObserverZ;
|
|
const PXreal max_range = (PXreal)m_pnSeeingDistance[nObserverID];
|
|
const PXreal max_range_sqr = max_range * max_range;
|
|
|
|
// Try on size of dx, dy, dz first
|
|
if (((PXreal)PXfabs(dx) > max_range) || ((PXreal)PXfabs(dy) > max_range) || ((PXreal)PXfabs(dz) > max_range)) {
|
|
m_eImpactType = NO_IMPACT;
|
|
m_oImpactPoint = oFrom;
|
|
return (FALSE8);
|
|
}
|
|
|
|
// Now try on squared distance
|
|
if ((dx * dx + dy * dy + dz * dz) > max_range_sqr) {
|
|
m_eImpactType = NO_IMPACT;
|
|
m_oImpactPoint = oFrom;
|
|
return (FALSE8);
|
|
}
|
|
|
|
// Object is in object's field of view. Now we need to check if anything is
|
|
// in the way.
|
|
oFrom.SetX(fObserverX);
|
|
oFrom.SetY(fObserverY);
|
|
oFrom.SetZ(fObserverZ);
|
|
|
|
oTo.SetX(fTargetX);
|
|
oTo.SetY(fTargetY);
|
|
oTo.SetZ(fTargetZ);
|
|
|
|
// If the observer is an actor, we must first check field of view.
|
|
if (bObserverIsActor) {
|
|
// The obesrver is an actor, so we need to retrieve the current field-of-view setting for them.
|
|
nFieldOfView = m_pnFieldOfView[nObserverID];
|
|
|
|
// Check field of view only if not 360.
|
|
if (nFieldOfView != 360) {
|
|
// Need to check field-of-view properly for 1-180 degrees.
|
|
if (InFieldOfView(fObserverX, fObserverZ, fObserverDirection, fTargetX, fTargetZ, nFieldOfView)) {
|
|
if (bCanSeeUs)
|
|
return (TRUE8);
|
|
int32 john_test_time = GetMicroTimer();
|
|
nRetVal = g_oTracer.Trace(oFrom, oTo, eRayType, m_oImpactPoint, m_eImpactType);
|
|
john_test_time = GetMicroTimer() - john_test_time;
|
|
john_total_traces += john_test_time;
|
|
john_number_traces++;
|
|
return (nRetVal);
|
|
} else {
|
|
// Observer is an actor but target is not in field-of-view.
|
|
m_eImpactType = NO_IMPACT;
|
|
m_oImpactPoint = oFrom;
|
|
return (FALSE8);
|
|
}
|
|
} else {
|
|
// Don't need to apply field-of-view for 360-degree vision.
|
|
int32 john_test_time = g_system->getMillis();
|
|
nRetVal = g_oTracer.Trace(oFrom, oTo, eRayType, m_oImpactPoint, m_eImpactType);
|
|
john_test_time = g_system->getMillis() - john_test_time;
|
|
john_total_traces += john_test_time;
|
|
john_number_traces++;
|
|
return (nRetVal);
|
|
}
|
|
} else {
|
|
// Objects are assumed to have 360-degree vision so we just have to do a line-of-sight.
|
|
int32 john_test_time = g_system->getMillis();
|
|
nRetVal = g_oTracer.Trace(oFrom, oTo, eRayType, m_oImpactPoint, m_eImpactType);
|
|
john_test_time = g_system->getMillis() - john_test_time;
|
|
john_total_traces += john_test_time;
|
|
john_number_traces++;
|
|
return (nRetVal);
|
|
}
|
|
}
|
|
|
|
void _line_of_sight::SetFieldOfView(uint32 nID, uint32 nFieldOfView) {
|
|
// Check ID of object is valid.
|
|
if (nID >= m_nNumObjects)
|
|
Fatal_error("ID %d out-of-range (%d objects) in _line_of_sight::SetFieldOfView()", nID, m_nNumObjects);
|
|
|
|
Zdebug("Setting FOV for %d to %d", nID, nFieldOfView);
|
|
|
|
// If object is non-mega, do nothing.
|
|
if (MS->logic_structs[nID]->image_type != VOXEL)
|
|
return;
|
|
|
|
// Right, ID is a valid mega.
|
|
m_pnFieldOfView[nID] = nFieldOfView;
|
|
}
|
|
|
|
void _line_of_sight::SetSightRange(uint32 nID, uint32 nRange) {
|
|
// Check ID of object is valid.
|
|
if (nID >= m_nNumObjects)
|
|
Fatal_error("ID %d out-of-range (%d objects) in _line_of_sight::SetSightRange()", nID, m_nNumObjects);
|
|
|
|
Zdebug("Setting sight range for %d to %d", nID, nRange);
|
|
|
|
// Set the range for the object.
|
|
m_pnSeeingDistance[nID] = nRange;
|
|
}
|
|
|
|
void _line_of_sight::SetSightHeight(uint32 nID, uint32 nHeight) {
|
|
// Check ID of object is valid.
|
|
if (nID >= m_nNumObjects)
|
|
Fatal_error("ID %d out-of-range (%d objects) in _line_of_sight::SetSightHeight()", nID, m_nNumObjects);
|
|
|
|
Zdebug("Setting sight height for %d to %d", nID, nHeight);
|
|
|
|
// Set the sight height for the object.
|
|
m_pfHeightOfView[nID] = (PXfloat)nHeight;
|
|
}
|
|
|
|
void _line_of_sight::Suspend(uint32 nObserverID) {
|
|
uint32 i;
|
|
|
|
// Set the suspended flag for the object.
|
|
m_pbSuspended[nObserverID] = TRUE8;
|
|
|
|
// So engine doesn't have to wait for the LOS engine to chug round, wipe the truth table row for the object now:
|
|
// it will not see anything. Also wipe the column so object becomes invisible to other objects.
|
|
for (i = 0; i < m_nNumObjects; ++i) {
|
|
SetPackedBit(m_pnTable, i, nObserverID, FALSE8);
|
|
SetPackedBit(m_pnTable, nObserverID, i, FALSE8);
|
|
}
|
|
}
|
|
|
|
void _line_of_sight::WhatSeesWhat() {
|
|
uint32 i, j;
|
|
bool8 nResult;
|
|
bool8 bSubscribed;
|
|
uint32 nProcessed = 0;
|
|
int32 *pnHowManyTimesObjectHasSubscribed = m_pnSubscribeNum + m_nFirstSubscriber;
|
|
bool8 bTargetRequiresLOSProcessing;
|
|
uint32 nPlayerID;
|
|
|
|
// Cache player ID.
|
|
nPlayerID = MS->player.Fetch_player_id();
|
|
|
|
// Do the player line of sight every game cycle. Only do any checking if the player has subscribed
|
|
// to look for something. And don't bother if the player is suspended.
|
|
if ((m_pnSubscribeNum[nPlayerID] > 0) && !m_pbSuspended[nPlayerID]) {
|
|
// Don't try to do anything if the object has been shut down.
|
|
// Hmmm, if the player has been shut-down then in big trouble
|
|
if (MS->logic_structs[nPlayerID]->ob_status != OB_STATUS_HELD) {
|
|
// Loop for each possible observed target.
|
|
for (j = 0; j < m_nNumObjects; ++j) {
|
|
// Player must ignore himself.
|
|
if (j != nPlayerID) {
|
|
// Check to make sure target is not shut down and check to see if the
|
|
// observer has registered for line-of-sight with the target.
|
|
// Help the lazy logic evaluation : m_pnSubscribers more likely to be false
|
|
bSubscribed = GetPackedBit(m_pnSubscribers, nPlayerID, j);
|
|
if (bSubscribed && (MS->logic_structs[j]->ob_status != OB_STATUS_HELD)) {
|
|
// Yes, this observer wants to know about line-of-sight for this target.
|
|
nResult = ObjectToObject(nPlayerID, j, LIGHT, FALSE8, USE_OBJECT_VALUE); // Pass false as bCanSeeUs, this means tracer
|
|
// will always get called for player->guard type
|
|
// seeing...
|
|
if (nResult != GetPackedBit(m_pnTable, nPlayerID, j)) {
|
|
SetPackedBit(m_pnTable, nPlayerID, j, nResult);
|
|
|
|
if (nResult) {
|
|
// Entries differ, so a character has either moved in-to or out-of view.
|
|
g_oEventManager->PostNamedEventToObject(EVENT_LINE_OF_SIGHT, nPlayerID, j);
|
|
}
|
|
}
|
|
} else {
|
|
// Not registered or object shutdown so LOS = false
|
|
SetPackedBit(m_pnTable, nPlayerID, j, FALSE8);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This loop does line-of-sight for all other megas. These don't get updated as often as the player.
|
|
i = m_nFirstSubscriber;
|
|
|
|
bool8 quittingOut = 0;
|
|
|
|
// Loop for as many objects as are going to be processed.
|
|
while ((!quittingOut) && (nProcessed < m_nSubsPerCycle) && (nProcessed < m_nTotalCurrentSubscribers)) {
|
|
// Only do any checking if this object has subscribed to look for something. And don't check if
|
|
// the object is suspended.
|
|
if ((i != nPlayerID) && (*pnHowManyTimesObjectHasSubscribed > 0) && !m_pbSuspended[i]) {
|
|
// Count the objects processed this call.
|
|
++nProcessed;
|
|
|
|
// There are a number of engine states that can be set for an object that mean it no longer
|
|
// requires LOS processing.
|
|
if ((MS->logic_structs[i]->ob_status != OB_STATUS_HELD) && (MS->logic_structs[i]->big_mode != __MEGA_SLICE_HELD) &&
|
|
(MS->logic_structs[i]->big_mode != __MEGA_PLAYER_FLOOR_HELD) && (MS->logic_structs[i]->big_mode != __MEGA_INITIAL_FLOOR_HELD)) {
|
|
// Loop for each possible observed target.
|
|
for (j = 0; j < m_nNumObjects; ++j) {
|
|
// Ignore ourselves.
|
|
if (i != j) {
|
|
if ((MS->logic_structs[j]->ob_status != OB_STATUS_HELD) && (MS->logic_structs[j]->big_mode != __MEGA_SLICE_HELD) &&
|
|
(MS->logic_structs[j]->big_mode != __MEGA_PLAYER_FLOOR_HELD) && (MS->logic_structs[j]->big_mode != __MEGA_INITIAL_FLOOR_HELD)) {
|
|
bTargetRequiresLOSProcessing = TRUE8;
|
|
} else {
|
|
bTargetRequiresLOSProcessing = FALSE8;
|
|
}
|
|
|
|
// Check to make sure target is not shut down and check to see if the observer has
|
|
// registered for line-of-sight with the target.
|
|
// Help the lazy logic evaluation : m_pnSubscribers more likely to be false
|
|
bSubscribed = GetPackedBit(m_pnSubscribers, i, j);
|
|
if (bSubscribed && !m_pbSuspended[j] && bTargetRequiresLOSProcessing) {
|
|
// Yes, this observer wants to know about line-of-sight for this target.
|
|
if (j == nPlayerID)
|
|
nResult = ObjectToObject(i, j, LIGHT, LineOfSight(nPlayerID, i), USE_OBJECT_VALUE);
|
|
else
|
|
nResult = ObjectToObject(i, j, LIGHT, FALSE8, USE_OBJECT_VALUE);
|
|
|
|
if (nResult != GetPackedBit(m_pnTable, i, j)) {
|
|
SetPackedBit(m_pnTable, i, j, nResult);
|
|
|
|
if (nResult) {
|
|
// Entries differ, so a character has either moved in-to or out-of
|
|
// view.
|
|
g_oEventManager->PostNamedEventToObject(EVENT_LINE_OF_SIGHT, i, j);
|
|
} else {
|
|
}
|
|
}
|
|
} else {
|
|
// Not registered or object shutdown so LOS = false
|
|
SetPackedBit(m_pnTable, i, j, FALSE8);
|
|
}
|
|
} // end if
|
|
} // end for
|
|
} // end if
|
|
} // end if
|
|
|
|
// Move i onto next object, but because we didn't start at the beginning of the list we must watch for
|
|
// wrapping at the end. Similarly, we must move on in the number of subscriber per object array.
|
|
++i;
|
|
if (i == m_nNumObjects) {
|
|
i = 0;
|
|
pnHowManyTimesObjectHasSubscribed = m_pnSubscribeNum;
|
|
} else {
|
|
++pnHowManyTimesObjectHasSubscribed;
|
|
}
|
|
|
|
// if we have looped round whole thing and found nothing to do then get out...
|
|
if (i == m_nFirstSubscriber)
|
|
quittingOut = 1;
|
|
} // end for
|
|
|
|
// Update which subscriber to start at next DutyCycle around.
|
|
m_nFirstSubscriber = i;
|
|
}
|
|
|
|
bool8 _line_of_sight::InFieldOfView(PXreal fLookingX, PXreal fLookingZ, PXfloat fLookingDirection, PXreal fObservedX, PXreal fObservedZ, uint32 nFieldOfView) const {
|
|
PXfloat fDirection;
|
|
PXreal fDirectionX, fDirectionZ;
|
|
PXreal fTargetX, fTargetZ;
|
|
PXfloat fDotProduct;
|
|
PXreal fBoundLeftX, fBoundLeftZ, fBoundRightX, fBoundRightZ;
|
|
PXfloat fBoundLeft, fBoundRight;
|
|
PXfloat fBoundLeftDotProduct, fBoundRightDotProduct;
|
|
PXfloat fFieldOfView;
|
|
|
|
// First calculate the direction we're looking in radians.
|
|
fDirection = fLookingDirection * TWO_PI;
|
|
|
|
// Now work out the vector of the target point relative to the character doing the looking. Note that we
|
|
// don't need the unit vector, as we're only interested in sign.
|
|
fTargetX = fObservedX - fLookingX;
|
|
fTargetZ = fObservedZ - fLookingZ;
|
|
|
|
// If it is 180-degree, we can use the fast version.
|
|
if (nFieldOfView == 180) {
|
|
// The line that demarks what can be seen and what is behind the character is normal to the direction
|
|
// they are looking (180-degree vision, don't forget). This is good, because it is actually the normal
|
|
// that we need to calculate which side of the demarkation line the target point is. The unit vector
|
|
// of the direction we are looking is simply given by sine and cosine if you think about it.
|
|
fDirectionX = (PXreal)PXsin((PXdouble)fDirection);
|
|
fDirectionZ = (PXreal)PXcos((PXdouble)fDirection);
|
|
|
|
// The sign of the dot product tells us whether or not the point can be seen. Note that we don't have
|
|
// to calculate the unit vector of the target point, since we are only interested in sign. And the sign
|
|
// is reversed because the Z axis is reversed.
|
|
fDotProduct = fDirectionX * fTargetX + fDirectionZ * fTargetZ;
|
|
|
|
// We'll treat a zero result as point-not-in-view, so characters will have a tiny bit less than
|
|
// 180-degree vision.
|
|
if (fDotProduct >= REAL_ZERO)
|
|
return (TRUE8);
|
|
else
|
|
return (FALSE8);
|
|
} else {
|
|
// We must use the slower version for non-180 vision. Work out the direction of the two bounding lines
|
|
// on the field of view.
|
|
fFieldOfView = (PXfloat)DEGREES_TO_RADIANS(nFieldOfView);
|
|
|
|
fBoundLeft = fDirection + fFieldOfView / REAL_TWO;
|
|
fBoundRight = fDirection - fFieldOfView / REAL_TWO;
|
|
|
|
// Work out the unit vector of the normals to the bounding lines.
|
|
fBoundLeftX = -(PXreal)PXcos((PXdouble)fBoundLeft);
|
|
fBoundLeftZ = (PXreal)PXsin((PXdouble)fBoundLeft);
|
|
|
|
fBoundRightX = (PXreal)PXcos((PXdouble)fBoundRight);
|
|
fBoundRightZ = -(PXreal)PXsin((PXdouble)fBoundRight);
|
|
|
|
// And work out dot product for target relative to field-of-view bounding lines.
|
|
fBoundLeftDotProduct = fBoundLeftX * fTargetX + fBoundLeftZ * fTargetZ;
|
|
fBoundRightDotProduct = fBoundRightX * fTargetX + fBoundRightZ * fTargetZ;
|
|
|
|
// Check signs to see if target is in view.
|
|
if ((fBoundLeftDotProduct > REAL_ZERO) && (fBoundRightDotProduct > REAL_ZERO))
|
|
return (TRUE8);
|
|
else
|
|
return (FALSE8);
|
|
}
|
|
}
|
|
|
|
} // End of namespace ICB
|