Created
August 27, 2024 00:57
-
-
Save jamesu/5b8893904bce52281b5f4b78e9162894 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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(¢er); | |
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(¢er); | |
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