#charset "us-ascii"
#include "adv3.h"
/*
Tads-3 OccludingConnector
Steve Breslin, 2004
email:
[email protected]
====
Licence:
Everyone is free to use this, but please alert me to your findings, and
provide me with all improvements and modifications.
====
About this module:
This module models a sense connector, like a window, which occludes some
objects that might otherwise be sensed through it. If you're looking
through a window, for example, it's a good bet that you can't see
everything in the room you're looking into. The OccludingConnector
allows you to model this, by defining what objects cannot be seen
through the connector.
This module formalizes a discussion on v-space over July and August
2004. (The curious reader is encouraged to read the 'occlusion' threads
for these months.)
We unanimously agreed that the behavior of the then-called
OccludingConnector was counter-untuitive in that it didn't filter by
sense-path, and didn't otherwise have any conceptual tie to
sense-connection (despite the fact that it was initially designed for
the purpose described above).
As of Tads-3 release 3.0.8, this class has been renamed Occluder, which
more accurately describes what it does: the Occluder makes a final pass
over objects in sense-scope, and filters that table of objects
irrespective of their sense-path to the POV.
This new OccludingConnector, by contrast, filters an object only if it
it is directly connected to the POV through the OccludingConnector in
question.
Brendan Barnwell proposed a solution roughly along the lines we adopt in
this module. That preliminary solution added significant computation
overhead to core processes. This module revises Brendan's initial
solution, and mitigates almost entirely the computation overhead.
In fact, if OccludingConnectors are used in a game, this module is
somewhat faster (on average) than the library's current Occluder
mechanism (Occluder.finishSensePath()), not only because it is directed
only at potentially relevant objects in the sense tree, and not at the
entire sense table, but also because it has the virtue, as Brendan
wrote, of occluding "pieces of the actual sense tree, not just
individual objects [and therefore] ensures that, if some object is
invisible, all objects 'under' it in the sense tree are also invisible."
====
The algorithm is fairly simple:
1) when the sense-path building mechanism passes through an
OccludingConnector, that connector is recorded as the
libGlobal.curOccludingConnector.
2) when the sense-path building mechanism passes through a
SenseConnector, the curOccludingConnector is reset to nil (or if it's
also an OccludingConnector, reset to itself).
This is because the object filtered by one occluding connector should be
able to be added to the path through another connector downpath
(provided the latter connector is not itself filtered by the occluding
connector). This means that occluding connectors don't indirectly
occlude, which seems slightly more intuitive and desirable.
3) The previous curOccludingConnector is recorded as
oldOccCon, which is used to restore curOccludingConnector when the
current connector is finished adding its connections.
4) An object is not added to the sense-path if it is occluded by the
current occluding connector; also, the occluded object doesn't allow
its containment children or containment parents to be added to the
sense-path. (That is, objects further down the sense-path are also
occluded.)
Note: objects (and their containment children and containment parents)
can be added to the sense-path through other connections, either
indirectly downpath of the occluding connector, or on a separate path
from the occluding connector.
*/
OccludingConnector: SenseConnector
/* Do we occlude the given object in the given sense?
* This returns true if the object is to be filtered, nil if not.
*/
checkOcclusion(obj, sense)
{
/* by default, we don't occlude anything. Here's where you
* override on your instance of the OccludingConnector to
* occlude certain objects. E.g.:
*. if (obj == bookCase)
*. return true;
*. return nil;
*/
return nil;
}
;
modify libGlobal
/* curOccludingConnector is the current OccludingConnector. It is
* used to filter out any objects (and their containment children)
* from the connector's locations (outward from the POV).
*/
curOccludingConnector = nil
;
/* We modify Thing to check for the current occluding connector, if one
* currently exists, so we don't add to the sense path objects which
* are occluded by the occluding connector.
*
* Thus we modify sensePathToLoc(), sensePathWithin(), and
* sensePathWithout(). Note that we don't have to modify
* sensePathToContents(), because that's only an intermediary step, and
* doesn't update sense path information itself.
*/
modify Thing
sensePathToLoc(sense, trans, obs, fill)
{
if (location != nil)
{
/* check if my location is being filtered by the current
* occluding connector. if it is, we do nothing. otherwise,
* we proceed as usual.
*/
if (!libGlobal.curOccludingConnector
|| !libGlobal.curOccludingConnector.
checkOcclusion(location, sense))
{
location.sensePathFromWithin(self, sense, trans, obs, fill);
}
}
}
sensePathFromWithin(fromChild, sense, trans, obs, fill)
{
/* if I'm occluded by the current occluding connector, I don't
* add myself (or my contents) to the sense path.
*/
if (libGlobal.curOccludingConnector
&& libGlobal.curOccludingConnector.checkOcclusion(self, sense))
return;
/* otherwise, proceed as usual. */
inherited(fromChild, sense, trans, obs, fill);
}
sensePathFromWithout(fromParent, sense, trans, obs, fill)
{
/* if I'm occluded by the current occluding connector, I don't
* add myself (or my contents) to the sense path.
*/
if (libGlobal.curOccludingConnector
&& libGlobal.curOccludingConnector.checkOcclusion(self, sense))
return;
/* otherwise, proceed as usual. */
inherited(fromParent, sense, trans, obs, fill);
}
;
modify SenseConnector
/*
* Build a sense path from a container to me
*/
sensePathFromWithout(fromParent, sense, trans, obs, fill)
{
/* if I'm occluded by the current occluding connector, I don't
* add myself (or my other containers (in my locationList)) to
* the sense path.
*/
if (libGlobal.curOccludingConnector
&& libGlobal.curOccludingConnector.checkOcclusion(self, sense))
return;
/*
* if there's better transparency along this path than along any
* previous path we've used to visit this item, take this path
*/
if (transparencyCompare(trans, tmpTrans_) > 0)
{
local transThru;
/* remember the new path to this point */
tmpTrans_ = trans;
tmpObstructor_ = obs;
/* we're coming to this object from outside */
tmpPathIsIn_ = true;
/* transmit to my contents */
sensePathToContents(sense, trans, obs, fill);
/*
* We must transmit this energy to each of our other
* parents, possibly reduced for traversing our connector.
* Calculate the new level after traversing our connector.
*/
transThru = transparencyAdd(trans, transSensingThru(sense));
/* if we changed the transparency, we're the obstructor */
if (transThru != trans)
obs = self;
/*
* if there's anything left, transmit it to the other
* containers
*/
if (transThru != opaque)
{
/* remember the old occluding connector. */
local oldOccCon = libGlobal.curOccludingConnector;
/* in the rare case that we exit unexpectedly from the
* following code block, we want definitely to restore
* the libGlobal.curOccludingConnector. So we use a
* try/finally structure to ensure this.
*/
try
{
/* reset the current occluding connector. */
libGlobal.curOccludingConnector =
(ofKind(OccludingConnector) ? self : nil);
/* transmit to each container except the source */
foreach (local cur in locationList)
{
/* if this isn't the sender, transmit to it */
if (cur != fromParent)
cur.sensePathFromWithin(self, sense,
transThru, obs, fill);
}
}
finally
{
/* restore the old occluding connector */
libGlobal.curOccludingConnector = oldOccCon;
}
}
}
}
;