diff --git a/src/p_mobj.c b/src/p_mobj.c
index f7ad7616df4d35b26ff1086a474d9975e5333bd9..de2c3a72c7136c484127c5a1c218963846ee70d4 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -12822,28 +12822,6 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 			skyboxviewpnts[tag] = mobj;
 		break;
 	}
-	case MT_PORTALREFPOINT:
-		{
-			size_t i;
-			for (i = 0; i < numsectors; i++)
-			{
-				sector_t *targetsec = &sectors[i];
-				for (int j = 0; j < targetsec->tags.count; j++)
-				{
-					if ((mthing->extrainfo == targetsec->tags.tags[j]) && (GETSECSPECIAL(targetsec->special, 3) == 1))
-					{
-						// origin
-						if (targetsec == mobj->subsector->sector)
-							targetsec->portals[1] = mobj;
-							// target
-							else
-							targetsec->portals[0] = mobj;
-							break;
-					}
-				}
-			}
-		}
-		break;
 	case MT_EGGSTATUE:
 		if (mthing->args[1])
 		{
diff --git a/src/p_setup.c b/src/p_setup.c
index 51c14e1474265dee752e3705cee58f1c50c63049..c590bba4f3808c71a1e991fcc4cc4831f9af4ee1 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -978,6 +978,13 @@ static void P_LoadVertices(UINT8 *data)
 	}
 }
 
+static void InitializeSectorPortal(sectorportal_t *secportal)
+{
+	secportal->target = NULL;
+	secportal->viewpoint.x = secportal->viewpoint.y = secportal->viewpoint.z = 0;
+	secportal->viewpoint.angle = 0;
+}
+
 static void P_InitializeSector(sector_t *ss)
 {
 	memset(&ss->soundorg, 0, sizeof(ss->soundorg));
@@ -989,10 +996,11 @@ static void P_InitializeSector(sector_t *ss)
 	ss->floordata = NULL;
 	ss->ceilingdata = NULL;
 	ss->lightingdata = NULL;
-	ss->portals[0] = NULL;
-	ss->portals[1] = NULL;
 	ss->fadecolormapdata = NULL;
 
+	InitializeSectorPortal(&ss->portal_plane_floor);
+	InitializeSectorPortal(&ss->portal_plane_ceiling);
+
 	ss->heightsec = -1;
 	ss->camsec = -1;
 
diff --git a/src/p_spec.c b/src/p_spec.c
index 28ecc60f4dedb5f67f9aa33e092232eb0b3782f6..4c34aaf05af422a9dcd28b14ee244ac1356a9e55 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -6186,6 +6186,38 @@ fixed_t P_GetSectorGravityFactor(sector_t *sec)
 		return sec->gravity;
 }
 
+static void SetSectorPortal(sectorportal_t *secportal, sector_t *target_sector, INT32 viewpoint_tag)
+{
+	secportal->target = target_sector;
+	secportal->viewpoint.x = target_sector->soundorg.x;
+	secportal->viewpoint.y = target_sector->soundorg.y;
+	secportal->viewpoint.z = target_sector->ceilingheight;
+	secportal->viewpoint.angle = 0;
+
+	if (viewpoint_tag <= 0)
+		return;
+
+	for (thinker_t *th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+	{
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
+
+		mobj_t *mo = (mobj_t *)th;
+
+		if (mo->type != MT_PORTALREFPOINT || mo->spawnpoint == NULL)
+			continue;
+
+		if (!Tag_Find(&mo->spawnpoint->tags, viewpoint_tag))
+			continue;
+
+		secportal->viewpoint.x = mo->x;
+		secportal->viewpoint.y = mo->y;
+		secportal->viewpoint.z = mo->z;
+		secportal->viewpoint.angle = mo->angle;
+		return;
+	}
+}
+
 /** After the map has loaded, scans for specials that spawn 3Dfloors and
   * thinkers.
   *
@@ -6351,6 +6383,33 @@ void P_SpawnSpecials(boolean fromnetsave)
 					P_AddCameraScanner(&sectors[sec], &sectors[s], R_PointToAngle2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y));
 				break;
 
+			case 6: // Sector portal
+			{
+				INT32 s1, s2;
+				TAG_ITER_SECTORS(lines[i].args[0], s1) // Target sector tag
+				{
+					TAG_ITER_SECTORS(lines[i].args[1], s2) // Sector tag to make a portal to
+					{
+						sector_t *target_sector = &sectors[s2];
+						boolean floor, ceiling;
+
+						if (lines[i].args[2] == TMP_BOTH)
+							floor = ceiling = true;
+						else
+						{
+							floor = lines[i].args[2] == TMP_FLOOR;
+							ceiling = lines[i].args[2] == TMP_CEILING;
+						}
+
+						if (floor)
+							SetSectorPortal(&sectors[s1].portal_plane_floor, target_sector, lines[i].args[3]);
+						if (ceiling)
+							SetSectorPortal(&sectors[s1].portal_plane_ceiling, target_sector, lines[i].args[3]);
+					}
+				}
+				break;
+			}
+
 			case 7: // Flat alignment - redone by toast
 			{
 				// Set calculated offsets such that line's v1 is the apparent origin
diff --git a/src/r_bsp.c b/src/r_bsp.c
index 70a9dbd943f835e8cc207534dc01c351e5562b6f..5c9aa3ff98a0da4e92f621150c3b95f36e1bab79 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -909,7 +909,7 @@ static void R_Subsector(size_t num)
 		|| (frontsector->heightsec != -1 && sectors[frontsector->heightsec].ceilingpic == skyflatnum))
 	{
 		floorplane = R_FindPlane(frontsector, frontsector->floorheight, frontsector->floorpic, floorlightlevel,
-			frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, frontsector->f_slope);
+			frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, frontsector->f_slope, frontsector->portal_plane_floor.target != NULL ? &frontsector->portal_plane_floor : NULL);
 	}
 	else
 		floorplane = NULL;
@@ -920,7 +920,7 @@ static void R_Subsector(size_t num)
 	{
 		ceilingplane = R_FindPlane(frontsector, frontsector->ceilingheight, frontsector->ceilingpic,
 			ceilinglightlevel, frontsector->ceilingxoffset, frontsector->ceilingyoffset, frontsector->ceilingangle,
-			ceilingcolormap, NULL, NULL, frontsector->c_slope);
+			ceilingcolormap, NULL, NULL, frontsector->c_slope, frontsector->portal_plane_ceiling.target != NULL ? &frontsector->portal_plane_ceiling : NULL);
 	}
 	else
 		ceilingplane = NULL;
@@ -963,7 +963,7 @@ static void R_Subsector(size_t num)
 
 				ffloor[numffloors].plane = R_FindPlane(rover->master->frontsector, *rover->bottomheight, *rover->bottompic,
 					*frontsector->lightlist[light].lightlevel, *rover->bottomxoffs,
-					*rover->bottomyoffs, *rover->bottomangle, *frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->b_slope);
+					*rover->bottomyoffs, *rover->bottomangle, *frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->b_slope, NULL);
 
 				ffloor[numffloors].slope = *rover->b_slope;
 
@@ -992,7 +992,7 @@ static void R_Subsector(size_t num)
 
 				ffloor[numffloors].plane = R_FindPlane(rover->master->frontsector, *rover->topheight, *rover->toppic,
 					*frontsector->lightlist[light].lightlevel, *rover->topxoffs, *rover->topyoffs, *rover->topangle,
-					*frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->t_slope);
+					*frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->t_slope, NULL);
 
 				ffloor[numffloors].slope = *rover->t_slope;
 
@@ -1036,12 +1036,11 @@ static void R_Subsector(size_t num)
 					(light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->floorxoffset, polysec->flooryoffset,
 					polysec->floorangle-po->angle,
 					(light == -1 ? frontsector->extra_colormap : *frontsector->lightlist[light].extra_colormap), NULL, po,
-					NULL); // will ffloors be slopable eventually?
+					NULL, NULL);
 
 				ffloor[numffloors].height = polysec->floorheight;
 				ffloor[numffloors].polyobj = po;
 				ffloor[numffloors].slope = NULL;
-				//ffloor[numffloors].ffloor = rover;
 				po->visplane = ffloor[numffloors].plane;
 				numffloors++;
 			}
@@ -1059,12 +1058,11 @@ static void R_Subsector(size_t num)
 				ffloor[numffloors].plane = R_FindPlane(polysec, polysec->ceilingheight, polysec->ceilingpic,
 					(light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->ceilingxoffset, polysec->ceilingyoffset, polysec->ceilingangle-po->angle,
 					(light == -1 ? frontsector->extra_colormap : *frontsector->lightlist[light].extra_colormap), NULL, po,
-					NULL); // will ffloors be slopable eventually?
+					NULL, NULL);
 
 				ffloor[numffloors].polyobj = po;
 				ffloor[numffloors].height = polysec->ceilingheight;
 				ffloor[numffloors].slope = NULL;
-				//ffloor[numffloors].ffloor = rover;
 				po->visplane = ffloor[numffloors].plane;
 				numffloors++;
 			}
diff --git a/src/r_defs.h b/src/r_defs.h
index 61f9eaa4ecb2a1341a52d53c8c476126bf578d7f..d9fe6193d2589ce12db64b0af39975344d2eeb8c 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -208,6 +208,15 @@ typedef enum
 	BT_STRONG,
 } busttype_e;
 
+typedef struct sectorportal_s
+{
+	struct sector_s *target;
+	struct {
+		fixed_t x, y, z;
+		angle_t angle;
+	} viewpoint;
+} sectorportal_t;
+
 typedef struct ffloor_s
 {
 	fixed_t *topheight;
@@ -495,8 +504,9 @@ typedef struct sector_s
 	// colormap structure
 	extracolormap_t *spawn_extra_colormap;
 
-	// floor portals
-	mobj_t *portals[2];
+	// portals
+	sectorportal_t portal_plane_floor;
+	sectorportal_t portal_plane_ceiling;
 } sector_t;
 
 //
diff --git a/src/r_plane.c b/src/r_plane.c
index a24ea9d28a56c436f56124d4a175648915fe2f70..5cd791023824bb640e07660c0917db409fa54570 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -392,7 +392,7 @@ static visplane_t *new_visplane(unsigned hash)
 //
 visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 lightlevel,
 	fixed_t xoff, fixed_t yoff, angle_t plangle, extracolormap_t *planecolormap,
-	ffloor_t *pfloor, polyobj_t *polyobj, pslope_t *slope)
+	ffloor_t *pfloor, polyobj_t *polyobj, pslope_t *slope, sectorportal_t *portalsector)
 {
 	visplane_t *check;
 	unsigned hash;
@@ -451,7 +451,8 @@ visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 li
 				&& check->viewx == viewx && check->viewy == viewy && check->viewz == viewz
 				&& check->viewangle == viewangle
 				&& check->plangle == plangle
-				&& check->slope == slope)
+				&& check->slope == slope
+				&& check->portalsector == portalsector)
 			{
 				return check;
 			}
@@ -479,6 +480,7 @@ visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 li
 	check->viewangle = viewangle;
 	check->plangle = plangle;
 	check->sector = sector;
+	check->portalsector = portalsector;
 	check->polyobj = polyobj;
 	check->slope = slope;
 
@@ -558,6 +560,7 @@ visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop)
 		new_pl->sector = pl->sector;
 		new_pl->polyobj = pl->polyobj;
 		new_pl->slope = pl->slope;
+		new_pl->portalsector = pl->portalsector;
 		pl = new_pl;
 		pl->minx = start;
 		pl->maxx = stop;
diff --git a/src/r_plane.h b/src/r_plane.h
index a3ae84f65cba11b69abdb1eca95a728de4f874df..e332677981faf637a9f46e64cbcd935775fc14ca 100644
--- a/src/r_plane.h
+++ b/src/r_plane.h
@@ -54,6 +54,7 @@ typedef struct visplane_s
 	struct ffloor_s *ffloor;
 	polyobj_t *polyobj;
 	pslope_t *slope;
+	sectorportal_t *portalsector;
 } visplane_t;
 
 extern visplane_t *visplanes[MAXVISPLANES];
@@ -77,7 +78,7 @@ void R_ClearFFloorClips (void);
 
 void R_DrawPlanes(void);
 visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 lightlevel, fixed_t xoff, fixed_t yoff, angle_t plangle,
-	extracolormap_t *planecolormap, ffloor_t *ffloor, polyobj_t *polyobj, pslope_t *slope);
+	extracolormap_t *planecolormap, ffloor_t *ffloor, polyobj_t *polyobj, pslope_t *slope, sectorportal_t *portalsector);
 visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop);
 void R_ExpandPlane(visplane_t *pl, INT32 start, INT32 stop);
 void R_PlaneBounds(visplane_t *plane);
diff --git a/src/r_portal.c b/src/r_portal.c
index 1a2b905f91cedf68007a615135e7ae81b33832fc..b4328a56e9c34014871fe9c08ca59e268d395d17 100644
--- a/src/r_portal.c
+++ b/src/r_portal.c
@@ -309,41 +309,38 @@ void Portal_AddSkybox (const visplane_t* plane)
 	portal->clipline = -1;
 }
 
-/** Creates a floor portal out of a visplane.
+/** Creates a sector portal out of a visplane.
  *
  * Mostly the same as Portal_AddSkybox.
  */
-void Portal_AddFloorPortal (const visplane_t* plane)
+void Portal_AddSectorPortal (const visplane_t* plane)
 {
 	INT16 start, end;
-	portal_t* portal;
-	sector_t *portalsector = plane->sector;
-	mobj_t *portalmobj = portalsector->portals[0];
-	mobj_t *refmobj = portalsector->portals[1];
-	fixed_t refx, refy;
+	sector_t *source = plane->sector;
+	sectorportal_t *target = plane->portalsector;
 
 	if (TrimVisplaneBounds(plane, &start, &end))
 		return;
 
-	portal = Portal_Add(start, end);
+	portal_t* portal = Portal_Add(start, end);
 
 	Portal_ClipVisplane(plane, portal);
 
-	if ((refmobj != NULL) && !(P_MobjWasRemoved(refmobj)))
-	{
-		refx = (refmobj->x - viewx);
-		refy = (refmobj->y - viewy);
-	}
-	else
+	fixed_t refx = source->soundorg.x - viewx;
+	fixed_t refy = source->soundorg.y - viewy;
+
+	if (target->viewpoint.angle)
 	{
-		refx = (portalsector->soundorg.x - viewx);
-		refy = (portalsector->soundorg.y - viewy);
+		fixed_t x = refx, y = refy;
+		angle_t ang = target->viewpoint.angle >> ANGLETOFINESHIFT;
+		refx = FixedMul(x, FINECOSINE(ang)) - FixedMul(y, FINESINE(ang));
+		refy = FixedMul(x, FINESINE(ang)) + FixedMul(y, FINECOSINE(ang));
 	}
 
-	portal->viewx = portalmobj->x - refx;
-	portal->viewy = portalmobj->y - refy;
-	portal->viewz = portalmobj->z + viewz;
-	portal->viewangle = viewangle + portalmobj->angle;
+	portal->viewx = target->viewpoint.x - refx;
+	portal->viewy = target->viewpoint.y - refy;
+	portal->viewz = target->viewpoint.z + viewz;
+	portal->viewangle = target->viewpoint.angle + viewangle;
 
 	portal->clipline = -1;
 }
@@ -354,16 +351,12 @@ void Portal_AddFloorPortal (const visplane_t* plane)
 void Portal_AddSkyboxPortals (void)
 {
 	visplane_t *pl;
-	INT32 i;
-	UINT16 count = 0;
 
-	for (i = 0; i < MAXVISPLANES; i++, pl++)
+	for (INT32 i = 0; i < MAXVISPLANES; i++, pl++)
 	{
 		for (pl = visplanes[i]; pl; pl = pl->next)
 		{
-			// true if added a portal for this visplane
-			boolean addedportal = false;
-			boolean floorportalpresent = (pl->sector->portals[0] != NULL && !P_MobjWasRemoved(pl->sector->portals[0]));
+			boolean added_portal = false;
 
 			// skybox portal
 			if (pl->picnum == skyflatnum)
@@ -371,29 +364,24 @@ void Portal_AddSkyboxPortals (void)
 				if (cv_skybox.value && skyboxmo[0])
 				{
 					Portal_AddSkybox(pl);
-					addedportal = true;
+					added_portal = true;
 				}
 			}
+
 			// floor portal
-			else if (floorportalpresent)
+			if (pl->portalsector && pl->portalsector->target && floorportalrender < cv_maxportals.value)
 			{
-				if (floorportalrender < cv_maxportals.value)
-				{
-					Portal_AddFloorPortal(pl);
-					floorportalrender++;
-					addedportal = true;
-				}
+				Portal_AddSectorPortal(pl);
+				floorportalrender++;
+				added_portal = true;
 			}
 
 			// don't render this visplane anymore
-			if (addedportal)
+			if (added_portal)
 			{
 				pl->minx = 0;
 				pl->maxx = -1;
-				count++;
 			}
 		}
 	}
-
-	CONS_Debug(DBG_RENDER, "Skybox portals: %d\n", count);
 }
diff --git a/src/r_portal.h b/src/r_portal.h
index 14b49f289af3de2a674b46d1da1aa1b2f3ecb682..6e2a6dedbe235c2861c008e18782dde33a0d797a 100644
--- a/src/r_portal.h
+++ b/src/r_portal.h
@@ -54,7 +54,7 @@ void Portal_InitList		(void);
 void Portal_Remove			(portal_t* portal);
 void Portal_Add2Lines		(const INT32 line1, const INT32 line2, const INT32 x1, const INT32 x2);
 void Portal_AddSkybox		(const visplane_t* plane);
-void Portal_AddFloorPortal	(const visplane_t* plane);
+void Portal_AddSectorPortal	(const visplane_t* plane);
 
 void Portal_ClipRange (portal_t* portal);
 void Portal_ClipApply (const portal_t* portal);