#charset "us-ascii" #include "adv3.h" /* Tads-3 OccludingConnector Steve Breslin, 2004 email: versim@hotmail.com ==== 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; } } } } ;