/* ** p_trace.cpp ** Generalized trace function, like most 3D games have ** **--------------------------------------------------------------------------- ** Copyright 1998-2006 Randy Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ #include "p_trace.h" #include "p_local.h" #include "r_sky.h" #include "doomstat.h" #include "p_maputl.h" #include "p_spec.h" #include "g_levellocals.h" #include "p_terrain.h" #include "vm.h" //========================================================================== // // // //========================================================================== struct FTraceInfo { DVector3 Start; DVector3 Vec; ActorFlags ActorMask; uint32_t WallMask; AActor *IgnoreThis; FTraceResults *Results; sector_t *CurSector; double MaxDist; double EnterDist; ETraceStatus (*TraceCallback)(FTraceResults &res, void *data); void *TraceCallbackData; uint32_t TraceFlags; int inshootthrough; double startfrac; double limitz; int ptflags; // These are required for 3D-floor checking // to create a fake sector with a floor // or ceiling plane coming from a 3D-floor sector_t DummySector[2]; int sectorsel; void Setup3DFloors(); bool LineCheck(intercept_t *in, double dist, DVector3 hit, bool special3dpass); bool ThingCheck(intercept_t *in, double dist, DVector3 hit); bool TraceTraverse (int ptflags); bool CheckPlane(const secplane_t &plane); void EnterLinePortal(FPathTraverse &pt, intercept_t *in); void EnterSectorPortal(FPathTraverse &pt, int position, double frac, sector_t *entersec); bool CheckSectorPlane(const sector_t *sector, bool checkFloor) { return CheckPlane(checkFloor ? sector->floorplane : sector->ceilingplane); } bool Check3DFloorPlane(const F3DFloor *ffloor, bool checkBottom) { return CheckPlane(checkBottom? *(ffloor->bottom.plane) : *(ffloor->top.plane)); } void SetSourcePosition() { Results->SrcFromTarget = Start; Results->HitVector = Vec; Results->SrcAngleFromTarget = Results->HitVector.Angle(); } }; static bool EditTraceResult (uint32_t flags, FTraceResults &res); static void GetPortalTransition(DVector3 &pos, sector_t *&sec) { bool moved = false; double testz = pos.Z; while (!sec->PortalBlocksMovement(sector_t::ceiling)) { if (pos.Z > sec->GetPortalPlaneZ(sector_t::ceiling)) { pos += sec->GetPortalDisplacement(sector_t::ceiling); sec = P_PointInSector(pos); moved = true; } else break; } if (!moved) { while (!sec->PortalBlocksMovement(sector_t::floor)) { if (pos.Z <= sec->GetPortalPlaneZ(sector_t::floor)) { pos += sec->GetPortalDisplacement(sector_t::floor); sec = P_PointInSector(pos); } else break; } } } static bool isLiquid(F3DFloor *ff) { if (ff->flags & FF_SWIMMABLE) return true; auto terrain = ff->model->GetTerrain(ff->flags & FF_INVERTPLANES ? sector_t::floor : sector_t::ceiling); return Terrains[terrain].IsLiquid && Terrains[terrain].Splash != -1; } //========================================================================== // // Trace entry point // //========================================================================== bool Trace(const DVector3 &start, sector_t *sector, const DVector3 &direction, double maxDist, ActorFlags actorMask, uint32_t wallMask, AActor *ignore, FTraceResults &res, uint32_t flags, ETraceStatus(*callback)(FTraceResults &res, void *), void *callbackdata) { FTraceInfo inf; FTraceResults tempResult; memset(&tempResult, 0, sizeof(tempResult)); tempResult.Fraction = tempResult.Distance = NO_VALUE; inf.Start = start; GetPortalTransition(inf.Start, sector); inf.ptflags = actorMask ? PT_ADDLINES|PT_ADDTHINGS|PT_COMPATIBLE : PT_ADDLINES; inf.Vec = direction; inf.ActorMask = actorMask; inf.WallMask = wallMask; inf.IgnoreThis = ignore; inf.CurSector = sector; inf.MaxDist = maxDist; inf.EnterDist = 0; inf.TraceCallback = callback; inf.TraceCallbackData = callbackdata; inf.TraceFlags = flags; inf.Results = &res; inf.inshootthrough = true; inf.sectorsel=0; inf.startfrac = 0; inf.limitz = inf.Start.Z; memset(&res, 0, sizeof(res)); if ((flags & TRACE_ReportPortals) && callback != NULL) { tempResult.HitType = TRACE_CrossingPortal; tempResult.HitPos = tempResult.SrcFromTarget = inf.Start; tempResult.HitVector = inf.Vec; callback(tempResult, inf.TraceCallbackData); } bool reslt = inf.TraceTraverse(inf.ptflags); if ((flags & TRACE_ReportPortals) && callback != NULL) { tempResult.HitType = TRACE_CrossingPortal; tempResult.HitPos = tempResult.SrcFromTarget = inf.Results->HitPos; tempResult.HitVector = inf.Vec; callback(tempResult, inf.TraceCallbackData); } if (reslt) { return flags ? EditTraceResult(flags, res) : true; } else { return false; } } //============================================================================ // // traverses a sector portal // //============================================================================ void FTraceInfo::EnterSectorPortal(FPathTraverse &pt, int position, double frac, sector_t *entersec) { DVector2 displacement = entersec->GetPortalDisplacement(position);; double enterdist = MaxDist * frac; DVector3 exit = Start + enterdist * Vec; DVector3 enter = exit + displacement; Start += displacement; CurSector = P_PointInSector(enter); inshootthrough = true; startfrac = frac; EnterDist = enterdist; pt.PortalRelocate(entersec->GetPortal(position)->mDisplacement, ptflags, frac); if ((TraceFlags & TRACE_ReportPortals) && TraceCallback != NULL) { enterdist = MaxDist * frac; Results->HitType = TRACE_CrossingPortal; Results->HitPos = exit; Results->SrcFromTarget = enter; Results->HitVector = Vec; TraceCallback(*Results, TraceCallbackData); } Setup3DFloors(); } //============================================================================ // // traverses a line portal // //============================================================================ void FTraceInfo::EnterLinePortal(FPathTraverse &pt, intercept_t *in) { line_t *li = in->d.line; FLinePortal *port = li->getPortal(); double frac = in->frac + EQUAL_EPSILON; double enterdist = MaxDist * frac; DVector3 exit = Start + MaxDist * in->frac * Vec; P_TranslatePortalXY(li, Start.X, Start.Y); P_TranslatePortalZ(li, Start.Z); P_TranslatePortalVXVY(li, Vec.X, Vec.Y); P_TranslatePortalZ(li, limitz); CurSector = P_PointInSector(Start + enterdist * Vec); EnterDist = enterdist; inshootthrough = true; startfrac = frac; Results->unlinked |= (port->mType != PORTT_LINKED); pt.PortalRelocate(in, ptflags); if ((TraceFlags & TRACE_ReportPortals) && TraceCallback != NULL) { enterdist = MaxDist * in->frac; Results->HitType = TRACE_CrossingPortal; Results->HitPos = exit; P_TranslatePortalXY(li, exit.X, exit.Y); P_TranslatePortalZ(li, exit.Z); Results->SrcFromTarget = exit; Results->HitVector = Vec; TraceCallback(*Results, TraceCallbackData); } } //========================================================================== // // Checks 3D floors at trace start and sets up related data // //========================================================================== void FTraceInfo::Setup3DFloors() { TDeletingArray &ff = CurSector->e->XFloor.ffloors; if (ff.Size()) { memcpy(&DummySector[0], CurSector, sizeof(sector_t)); CurSector = &DummySector[0]; sectorsel = 1; double sdist = MaxDist * startfrac; DVector3 pos = Start + Vec * sdist; double bf = CurSector->floorplane.ZatPoint(pos); double bc = CurSector->ceilingplane.ZatPoint(pos); for (auto rover : ff) { if (!(rover->flags&FF_EXISTS)) continue; if (Results->Crossed3DWater == NULL) { if (Check3DFloorPlane(rover, false) && isLiquid(rover)) { // only consider if the plane is above the actual floor. if (rover->top.plane->ZatPoint(Results->HitPos) > bf) { Results->Crossed3DWater = rover; Results->Crossed3DWaterPos = Results->HitPos; Results->Distance = 0; } } } if (!(rover->flags&FF_SHOOTTHROUGH)) { double ff_bottom = rover->bottom.plane->ZatPoint(pos); double ff_top = rover->top.plane->ZatPoint(pos); // clip to the part of the sector we are in if (pos.Z > ff_top) { // above if (bf < ff_top) { CurSector->floorplane = *rover->top.plane; CurSector->SetTexture(sector_t::floor, *rover->top.texture, false); CurSector->ClearPortal(sector_t::floor); bf = ff_top; } } else if (pos.Z < ff_bottom) { //below if (bc > ff_bottom) { CurSector->ceilingplane = *rover->bottom.plane; CurSector->SetTexture(sector_t::ceiling, *rover->bottom.texture, false); bc = ff_bottom; CurSector->ClearPortal(sector_t::ceiling); } } else { // inside if (bf < ff_bottom) { CurSector->floorplane = *rover->bottom.plane; CurSector->SetTexture(sector_t::floor, *rover->bottom.texture, false); CurSector->ClearPortal(sector_t::floor); bf = ff_bottom; } if (bc > ff_top) { CurSector->ceilingplane = *rover->top.plane; CurSector->SetTexture(sector_t::ceiling, *rover->top.texture, false); CurSector->ClearPortal(sector_t::ceiling); bc = ff_top; } inshootthrough = false; } } } } } //========================================================================== // // Processes one line intercept // //========================================================================== bool FTraceInfo::LineCheck(intercept_t *in, double dist, DVector3 hit, bool special3dpass) { int lineside; sector_t *entersector; double ff, fc, bf = 0, bc = 0; if (in->d.line->frontsector->sectornum == CurSector->sectornum) { lineside = 0; } else if (in->d.line->backsector && in->d.line->backsector->sectornum == CurSector->sectornum) { lineside = 1; } else { // Dammit. Why does Doom have to allow non-closed sectors? if (in->d.line->backsector == NULL) { lineside = 0; CurSector = in->d.line->frontsector; } else { lineside = P_PointOnLineSide(Start, in->d.line); CurSector = lineside ? in->d.line->backsector : in->d.line->frontsector; } } if (!(in->d.line->flags & ML_TWOSIDED)) { entersector = NULL; } else { entersector = (lineside == 0) ? in->d.line->backsector : in->d.line->frontsector; // For backwards compatibility: Ignore lines with the same sector on both sides. // This is the way Doom.exe did it and some WADs (e.g. Alien Vendetta MAP15) need it. if (i_compatflags & COMPATF_TRACE && in->d.line->backsector == in->d.line->frontsector && !special3dpass) { // We must check special activation here because the code below is never reached. if (TraceFlags & TRACE_PCross) { P_ActivateLine(in->d.line, IgnoreThis, lineside, SPAC_PCross); } if (TraceFlags & TRACE_Impact) { P_ActivateLine(in->d.line, IgnoreThis, lineside, SPAC_Impact); } return true; } } ff = CurSector->floorplane.ZatPoint(hit); fc = CurSector->ceilingplane.ZatPoint(hit); if (entersector != NULL) { bf = entersector->floorplane.ZatPoint(hit); bc = entersector->ceilingplane.ZatPoint(hit); } sector_t *hsec = CurSector->GetHeightSec(); if (Results->CrossedWater == NULL && hsec != NULL && Start.Z > hsec->floorplane.ZatPoint(Start) && hit.Z <= hsec->floorplane.ZatPoint(hit)) { // hit crossed a water plane if (CheckSectorPlane(hsec, true)) { Results->CrossedWater = &level.sectors[CurSector->sectornum]; Results->CrossedWaterPos = Results->HitPos; Results->Distance = 0; } } if (hit.Z <= ff) { // hit floor in front of wall Results->HitType = TRACE_HitFloor; Results->HitTexture = CurSector->GetTexture(sector_t::floor); } else if (hit.Z >= fc) { // hit ceiling in front of wall Results->HitType = TRACE_HitCeiling; Results->HitTexture = CurSector->GetTexture(sector_t::ceiling); } else if (entersector == NULL || hit.Z < bf || hit.Z > bc || ((in->d.line->flags & WallMask) && !special3dpass)) { // hit the wall Results->HitType = TRACE_HitWall; Results->Tier = entersector == NULL ? TIER_Middle : hit.Z <= bf ? TIER_Lower : hit.Z >= bc ? TIER_Upper : TIER_Middle; if ((TraceFlags & TRACE_Impact) && !special3dpass) { P_ActivateLine(in->d.line, IgnoreThis, lineside, SPAC_Impact); } } else { // made it past the wall // check for 3D floors first if (entersector->e->XFloor.ffloors.Size()) { memcpy(&DummySector[sectorsel], entersector, sizeof(sector_t)); entersector = &DummySector[sectorsel]; sectorsel ^= 1; for (auto rover : entersector->e->XFloor.ffloors) { int entershootthrough = !!(rover->flags&FF_SHOOTTHROUGH); if (entershootthrough != inshootthrough && rover->flags&FF_EXISTS) { double ff_bottom = rover->bottom.plane->ZatPoint(hit); double ff_top = rover->top.plane->ZatPoint(hit); // clip to the part of the sector we are in if (hit.Z > ff_top) { // 3D floor height is the same as the floor height. We need to test a second spot to see if it is above or below if (fabs(bf - ff_top) < EQUAL_EPSILON) { double cf = entersector->floorplane.ZatPoint(entersector->centerspot); double ffc = rover->top.plane->ZatPoint(entersector->centerspot); if (ffc > cf) { bf = ff_top - EQUAL_EPSILON; } } // above if (bf < ff_top) { entersector->floorplane = *rover->top.plane; entersector->SetTexture(sector_t::floor, *rover->top.texture, false); entersector->ClearPortal(sector_t::floor); bf = ff_top; } } else if (hit.Z < ff_bottom) { // 3D floor height is the same as the ceiling height. We need to test a second spot to see if it is above or below if (fabs(bc - ff_bottom) < EQUAL_EPSILON) { double cc = entersector->ceilingplane.ZatPoint(entersector->centerspot); double fcc = rover->bottom.plane->ZatPoint(entersector->centerspot); if (fcc < cc) { bc = ff_bottom + EQUAL_EPSILON; } } //below if (bc > ff_bottom) { entersector->ceilingplane = *rover->bottom.plane; entersector->SetTexture(sector_t::ceiling, *rover->bottom.texture, false); entersector->ClearPortal(sector_t::ceiling); bc = ff_bottom; } } else { //hit the edge - equivalent to hitting the wall Results->HitType = TRACE_HitWall; Results->Tier = TIER_FFloor; Results->ffloor = rover; if ((TraceFlags & TRACE_Impact) && in->d.line->special) { P_ActivateLine(in->d.line, IgnoreThis, lineside, SPAC_Impact); } goto cont; } } } } Results->HitType = TRACE_HitNone; if (!special3dpass) { if (TraceFlags & TRACE_PCross) { P_ActivateLine(in->d.line, IgnoreThis, lineside, SPAC_PCross); } if (TraceFlags & TRACE_Impact) { // This is incorrect for "impact", but Hexen did this, so // we need to as well, for compatibility P_ActivateLine(in->d.line, IgnoreThis, lineside, SPAC_Impact); } } } cont: if (Results->HitType != TRACE_HitNone) { // We hit something, so figure out where exactly Results->Sector = &level.sectors[CurSector->sectornum]; if (Results->HitType != TRACE_HitWall && !CheckSectorPlane(CurSector, Results->HitType == TRACE_HitFloor)) { // trace is parallel to the plane (or right on it) if (entersector == NULL) { Results->HitType = TRACE_HitWall; Results->Tier = TIER_Middle; } else { if (hit.Z <= bf || hit.Z >= bc) { Results->HitType = TRACE_HitWall; Results->Tier = hit.Z <= bf ? TIER_Lower : hit.Z >= bc ? TIER_Upper : TIER_Middle; } else { Results->HitType = TRACE_HitNone; } } if (Results->HitType == TRACE_HitWall && TraceFlags & TRACE_Impact && (!special3dpass || Results->Tier != TIER_FFloor)) { P_ActivateLine(in->d.line, IgnoreThis, lineside, SPAC_Impact); } } if (Results->HitType == TRACE_HitWall) { Results->HitPos = hit; SetSourcePosition(); Results->Distance = dist; Results->Fraction = in->frac; Results->Line = in->d.line; Results->Side = lineside; } } if (TraceCallback != NULL && Results->HitType != TRACE_HitNone) { switch (TraceCallback(*Results, TraceCallbackData)) { case TRACE_Stop: return false; case TRACE_Abort: Results->HitType = TRACE_HitNone; return false; case TRACE_Skip: Results->HitType = TRACE_HitNone; if (!special3dpass && (TraceFlags & TRACE_3DCallback) && entersector->e->XFloor.ffloors.Size()) return LineCheck(in, dist, hit, true); break; default: break; } } if (Results->HitType == TRACE_HitNone) { CurSector = entersector; EnterDist = dist; return true; } else { return false; } } //========================================================================== // // // //========================================================================== bool FTraceInfo::ThingCheck(intercept_t *in, double dist, DVector3 hit) { if (hit.Z > in->d.thing->Top()) { // trace enters above actor if (Vec.Z >= 0) return true; // Going up: can't hit // Does it hit the top of the actor? dist = (in->d.thing->Top() - Start.Z) / Vec.Z; if (dist > MaxDist) return true; in->frac = dist / MaxDist; hit = Start + Vec * dist; // calculated coordinate is outside the actor's bounding box if (fabs(hit.X - in->d.thing->X()) > in->d.thing->radius || fabs(hit.Y - in->d.thing->Y()) > in->d.thing->radius) return true; } else if (hit.Z < in->d.thing->Z()) { // trace enters below actor if (Vec.Z <= 0) return true; // Going down: can't hit // Does it hit the bottom of the actor? dist = (in->d.thing->Z() - Start.Z) / Vec.Z; if (dist > MaxDist) return true; in->frac = dist / MaxDist; hit = Start + Vec * dist; // calculated coordinate is outside the actor's bounding box if (fabs(hit.X - in->d.thing->X()) > in->d.thing->radius || fabs(hit.Y - in->d.thing->Y()) > in->d.thing->radius) return true; } if (CurSector->e->XFloor.ffloors.Size()) { // check for 3D floor hits first. double ff_floor = CurSector->floorplane.ZatPoint(hit); double ff_ceiling = CurSector->ceilingplane.ZatPoint(hit); if (hit.Z > ff_ceiling && CurSector->PortalBlocksMovement(sector_t::ceiling)) // actor is hit above the current ceiling { Results->HitType = TRACE_HitCeiling; Results->HitTexture = CurSector->GetTexture(sector_t::ceiling); } else if (hit.Z < ff_floor && CurSector->PortalBlocksMovement(sector_t::floor)) // actor is hit below the current floor { Results->HitType = TRACE_HitFloor; Results->HitTexture = CurSector->GetTexture(sector_t::floor); } else goto cont1; // the trace hit a 3D floor before the thing. // Calculate an intersection and abort. Results->Sector = &level.sectors[CurSector->sectornum]; if (!CheckSectorPlane(CurSector, Results->HitType == TRACE_HitFloor)) { Results->HitType = TRACE_HitNone; } if (TraceCallback != NULL) { switch (TraceCallback(*Results, TraceCallbackData)) { case TRACE_Continue: return true; case TRACE_Stop: return false; case TRACE_Abort: Results->HitType = TRACE_HitNone; return false; case TRACE_Skip: Results->HitType = TRACE_HitNone; return true; } } else { return false; } } cont1: Results->HitType = TRACE_HitActor; Results->HitPos = hit; SetSourcePosition(); Results->Distance = dist; Results->Fraction = in->frac; Results->Actor = in->d.thing; if (TraceCallback != NULL) { switch (TraceCallback(*Results, TraceCallbackData)) { case TRACE_Stop: return false; case TRACE_Abort: Results->HitType = TRACE_HitNone; return false; case TRACE_Skip: Results->HitType = TRACE_HitNone; return true; default: return true; } } else { return false; } } //========================================================================== // // // //========================================================================== bool FTraceInfo::TraceTraverse (int ptflags) { // Do a 3D floor check in the starting sector Setup3DFloors(); FPathTraverse it(Start.X, Start.Y, Vec.X * MaxDist, Vec.Y * MaxDist, ptflags | PT_DELTA, startfrac); intercept_t *in; int lastsplashsector = -1; while ((in = it.Next())) { // Deal with splashes in 3D floors (but only run once per sector, not each iteration - and stop if something was found.) if (Results->Crossed3DWater == NULL && lastsplashsector != CurSector->sectornum) { for (auto rover : CurSector->e->XFloor.ffloors) { if ((rover->flags & FF_EXISTS) && isLiquid(rover)) { if (Check3DFloorPlane(rover, false)) { // only consider if the plane is above the actual floor. if (rover->top.plane->ZatPoint(Results->HitPos) > CurSector->floorplane.ZatPoint(Results->HitPos)) { Results->Crossed3DWater = rover; Results->Crossed3DWaterPos = Results->HitPos; Results->Distance = 0; } } } } lastsplashsector = CurSector->sectornum; } double dist = MaxDist * in->frac; DVector3 hit = Start + Vec * dist; // Crossed a floor portal? if (Vec.Z < 0 && !CurSector->PortalBlocksMovement(sector_t::floor)) { // calculate position where the portal is crossed double portz = CurSector->GetPortalPlaneZ(sector_t::floor); if (hit.Z < portz && limitz > portz) { limitz = portz; EnterSectorPortal(it, sector_t::floor, (portz - Start.Z) / (Vec.Z * MaxDist), CurSector); continue; } } // ... or a ceiling portal? else if (Vec.Z > 0 && !CurSector->PortalBlocksMovement(sector_t::ceiling)) { // calculate position where the portal is crossed double portz = CurSector->GetPortalPlaneZ(sector_t::ceiling); if (hit.Z > portz && limitz < portz) { limitz = portz; EnterSectorPortal(it, sector_t::ceiling, (portz - Start.Z) / (Vec.Z * MaxDist), CurSector); continue; } } if (in->isaline) { if (in->d.line->isLinePortal() && P_PointOnLineSidePrecise(Start, in->d.line) == 0) { sector_t *entersector = in->d.line->backsector; if (entersector == NULL || (hit.Z >= entersector->floorplane.ZatPoint(hit) && hit.Z <= entersector->ceilingplane.ZatPoint(hit))) { FLinePortal *port = in->d.line->getPortal(); // The caller cannot handle portals without global offset. if (port->mType == PORTT_LINKED || !(TraceFlags & TRACE_PortalRestrict)) { EnterLinePortal(it, in); continue; } } } if (!LineCheck(in, dist, hit, false)) break; } else if ((in->d.thing->flags & ActorMask) && in->d.thing != IgnoreThis) { if (!ThingCheck(in, dist, hit)) break; } } if (Results->HitType == TRACE_HitNone) { if (CurSector->PortalBlocksMovement(sector_t::floor) && CheckSectorPlane(CurSector, true)) { Results->HitType = TRACE_HitFloor; Results->HitTexture = CurSector->GetTexture(sector_t::floor); } else if (CurSector->PortalBlocksMovement(sector_t::ceiling) && CheckSectorPlane(CurSector, false)) { Results->HitType = TRACE_HitCeiling; Results->HitTexture = CurSector->GetTexture(sector_t::ceiling); } } // check for intersection with floor/ceiling Results->Sector = &level.sectors[CurSector->sectornum]; if (Results->CrossedWater == NULL && CurSector->heightsec != NULL && CurSector->heightsec->floorplane.ZatPoint(Start) < Start.Z && CurSector->heightsec->floorplane.ZatPoint(Results->HitPos) >= Results->HitPos.Z) { // Save the result so that the water check doesn't destroy it. FTraceResults *res = Results; FTraceResults hsecResult; Results = &hsecResult; if (CheckSectorPlane(CurSector->heightsec, true)) { Results->CrossedWater = &level.sectors[CurSector->sectornum]; Results->CrossedWaterPos = Results->HitPos; Results->Distance = 0; } Results = res; } if (Results->HitType == TRACE_HitNone && Results->Distance == 0) { Results->HitPos = Start + Vec * MaxDist; SetSourcePosition(); Results->Distance = MaxDist; Results->Fraction = 1.; } return Results->HitType != TRACE_HitNone; } //========================================================================== // // // //========================================================================== bool FTraceInfo::CheckPlane (const secplane_t &plane) { double den = plane.Normal() | Vec; if (den != 0) { double num = (plane.Normal() | Start) + plane.fD(); double hitdist = -num / den; if (hitdist > EnterDist && hitdist < MaxDist) { Results->HitPos = Start + Vec * hitdist; SetSourcePosition(); Results->Distance = hitdist; Results->Fraction = hitdist / MaxDist; return true; } } return false; } //========================================================================== // // // //========================================================================== static bool EditTraceResult (uint32_t flags, FTraceResults &res) { if (flags & TRACE_NoSky) { // Throw away sky hits if (res.HitType == TRACE_HitFloor || res.HitType == TRACE_HitCeiling) { if (res.HitTexture == skyflatnum) { res.HitType = TRACE_HitNone; return false; } } else if (res.HitType == TRACE_HitWall) { if (res.Tier == TIER_Upper && res.Line->frontsector->GetTexture(sector_t::ceiling) == skyflatnum && res.Line->backsector->GetTexture(sector_t::ceiling) == skyflatnum) { res.HitType = TRACE_HitNone; return false; } } } return true; } //========================================================================== // // [ZZ] here go the methods for the ZScript interface // //========================================================================== IMPLEMENT_CLASS(DLineTracer, false, false) DEFINE_FIELD(DLineTracer, Results) // define TraceResults fields DEFINE_FIELD_NAMED_X(TraceResults, FTraceResults, Sector, HitSector) DEFINE_FIELD_X(TraceResults, FTraceResults, HitTexture) DEFINE_FIELD_X(TraceResults, FTraceResults, HitPos) DEFINE_FIELD_X(TraceResults, FTraceResults, HitVector) DEFINE_FIELD_X(TraceResults, FTraceResults, SrcFromTarget) DEFINE_FIELD_X(TraceResults, FTraceResults, SrcAngleFromTarget) DEFINE_FIELD_X(TraceResults, FTraceResults, Distance) DEFINE_FIELD_X(TraceResults, FTraceResults, Fraction) DEFINE_FIELD_NAMED_X(TraceResults, FTraceResults, Actor, HitActor) DEFINE_FIELD_NAMED_X(TraceResults, FTraceResults, Line, HitLine) DEFINE_FIELD_X(TraceResults, FTraceResults, Side) DEFINE_FIELD_X(TraceResults, FTraceResults, Tier) DEFINE_FIELD_X(TraceResults, FTraceResults, unlinked) DEFINE_FIELD_X(TraceResults, FTraceResults, HitType) DEFINE_FIELD_X(TraceResults, FTraceResults, CrossedWater) DEFINE_FIELD_X(TraceResults, FTraceResults, CrossedWaterPos) DEFINE_FIELD_X(TraceResults, FTraceResults, Crossed3DWater) DEFINE_FIELD_X(TraceResults, FTraceResults, Crossed3DWaterPos) DEFINE_ACTION_FUNCTION(DLineTracer, Trace) { PARAM_SELF_PROLOGUE(DLineTracer); /*bool Trace(const DVector3 &start, sector_t *sector, const DVector3 &direction, double maxDist, ActorFlags ActorMask, uint32_t WallMask, AActor *ignore, FTraceResults &res, uint32_t traceFlags = 0, ETraceStatus(*callback)(FTraceResults &res, void *) = NULL, void *callbackdata = NULL);*/ PARAM_FLOAT(start_x); PARAM_FLOAT(start_y); PARAM_FLOAT(start_z); PARAM_POINTER(sector, sector_t); PARAM_FLOAT(direction_x); PARAM_FLOAT(direction_y); PARAM_FLOAT(direction_z); PARAM_FLOAT(maxDist); // actor flags and wall flags are not supported due to how flags are implemented on the ZScript side. // say thanks to oversimplifying the user API. PARAM_INT(traceFlags); // these are internal hacks. traceFlags &= ~(TRACE_PCross | TRACE_Impact); // this too traceFlags |= TRACE_3DCallback; // Trace(vector3 start, Sector sector, vector3 direction, double maxDist, ETraceFlags traceFlags) bool res = Trace(DVector3(start_x, start_y, start_z), sector, DVector3(direction_x, direction_y, direction_z), maxDist, (ActorFlag)0xFFFFFFFF, 0xFFFFFFFF, nullptr, self->Results, traceFlags, &DLineTracer::TraceCallback, self); ACTION_RETURN_BOOL(res); } ETraceStatus DLineTracer::TraceCallback(FTraceResults& res, void* pthis) { DLineTracer* self = (DLineTracer*)pthis; // "res" here should refer to self->Results anyway. // patch results a bit. modders don't expect it to work like this most likely. // code by MarisaKirisame if (res.HitType == TRACE_HitWall) { int txpart; switch (res.Tier) { case TIER_Middle: res.HitTexture = res.Line->sidedef[res.Side]->textures[1].texture; break; case TIER_Upper: res.HitTexture = res.Line->sidedef[res.Side]->textures[0].texture; break; case TIER_Lower: res.HitTexture = res.Line->sidedef[res.Side]->textures[2].texture; break; case TIER_FFloor: txpart = (res.ffloor->flags & FF_UPPERTEXTURE) ? 0 : (res.ffloor->flags & FF_LOWERTEXTURE) ? 2 : 1; res.HitTexture = res.ffloor->master->sidedef[0]->textures[txpart].texture; break; } } return self->CallZScriptCallback(); } ETraceStatus DLineTracer::CallZScriptCallback() { IFVIRTUAL(DLineTracer, TraceCallback) { int status; VMReturn results[1] = { &status }; VMValue params[1] = { (DLineTracer*)this }; VMCall(func, params, 1, results, 1); return (ETraceStatus)status; } return TRACE_Stop; }