// SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2023 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- /// \file r_portal.c /// \brief Software renderer portals. #include "r_portal.h" #include "r_plane.h" #include "r_main.h" #include "doomstat.h" #include "p_spec.h" // Skybox viewpoints #include "p_slopes.h" // P_GetSectorFloorZAt and P_GetSectorCeilingZAt #include "p_local.h" #include "z_zone.h" #include "r_things.h" #include "r_sky.h" UINT8 portalrender; /**< When rendering a portal, it establishes the depth of the current BSP traversal. */ // Linked list for portals. portal_t *portal_base, *portal_cap; line_t *portalclipline; sector_t *portalcullsector; INT32 portalclipstart, portalclipend; boolean portalline; // is curline a portal seg? void Portal_InitList (void) { portalrender = 0; portal_base = portal_cap = NULL; } /** Store the clipping window for a portal in its given range. * * The window is copied from the current window at the time * the function is called, so it is useful for converting one-sided * lines into portals. */ void Portal_ClipRange (portal_t* portal) { INT32 start = portal->start; INT32 end = portal->end; INT16 *ceil = portal->ceilingclip; INT16 *floor = portal->floorclip; fixed_t *scale = portal->frontscale; INT32 i; for (i = 0; i < end-start; i++) { *ceil = ceilingclip[start+i]; ceil++; *floor = floorclip[start+i]; floor++; *scale = frontscale[start+i]; scale++; } } /** Apply the clipping window from a portal. */ void Portal_ClipApply (const portal_t* portal) { INT32 i; INT32 start = portal->start; INT32 end = portal->end; INT16 *ceil = portal->ceilingclip; INT16 *floor = portal->floorclip; fixed_t *scale = portal->frontscale; for (i = 0; i < end-start; i++) { ceilingclip[start+i] = *ceil; ceil++; floorclip[start+i] = *floor; floor++; frontscale[start+i] = *scale; scale++; } // HACKS FOLLOW for (i = 0; i < start; i++) { floorclip[i] = -1; ceilingclip[i] = (INT16)viewheight; } for (i = end; i < vid.width; i++) { floorclip[i] = -1; ceilingclip[i] = (INT16)viewheight; } } static portal_t* Portal_Add (const INT16 x1, const INT16 x2) { portal_t *portal = Z_Malloc(sizeof(portal_t), PU_LEVEL, NULL); INT16 *ceilingclipsave = Z_Malloc(sizeof(INT16)*(x2-x1 + 1), PU_LEVEL, NULL); INT16 *floorclipsave = Z_Malloc(sizeof(INT16)*(x2-x1 + 1), PU_LEVEL, NULL); fixed_t *frontscalesave = Z_Malloc(sizeof(fixed_t)*(x2-x1 + 1), PU_LEVEL, NULL); // Linked list. if (!portal_base) { portal_base = portal; portal_cap = portal; } else { portal_cap->next = portal; portal_cap = portal; } portal->next = NULL; // Store clipping values so they can be restored once the portal is rendered. portal->ceilingclip = ceilingclipsave; portal->floorclip = floorclipsave; portal->frontscale = frontscalesave; portal->start = x1; portal->end = x2; // Increase recursion level. portal->pass = portalrender+1; return portal; } void Portal_Remove (portal_t* portal) { portalcullsector = NULL; portal_base = portal->next; Z_Free(portal->ceilingclip); Z_Free(portal->floorclip); Z_Free(portal->frontscale); Z_Free(portal); } static void Portal_GetViewpointForLine(portal_t *portal, line_t *start, line_t *dest) { // Offset the portal view by the linedef centers angle_t dangle = R_PointToAngle2(0,0,dest->dx,dest->dy) - R_PointToAngle2(start->dx,start->dy,0,0); fixed_t disttopoint; angle_t angtopoint; struct { fixed_t x, y; } dest_c, start_c; // looking glass center start_c.x = (start->v1->x + start->v2->x) / 2; start_c.y = (start->v1->y + start->v2->y) / 2; // other side center dest_c.x = (dest->v1->x + dest->v2->x) / 2; dest_c.y = (dest->v1->y + dest->v2->y) / 2; disttopoint = R_PointToDist2(start_c.x, start_c.y, viewx, viewy); angtopoint = R_PointToAngle2(start_c.x, start_c.y, viewx, viewy); angtopoint += dangle; portal->viewx = dest_c.x + FixedMul(FINECOSINE(angtopoint>>ANGLETOFINESHIFT), disttopoint); portal->viewy = dest_c.y + FixedMul(FINESINE(angtopoint>>ANGLETOFINESHIFT), disttopoint); portal->viewz = viewz + dest->frontsector->floorheight - start->frontsector->floorheight; portal->viewangle = viewangle + dangle; } /** Creates a portal out of two lines and a determined screen range. * * line1 determines the entrance, and line2 the exit. * x1 and x2 determine the screen's column bounds. * The view's offset from the entry line center is obtained, * and then rotated&translated to the exit line's center. * When the portal renders, it will create the illusion of * the two lines being seamed together. */ void Portal_Add2Lines (const INT32 line1, const INT32 line2, const INT32 x1, const INT32 x2) { portal_t* portal = Portal_Add(x1, x2); line_t* start = &lines[line1]; line_t* dest = &lines[line2]; Portal_GetViewpointForLine(portal, start, dest); portal->clipline = line2; portal->is_skybox = false; portal->is_horizon = false; portal->horizon_sector = NULL; Portal_ClipRange(portal); portalline = true; // this tells R_StoreWallRange that curline is a portal seg } /** Store the clipping window for a portal using a visplane. * * Since visplanes top/bottom windows work in an identical way, * it can just be copied almost directly. */ static void Portal_ClipVisplane (const visplane_t* plane, portal_t* portal) { INT16 start = portal->start; INT16 end = portal->end; INT32 i; for (i = 0; i < end - start; i++) { // Invalid column. if (plane->top[i + start] == 65535) { portal->ceilingclip[i] = -1; portal->floorclip[i] = -1; continue; } portal->ceilingclip[i] = plane->top[i + start] - 1; portal->floorclip[i] = plane->bottom[i + start] + 1; portal->frontscale[i] = INT32_MAX; } } extern INT32 viewwidth; static boolean TrimVisplaneBounds (const visplane_t* plane, INT16* start, INT16* end) { *start = plane->minx; *end = plane->maxx + 1; // Visplanes have 1-px pads on their sides (extra columns). // Trim them, else it may render out of bounds. if (*end > viewwidth) *end = viewwidth; if (!(*start < *end)) return true; /** Trims a visplane's horizontal gap to match its render area. * * Visplanes' minx/maxx may sometimes exceed the area they're * covering. This merely adjusts the boundaries to the next * valid area. */ while (plane->bottom[*start] == 0 && plane->top[*start] == 65535 && *start < *end) { (*start)++; } while (plane->bottom[*end - 1] == 0 && plane->top[*start] == 65535 && *end > *start) { (*end)--; } return false; } static void Portal_GetViewpointForSkybox(portal_t *portal) { portal->viewx = skyboxmo[0]->x; portal->viewy = skyboxmo[0]->y; portal->viewz = skyboxmo[0]->z; portal->viewangle = viewangle + skyboxmo[0]->angle; mapheader_t *mh = mapheaderinfo[gamemap-1]; // If a relative viewpoint exists, offset the viewpoint. if (skyboxmo[1]) { fixed_t x = 0, y = 0; angle_t ang = skyboxmo[0]->angle>>ANGLETOFINESHIFT; if (mh->skybox_scalex > 0) x = (viewx - skyboxmo[1]->x) / mh->skybox_scalex; else if (mh->skybox_scalex < 0) x = (viewx - skyboxmo[1]->x) * -mh->skybox_scalex; if (mh->skybox_scaley > 0) y = (viewy - skyboxmo[1]->y) / mh->skybox_scaley; else if (mh->skybox_scaley < 0) y = (viewy - skyboxmo[1]->y) * -mh->skybox_scaley; // Apply transform to account for the skybox viewport angle. portal->viewx += FixedMul(x,FINECOSINE(ang)) - FixedMul(y, FINESINE(ang)); portal->viewy += FixedMul(x, FINESINE(ang)) + FixedMul(y,FINECOSINE(ang)); } if (mh->skybox_scalez > 0) portal->viewz += viewz / mh->skybox_scalez; else if (mh->skybox_scalez < 0) portal->viewz += viewz * -mh->skybox_scalez; } /** Creates a skybox portal out of a visplane. * * Applies the necessary offsets and rotation to give * a depth illusion to the skybox. */ static boolean Portal_AddSkybox (const visplane_t* plane) { INT16 start, end; portal_t* portal; if (TrimVisplaneBounds(plane, &start, &end)) return false; portal = Portal_Add(start, end); Portal_ClipVisplane(plane, portal); portal->clipline = -1; portal->is_skybox = true; portal->is_horizon = false; portal->horizon_sector = NULL; Portal_GetViewpointForSkybox(portal); return true; } static void Portal_GetViewpointForSecPortal(portal_t *portal, sectorportal_t *secportal) { fixed_t x, y, z; angle_t angle; switch (secportal->type) { case SECPORTAL_LINE: Portal_GetViewpointForLine(portal, secportal->line.start, secportal->line.dest); return; case SECPORTAL_OBJECT: if (!secportal->mobj || P_MobjWasRemoved(secportal->mobj)) return; x = secportal->mobj->x; y = secportal->mobj->y; z = secportal->mobj->z; angle = secportal->mobj->angle; break; case SECPORTAL_FLOOR: x = secportal->sector->soundorg.x; y = secportal->sector->soundorg.y; z = P_GetSectorFloorZAt(secportal->sector, x, y); angle = 0; break; case SECPORTAL_CEILING: x = secportal->sector->soundorg.x; y = secportal->sector->soundorg.y; z = P_GetSectorCeilingZAt(secportal->sector, x, y); angle = 0; break; case SECPORTAL_PLANE: case SECPORTAL_HORIZON: portal->is_horizon = true; portal->horizon_sector = secportal->sector; x = secportal->sector->soundorg.x; y = secportal->sector->soundorg.y; if (secportal->type == SECPORTAL_PLANE) z = -viewz; else z = 0; angle = 0; break; default: return; } fixed_t refx = secportal->origin.x - viewx; fixed_t refy = secportal->origin.y - viewy; // Rotate the X/Y to match the target angle if (angle != 0) { fixed_t tr_x = refx, tr_y = refy; angle_t ang = angle >> ANGLETOFINESHIFT; refx = FixedMul(tr_x, FINECOSINE(ang)) - FixedMul(tr_y, FINESINE(ang)); refy = FixedMul(tr_x, FINESINE(ang)) + FixedMul(tr_y, FINECOSINE(ang)); } portal->viewx = x - refx; portal->viewy = y - refy; portal->viewz = z + viewz; portal->viewangle = angle + viewangle; } /** Creates a sector portal out of a visplane. */ static boolean Portal_AddSectorPortal (const visplane_t* plane) { INT16 start, end; sectorportal_t *secportal = plane->portalsector; // Shortcut if (secportal->type == SECPORTAL_SKYBOX) { if (cv_skybox.value && skyboxmo[0]) return Portal_AddSkybox(plane); return false; } if (TrimVisplaneBounds(plane, &start, &end)) return false; portal_t* portal = Portal_Add(start, end); Portal_ClipVisplane(plane, portal); portal->clipline = -1; portal->is_horizon = false; portal->is_skybox = false; portal->horizon_sector = NULL; Portal_GetViewpointForSecPortal(portal, secportal); return true; } /** Creates a transferred sector portal. */ void Portal_AddTransferred (UINT32 secportalnum, const INT32 x1, const INT32 x2) { if (secportalnum >= secportalcount) return; sectorportal_t *secportal = &secportals[secportalnum]; if (!P_IsSectorPortalValid(secportal)) return; portal_t* portal = Portal_Add(x1, x2); portal->is_skybox = false; portal->is_horizon = false; portal->horizon_sector = NULL; if (secportal->type == SECPORTAL_SKYBOX) Portal_GetViewpointForSkybox(portal); else Portal_GetViewpointForSecPortal(portal, secportal); if (secportal->type == SECPORTAL_LINE) portal->clipline = secportal->line.dest - lines; else portal->clipline = -1; Portal_ClipRange(portal); portalline = true; } /** Creates portals for the currently existing portal visplanes. * The visplanes are also removed and cleared from the list. */ void Portal_AddPlanePortals (boolean add_skyboxes) { visplane_t *pl; for (INT32 i = 0; i < MAXVISPLANES; i++, pl++) { for (pl = visplanes[i]; pl; pl = pl->next) { if (pl->minx >= pl->maxx) continue; boolean added_portal = false; // Render sector portal if recursiveness limit hasn't been reached if (pl->portalsector && portalrender < cv_maxportals.value) added_portal = Portal_AddSectorPortal(pl); // Render skybox portal if (!added_portal && pl->picnum == skyflatnum && add_skyboxes && skyboxmo[0]) added_portal = Portal_AddSkybox(pl); // don't render this visplane anymore if (added_portal) { pl->minx = 0; pl->maxx = -1; } } } }