Skip to content

Instantly share code, notes, and snippets.

@jamesu
Created August 27, 2024 00:57
Show Gist options
  • Save jamesu/5b8893904bce52281b5f4b78e9162894 to your computer and use it in GitHub Desktop.
Save jamesu/5b8893904bce52281b5f4b78e9162894 to your computer and use it in GitHub Desktop.
/*
ZonedPortalObject
Implementation of a zoned portal object for TGE 1.4.2
Set "targetName" to the name of the target object used as the basis of the
view through the portal.
The view will oriented along the objects +y axis.
The object also manages a zone, so both the portal and objects within the object will
only be visible provided the camera is inside the objects bounds.
The same logic is also applied to the network ghost scope.
*/
class ZonedPortalObject : public SceneObject
{
public:
typedef SceneObject Parent;
DECLARE_CONOBJECT(ZonedPortalObject);
S32 mNumPrepRenderCalls;
S32 mNumRenderCalls;
S32 mNumPortalsOpened;
S32 mNumPortalsClosed;
S32 mNumPortalsPlanesQueried;
S32 mNumPortalsFrustumsQueried;
MatrixF mPortalObjToWorldTransform;
MatrixF mPortalWorldToObjTransform;
PlaneF mPortalPlane;
StringTableEntry mPortalObjectName;
SimObjectPtr<ZonedPortalObject> mTarget;
ZonedPortalObject() :
mNumRenderCalls(0),
mNumPortalsOpened(0),
mNumPortalsClosed(0),
mNumPortalsPlanesQueried(0),
mNumPortalsFrustumsQueried(0)
{
mTypeMask |= StaticObjectType;
mNetFlags.set(Ghostable | ScopeAlways);
mPortalObjToWorldTransform = MatrixF(1);
mPortalWorldToObjTransform = MatrixF(1);
mPortalObjectName = "";
}
void setObjectBox(Point3F minb, Point3F maxb)
{
mObjBox = Box3F(minb, maxb);
resetWorldBox();
resetRenderWorldBox();
}
static void initPersistFields()
{
Parent::initPersistFields();
addField("targetName", TypeString, Offset(mPortalObjectName, ZonedPortalObject));
}
bool onAdd()
{
if (!Parent::onAdd())
return false;
setObjectBox(Point3F(-10, -10, -10), Point3F(10, 10, 10));
addToScene();
return true;
}
U32 packUpdate(NetConnection * con, U32 mask, BitStream * stream)
{
// Pack Parent.
U32 retMask = Parent::packUpdate(con, mask, stream);
stream->writeAffineTransform(mObjToWorld);
stream->writeString(mPortalObjectName);
// Were done ...
return(retMask);
}
void unpackUpdate(NetConnection * con, BitStream * stream)
{
// Unpack Parent.
Parent::unpackUpdate(con, stream);
MatrixF xfm(1);
stream->readAffineTransform(&xfm);
mPortalObjectName = stream->readSTString();
if (dMemcmp(&xfm, &getTransform(), sizeof(MatrixF)) != 0)
{
setTransform(xfm);
}
}
virtual bool onSceneAdd(SceneGraph* graph)
{
// NOTE: Any objects within the zone should be added
// once registerZones is called.
// The same logic is also called by onSceneAdd but as
// there is no way of registering the object as a zone
// manager before its added to the scene it will never
// get invoked at that point.
if (!Parent::onSceneAdd(graph))
{
return false;
}
mSceneManager->registerZones(this, 1);
return true;
}
virtual void onSceneRemove()
{
mSceneManager->unregisterZones(this);
Parent::onSceneRemove();
}
virtual void setTransform(const MatrixF& mat)
{
Parent::setTransform(mat);
}
virtual void setScale(const VectorF& scale)
{
Parent::setScale(scale);
}
virtual void setRenderTransform(const MatrixF& mat)
{
Parent::setRenderTransform(mat);
}
virtual bool getOverlappingZones(SceneObject* obj, U32* zones, U32* numZones)
{
// NOTE: this is usually used to set the master zones of an object
Box3F objBox = obj->getWorldBox();
Point3F worldPos = obj->getBoxCenter();
mWorldToObj.mul(objBox);
objBox.min.convolveInverse(mObjScale);
objBox.max.convolveInverse(mObjScale);
bool overlaps = mObjBox.isOverlapped(objBox);
if (overlaps)
{
// Its AT LEAST half-way in...
zones[0] = mZoneRangeStart; // This is our zone's ID
*numZones = 1;
if (mObjBox.isContained(objBox))
{
// Its FULLY IN
return false;
}
return true;
}
return true;
}
virtual U32 getPointZone(const Point3F& p)
{
// NOTE: this is usually used by SceneGraph::findZone to figure out
// which zone a point belongs to for this object.
Point3F osPoint = p;
mWorldToObj.mulP(osPoint);
osPoint.convolveInverse(mObjScale);
if (!mObjBox.isContained(osPoint))
return 0; // outside
return mZoneRangeStart; // in first zone
}
virtual bool scopeObject(const Point3F& rootPosition,
const F32 rootDistance,
bool* zoneScopeState)
{
// NOTE: this is used for network scoping
Point3F localPoint = rootPosition;
getWorldTransform().mulP(localPoint);
localPoint.convolveInverse(getScale());
// NOTE: If object is outside we return true,
// if object is inside we return false
U32 realStartZone = getPointZone(rootPosition);
if (realStartZone == 0)
return false;
return true;
}
virtual void renderObject(SceneState* state, SceneRenderImage* image)
{
// NOTE: renders the object. This is here mostly for debugging.
mNumRenderCalls++;
RectI viewport;
glMatrixMode(GL_PROJECTION);
glPushMatrix();
dglGetViewport(&viewport);
state->setupObjectProjection(this);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
dglMultMatrix(&mRenderObjToWorld);
glScalef(mObjScale.x, mObjScale.y, mObjScale.z);
Point3F extent = mObjBox.max - mObjBox.min;
Point3F center;
mObjBox.getCenter(&center);
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
dglWireCube(extent, center);
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
dglSetViewport(viewport);
}
virtual bool prepRenderImage(SceneState* state, const U32 stateKey,
const U32 /*startZone*/, const bool /*modifyBaseState*/)
{
// NOTE: this is called before rendering to setup the scene tree.
// This is where zone visibility is determined and also where
// any transform portals should be registered.
mNumPrepRenderCalls++;
if (isLastState(state, stateKey))
return false;
if (mTarget == NULL)
{
ZonedPortalObject* fo = NULL;
Sim::findObject(mPortalObjectName, fo);
mTarget = fo;
}
setLastState(state, stateKey);
if (mTarget)
{
mPortalObjToWorldTransform = mTarget->getTransform();
mPortalWorldToObjTransform = mTarget->getWorldTransform();
Point3F center;
mObjBox.getCenter(&center);
mPortalPlane = PlaneF(center, Point3F(0,1,0));
}
U32 baseZone = getCurrZone(0); // i.e. which zone we are in
bool multipleZones = getNumCurrZones() > 1;
SceneRenderImage* image = new SceneRenderImage;
image->obj = this;
state->insertRenderImage(image);
SceneState::ZoneState baseState = state->getBaseZoneState();
SceneState::ZoneState& insideState = state->getZoneStateNC(mZoneRangeStart);
// Derive zone state from base
insideState = baseState;
// Zone is only visible if the camera is inside
// NOTE: usually there would be some sort of portal test here
Point3F camPos = state->getCameraPosition();
mWorldToObj.mulP(camPos);
camPos.convolveInverse(mObjScale);
insideState.render = mObjBox.isContained(camPos); // Important! marks the zone as visible
if (insideState.render)
{
// Add in transform portal provided we are on the right side
Point3F boxCenter;
mObjBox.getCenter(&boxCenter);
getTransform().mulP(boxCenter);
boxCenter.convolve(getScale());
// Get camera in object space
Point3F camPos = state->getCameraPosition();
mWorldToObj.mulP(camPos);
camPos.convolveInverse(getScale());
if (mTarget && mPortalPlane.whichSide(camPos) == PlaneF::Back)
{
// NOTE: the zone id here should be the zone the portal object resides in
state->insertTransformPortal(this, 0, mZoneRangeStart, boxCenter, false);
}
}
// Continue outside if we're invisible
return true;//!insideState.render;
}
// NOTE: transform portal specific
virtual void transformModelview(const U32 portalIndex, const MatrixF& oldMV, MatrixF* newMV)
{
// NOTE: this is for determining the base modelView matrix for any objects inside the portal
if (mTarget == NULL)
return;
MatrixF tmp1(1);
MatrixF tmp2(1);
tmp1.mul(oldMV, getRenderTransform()); // -> portal space
tmp2.mul(tmp1, mTarget->getRenderWorldTransform()); // -> world space relative to target
*newMV = tmp2;
}
// NOTE: transform portal specific
virtual void transformPosition(const U32 portalIndex, Point3F& point)
{
// NOTE: this is for determining the base camera position for the SceneState derived from
// the transform portal.
if (mTarget == NULL)
return;
// Get in portal space
Point3F localPos = point;
mWorldToObj.mulP(localPos);
localPos.convolveInverse(mObjScale);
// Transform back out to target
localPos.convolve(mTarget->getScale());
mPortalObjToWorldTransform.mulP(localPos);
}
// NOTE: transform portal specific
virtual bool computeNewFrustum(
const U32 portalIndex,
const F64* oldFrustum,
const F64 nearPlane,
const F64 farPlane,
const RectI& oldViewport,
F64* newFrustum,
RectI& newViewport,
const bool flippedMatrix)
{
// NOTE: this is for clipping the view frustum in case the portal
// area is smaller than the entire screen.
mNumPortalsFrustumsQueried++;
SGWinding portalSurface = {};
portalSurface.numPoints = 4;
getSurfaceDrawPoints(&portalSurface.points[0]);
MatrixF finalModelView;
dglGetModelview(&finalModelView);
finalModelView.mul(getTransform());
finalModelView.scale(getScale());
dMemcpy(newFrustum, oldFrustum, sizeof(F64) * 4);
newViewport = oldViewport;
// NOTE: the portal will still work even if we dont clip the
// frustum.
return true;
return sgComputeNewFrustum(oldFrustum, nearPlane, farPlane,
oldViewport,
&portalSurface,
1,
finalModelView,
newFrustum, newViewport,
flippedMatrix);
}
void getSurfaceDrawPoints(Point3F* verts)
{
float xSize = (mObjBox.max.x - mObjBox.min.x) * 0.5f;
float zSize = (mObjBox.max.z - mObjBox.min.z) * 0.5f;
Point3F startVert;
mObjBox.getCenter(&startVert);
verts[0] = startVert + Point3F(-xSize, 0, -zSize);
verts[1] = startVert + Point3F(-xSize, 0, zSize);
verts[2] = startVert + Point3F(xSize, 0, zSize);
verts[3] = startVert + Point3F(xSize, 0, -zSize);
}
void drawSurface()
{
Point3F verts[4];
getSurfaceDrawPoints(&verts[0]);
glDisable(GL_CULL_FACE);
glVertexPointer(3, GL_FLOAT, 4*3, &verts[0]);
GLuint inds[] = {
0,1,2,
2,3,0,
};
glDrawElements(GL_TRIANGLES,
6,
GL_UNSIGNED_INT,
&inds);
}
// NOTE: transform portal specific
virtual void openPortal(const U32 portalIndex,
SceneState* pCurrState,
SceneState* pParentState)
{
// NOTE: this is for drawing anything before the portal contents
// are drawn, and also setting up any stencil state.
//
// If there are nested portals the deepest portal will be
// opened and closed first.
// This code does not setup the draw state correctly for nested portals.
mNumPortalsOpened++;
pParentState->setupZoneProjection(mZoneRangeStart);
// setup transformation
glPushMatrix();
dglMultMatrix(&getTransform());
glScalef(getScale().x, getScale().y, getScale().z);
// setup stencil buffer:
glClearStencil(0x0);
glStencilMask(~0u);
glEnable(GL_STENCIL_TEST);
static U32 lastStateKey = 0;
U32 stateKey = gClientSceneGraph->getStateKey();
// dont clear the stencil for every portal
if(lastStateKey != stateKey)
{
lastStateKey = stateKey;
glClear(GL_STENCIL_BUFFER_BIT);
}
// clear with the fog color
ColorF fogColor = gClientSceneGraph->getFogColor();
glColor3f(fogColor.red, fogColor.green, fogColor.blue);
glEnableClientState(GL_VERTEX_ARRAY);
// render the visible surface into the stencil buffer (also render the fog color
// since terrain may not be rendered and it assumes a fog clear)
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
drawSurface();
// now clear the visible surface depth (use stencil). disable color buffers (already
// rendered fog in previous step).
glDepthRange(1, 1);
glDepthFunc(GL_ALWAYS);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glStencilFunc(GL_EQUAL, 1, 0xffffffff);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
drawSurface();
// reset states (stencil is setup correctly)
glDepthFunc(GL_LEQUAL);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthRange(0, 1);
glDisableClientState(GL_VERTEX_ARRAY);
// reset transform
glPopMatrix();
dglSetCanonicalState();
}
// NOTE: transform portal specific
virtual void closePortal(const U32 portalIndex,
SceneState* pCurrState,
SceneState* pParentState)
{
// NOTE: this is for drawing any content after the contents of the portal
// are drawn. It's also for resetting any stencil states previously set.
mNumPortalsClosed++;
// setup transformation
glPushMatrix();
dglMultMatrix(&getTransform());
glScalef(getScale().x, getScale().y, getScale().z);
glEnableClientState(GL_VERTEX_ARRAY);
// want to clear stencil value
glStencilFunc(GL_EQUAL, 1, 0xffffffff);
glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
// render the alpha surface
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Point3F camPos = pParentState->getCameraPosition();
mWorldToObj.mulP(camPos);
camPos.convolveInverse(getScale());
if (mPortalPlane.whichSide(camPos) == PlaneF::Front)
{
glColor4f(1, 1, 0, 0.1f);
}
else
{
glColor4f(1, 0, 0, 0.1f);
}
drawSurface();
glDisableClientState(GL_VERTEX_ARRAY);
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
glDisable(GL_TEXTURE_2D);
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
glDepthFunc(GL_LEQUAL);
// reset transform
glPopMatrix();
// disable stencil test
glDisable(GL_STENCIL_TEST);
dglSetCanonicalState();
}
// NOTE: transform portal specific
virtual void getWSPortalPlane(const U32 portalIndex, PlaneF* pPlane)
{
// NOTE: This should return a clipping plane which removes everything
// in front of the portal.
mNumPortalsPlanesQueried++;
mTransformPlane(getTransform(), getScale(), mPortalPlane, pPlane);
}
};
IMPLEMENT_CO_NETOBJECT_V1(ZonedPortalObject);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment