mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-11 07:11:54 +00:00
- moved all portal code that sets up a scene to be rendered into API independent code and let it be handled by a common wrapper class.
This commit is contained in:
parent
3936e3018d
commit
6ebec37baf
13 changed files with 728 additions and 689 deletions
|
@ -175,23 +175,6 @@ void FDrawInfoList::Release(FDrawInfo * di)
|
|||
mList.Push(di);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FDrawInfo::FDrawInfo()
|
||||
{
|
||||
next = NULL;
|
||||
}
|
||||
|
||||
FDrawInfo::~FDrawInfo()
|
||||
{
|
||||
ClearBuffers();
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Sets up a new drawinfo struct
|
||||
|
@ -225,7 +208,7 @@ void FDrawInfo::StartScene()
|
|||
{
|
||||
ClearBuffers();
|
||||
|
||||
next = gl_drawinfo;
|
||||
outer = gl_drawinfo;
|
||||
gl_drawinfo = this;
|
||||
for (int i = 0; i < GLDL_TYPES; i++) drawlists[i].Reset();
|
||||
decals[0].Clear();
|
||||
|
@ -233,7 +216,7 @@ void FDrawInfo::StartScene()
|
|||
hudsprites.Clear();
|
||||
|
||||
// Fullbright information needs to be propagated from the main view.
|
||||
if (next != nullptr) FullbrightFlags = next->FullbrightFlags;
|
||||
if (outer != nullptr) FullbrightFlags = outer->FullbrightFlags;
|
||||
else FullbrightFlags = 0;
|
||||
|
||||
}
|
||||
|
@ -247,7 +230,7 @@ FDrawInfo *FDrawInfo::EndDrawInfo()
|
|||
{
|
||||
assert(this == gl_drawinfo);
|
||||
for(int i=0;i<GLDL_TYPES;i++) drawlists[i].Reset();
|
||||
gl_drawinfo=next;
|
||||
gl_drawinfo=static_cast<FDrawInfo*>(outer);
|
||||
di_list.Release(this);
|
||||
if (gl_drawinfo == nullptr)
|
||||
ResetRenderDataAllocator();
|
||||
|
@ -499,9 +482,15 @@ void FDrawInfo::FloodLowerGap(seg_t * seg)
|
|||
}
|
||||
|
||||
// Same here for the dependency on the portal.
|
||||
void FDrawInfo::AddSubsectorToPortal(FSectorPortalGroup *portal, subsector_t *sub)
|
||||
void FDrawInfo::AddSubsectorToPortal(FSectorPortalGroup *ptg, subsector_t *sub)
|
||||
{
|
||||
portal->GetRenderState()->AddSubsector(sub);
|
||||
auto portal = GLRenderer->mPortalState.FindPortal(ptg);
|
||||
if (!portal)
|
||||
{
|
||||
portal = new GLScenePortal(&GLRenderer->mPortalState, new HWSectorStackPortal(ptg));
|
||||
}
|
||||
auto ptl = static_cast<HWSectorStackPortal*>(static_cast<GLScenePortal*>(portal)->mScene);
|
||||
ptl->AddSubsector(sub);
|
||||
}
|
||||
|
||||
std::pair<FFlatVertex *, unsigned int> FDrawInfo::AllocVertices(unsigned int count)
|
||||
|
@ -523,5 +512,10 @@ int FDrawInfo::UploadLights(FDynLightData &data)
|
|||
return GLRenderer->mLights->UploadLights(data);
|
||||
}
|
||||
|
||||
bool FDrawInfo::SetDepthClamp(bool on)
|
||||
{
|
||||
return gl_RenderState.SetDepthClamp(on);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -35,14 +35,10 @@ enum Drawpasses
|
|||
|
||||
struct FDrawInfo : public HWDrawInfo
|
||||
{
|
||||
FDrawInfo * next;
|
||||
HWDrawList drawlists[GLDL_TYPES];
|
||||
TArray<HUDSprite> hudsprites; // These may just be stored by value.
|
||||
TArray<GLDecal *> decals[2]; // the second slot is for mirrors which get rendered in a separate pass.
|
||||
|
||||
FDrawInfo();
|
||||
~FDrawInfo();
|
||||
|
||||
void ApplyVPUniforms() override;
|
||||
|
||||
void AddWall(GLWall *wall) override;
|
||||
|
@ -56,15 +52,6 @@ struct FDrawInfo : public HWDrawInfo
|
|||
std::pair<FFlatVertex *, unsigned int> AllocVertices(unsigned int count) override;
|
||||
int UploadLights(FDynLightData &data) override;
|
||||
|
||||
// Legacy GL only.
|
||||
bool PutWallCompat(GLWall *wall, int passflag);
|
||||
bool PutFlatCompat(GLFlat *flat, bool fog);
|
||||
void RenderFogBoundaryCompat(GLWall *wall);
|
||||
void RenderLightsCompat(GLWall *wall, int pass);
|
||||
void DrawSubsectorLights(GLFlat *flat, subsector_t * sub, int pass);
|
||||
void DrawLightsCompat(GLFlat *flat, int pass);
|
||||
|
||||
|
||||
void DrawDecal(GLDecal *gldecal);
|
||||
void DrawDecals();
|
||||
void DrawDecalsForMirror(GLWall *wall);
|
||||
|
@ -111,7 +98,8 @@ struct FDrawInfo : public HWDrawInfo
|
|||
void ProcessScene(bool toscreen = false);
|
||||
void EndDrawScene(sector_t * viewsector);
|
||||
void DrawEndScene2D(sector_t * viewsector);
|
||||
|
||||
bool SetDepthClamp(bool on) override;
|
||||
|
||||
static FDrawInfo *StartDrawInfo(FRenderViewpoint &parentvp, HWViewpointUniforms *uniforms);
|
||||
FDrawInfo *EndDrawInfo();
|
||||
|
||||
|
|
|
@ -222,34 +222,6 @@ bool GLPortal::Start(bool usestencil, bool doquery, HWDrawInfo *outer_di, HWDraw
|
|||
}
|
||||
|
||||
|
||||
inline void GLPortal::ClearClipper(HWDrawInfo *di)
|
||||
{
|
||||
auto outer_di = static_cast<FDrawInfo*>(di)->next;
|
||||
DAngle angleOffset = deltaangle(outer_di->Viewpoint.Angles.Yaw, di->Viewpoint.Angles.Yaw);
|
||||
|
||||
di->mClipper->Clear();
|
||||
|
||||
// Set the clipper to the minimal visible area
|
||||
di->mClipper->SafeAddClipRange(0,0xffffffff);
|
||||
for (unsigned int i = 0; i < lines.Size(); i++)
|
||||
{
|
||||
DAngle startAngle = (DVector2(lines[i].glseg.x2, lines[i].glseg.y2) - outer_di->Viewpoint.Pos).Angle() + angleOffset;
|
||||
DAngle endAngle = (DVector2(lines[i].glseg.x1, lines[i].glseg.y1) - outer_di->Viewpoint.Pos).Angle() + angleOffset;
|
||||
|
||||
if (deltaangle(endAngle, startAngle) < 0)
|
||||
{
|
||||
di->mClipper->SafeRemoveClipRangeRealAngles(startAngle.BAMs(), endAngle.BAMs());
|
||||
}
|
||||
}
|
||||
|
||||
// and finally clip it to the visible area
|
||||
angle_t a1 = di->FrustumAngle();
|
||||
if (a1 < ANGLE_180) di->mClipper->SafeAddClipRangeRealAngles(di->Viewpoint.Angles.Yaw.BAMs() + a1, di->Viewpoint.Angles.Yaw.BAMs() - a1);
|
||||
|
||||
// lock the parts that have just been clipped out.
|
||||
di->mClipper->SetSilhouette();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// End
|
||||
|
@ -346,454 +318,12 @@ void GLPortal::End(HWDrawInfo *di, bool usestencil)
|
|||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
// Skybox Portal
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// GLSkyboxPortal::DrawContents
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void GLSkyboxPortal::DrawContents(HWDrawInfo *di)
|
||||
{
|
||||
int old_pm = mState->PlaneMirrorMode;
|
||||
|
||||
if (mState->skyboxrecursion >= 3)
|
||||
{
|
||||
ClearScreen(di);
|
||||
return;
|
||||
}
|
||||
auto &vp = di->Viewpoint;
|
||||
|
||||
mState->skyboxrecursion++;
|
||||
AActor *origin = portal->mSkybox;
|
||||
portal->mFlags |= PORTSF_INSKYBOX;
|
||||
vp.extralight = 0;
|
||||
|
||||
mState->PlaneMirrorMode = 0;
|
||||
|
||||
bool oldclamp = gl_RenderState.SetDepthClamp(false);
|
||||
vp.Pos = origin->InterpolatedPosition(vp.TicFrac);
|
||||
vp.ActorPos = origin->Pos();
|
||||
vp.Angles.Yaw += (origin->PrevAngles.Yaw + deltaangle(origin->PrevAngles.Yaw, origin->Angles.Yaw) * vp.TicFrac);
|
||||
|
||||
// Don't let the viewpoint be too close to a floor or ceiling
|
||||
double floorh = origin->Sector->floorplane.ZatPoint(origin->Pos());
|
||||
double ceilh = origin->Sector->ceilingplane.ZatPoint(origin->Pos());
|
||||
if (vp.Pos.Z < floorh + 4) vp.Pos.Z = floorh + 4;
|
||||
if (vp.Pos.Z > ceilh - 4) vp.Pos.Z = ceilh - 4;
|
||||
|
||||
vp.ViewActor = origin;
|
||||
|
||||
mState->inskybox = true;
|
||||
di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(mState->MirrorFlag & 1), !!(mState->PlaneMirrorFlag & 1));
|
||||
di->SetViewArea();
|
||||
ClearClipper(di);
|
||||
|
||||
di->UpdateCurrentMapSection();
|
||||
|
||||
static_cast<FDrawInfo*>(di)->DrawScene(DM_PORTAL);
|
||||
portal->mFlags &= ~PORTSF_INSKYBOX;
|
||||
mState->inskybox = false;
|
||||
gl_RenderState.SetDepthClamp(oldclamp);
|
||||
mState->skyboxrecursion--;
|
||||
|
||||
mState->PlaneMirrorMode = old_pm;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
// Sector stack Portal
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Fixme: This needs abstraction.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
GLSectorStackPortal *FSectorPortalGroup::GetRenderState()
|
||||
{
|
||||
if (glportal == nullptr) glportal = new GLSectorStackPortal(&GLRenderer->mPortalState, this);
|
||||
return glportal;
|
||||
}
|
||||
|
||||
|
||||
|
||||
GLSectorStackPortal::~GLSectorStackPortal()
|
||||
{
|
||||
if (origin != nullptr && origin->glportal == this)
|
||||
{
|
||||
origin->glportal = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// GLSectorStackPortal::SetupCoverage
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static uint8_t SetCoverage(HWDrawInfo *di, void *node)
|
||||
{
|
||||
if (level.nodes.Size() == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (!((size_t)node & 1)) // Keep going until found a subsector
|
||||
{
|
||||
node_t *bsp = (node_t *)node;
|
||||
uint8_t coverage = SetCoverage(di, bsp->children[0]) | SetCoverage(di, bsp->children[1]);
|
||||
di->no_renderflags[bsp->Index()] = coverage;
|
||||
return coverage;
|
||||
}
|
||||
else
|
||||
{
|
||||
subsector_t *sub = (subsector_t *)((uint8_t *)node - 1);
|
||||
return di->ss_renderflags[sub->Index()] & SSRF_SEEN;
|
||||
}
|
||||
}
|
||||
|
||||
void GLSectorStackPortal::SetupCoverage(HWDrawInfo *di)
|
||||
{
|
||||
for(unsigned i=0; i<subsectors.Size(); i++)
|
||||
{
|
||||
subsector_t *sub = subsectors[i];
|
||||
int plane = origin->plane;
|
||||
for(int j=0;j<sub->portalcoverage[plane].sscount; j++)
|
||||
{
|
||||
subsector_t *dsub = &::level.subsectors[sub->portalcoverage[plane].subsectors[j]];
|
||||
di->CurrentMapSections.Set(dsub->mapsection);
|
||||
di->ss_renderflags[dsub->Index()] |= SSRF_SEEN;
|
||||
}
|
||||
}
|
||||
SetCoverage(di, ::level.HeadNode());
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// GLSectorStackPortal::DrawContents
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
void GLSectorStackPortal::DrawContents(HWDrawInfo *di)
|
||||
{
|
||||
FSectorPortalGroup *portal = origin;
|
||||
auto &vp = di->Viewpoint;
|
||||
|
||||
vp.Pos += origin->mDisplacement;
|
||||
vp.ActorPos += origin->mDisplacement;
|
||||
vp.ViewActor = nullptr;
|
||||
|
||||
// avoid recursions!
|
||||
if (origin->plane != -1) screen->instack[origin->plane]++;
|
||||
|
||||
di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(mState->MirrorFlag&1), !!(mState->PlaneMirrorFlag&1));
|
||||
SetupCoverage(di);
|
||||
ClearClipper(di);
|
||||
|
||||
// If the viewpoint is not within the portal, we need to invalidate the entire clip area.
|
||||
// The portal will re-validate the necessary parts when its subsectors get traversed.
|
||||
subsector_t *sub = R_PointInSubsector(vp.Pos);
|
||||
if (!(di->ss_renderflags[sub->Index()] & SSRF_SEEN))
|
||||
{
|
||||
di->mClipper->SafeAddClipRange(0, ANGLE_MAX);
|
||||
di->mClipper->SetBlocked(true);
|
||||
}
|
||||
|
||||
static_cast<FDrawInfo*>(di)->DrawScene(DM_PORTAL);
|
||||
|
||||
if (origin->plane != -1) screen->instack[origin->plane]--;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
// Plane Mirror Portal
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// GLPlaneMirrorPortal::DrawContents
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void GLPlaneMirrorPortal::DrawContents(HWDrawInfo *di)
|
||||
{
|
||||
if (mState->renderdepth > r_mirror_recursions)
|
||||
{
|
||||
ClearScreen(di);
|
||||
return;
|
||||
}
|
||||
// A plane mirror needs to flip the portal exclusion logic because inside the mirror, up is down and down is up.
|
||||
std::swap(screen->instack[sector_t::floor], screen->instack[sector_t::ceiling]);
|
||||
|
||||
auto &vp = di->Viewpoint;
|
||||
int old_pm = mState->PlaneMirrorMode;
|
||||
|
||||
// the player is always visible in a mirror.
|
||||
vp.showviewer = true;
|
||||
|
||||
double planez = origin->ZatPoint(vp.Pos);
|
||||
vp.Pos.Z = 2 * planez - vp.Pos.Z;
|
||||
vp.ViewActor = nullptr;
|
||||
mState->PlaneMirrorMode = origin->fC() < 0 ? -1 : 1;
|
||||
|
||||
mState->PlaneMirrorFlag++;
|
||||
di->SetClipHeight(planez, mState->PlaneMirrorMode < 0 ? -1.f : 1.f);
|
||||
di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(mState->MirrorFlag & 1), !!(mState->PlaneMirrorFlag & 1));
|
||||
ClearClipper(di);
|
||||
|
||||
di->UpdateCurrentMapSection();
|
||||
|
||||
static_cast<FDrawInfo*>(di)->DrawScene(DM_PORTAL);
|
||||
mState->PlaneMirrorFlag--;
|
||||
mState->PlaneMirrorMode = old_pm;
|
||||
std::swap(screen->instack[sector_t::floor], screen->instack[sector_t::ceiling]);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// Common code for line to line and mirror portals
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
int GLLinePortal::ClipSeg(seg_t *seg, const DVector3 &viewpos)
|
||||
{
|
||||
line_t *linedef = seg->linedef;
|
||||
if (!linedef)
|
||||
{
|
||||
return PClip_Inside; // should be handled properly.
|
||||
}
|
||||
return P_ClipLineToPortal(linedef, line(), viewpos) ? PClip_InFront : PClip_Inside;
|
||||
}
|
||||
|
||||
int GLLinePortal::ClipSubsector(subsector_t *sub)
|
||||
{
|
||||
// this seg is completely behind the mirror
|
||||
for(unsigned int i=0;i<sub->numlines;i++)
|
||||
{
|
||||
if (P_PointOnLineSidePrecise(sub->firstline[i].v1->fPos(), line()) == 0) return PClip_Inside;
|
||||
}
|
||||
return PClip_InFront;
|
||||
}
|
||||
|
||||
int GLLinePortal::ClipPoint(const DVector2 &pos)
|
||||
{
|
||||
if (P_PointOnLineSidePrecise(pos, line()))
|
||||
{
|
||||
return PClip_InFront;
|
||||
}
|
||||
return PClip_Inside;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
// Mirror Portal
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// R_EnterMirror
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
void GLMirrorPortal::DrawContents(HWDrawInfo *di)
|
||||
{
|
||||
if (mState->renderdepth>r_mirror_recursions)
|
||||
{
|
||||
ClearScreen(di);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &vp = di->Viewpoint;
|
||||
di->UpdateCurrentMapSection();
|
||||
|
||||
di->mClipPortal = this;
|
||||
DAngle StartAngle = vp.Angles.Yaw;
|
||||
DVector3 StartPos = vp.Pos;
|
||||
|
||||
vertex_t *v1 = linedef->v1;
|
||||
vertex_t *v2 = linedef->v2;
|
||||
|
||||
// the player is always visible in a mirror.
|
||||
vp.showviewer = true;
|
||||
// Reflect the current view behind the mirror.
|
||||
if (linedef->Delta().X == 0)
|
||||
{
|
||||
// vertical mirror
|
||||
vp.Pos.X = 2 * v1->fX() - StartPos.X;
|
||||
|
||||
// Compensation for reendering inaccuracies
|
||||
if (StartPos.X < v1->fX()) vp.Pos.X -= 0.1;
|
||||
else vp.Pos.X += 0.1;
|
||||
}
|
||||
else if (linedef->Delta().Y == 0)
|
||||
{
|
||||
// horizontal mirror
|
||||
vp.Pos.Y = 2*v1->fY() - StartPos.Y;
|
||||
|
||||
// Compensation for reendering inaccuracies
|
||||
if (StartPos.Y<v1->fY()) vp.Pos.Y -= 0.1;
|
||||
else vp.Pos.Y += 0.1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// any mirror--use floats to avoid integer overflow.
|
||||
// Use doubles to avoid losing precision which is very important here.
|
||||
|
||||
double dx = v2->fX() - v1->fX();
|
||||
double dy = v2->fY() - v1->fY();
|
||||
double x1 = v1->fX();
|
||||
double y1 = v1->fY();
|
||||
double x = StartPos.X;
|
||||
double y = StartPos.Y;
|
||||
|
||||
// the above two cases catch len == 0
|
||||
double r = ((x - x1)*dx + (y - y1)*dy) / (dx*dx + dy*dy);
|
||||
|
||||
vp.Pos.X = (x1 + r * dx)*2 - x;
|
||||
vp.Pos.Y = (y1 + r * dy)*2 - y;
|
||||
|
||||
// Compensation for reendering inaccuracies
|
||||
FVector2 v(-dx, dy);
|
||||
v.MakeUnit();
|
||||
|
||||
vp.Pos.X+= v[1] * mState->renderdepth / 2;
|
||||
vp.Pos.Y+= v[0] * mState->renderdepth / 2;
|
||||
}
|
||||
vp.Angles.Yaw = linedef->Delta().Angle() * 2. - StartAngle;
|
||||
|
||||
vp.ViewActor = nullptr;
|
||||
|
||||
mState->MirrorFlag++;
|
||||
di->SetClipLine(linedef);
|
||||
di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(mState->MirrorFlag&1), !!(mState->PlaneMirrorFlag&1));
|
||||
|
||||
di->mClipper->Clear();
|
||||
|
||||
angle_t af = di->FrustumAngle();
|
||||
if (af<ANGLE_180) di->mClipper->SafeAddClipRangeRealAngles(vp.Angles.Yaw.BAMs()+af, vp.Angles.Yaw.BAMs()-af);
|
||||
|
||||
di->mClipper->SafeAddClipRange(linedef->v1, linedef->v2);
|
||||
|
||||
static_cast<FDrawInfo*>(di)->DrawScene(DM_PORTAL);
|
||||
|
||||
mState->MirrorFlag--;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
// Line to line Portal
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
void GLLineToLinePortal::DrawContents(HWDrawInfo *di)
|
||||
{
|
||||
// TODO: Handle recursion more intelligently
|
||||
if (mState->renderdepth>r_mirror_recursions)
|
||||
{
|
||||
ClearScreen(di);
|
||||
return;
|
||||
}
|
||||
auto &vp = di->Viewpoint;
|
||||
di->mClipPortal = this;
|
||||
|
||||
line_t *origin = glport->lines[0]->mOrigin;
|
||||
P_TranslatePortalXY(origin, vp.Pos.X, vp.Pos.Y);
|
||||
P_TranslatePortalXY(origin, vp.ActorPos.X, vp.ActorPos.Y);
|
||||
P_TranslatePortalAngle(origin, vp.Angles.Yaw);
|
||||
P_TranslatePortalZ(origin, vp.Pos.Z);
|
||||
P_TranslatePortalXY(origin, vp.Path[0].X, vp.Path[0].Y);
|
||||
P_TranslatePortalXY(origin, vp.Path[1].X, vp.Path[1].Y);
|
||||
if (!vp.showviewer && vp.camera != nullptr && P_PointOnLineSidePrecise(vp.Path[0], glport->lines[0]->mDestination) != P_PointOnLineSidePrecise(vp.Path[1], glport->lines[0]->mDestination))
|
||||
{
|
||||
double distp = (vp.Path[0] - vp.Path[1]).Length();
|
||||
if (distp > EQUAL_EPSILON)
|
||||
{
|
||||
double dist1 = (vp.Pos - vp.Path[0]).Length();
|
||||
double dist2 = (vp.Pos - vp.Path[1]).Length();
|
||||
|
||||
if (dist1 + dist2 < distp + 1)
|
||||
{
|
||||
vp.camera->renderflags |= RF_MAYBEINVISIBLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (unsigned i = 0; i < lines.Size(); i++)
|
||||
{
|
||||
line_t *line = lines[i].seg->linedef->getPortalDestination();
|
||||
subsector_t *sub;
|
||||
if (line->sidedef[0]->Flags & WALLF_POLYOBJ)
|
||||
sub = R_PointInSubsector(line->v1->fixX(), line->v1->fixY());
|
||||
else sub = line->frontsector->subsectors[0];
|
||||
di->CurrentMapSections.Set(sub->mapsection);
|
||||
}
|
||||
|
||||
vp.ViewActor = nullptr;
|
||||
di->SetClipLine(glport->lines[0]->mDestination);
|
||||
di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(mState->MirrorFlag&1), !!(mState->PlaneMirrorFlag&1));
|
||||
|
||||
ClearClipper(di);
|
||||
static_cast<FDrawInfo*>(di)->DrawScene(DM_PORTAL);
|
||||
}
|
||||
|
||||
void GLLineToLinePortal::RenderAttached(HWDrawInfo *di)
|
||||
{
|
||||
di->ProcessActorsInPortal(glport, di->in_area);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
// Horizon Portal
|
||||
//
|
||||
// This simply draws the area in medium sized squares. Drawing it as a whole
|
||||
// polygon creates visible inaccuracies.
|
||||
//
|
||||
// Originally I tried to minimize the amount of data to be drawn but there
|
||||
// are 2 problems with it:
|
||||
//
|
||||
// 1. Setting this up completely negates any performance gains.
|
||||
// 2. It doesn't work with a 360° field of view (as when you are looking up.)
|
||||
//
|
||||
//
|
||||
// So the brute force mechanism is just as good.
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -978,17 +508,6 @@ void GLEEHorizonPortal::DrawContents(HWDrawInfo *di)
|
|||
}
|
||||
|
||||
const char *GLSkyPortal::GetName() { return "Sky"; }
|
||||
const char *GLSkyboxPortal::GetName() { return "Skybox"; }
|
||||
const char *GLSectorStackPortal::GetName() { return "Sectorstack"; }
|
||||
const char *GLPlaneMirrorPortal::GetName() { return "Planemirror"; }
|
||||
const char *GLMirrorPortal::GetName() { return "Mirror"; }
|
||||
const char *GLLineToLinePortal::GetName() { return "LineToLine"; }
|
||||
const char *GLHorizonPortal::GetName() { return "Horizon"; }
|
||||
const char *GLEEHorizonPortal::GetName() { return "EEHorizon"; }
|
||||
|
||||
// This needs to remain on the renderer side until the portal interface can be abstracted.
|
||||
void FSectorPortalGroup::AddSubsector(subsector_t *sub)
|
||||
{
|
||||
GLSectorStackPortal *glportal = GetRenderState();
|
||||
glportal->AddSubsector(sub);
|
||||
}
|
||||
|
|
|
@ -66,115 +66,30 @@ protected:
|
|||
void ClearScreen(HWDrawInfo *di);
|
||||
};
|
||||
|
||||
struct GLLinePortal : public GLPortal
|
||||
class GLScenePortal : public GLPortal
|
||||
{
|
||||
// this must be the same as at the start of line_t, so that we can pass in this structure directly to P_ClipLineToPortal.
|
||||
vertex_t *v1, *v2; // vertices, from v1 to v2
|
||||
DVector2 delta; // precalculated v2 - v1 for side checking
|
||||
|
||||
angle_t angv1, angv2; // for quick comparisons with a line or subsector
|
||||
|
||||
GLLinePortal(FPortalSceneState *state, line_t *line) : GLPortal(state)
|
||||
public:
|
||||
HWScenePortalBase *mScene;
|
||||
GLScenePortal(FPortalSceneState *state, HWScenePortalBase *handler) : GLPortal(state)
|
||||
{
|
||||
v1 = line->v1;
|
||||
v2 = line->v2;
|
||||
CalcDelta();
|
||||
mScene = handler;
|
||||
handler->SetOwner(this);
|
||||
}
|
||||
|
||||
GLLinePortal(FPortalSceneState *state, FLinePortalSpan *line) : GLPortal(state)
|
||||
{
|
||||
if (line->lines[0]->mType != PORTT_LINKED || line->v1 == nullptr)
|
||||
~GLScenePortal() { delete mScene; }
|
||||
virtual void * GetSource() const { return mScene->GetSource(); }
|
||||
virtual const char *GetName() { return mScene->GetName(); }
|
||||
virtual bool IsSky() { return false; }
|
||||
virtual bool NeedCap() { return true; }
|
||||
virtual bool NeedDepthBuffer() { return true; }
|
||||
virtual void DrawContents(HWDrawInfo *di)
|
||||
{
|
||||
if (mScene->Setup(di, di->mClipper))
|
||||
{
|
||||
// For non-linked portals we must check the actual linedef.
|
||||
line_t *lline = line->lines[0]->mDestination;
|
||||
v1 = lline->v1;
|
||||
v2 = lline->v2;
|
||||
static_cast<FDrawInfo*>(di)->DrawScene(DM_PORTAL);
|
||||
mScene->Shutdown(di);
|
||||
}
|
||||
else
|
||||
{
|
||||
// For linked portals we can check the merged span.
|
||||
v1 = line->v1;
|
||||
v2 = line->v2;
|
||||
}
|
||||
CalcDelta();
|
||||
}
|
||||
|
||||
void CalcDelta()
|
||||
{
|
||||
delta = v2->fPos() - v1->fPos();
|
||||
}
|
||||
|
||||
line_t *line()
|
||||
{
|
||||
vertex_t **pv = &v1;
|
||||
return reinterpret_cast<line_t*>(pv);
|
||||
}
|
||||
|
||||
virtual int ClipSeg(seg_t *seg, const DVector3 &viewpos);
|
||||
virtual int ClipSubsector(subsector_t *sub);
|
||||
virtual int ClipPoint(const DVector2 &pos);
|
||||
virtual bool NeedCap() { return false; }
|
||||
};
|
||||
|
||||
|
||||
struct GLMirrorPortal : public GLLinePortal
|
||||
{
|
||||
// mirror portals always consist of single linedefs!
|
||||
line_t * linedef;
|
||||
|
||||
protected:
|
||||
virtual void DrawContents(HWDrawInfo *di);
|
||||
virtual void * GetSource() const { return linedef; }
|
||||
virtual const char *GetName();
|
||||
|
||||
public:
|
||||
|
||||
GLMirrorPortal(FPortalSceneState *state, line_t * line)
|
||||
: GLLinePortal(state, line)
|
||||
{
|
||||
linedef=line;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct GLLineToLinePortal : public GLLinePortal
|
||||
{
|
||||
FLinePortalSpan *glport;
|
||||
protected:
|
||||
virtual void DrawContents(HWDrawInfo *di);
|
||||
virtual void * GetSource() const { return glport; }
|
||||
virtual const char *GetName();
|
||||
virtual line_t *ClipLine() { return line(); }
|
||||
virtual void RenderAttached(HWDrawInfo *di);
|
||||
|
||||
public:
|
||||
|
||||
GLLineToLinePortal(FPortalSceneState *state, FLinePortalSpan *ll)
|
||||
: GLLinePortal(state, ll)
|
||||
{
|
||||
glport = ll;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct GLSkyboxPortal : public GLPortal
|
||||
{
|
||||
FSectorPortal * portal;
|
||||
|
||||
protected:
|
||||
virtual void DrawContents(HWDrawInfo *di);
|
||||
virtual void * GetSource() const { return portal; }
|
||||
virtual bool IsSky() { return true; }
|
||||
virtual const char *GetName();
|
||||
|
||||
public:
|
||||
|
||||
|
||||
GLSkyboxPortal(FPortalSceneState *state, FSectorPortal * pt) : GLPortal(state)
|
||||
{
|
||||
portal=pt;
|
||||
}
|
||||
|
||||
virtual void RenderAttached(HWDrawInfo *di) { return mScene->RenderAttached(di); }
|
||||
};
|
||||
|
||||
|
||||
|
@ -201,51 +116,6 @@ public:
|
|||
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct GLSectorStackPortal : public GLPortal
|
||||
{
|
||||
TArray<subsector_t *> subsectors;
|
||||
protected:
|
||||
virtual ~GLSectorStackPortal();
|
||||
virtual void DrawContents(HWDrawInfo *di);
|
||||
virtual void * GetSource() const { return origin; }
|
||||
virtual bool IsSky() { return true; } // although this isn't a real sky it can be handled as one.
|
||||
virtual const char *GetName();
|
||||
FSectorPortalGroup *origin;
|
||||
|
||||
public:
|
||||
|
||||
GLSectorStackPortal(FPortalSceneState *state, FSectorPortalGroup *pt) : GLPortal(state)
|
||||
{
|
||||
origin=pt;
|
||||
}
|
||||
void SetupCoverage(HWDrawInfo *di);
|
||||
void AddSubsector(subsector_t *sub)
|
||||
{
|
||||
subsectors.Push(sub);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct GLPlaneMirrorPortal : public GLPortal
|
||||
{
|
||||
protected:
|
||||
virtual void DrawContents(HWDrawInfo *di);
|
||||
virtual void * GetSource() const { return origin; }
|
||||
virtual const char *GetName();
|
||||
secplane_t * origin;
|
||||
|
||||
public:
|
||||
|
||||
GLPlaneMirrorPortal(FPortalSceneState *state, secplane_t * pt) : GLPortal(state)
|
||||
{
|
||||
origin=pt;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
struct GLHorizonPortal : public GLPortal
|
||||
{
|
||||
GLHorizonInfo * origin;
|
||||
|
|
|
@ -113,7 +113,6 @@ void FDrawInfo::CreateScene()
|
|||
ProcessAll.Clock();
|
||||
|
||||
// clip the scene and fill the drawlists
|
||||
for(auto p : level.portalGroups) p->glportal = nullptr;
|
||||
Bsp.Clock();
|
||||
GLRenderer->mVBO->Map();
|
||||
GLRenderer->mLights->Begin();
|
||||
|
|
|
@ -358,13 +358,17 @@ void FDrawInfo::AddPortal(GLWall *wall, int ptype)
|
|||
{
|
||||
// either a regular skybox or an Eternity-style horizon
|
||||
if (wall->secportal->mType != PORTS_SKYVIEWPOINT) portal = new GLEEHorizonPortal(&pstate, wall->secportal);
|
||||
else portal = new GLSkyboxPortal(&pstate, wall->secportal);
|
||||
else portal = new GLScenePortal(&pstate, new HWSkyboxPortal(wall->secportal));
|
||||
}
|
||||
portal->AddLine(wall);
|
||||
break;
|
||||
|
||||
case PORTALTYPE_SECTORSTACK:
|
||||
portal = wall->portal->GetRenderState();
|
||||
portal = pstate.FindPortal(wall->portal);
|
||||
if (!portal)
|
||||
{
|
||||
portal = new GLScenePortal(&pstate, new HWSectorStackPortal(wall->portal));
|
||||
}
|
||||
portal->AddLine(wall);
|
||||
break;
|
||||
|
||||
|
@ -374,14 +378,14 @@ void FDrawInfo::AddPortal(GLWall *wall, int ptype)
|
|||
//@sync-portal
|
||||
wall->planemirror = pstate.UniquePlaneMirrors.Get(wall->planemirror);
|
||||
portal = pstate.FindPortal(wall->planemirror);
|
||||
if (!portal) portal = new GLPlaneMirrorPortal(&pstate, wall->planemirror);
|
||||
if (!portal) portal = new GLScenePortal(&pstate, new HWPlaneMirrorPortal(wall->planemirror));
|
||||
portal->AddLine(wall);
|
||||
}
|
||||
break;
|
||||
|
||||
case PORTALTYPE_MIRROR:
|
||||
portal = pstate.FindPortal(wall->seg->linedef);
|
||||
if (!portal) portal = new GLMirrorPortal(&pstate, wall->seg->linedef);
|
||||
if (!portal) portal = new GLScenePortal(&pstate, new HWMirrorPortal(wall->seg->linedef));
|
||||
portal->AddLine(wall);
|
||||
if (gl_mirror_envmap)
|
||||
{
|
||||
|
@ -399,7 +403,7 @@ void FDrawInfo::AddPortal(GLWall *wall, int ptype)
|
|||
{
|
||||
ProcessActorsInPortal(otherside->getPortal()->mGroup, in_area);
|
||||
}
|
||||
portal = new GLLineToLinePortal(&pstate, wall->lineportal);
|
||||
portal = new GLScenePortal(&pstate, new HWLineToLinePortal(wall->lineportal));
|
||||
}
|
||||
portal->AddLine(wall);
|
||||
break;
|
||||
|
|
|
@ -538,13 +538,13 @@ void HWDrawInfo::DoSubsector(subsector_t * sub)
|
|||
portal = fakesector->GetPortalGroup(sector_t::ceiling);
|
||||
if (portal != nullptr)
|
||||
{
|
||||
portal->AddSubsector(sub);
|
||||
AddSubsectorToPortal(portal, sub);
|
||||
}
|
||||
|
||||
portal = fakesector->GetPortalGroup(sector_t::floor);
|
||||
if (portal != nullptr)
|
||||
{
|
||||
portal->AddSubsector(sub);
|
||||
AddSubsectorToPortal(portal, sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -223,7 +223,7 @@ angle_t HWDrawInfo::FrustumAngle()
|
|||
|
||||
void HWDrawInfo::SetViewMatrix(const FRotator &angles, float vx, float vy, float vz, bool mirror, bool planemirror)
|
||||
{
|
||||
float mult = mirror ? -1 : 1;
|
||||
float mult = mirror ? -1.f : 1.f;
|
||||
float planemult = planemirror ? -level.info->pixelstretch : level.info->pixelstretch;
|
||||
|
||||
VPUniforms.mViewMatrix.loadIdentity();
|
||||
|
@ -250,7 +250,6 @@ void HWDrawInfo::SetupView(float vx, float vy, float vz, bool mirror, bool plane
|
|||
ApplyVPUniforms();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
|
@ -268,3 +267,4 @@ void HWViewpointUniforms::SetDefaults()
|
|||
mClipLine.X = -10000000.0f;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ class Clipper;
|
|||
class IPortal;
|
||||
class FFlatVertexGenerator;
|
||||
class IRenderQueue;
|
||||
class HWScenePortalBase;
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
|
@ -58,7 +59,10 @@ enum EPortalClip
|
|||
|
||||
struct HWDrawInfo
|
||||
{
|
||||
virtual ~HWDrawInfo() {}
|
||||
virtual ~HWDrawInfo()
|
||||
{
|
||||
ClearBuffers();
|
||||
}
|
||||
|
||||
struct wallseg
|
||||
{
|
||||
|
@ -96,9 +100,10 @@ struct HWDrawInfo
|
|||
bool isNightvision() const { return !!(FullbrightFlags & Nightvision); }
|
||||
bool isStealthVision() const { return !!(FullbrightFlags & StealthVision); }
|
||||
|
||||
HWDrawInfo * outer = nullptr;
|
||||
int FullbrightFlags;
|
||||
std::atomic<int> spriteindex;
|
||||
IPortal *mClipPortal;
|
||||
HWScenePortalBase *mClipPortal;
|
||||
IPortal *mCurrentPortal;
|
||||
//FRotator mAngles;
|
||||
IShadowMap *mShadowMap;
|
||||
|
@ -240,6 +245,7 @@ public:
|
|||
|
||||
virtual int UploadLights(FDynLightData &data) = 0;
|
||||
virtual void ApplyVPUniforms() = 0;
|
||||
virtual bool SetDepthClamp(bool on) = 0;
|
||||
|
||||
virtual GLDecal *AddDecal(bool onmirror) = 0;
|
||||
virtual std::pair<FFlatVertex *, unsigned int> AllocVertices(unsigned int count) = 0;
|
||||
|
|
|
@ -26,7 +26,14 @@
|
|||
*/
|
||||
|
||||
#include "c_dispatch.h"
|
||||
#include "portal.h"
|
||||
#include "p_maputl.h"
|
||||
#include "hw_portal.h"
|
||||
#include "hw_clipper.h"
|
||||
#include "actor.h"
|
||||
#include "g_levellocals.h"
|
||||
|
||||
EXTERN_CVAR(Int, r_mirror_recursions)
|
||||
|
||||
IPortal * FPortalSceneState::FindPortal(const void * src)
|
||||
{
|
||||
|
@ -155,3 +162,476 @@ bool FPortalSceneState::RenderFirstSkyPortal(int recursion, HWDrawInfo *outer_di
|
|||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void HWScenePortalBase::ClearClipper(HWDrawInfo *di)
|
||||
{
|
||||
auto outer_di = di->outer;
|
||||
DAngle angleOffset = deltaangle(outer_di->Viewpoint.Angles.Yaw, di->Viewpoint.Angles.Yaw);
|
||||
|
||||
di->mClipper->Clear();
|
||||
|
||||
auto &lines = mOwner->lines;
|
||||
// Set the clipper to the minimal visible area
|
||||
di->mClipper->SafeAddClipRange(0, 0xffffffff);
|
||||
for (unsigned int i = 0; i < lines.Size(); i++)
|
||||
{
|
||||
DAngle startAngle = (DVector2(lines[i].glseg.x2, lines[i].glseg.y2) - outer_di->Viewpoint.Pos).Angle() + angleOffset;
|
||||
DAngle endAngle = (DVector2(lines[i].glseg.x1, lines[i].glseg.y1) - outer_di->Viewpoint.Pos).Angle() + angleOffset;
|
||||
|
||||
if (deltaangle(endAngle, startAngle) < 0)
|
||||
{
|
||||
di->mClipper->SafeRemoveClipRangeRealAngles(startAngle.BAMs(), endAngle.BAMs());
|
||||
}
|
||||
}
|
||||
|
||||
// and finally clip it to the visible area
|
||||
angle_t a1 = di->FrustumAngle();
|
||||
if (a1 < ANGLE_180) di->mClipper->SafeAddClipRangeRealAngles(di->Viewpoint.Angles.Yaw.BAMs() + a1, di->Viewpoint.Angles.Yaw.BAMs() - a1);
|
||||
|
||||
// lock the parts that have just been clipped out.
|
||||
di->mClipper->SetSilhouette();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
// Common code for line to line and mirror portals
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
int HWLinePortal::ClipSeg(seg_t *seg, const DVector3 &viewpos)
|
||||
{
|
||||
line_t *linedef = seg->linedef;
|
||||
if (!linedef)
|
||||
{
|
||||
return PClip_Inside; // should be handled properly.
|
||||
}
|
||||
return P_ClipLineToPortal(linedef, line(), viewpos) ? PClip_InFront : PClip_Inside;
|
||||
}
|
||||
|
||||
int HWLinePortal::ClipSubsector(subsector_t *sub)
|
||||
{
|
||||
// this seg is completely behind the mirror
|
||||
for (unsigned int i = 0; i<sub->numlines; i++)
|
||||
{
|
||||
if (P_PointOnLineSidePrecise(sub->firstline[i].v1->fPos(), line()) == 0) return PClip_Inside;
|
||||
}
|
||||
return PClip_InFront;
|
||||
}
|
||||
|
||||
int HWLinePortal::ClipPoint(const DVector2 &pos)
|
||||
{
|
||||
if (P_PointOnLineSidePrecise(pos, line()))
|
||||
{
|
||||
return PClip_InFront;
|
||||
}
|
||||
return PClip_Inside;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
// Mirror Portal
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool HWMirrorPortal::Setup(HWDrawInfo *di, Clipper *clipper)
|
||||
{
|
||||
auto state = mOwner->mState;
|
||||
if (state->renderdepth > r_mirror_recursions)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto &vp = di->Viewpoint;
|
||||
di->UpdateCurrentMapSection();
|
||||
|
||||
di->mClipPortal = this;
|
||||
DAngle StartAngle = vp.Angles.Yaw;
|
||||
DVector3 StartPos = vp.Pos;
|
||||
|
||||
vertex_t *v1 = linedef->v1;
|
||||
vertex_t *v2 = linedef->v2;
|
||||
|
||||
// the player is always visible in a mirror.
|
||||
vp.showviewer = true;
|
||||
// Reflect the current view behind the mirror.
|
||||
if (linedef->Delta().X == 0)
|
||||
{
|
||||
// vertical mirror
|
||||
vp.Pos.X = 2 * v1->fX() - StartPos.X;
|
||||
|
||||
// Compensation for reendering inaccuracies
|
||||
if (StartPos.X < v1->fX()) vp.Pos.X -= 0.1;
|
||||
else vp.Pos.X += 0.1;
|
||||
}
|
||||
else if (linedef->Delta().Y == 0)
|
||||
{
|
||||
// horizontal mirror
|
||||
vp.Pos.Y = 2 * v1->fY() - StartPos.Y;
|
||||
|
||||
// Compensation for reendering inaccuracies
|
||||
if (StartPos.Y < v1->fY()) vp.Pos.Y -= 0.1;
|
||||
else vp.Pos.Y += 0.1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// any mirror--use floats to avoid integer overflow.
|
||||
// Use doubles to avoid losing precision which is very important here.
|
||||
|
||||
double dx = v2->fX() - v1->fX();
|
||||
double dy = v2->fY() - v1->fY();
|
||||
double x1 = v1->fX();
|
||||
double y1 = v1->fY();
|
||||
double x = StartPos.X;
|
||||
double y = StartPos.Y;
|
||||
|
||||
// the above two cases catch len == 0
|
||||
double r = ((x - x1)*dx + (y - y1)*dy) / (dx*dx + dy * dy);
|
||||
|
||||
vp.Pos.X = (x1 + r * dx) * 2 - x;
|
||||
vp.Pos.Y = (y1 + r * dy) * 2 - y;
|
||||
|
||||
// Compensation for reendering inaccuracies
|
||||
FVector2 v(-dx, dy);
|
||||
v.MakeUnit();
|
||||
|
||||
vp.Pos.X += v[1] * state->renderdepth / 2;
|
||||
vp.Pos.Y += v[0] * state->renderdepth / 2;
|
||||
}
|
||||
vp.Angles.Yaw = linedef->Delta().Angle() * 2. - StartAngle;
|
||||
|
||||
vp.ViewActor = nullptr;
|
||||
|
||||
state->MirrorFlag++;
|
||||
di->SetClipLine(linedef);
|
||||
di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1));
|
||||
|
||||
clipper->Clear();
|
||||
|
||||
angle_t af = di->FrustumAngle();
|
||||
if (af < ANGLE_180) clipper->SafeAddClipRangeRealAngles(vp.Angles.Yaw.BAMs() + af, vp.Angles.Yaw.BAMs() - af);
|
||||
|
||||
clipper->SafeAddClipRange(linedef->v1, linedef->v2);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HWMirrorPortal::Shutdown(HWDrawInfo *di)
|
||||
{
|
||||
mOwner->mState->MirrorFlag--;
|
||||
}
|
||||
|
||||
const char *HWMirrorPortal::GetName() { return "Mirror"; }
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
// Line to line Portal
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
bool HWLineToLinePortal::Setup(HWDrawInfo *di, Clipper *clipper)
|
||||
{
|
||||
// TODO: Handle recursion more intelligently
|
||||
auto &state = mOwner->mState;
|
||||
if (state->renderdepth>r_mirror_recursions)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto &vp = di->Viewpoint;
|
||||
di->mClipPortal = this;
|
||||
|
||||
line_t *origin = glport->lines[0]->mOrigin;
|
||||
P_TranslatePortalXY(origin, vp.Pos.X, vp.Pos.Y);
|
||||
P_TranslatePortalXY(origin, vp.ActorPos.X, vp.ActorPos.Y);
|
||||
P_TranslatePortalAngle(origin, vp.Angles.Yaw);
|
||||
P_TranslatePortalZ(origin, vp.Pos.Z);
|
||||
P_TranslatePortalXY(origin, vp.Path[0].X, vp.Path[0].Y);
|
||||
P_TranslatePortalXY(origin, vp.Path[1].X, vp.Path[1].Y);
|
||||
if (!vp.showviewer && vp.camera != nullptr && P_PointOnLineSidePrecise(vp.Path[0], glport->lines[0]->mDestination) != P_PointOnLineSidePrecise(vp.Path[1], glport->lines[0]->mDestination))
|
||||
{
|
||||
double distp = (vp.Path[0] - vp.Path[1]).Length();
|
||||
if (distp > EQUAL_EPSILON)
|
||||
{
|
||||
double dist1 = (vp.Pos - vp.Path[0]).Length();
|
||||
double dist2 = (vp.Pos - vp.Path[1]).Length();
|
||||
|
||||
if (dist1 + dist2 < distp + 1)
|
||||
{
|
||||
vp.camera->renderflags |= RF_MAYBEINVISIBLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto &lines = mOwner->lines;
|
||||
|
||||
for (unsigned i = 0; i < lines.Size(); i++)
|
||||
{
|
||||
line_t *line = lines[i].seg->linedef->getPortalDestination();
|
||||
subsector_t *sub;
|
||||
if (line->sidedef[0]->Flags & WALLF_POLYOBJ)
|
||||
sub = R_PointInSubsector(line->v1->fixX(), line->v1->fixY());
|
||||
else sub = line->frontsector->subsectors[0];
|
||||
di->CurrentMapSections.Set(sub->mapsection);
|
||||
}
|
||||
|
||||
vp.ViewActor = nullptr;
|
||||
di->SetClipLine(glport->lines[0]->mDestination);
|
||||
di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1));
|
||||
|
||||
ClearClipper(di);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HWLineToLinePortal::RenderAttached(HWDrawInfo *di)
|
||||
{
|
||||
di->ProcessActorsInPortal(glport, di->in_area);
|
||||
}
|
||||
|
||||
const char *HWLineToLinePortal::GetName() { return "LineToLine"; }
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
// Skybox Portal
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// GLSkyboxPortal::DrawContents
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool HWSkyboxPortal::Setup(HWDrawInfo *di, Clipper *clipper)
|
||||
{
|
||||
auto state = mOwner->mState;
|
||||
old_pm = state->PlaneMirrorMode;
|
||||
|
||||
if (mOwner->mState->skyboxrecursion >= 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto &vp = di->Viewpoint;
|
||||
|
||||
state->skyboxrecursion++;
|
||||
state->PlaneMirrorMode = 0;
|
||||
state->inskybox = true;
|
||||
|
||||
AActor *origin = portal->mSkybox;
|
||||
portal->mFlags |= PORTSF_INSKYBOX;
|
||||
vp.extralight = 0;
|
||||
|
||||
|
||||
oldclamp = di->SetDepthClamp(false);
|
||||
vp.Pos = origin->InterpolatedPosition(vp.TicFrac);
|
||||
vp.ActorPos = origin->Pos();
|
||||
vp.Angles.Yaw += (origin->PrevAngles.Yaw + deltaangle(origin->PrevAngles.Yaw, origin->Angles.Yaw) * vp.TicFrac);
|
||||
|
||||
// Don't let the viewpoint be too close to a floor or ceiling
|
||||
double floorh = origin->Sector->floorplane.ZatPoint(origin->Pos());
|
||||
double ceilh = origin->Sector->ceilingplane.ZatPoint(origin->Pos());
|
||||
if (vp.Pos.Z < floorh + 4) vp.Pos.Z = floorh + 4;
|
||||
if (vp.Pos.Z > ceilh - 4) vp.Pos.Z = ceilh - 4;
|
||||
|
||||
vp.ViewActor = origin;
|
||||
|
||||
di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1));
|
||||
di->SetViewArea();
|
||||
ClearClipper(di);
|
||||
di->UpdateCurrentMapSection();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void HWSkyboxPortal::Shutdown(HWDrawInfo *di)
|
||||
{
|
||||
auto state = mOwner->mState;
|
||||
portal->mFlags &= ~PORTSF_INSKYBOX;
|
||||
di->SetDepthClamp(oldclamp);
|
||||
state->inskybox = false;
|
||||
state->skyboxrecursion--;
|
||||
state->PlaneMirrorMode = old_pm;
|
||||
}
|
||||
|
||||
const char *HWSkyboxPortal::GetName() { return "Skybox"; }
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
// Sector stack Portal
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// GLSectorStackPortal::SetupCoverage
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static uint8_t SetCoverage(HWDrawInfo *di, void *node)
|
||||
{
|
||||
if (level.nodes.Size() == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (!((size_t)node & 1)) // Keep going until found a subsector
|
||||
{
|
||||
node_t *bsp = (node_t *)node;
|
||||
uint8_t coverage = SetCoverage(di, bsp->children[0]) | SetCoverage(di, bsp->children[1]);
|
||||
di->no_renderflags[bsp->Index()] = coverage;
|
||||
return coverage;
|
||||
}
|
||||
else
|
||||
{
|
||||
subsector_t *sub = (subsector_t *)((uint8_t *)node - 1);
|
||||
return di->ss_renderflags[sub->Index()] & SSRF_SEEN;
|
||||
}
|
||||
}
|
||||
|
||||
void HWSectorStackPortal::SetupCoverage(HWDrawInfo *di)
|
||||
{
|
||||
for (unsigned i = 0; i<subsectors.Size(); i++)
|
||||
{
|
||||
subsector_t *sub = subsectors[i];
|
||||
int plane = origin->plane;
|
||||
for (int j = 0; j<sub->portalcoverage[plane].sscount; j++)
|
||||
{
|
||||
subsector_t *dsub = &::level.subsectors[sub->portalcoverage[plane].subsectors[j]];
|
||||
di->CurrentMapSections.Set(dsub->mapsection);
|
||||
di->ss_renderflags[dsub->Index()] |= SSRF_SEEN;
|
||||
}
|
||||
}
|
||||
SetCoverage(di, ::level.HeadNode());
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// GLSectorStackPortal::DrawContents
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
bool HWSectorStackPortal::Setup(HWDrawInfo *di, Clipper *clipper)
|
||||
{
|
||||
auto state = mOwner->mState;
|
||||
FSectorPortalGroup *portal = origin;
|
||||
auto &vp = di->Viewpoint;
|
||||
|
||||
vp.Pos += origin->mDisplacement;
|
||||
vp.ActorPos += origin->mDisplacement;
|
||||
vp.ViewActor = nullptr;
|
||||
|
||||
// avoid recursions!
|
||||
if (origin->plane != -1) screen->instack[origin->plane]++;
|
||||
|
||||
di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1));
|
||||
SetupCoverage(di);
|
||||
ClearClipper(di);
|
||||
|
||||
// If the viewpoint is not within the portal, we need to invalidate the entire clip area.
|
||||
// The portal will re-validate the necessary parts when its subsectors get traversed.
|
||||
subsector_t *sub = R_PointInSubsector(vp.Pos);
|
||||
if (!(di->ss_renderflags[sub->Index()] & SSRF_SEEN))
|
||||
{
|
||||
di->mClipper->SafeAddClipRange(0, ANGLE_MAX);
|
||||
di->mClipper->SetBlocked(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void HWSectorStackPortal::Shutdown(HWDrawInfo *di)
|
||||
{
|
||||
if (origin->plane != -1) screen->instack[origin->plane]--;
|
||||
}
|
||||
|
||||
const char *HWSectorStackPortal::GetName() { return "Sectorstack"; }
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
// Plane Mirror Portal
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// GLPlaneMirrorPortal::DrawContents
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool HWPlaneMirrorPortal::Setup(HWDrawInfo *di, Clipper *clipper)
|
||||
{
|
||||
auto state = mOwner->mState;
|
||||
if (state->renderdepth > r_mirror_recursions)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// A plane mirror needs to flip the portal exclusion logic because inside the mirror, up is down and down is up.
|
||||
std::swap(screen->instack[sector_t::floor], screen->instack[sector_t::ceiling]);
|
||||
|
||||
auto &vp = di->Viewpoint;
|
||||
int old_pm = state->PlaneMirrorMode;
|
||||
|
||||
// the player is always visible in a mirror.
|
||||
vp.showviewer = true;
|
||||
|
||||
double planez = origin->ZatPoint(vp.Pos);
|
||||
vp.Pos.Z = 2 * planez - vp.Pos.Z;
|
||||
vp.ViewActor = nullptr;
|
||||
state->PlaneMirrorMode = origin->fC() < 0 ? -1 : 1;
|
||||
|
||||
state->PlaneMirrorFlag++;
|
||||
di->SetClipHeight(planez, state->PlaneMirrorMode < 0 ? -1.f : 1.f);
|
||||
di->SetupView(vp.Pos.X, vp.Pos.Y, vp.Pos.Z, !!(state->MirrorFlag & 1), !!(state->PlaneMirrorFlag & 1));
|
||||
ClearClipper(di);
|
||||
|
||||
di->UpdateCurrentMapSection();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HWPlaneMirrorPortal::Shutdown(HWDrawInfo *di)
|
||||
{
|
||||
auto state = mOwner->mState;
|
||||
state->PlaneMirrorFlag--;
|
||||
state->PlaneMirrorMode = old_pm;
|
||||
std::swap(screen->instack[sector_t::floor], screen->instack[sector_t::ceiling]);
|
||||
}
|
||||
|
||||
const char *HWPlaneMirrorPortal::GetName() { return "Planemirror"; }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "portal.h"
|
||||
#include "hw_drawinfo.h"
|
||||
#include "hw_drawstructs.h"
|
||||
#include "hwrenderer/textures/hw_material.h"
|
||||
|
@ -39,16 +40,12 @@ struct FPortalSceneState;
|
|||
class IPortal
|
||||
{
|
||||
friend struct FPortalSceneState;
|
||||
protected:
|
||||
public:
|
||||
FPortalSceneState * mState;
|
||||
TArray<GLWall> lines;
|
||||
public:
|
||||
|
||||
IPortal(FPortalSceneState *s, bool local);
|
||||
virtual ~IPortal() {}
|
||||
virtual int ClipSeg(seg_t *seg, const DVector3 &viewpos) { return PClip_Inside; }
|
||||
virtual int ClipSubsector(subsector_t *sub) { return PClip_Inside; }
|
||||
virtual int ClipPoint(const DVector2 &pos) { return PClip_Inside; }
|
||||
virtual line_t *ClipLine() { return nullptr; }
|
||||
virtual void * GetSource() const = 0; // GetSource MUST be implemented!
|
||||
virtual const char *GetName() = 0;
|
||||
virtual bool IsSky() { return false; }
|
||||
|
@ -127,3 +124,191 @@ inline IPortal::IPortal(FPortalSceneState *s, bool local) : mState(s)
|
|||
{
|
||||
if (!local) s->portals.Push(this);
|
||||
}
|
||||
|
||||
|
||||
class HWScenePortalBase
|
||||
{
|
||||
protected:
|
||||
IPortal *mOwner;
|
||||
public:
|
||||
HWScenePortalBase() {}
|
||||
virtual ~HWScenePortalBase() {}
|
||||
void SetOwner(IPortal *p) { mOwner = p; }
|
||||
void ClearClipper(HWDrawInfo *di);
|
||||
|
||||
virtual int ClipSeg(seg_t *seg, const DVector3 &viewpos) { return PClip_Inside; }
|
||||
virtual int ClipSubsector(subsector_t *sub) { return PClip_Inside; }
|
||||
virtual int ClipPoint(const DVector2 &pos) { return PClip_Inside; }
|
||||
virtual line_t *ClipLine() { return nullptr; }
|
||||
|
||||
virtual bool IsSky() { return false; }
|
||||
virtual bool NeedCap() { return false; }
|
||||
virtual bool NeedDepthBuffer() { return true; }
|
||||
virtual void * GetSource() const = 0; // GetSource MUST be implemented!
|
||||
virtual const char *GetName() = 0;
|
||||
|
||||
virtual bool Setup(HWDrawInfo *di, Clipper *clipper) = 0;
|
||||
virtual void Shutdown(HWDrawInfo *di) {}
|
||||
virtual void RenderAttached(HWDrawInfo *di) {}
|
||||
|
||||
};
|
||||
|
||||
struct HWLinePortal : public HWScenePortalBase
|
||||
{
|
||||
// this must be the same as at the start of line_t, so that we can pass in this structure directly to P_ClipLineToPortal.
|
||||
vertex_t *v1, *v2; // vertices, from v1 to v2
|
||||
DVector2 delta; // precalculated v2 - v1 for side checking
|
||||
|
||||
angle_t angv1, angv2; // for quick comparisons with a line or subsector
|
||||
|
||||
HWLinePortal(line_t *line)
|
||||
{
|
||||
v1 = line->v1;
|
||||
v2 = line->v2;
|
||||
CalcDelta();
|
||||
}
|
||||
|
||||
HWLinePortal(FLinePortalSpan *line)
|
||||
{
|
||||
if (line->lines[0]->mType != PORTT_LINKED || line->v1 == nullptr)
|
||||
{
|
||||
// For non-linked portals we must check the actual linedef.
|
||||
line_t *lline = line->lines[0]->mDestination;
|
||||
v1 = lline->v1;
|
||||
v2 = lline->v2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For linked portals we can check the merged span.
|
||||
v1 = line->v1;
|
||||
v2 = line->v2;
|
||||
}
|
||||
CalcDelta();
|
||||
}
|
||||
|
||||
void CalcDelta()
|
||||
{
|
||||
delta = v2->fPos() - v1->fPos();
|
||||
}
|
||||
|
||||
line_t *line()
|
||||
{
|
||||
vertex_t **pv = &v1;
|
||||
return reinterpret_cast<line_t*>(pv);
|
||||
}
|
||||
|
||||
int ClipSeg(seg_t *seg, const DVector3 &viewpos) override;
|
||||
int ClipSubsector(subsector_t *sub) override;
|
||||
int ClipPoint(const DVector2 &pos);
|
||||
bool NeedCap() override { return false; }
|
||||
};
|
||||
|
||||
struct HWMirrorPortal : public HWLinePortal
|
||||
{
|
||||
// mirror portals always consist of single linedefs!
|
||||
line_t * linedef;
|
||||
|
||||
protected:
|
||||
bool Setup(HWDrawInfo *di, Clipper *clipper) override;
|
||||
void Shutdown(HWDrawInfo *di) override;
|
||||
void * GetSource() const override { return linedef; }
|
||||
const char *GetName() override;
|
||||
|
||||
public:
|
||||
|
||||
HWMirrorPortal(line_t * line)
|
||||
: HWLinePortal(line)
|
||||
{
|
||||
linedef = line;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct HWLineToLinePortal : public HWLinePortal
|
||||
{
|
||||
FLinePortalSpan *glport;
|
||||
protected:
|
||||
bool Setup(HWDrawInfo *di, Clipper *clipper) override;
|
||||
virtual void * GetSource() const override { return glport; }
|
||||
virtual const char *GetName() override;
|
||||
virtual line_t *ClipLine() override { return line(); }
|
||||
virtual void RenderAttached(HWDrawInfo *di) override;
|
||||
|
||||
public:
|
||||
|
||||
HWLineToLinePortal(FLinePortalSpan *ll)
|
||||
: HWLinePortal(ll)
|
||||
{
|
||||
glport = ll;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct HWSkyboxPortal : public HWScenePortalBase
|
||||
{
|
||||
bool oldclamp;
|
||||
int old_pm;
|
||||
FSectorPortal * portal;
|
||||
|
||||
protected:
|
||||
bool Setup(HWDrawInfo *di, Clipper *clipper) override;
|
||||
void Shutdown(HWDrawInfo *di) override;
|
||||
virtual void * GetSource() const { return portal; }
|
||||
virtual bool IsSky() { return true; }
|
||||
virtual const char *GetName();
|
||||
|
||||
public:
|
||||
|
||||
|
||||
HWSkyboxPortal(FSectorPortal * pt)
|
||||
{
|
||||
portal = pt;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
struct HWSectorStackPortal : public HWScenePortalBase
|
||||
{
|
||||
TArray<subsector_t *> subsectors;
|
||||
protected:
|
||||
bool Setup(HWDrawInfo *di, Clipper *clipper) override;
|
||||
void Shutdown(HWDrawInfo *di) override;
|
||||
virtual void * GetSource() const { return origin; }
|
||||
virtual bool IsSky() { return true; } // although this isn't a real sky it can be handled as one.
|
||||
virtual const char *GetName();
|
||||
FSectorPortalGroup *origin;
|
||||
|
||||
public:
|
||||
|
||||
HWSectorStackPortal(FSectorPortalGroup *pt)
|
||||
{
|
||||
origin = pt;
|
||||
}
|
||||
void SetupCoverage(HWDrawInfo *di);
|
||||
void AddSubsector(subsector_t *sub)
|
||||
{
|
||||
subsectors.Push(sub);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct HWPlaneMirrorPortal : public HWScenePortalBase
|
||||
{
|
||||
int old_pm;
|
||||
protected:
|
||||
bool Setup(HWDrawInfo *di, Clipper *clipper) override;
|
||||
void Shutdown(HWDrawInfo *di) override;
|
||||
virtual void * GetSource() const { return origin; }
|
||||
virtual const char *GetName();
|
||||
secplane_t * origin;
|
||||
|
||||
public:
|
||||
|
||||
HWPlaneMirrorPortal(secplane_t * pt)
|
||||
{
|
||||
origin = pt;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -254,11 +254,6 @@ struct FSectorPortalGroup
|
|||
{
|
||||
DVector2 mDisplacement;
|
||||
int plane;
|
||||
GLSectorStackPortal *glportal; // for quick access to the render data. This is only valid during BSP traversal!
|
||||
|
||||
GLSectorStackPortal *GetRenderState();
|
||||
|
||||
void AddSubsector(subsector_t *sub);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -363,7 +363,6 @@ static void GroupSectorPortals()
|
|||
FSectorPortalGroup *portal = new FSectorPortalGroup;
|
||||
portal->mDisplacement = pair->Key.mDisplacement;
|
||||
portal->plane = (i == 1 ? sector_t::floor : sector_t::ceiling); /**/
|
||||
portal->glportal = NULL;
|
||||
level.portalGroups.Push(portal);
|
||||
for (unsigned j = 0; j < pair->Value.Size(); j++)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue