#include #include "constants.h" #include "game/debug.h" #include "game/dlights.h" #include "game/game_006900.h" #include "game/portal.h" #include "game/room.h" #include "game/chr.h" #include "game/prop.h" #include "game/ceil.h" #include "game/bondgun.h" #include "game/tex.h" #include "game/camera.h" #include "game/player.h" #include "game/sky.h" #include "game/stars.h" #include "game/dyntex.h" #include "game/game_13c510.h" #include "game/game_1531a0.h" #include "game/gfxmemory.h" #include "game/gfxreplace.h" #include "game/bg.h" #include "game/portalconv.h" #include "game/stagetable.h" #include "game/env.h" #include "game/room.h" #include "game/file.h" #include "game/lv.h" #include "game/texdecompress.h" #include "game/wallhit.h" #include "bss.h" #include "lib/crash.h" #include "lib/dma.h" #include "lib/lib_17ce0.h" #include "lib/lib_2f490.h" #include "lib/main.h" #include "lib/mema.h" #include "lib/memp.h" #include "lib/mtx.h" #include "lib/rng.h" #include "lib/rzip.h" #include "lib/vi.h" #include "data.h" #include "gbiex.h" #include "types.h" #ifndef PLATFORM_N64 #include "preprocess.h" #include "system.h" #include "video.h" #endif #define BGCMD_END 0x00 #define BGCMD_PUSH 0x01 #define BGCMD_POP 0x02 #define BGCMD_AND 0x03 #define BGCMD_OR 0x04 #define BGCMD_NOT 0x05 #define BGCMD_XOR 0x06 #define BGCMD_PUSH_CAMINROOMRANGE 0x14 #define BGCMD_SETRESULT_TRUE 0x1e #define BGCMD_SETRESULT_IFPORTALINFOV 0x1f #define BGCMD_IFRESULT_SHOWROOM 0x20 #define BGCMD_SETRESULT_FALSE 0x21 #define BGCMD_SETRESULT_TRUEIFTHROUGHPORTAL 0x22 #define BGCMD_SETRESULT_FALSEIFNOTTHROUGHPORTAL 0x23 #define BGCMD_DISABLEROOM 0x24 #define BGCMD_DISABLEROOMRANGE 0x25 #define BGCMD_LOADROOM 0x26 #define BGCMD_LOADROOMRANGE 0x27 #define BGCMD_SETROOMTESTSDISABLED 0x28 #define BGCMD_PUSH_PORTALISOPEN 0x29 #define BGCMD_2A 0x2a #define BGCMD_BRANCH 0x50 #define BGCMD_THROW 0x51 #define BGCMD_CATCH 0x52 #define BGCMD_IF 0x5a #define BGCMD_ELSE 0x5b #define BGCMD_ENDIF 0x5c #define BGCMD_PORTALARG 0x64 #define BGCMD_ROOMARG 0x65 #define BGRESULT_TRUE 0 #define BGRESULT_FALSE 1 #define VTXBATCHTYPE_OPA 0x01 #define VTXBATCHTYPE_XLU 0x02 struct drawslot g_BgDrawSlots[61]; u8 *g_BgPrimaryData; u32 var800a4920; u32 g_BgSection3; struct room *g_Rooms; u8 *g_MpRoomVisibility; RoomNum g_BgForceOnscreenRooms[350]; s32 g_BgNumForceOnscreenRooms; u16 g_BgUnloadDelay240; u16 g_BgUnloadDelay240_2; u32 var800a4bf4; RoomNum g_GlareRooms[100]; uintptr_t *g_BgPrimaryData2; struct bgroom *g_BgRooms; struct bgportal *g_BgPortals; struct portalmetric *g_PortalMetrics; u8 *g_BgPortalAlphas; struct bgcmd *g_BgCommands; u8 *g_BgLightsFileData; f32 *g_BgStanThings; s16 *g_RoomPortals; s16 g_BgMinDrawOrder; s16 g_BgMaxDrawOrder; struct drawslotpointer *g_BgDrawSlotsByRoom; struct portalcamcacheitem *g_PortalCameraCache; struct bgsnake g_BgSnake; s32 g_StageIndex = 1; uintptr_t var8007fc04 = 0; u8 *var8007fc08 = NULL; #if VERSION < VERSION_NTSC_1_0 u32 var80082474nb = 0; #endif s16 var8007fc0c = 0; s16 var8007fc10 = 0; s32 g_NumRoomsWithGlares = 0; u32 var8007fc18 = 0x01000100; u32 var8007fc1c = 0; s32 g_CamRoom = 1; struct drawslot *g_BgSpecialDrawSlot = &g_BgDrawSlots[60]; s32 g_BgLoadCandidateTimer240 = 0; s32 g_BgNumDrawSlots = 0; s32 g_BgNumAttemptedDrawSlots = 0; s32 g_BgMostAttemptedDrawSlots = 0; s32 g_BgNumRoomLoadCandidates = 0; u16 g_BgFrameCount = 0xfffe; s32 g_BgNumPortalCameraCacheItems = 0; void bgUnpausePropsInRoom(u32 roomnum, bool tintedglassonly) { struct prop *prop; struct defaultobj *obj; s16 *propnumptr; RoomNum rooms[2]; s16 propnums[256]; rooms[0] = roomnum; rooms[1] = -1; roomGetProps(rooms, propnums, 256); propnumptr = propnums; while (*propnumptr >= 0) { prop = &g_Vars.props[*propnumptr]; if (!prop->active) { if (tintedglassonly) { /** * @bug: A missing prop->type check means this is inadvertently * casting other pointer types to obj pointers. By chance this * happens to be harmless. */ obj = prop->obj; if (obj->type == OBJTYPE_TINTEDGLASS) { propUnpause(prop); } } else { propUnpause(prop); } } propnumptr++; } } void bgSetRoomOnscreen(s32 roomnum, s32 draworder, struct screenbox *box) { s32 index; #if VERSION < VERSION_NTSC_1_0 g_Rooms[roomnum].flags |= ROOMFLAG_ONSCREEN; #endif if ((g_Rooms[roomnum].flags & ROOMFLAG_DISABLEDBYSCRIPT) == 0) { #if VERSION >= VERSION_NTSC_1_0 g_Rooms[roomnum].flags |= ROOMFLAG_ONSCREEN; #endif if (g_Rooms[roomnum].flags & ROOMFLAG_BBOXHACK) { box->xmin = g_BgDrawSlots[60].box.xmin; box->ymin = g_BgDrawSlots[60].box.ymin; box->xmax = g_BgDrawSlots[60].box.xmax; box->ymax = g_BgDrawSlots[60].box.ymax; } if (g_BgFrameCount == g_BgDrawSlotsByRoom[roomnum].updatedframe) { index = g_BgDrawSlotsByRoom[roomnum].slotnum; if (draworder > g_BgDrawSlots[index].draworder) { g_BgDrawSlots[index].draworder = draworder; if (g_BgDrawSlots[index].draworder > g_BgMaxDrawOrder) { g_BgMaxDrawOrder = g_BgDrawSlots[index].draworder; } if (g_BgDrawSlots[index].draworder < g_BgMinDrawOrder) { g_BgMinDrawOrder = g_BgDrawSlots[index].draworder; } } bgExpandBox(&g_BgDrawSlots[index].box, box); } else { index = g_BgNumDrawSlots; if (index > 59) { index = 59; } g_BgDrawSlots[index].roomnum = roomnum; g_BgDrawSlots[index].draworder = draworder; g_BgDrawSlots[index].box.xmin = box->xmin; g_BgDrawSlots[index].box.ymin = box->ymin; g_BgDrawSlots[index].box.xmax = box->xmax; g_BgDrawSlots[index].box.ymax = box->ymax; if (g_BgDrawSlots[index].draworder > g_BgMaxDrawOrder) { g_BgMaxDrawOrder = g_BgDrawSlots[index].draworder; } if (g_BgDrawSlots[index].draworder < g_BgMinDrawOrder) { g_BgMinDrawOrder = g_BgDrawSlots[index].draworder; } g_BgDrawSlotsByRoom[roomnum].updatedframe = g_BgFrameCount; g_BgDrawSlotsByRoom[roomnum].slotnum = index; g_BgNumAttemptedDrawSlots++; if (g_BgNumAttemptedDrawSlots < 60) { g_BgNumDrawSlots = g_BgNumAttemptedDrawSlots; } bgUnpausePropsInRoom(roomnum, false); if (g_Rooms[roomnum].loaded240 == 0 && var8007fc10 > 0) { var8007fc10--; bgLoadRoom(roomnum); } else if (g_Rooms[roomnum].loaded240 == 0) { var8007fc10--; } } } } void bgGetRoomBrightnessRange(s32 roomnum, u8 *min, u8 *max) { *min = g_BgRooms[roomnum].br_light_min; *max = g_BgRooms[roomnum].br_light_max; } struct drawslot *bgGetRoomDrawSlot(s32 roomnum) { s32 index = 60; if (g_BgFrameCount == g_BgDrawSlotsByRoom[roomnum].updatedframe) { index = g_BgDrawSlotsByRoom[roomnum].slotnum; } return &g_BgDrawSlots[index]; } Gfx *bgRenderXrayData(Gfx *gdl, struct xraydata *xraydata) { Vtx *vertices; Col *colours; s32 numgroups; s32 i; s32 count; if (xraydata->numtris > 0) { vertices = gfxAllocateVertices(xraydata->numvertices); colours = gfxAllocateColours(xraydata->numvertices); for (i = 0; i < xraydata->numvertices; i++) { vertices[i].x = xraydata->vertices[i][0]; vertices[i].y = xraydata->vertices[i][1]; vertices[i].z = xraydata->vertices[i][2]; vertices[i].colour = i << 2; colours[i].word = PD_BE32(xraydata->colours[i]); } count = xraydata->numvertices; gSPColor(gdl++, colours, count); count = xraydata->numvertices; gSPVertex(gdl++, vertices, count, 0); numgroups = (xraydata->numtris - 1) / 4 + 1; // @bug: The original code overflows the tris array and unintentionally writes zero // into the xraydata->numtris property. IDO reloads the xraydata->numtris value // on each loop iteration so it reads the 0 value and ends the loop. #ifdef AVOID_UB for (i = xraydata->numtris; i < numgroups * 4; i++) { xraydata->tris[i][0] = xraydata->tris[i][1] = xraydata->tris[i][2] = 0; } #else for (i = xraydata->numtris; i < xraydata->numtris * 4; i++) { xraydata->tris[i][0] = xraydata->tris[i][1] = xraydata->tris[i][2] = 0; } #endif for (i = 0; i < numgroups; i++) { gSPTri4(gdl++, xraydata->tris[i * 4 + 0][0], xraydata->tris[i * 4 + 0][1], xraydata->tris[i * 4 + 0][2], xraydata->tris[i * 4 + 1][0], xraydata->tris[i * 4 + 1][1], xraydata->tris[i * 4 + 1][2], xraydata->tris[i * 4 + 2][0], xraydata->tris[i * 4 + 2][1], xraydata->tris[i * 4 + 2][2], xraydata->tris[i * 4 + 3][0], xraydata->tris[i * 4 + 3][1], xraydata->tris[i * 4 + 3][2]); } } xraydata->numtris = 0; xraydata->numvertices = 0; return gdl; } Gfx *bgAddXrayTri(Gfx *gdl, struct xraydata *xraydata, s16 vertices1[3], s16 vertices2[3], s16 vertices3[3], u32 colour1, u32 colour2, u32 colour3) { s16 sp30[3] = {-1, -1, -1}; s32 count = 0; s16 i; if (xraydata->numtris >= 64) { gdl = bgRenderXrayData(gdl, xraydata); } for (i = 0; i < xraydata->numvertices && sp30[0] == -1; i++) { if (vertices1[0] == xraydata->vertices[i][0] && vertices1[1] == xraydata->vertices[i][1] && vertices1[2] == xraydata->vertices[i][2]) { sp30[0] = i; count++; } } for (i = 0; i < xraydata->numvertices && sp30[1] == -1; i++) { if (vertices2[0] == xraydata->vertices[i][0] && vertices2[1] == xraydata->vertices[i][1] && vertices2[2] == xraydata->vertices[i][2]) { sp30[1] = i; count++; } } for (i = 0; i < xraydata->numvertices && sp30[2] == -1; i++) { if (vertices3[0] == xraydata->vertices[i][0] && vertices3[1] == xraydata->vertices[i][1] && vertices3[2] == xraydata->vertices[i][2]) { sp30[2] = i; count++; } } if (count < 3) { if (xraydata->numvertices - count + 3 > 16) { gdl = bgRenderXrayData(gdl, xraydata); xraydata->vertices[0][0] = vertices1[0]; xraydata->vertices[0][1] = vertices1[1]; xraydata->vertices[0][2] = vertices1[2]; xraydata->colours[0] = colour1; xraydata->vertices[1][0] = vertices2[0]; xraydata->vertices[1][1] = vertices2[1]; xraydata->vertices[1][2] = vertices2[2]; xraydata->colours[1] = colour2; xraydata->vertices[2][0] = vertices3[0]; xraydata->vertices[2][1] = vertices3[1]; xraydata->vertices[2][2] = vertices3[2]; xraydata->colours[2] = colour3; xraydata->numvertices = 3; sp30[0] = 0; sp30[1] = 1; sp30[2] = 2; } else { if (sp30[0] == -1) { xraydata->vertices[xraydata->numvertices][0] = vertices1[0]; xraydata->vertices[xraydata->numvertices][1] = vertices1[1]; xraydata->vertices[xraydata->numvertices][2] = vertices1[2]; xraydata->colours[xraydata->numvertices] = colour1; sp30[0] = xraydata->numvertices; xraydata->numvertices++; } if (sp30[1] == -1) { xraydata->vertices[xraydata->numvertices][0] = vertices2[0]; xraydata->vertices[xraydata->numvertices][1] = vertices2[1]; xraydata->vertices[xraydata->numvertices][2] = vertices2[2]; xraydata->colours[xraydata->numvertices] = colour2; sp30[1] = xraydata->numvertices; xraydata->numvertices++; } if (sp30[2] == -1) { xraydata->vertices[xraydata->numvertices][0] = vertices3[0]; xraydata->vertices[xraydata->numvertices][1] = vertices3[1]; xraydata->vertices[xraydata->numvertices][2] = vertices3[2]; xraydata->colours[xraydata->numvertices] = colour3; sp30[2] = xraydata->numvertices; xraydata->numvertices++; } } } xraydata->tris[xraydata->numtris][0] = sp30[0]; xraydata->tris[xraydata->numtris][1] = sp30[1]; xraydata->tris[xraydata->numtris][2] = sp30[2]; xraydata->numtris++; return gdl; } void bgChooseXrayVtxColour(bool *inrange, s16 vertex[3], u32 *colour, struct xraydata *xraydata) { f32 sp2c[3]; f32 f12; f32 alphafrac; f32 anglefrac; struct player *player = g_Vars.currentplayer; f32 colfrac; *inrange = false; sp2c[0] = (f32) vertex[0] - (f32) xraydata->unk000; sp2c[0] = sp2c[0] * sp2c[0]; if (sp2c[0] < xraydata->unk010) { sp2c[2] = (f32) vertex[2] - (f32) xraydata->unk008; sp2c[2] = sp2c[2] * sp2c[2]; if (sp2c[2] < xraydata->unk010) { sp2c[1] = (f32) vertex[1] - (f32) xraydata->unk004; sp2c[1] = sp2c[1] * sp2c[1]; if (sp2c[1] < xraydata->unk010) { f32 dist = sqrtf(sp2c[0] + sp2c[1] + sp2c[2]); if (dist < xraydata->unk00c) { *inrange = true; f12 = dist / xraydata->unk00c; if (xraydata->unk014 < f12) { alphafrac = 1.0f - (f12 - xraydata->unk014) / (1.0f - xraydata->unk014); } else { alphafrac = 1.0f; } if (f12 < xraydata->unk01c) { anglefrac = xraydata->unk01c; anglefrac = f12 / anglefrac; if (1); colfrac = sinf((1.0f - anglefrac) * 1.5707964f); *colour = (u32)(colfrac * 255.0f) << player->ecol_1 | (u32)((1.0f - colfrac) * 255.0f) << player->ecol_2 | (u32)(alphafrac * 128.0f); } else { anglefrac = (f12 - xraydata->unk01c) / (1.0f - xraydata->unk01c); anglefrac = 0.65f * anglefrac + 0.35f; if (1); colfrac = sinf(anglefrac * 1.5707964f); *colour = (u32)(colfrac * 255.0f) << player->ecol_3 | 0xff << player->ecol_2 | (u32)(alphafrac * 128.0f); } } } } } if (*inrange == false) { *colour = 0x0000ff00; } } Gfx *bgProcessXrayTri(Gfx *gdl, struct xraydata *xraydata, s16 arg2[3], s16 arg3[3], s16 arg4[3], s32 arg5, s32 arg6, s32 arg7, s32 arg8, s32 arg9, s32 arg10) { s32 spa4[3]; s16 sp9c[3] = {0, 0, 0}; s32 sum; s16 sp84[3][3]; bool inrange[3]; u32 colours[3]; s32 sp68 = -1; s32 sp64 = 0; if (xraydata->unk020 > 0) { spa4[0] = arg3[0] - arg2[0]; spa4[1] = arg3[1] - arg2[1]; spa4[2] = arg3[2] - arg2[2]; sum = spa4[0] * spa4[0] + spa4[1] * spa4[1] + spa4[2] * spa4[2]; if (sum > xraydata->unk024) { sp84[0][0] = (arg3[0] + arg2[0]) / 2; sp84[0][1] = (arg3[1] + arg2[1]) / 2; sp84[0][2] = (arg3[2] + arg2[2]) / 2; sp9c[0] = 1; sp64++; sp68 = 0; bgChooseXrayVtxColour(&inrange[0], sp84[0], &colours[0], xraydata); } spa4[0] = arg4[0] - arg3[0]; spa4[1] = arg4[1] - arg3[1]; spa4[2] = arg4[2] - arg3[2]; sum = spa4[0] * spa4[0] + spa4[1] * spa4[1] + spa4[2] * spa4[2]; if (sum > xraydata->unk024) { sp84[1][0] = (arg4[0] + arg3[0]) / 2; sp84[1][1] = (arg4[1] + arg3[1]) / 2; sp84[1][2] = (arg4[2] + arg3[2]) / 2; sp9c[1] = 1; sp64++; sp68 = 1; bgChooseXrayVtxColour(&inrange[1], sp84[1], &colours[1], xraydata); } spa4[0] = arg2[0] - arg4[0]; spa4[1] = arg2[1] - arg4[1]; spa4[2] = arg2[2] - arg4[2]; sum = spa4[0] * spa4[0] + spa4[1] * spa4[1] + spa4[2] * spa4[2]; if (sum > xraydata->unk024) { sp84[2][0] = (arg2[0] + arg4[0]) / 2; sp84[2][1] = (arg2[1] + arg4[1]) / 2; sp84[2][2] = (arg2[2] + arg4[2]) / 2; sp9c[2] = 1; sp64++; sp68 = 2; bgChooseXrayVtxColour(&inrange[2], sp84[2], &colours[2], xraydata); } } if (sp64 == 0) { if (arg8 || arg9 || arg10) { return bgAddXrayTri(gdl, xraydata, arg2, arg3, arg4, arg5, arg6, arg7); } } else { bool render; if (arg8 || arg9 || arg10) { render = true; } else { u32 mask1 = 0; u32 mask2 = 0; render = true; mask1 = (arg2[0] < xraydata->unk000) ? 1 : 0; if (arg2[1] < xraydata->unk004) { mask1 |= 2; } if (arg2[2] < xraydata->unk008) { mask1 |= 4; } mask2 = (arg3[0] < xraydata->unk000) ? 1 : 0; if (arg3[1] < xraydata->unk004) { mask2 |= 2; } if (arg3[2] < xraydata->unk008) { mask2 |= 4; } if (mask1 == mask2) { mask2 = (arg4[0] < xraydata->unk000) ? 1 : 0; if (arg4[1] < xraydata->unk004) { mask2 |= 2; } if (arg4[2] < xraydata->unk008) { mask2 |= 4; } if (mask1 == mask2) { render = false; } } } if (render) { if (sp64 == 1) { if (sp68 == 0) { gdl = bgProcessXrayTri(gdl, xraydata, arg2, sp84[0], arg4, arg5, colours[0], arg7, arg8, inrange[0], arg10); gdl = bgProcessXrayTri(gdl, xraydata, arg4, sp84[0], arg3, arg7, colours[0], arg6, arg10, inrange[0], arg9); } else if (sp68 == 1) { gdl = bgProcessXrayTri(gdl, xraydata, arg3, sp84[1], arg2, arg6, colours[1], arg5, arg9, inrange[1], arg8); gdl = bgProcessXrayTri(gdl, xraydata, arg2, sp84[1], arg4, arg5, colours[1], arg7, arg8, inrange[1], arg10); } else if (sp68 == 2) { gdl = bgProcessXrayTri(gdl, xraydata, arg4, sp84[2], arg3, arg7, colours[2], arg6, arg10, inrange[2], arg9); gdl = bgProcessXrayTri(gdl, xraydata, arg3, sp84[2], arg2, arg6, colours[2], arg5, arg9, inrange[2], arg8); } } else if (sp64 == 2) { s32 v0 = 0; if (sp9c[1] == 0) { v0 = 1; } if (sp9c[2] == 0) { v0 = 2; } if (v0 == 0) { gdl = bgProcessXrayTri(gdl, xraydata, arg4, sp84[2], sp84[1], arg7, colours[2], colours[1], arg10, inrange[2], inrange[1]); gdl = bgProcessXrayTri(gdl, xraydata, arg3, sp84[1], sp84[2], arg6, colours[1], colours[2], arg9, inrange[1], inrange[2]); gdl = bgProcessXrayTri(gdl, xraydata, arg2, arg3, sp84[2], arg5, arg6, colours[2], arg8, arg9, inrange[2]); } else if (v0 == 1) { gdl = bgProcessXrayTri(gdl, xraydata, arg2, sp84[0], sp84[2], arg5, colours[0], colours[2], arg8, inrange[0], inrange[2]); gdl = bgProcessXrayTri(gdl, xraydata, arg4, sp84[2], sp84[0], arg7, colours[2], colours[0], arg10, inrange[2], inrange[0]); gdl = bgProcessXrayTri(gdl, xraydata, arg3, arg4, sp84[0], arg6, arg7, colours[0], arg9, arg10, inrange[0]); } else { gdl = bgProcessXrayTri(gdl, xraydata, arg3, sp84[1], sp84[0], arg6, colours[1], colours[0], arg9, inrange[1], inrange[0]); gdl = bgProcessXrayTri(gdl, xraydata, arg2, sp84[0], sp84[1], arg5, colours[0], colours[1], arg8, inrange[0], inrange[1]); gdl = bgProcessXrayTri(gdl, xraydata, arg4, arg2, sp84[1], arg7, arg5, colours[1], arg10, arg8, inrange[1]); } } else if (sp64 == 3) { gdl = bgProcessXrayTri(gdl, xraydata, arg2, sp84[0], sp84[2], arg5, colours[0], colours[2], arg8, inrange[0], inrange[2]); gdl = bgProcessXrayTri(gdl, xraydata, arg3, sp84[1], sp84[0], arg6, colours[1], colours[0], arg9, inrange[1], inrange[0]); gdl = bgProcessXrayTri(gdl, xraydata, arg4, sp84[2], sp84[1], arg7, colours[2], colours[1], arg10, inrange[2], inrange[1]); gdl = bgProcessXrayTri(gdl, xraydata, sp84[0], sp84[1], sp84[2], colours[0], colours[1], colours[2], inrange[0], inrange[1], inrange[2]); } } } return gdl; } u32 var8007fc54 = 0; bool g_BgCmdStack[20] = {0}; s32 g_BgCmdStackIndex = 0; u32 g_BgCmdResult = BGRESULT_TRUE; Gfx *bgRenderGdlInXray(Gfx *gdl, s8 *readgdl, Vtx *vertices, s16 arg3[3]) { s32 i; u8 *verticesu8 = (u8 *) vertices; struct xraydata xraydata; struct stagetableentry *stage = stageGetCurrent(); s16 dmemvertices[16][3]; u32 dmemcolours[16]; bool inrange[16]; xraydata.unk00c = g_Vars.currentplayer->eraserbgdist; xraydata.unk010 = xraydata.unk00c * xraydata.unk00c; xraydata.unk018 = xraydata.unk00c * 0.25f; xraydata.unk01c = g_Vars.currentplayer->eraserpropdist / xraydata.unk00c; if (xraydata.unk01c > 0.7f) { xraydata.unk01c = 0.7f; } xraydata.unk014 = 0.250f; xraydata.unk020 = stage->unk2c; xraydata.unk024 = xraydata.unk020 * xraydata.unk020; xraydata.unk000 = arg3[0]; xraydata.unk004 = arg3[1]; xraydata.unk008 = arg3[2]; xraydata.numtris = 0; xraydata.numvertices = 0; while (true) { if (readgdl[GFX_W0_BYTE(0)] == G_ENDDL) { break; } if (readgdl[GFX_W0_BYTE(0)] == G_MTX) { // empty } else if (readgdl[GFX_W0_BYTE(0)] == G_VTX) { Gfx *cmd = (Gfx *) readgdl; s32 dmemindex = cmd->bytes[GFX_W0_BYTE(1)] & 0xf; s32 numvertices = ((u32) cmd->bytes[GFX_W0_BYTE(1)] >> 4) + 1; u32 offset = UNSEGADDR(cmd->words.w1) & 0xffffff; for (i = 0; i < numvertices; i++) { Vtx *vtx = (Vtx *) (verticesu8 + offset); dmemvertices[dmemindex + i][0] = vtx->x; dmemvertices[dmemindex + i][1] = vtx->y; dmemvertices[dmemindex + i][2] = vtx->z; bgChooseXrayVtxColour(&inrange[i], dmemvertices[dmemindex + i], &dmemcolours[dmemindex + i], &xraydata); offset += sizeof(Vtx); } } else if (readgdl[GFX_W0_BYTE(0)] == G_TRI1) { Gfx *cmd = (Gfx *) readgdl; s16 x = cmd->tri.tri.v[GFX_TRI_VTX(0)] / 10; s16 y = cmd->tri.tri.v[GFX_TRI_VTX(1)] / 10; s16 z = cmd->tri.tri.v[GFX_TRI_VTX(2)] / 10; gdl = bgProcessXrayTri(gdl, &xraydata, dmemvertices[x], dmemvertices[y], dmemvertices[z], dmemcolours[x], dmemcolours[y], dmemcolours[z], inrange[x], inrange[y], inrange[z]); } else if (readgdl[GFX_W0_BYTE(0)] == G_TRI4) { Gfx *cmd = (Gfx *) readgdl; s16 x; s16 y; s16 z; x = cmd->tri4.x1; y = cmd->tri4.y1; z = cmd->tri4.z1; gdl = bgProcessXrayTri(gdl, &xraydata, dmemvertices[x], dmemvertices[y], dmemvertices[z], dmemcolours[x], dmemcolours[y], dmemcolours[z], inrange[x], inrange[y], inrange[z]); x = cmd->tri4.x2; y = cmd->tri4.y2; z = cmd->tri4.z2; gdl = bgProcessXrayTri(gdl, &xraydata, dmemvertices[x], dmemvertices[y], dmemvertices[z], dmemcolours[x], dmemcolours[y], dmemcolours[z], inrange[x], inrange[y], inrange[z]); x = cmd->tri4.x3; y = cmd->tri4.y3; z = cmd->tri4.z3; gdl = bgProcessXrayTri(gdl, &xraydata, dmemvertices[x], dmemvertices[y], dmemvertices[z], dmemcolours[x], dmemcolours[y], dmemcolours[z], inrange[x], inrange[y], inrange[z]); x = cmd->tri4.x4; y = cmd->tri4.y4; z = cmd->tri4.z4; gdl = bgProcessXrayTri(gdl, &xraydata, dmemvertices[x], dmemvertices[y], dmemvertices[z], dmemcolours[x], dmemcolours[y], dmemcolours[z], inrange[x], inrange[y], inrange[z]); } readgdl += sizeof(Gfx); } gdl = bgRenderXrayData(gdl, &xraydata); return gdl; } Gfx *bgRenderRoomXrayPass(Gfx *gdl, s32 roomnum, struct roomblock *block, bool recurse, s16 arg4[3]) { struct player *player = g_Vars.currentplayer; if (block == NULL) { return gdl; } switch (block->type) { case ROOMBLOCKTYPE_LEAF: gdl = bgRenderGdlInXray(gdl, (s8 *) block->gdl, block->vertices, arg4); if (recurse) { gdl = bgRenderRoomXrayPass(gdl, roomnum, block->next, true, arg4); } break; case ROOMBLOCKTYPE_PARENT: if (block->child != NULL) { struct roomblock *child1 = block->child; struct roomblock *child2 = child1->next; struct coord *coords = block->unk0c; struct coord sp34; struct coord sp28; f32 sum; sp34.x = coords[1].x; sp34.y = coords[1].y; sp34.z = coords[1].z; sp28.x = coords[0].x - player->cam_pos.x; sp28.y = coords[0].y - player->cam_pos.y; sp28.z = coords[0].z - player->cam_pos.z; sum = sp34.f[0] * sp28.f[0] + sp34.f[1] * sp28.f[1] + sp34.f[2] * sp28.f[2]; if (sum < 0.0f) { gdl = bgRenderRoomXrayPass(gdl, roomnum, child1, false, arg4); gdl = bgRenderRoomXrayPass(gdl, roomnum, child2, false, arg4); } else { gdl = bgRenderRoomXrayPass(gdl, roomnum, child2, false, arg4); gdl = bgRenderRoomXrayPass(gdl, roomnum, child1, false, arg4); } if (recurse) { gdl = bgRenderRoomXrayPass(gdl, roomnum, block->next, true, arg4); } } break; } return gdl; } /** * Render the given room for the purpose of the FarSight or xray scanner. */ Gfx *bgRenderRoomInXray(Gfx *gdl, s32 roomnum) { struct coord sp54; struct coord globaldrawworldoffset; s16 sp40[3]; struct player *player = g_Vars.currentplayer; if (roomnum == 0 || roomnum >= g_Vars.roomcount) { return gdl; } if (g_Rooms[roomnum].loaded240 == 0) { if (var8007fc10 > 0) { var8007fc10--; bgLoadRoom(roomnum); } } if (g_Rooms[roomnum].loaded240 == 0) { var8007fc10--; } if (g_Rooms[roomnum].loaded240 == 0) { return gdl; } roomGetPos(roomnum, &globaldrawworldoffset); sp54.x = player->eraserpos.x - globaldrawworldoffset.x; sp54.y = player->eraserpos.y - globaldrawworldoffset.y; sp54.z = player->eraserpos.z - globaldrawworldoffset.z; sp40[0] = sp54.f[0]; sp40[1] = sp54.f[1]; sp40[2] = sp54.f[2]; gdl = roomApplyMtx(gdl, roomnum); gdl = bgRenderRoomXrayPass(gdl, roomnum, g_Rooms[roomnum].gfxdata->opablocks, true, sp40); gdl = bgRenderRoomXrayPass(gdl, roomnum, g_Rooms[roomnum].gfxdata->xlublocks, true, sp40); g_Rooms[roomnum].loaded240 = 1; return gdl; } Gfx *bgRenderSceneInXray(Gfx *gdl) { RoomNum *roomnumptr; RoomNum *room; s16 i; s32 j; RoomNum roomnumsbyprop[200]; struct prop *prop; struct prop **ptr; s32 k; roomnumptr = roomnumsbyprop; for (ptr = g_Vars.onscreenprops; ptr < g_Vars.endonscreenprops; ptr++) { *roomnumptr = 0; prop = *ptr; if (prop) { room = prop->rooms; while (*room != -1) { if (g_Rooms[*room].flags & ROOMFLAG_ONSCREEN) { *roomnumptr = *room; break; } room++; } } roomnumptr++; } gdl = envStopFog(gdl); gSPClearGeometryMode(gdl++, G_CULL_BOTH); gSPSetGeometryMode(gdl++, G_SHADE | G_SHADING_SMOOTH); gDPSetCombineMode(gdl++, G_CC_SHADE, G_CC_SHADE); gDPSetTextureFilter(gdl++, G_TF_BILERP); gDPSetCycleType(gdl++, G_CYC_1CYCLE); gDPSetRenderMode(gdl++, G_RM_AA_XLU_SURF, G_RM_AA_XLU_SURF2); gSPMatrix(gdl++, osVirtualToPhysical(camGetOrthogonalMtxL()), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); texSelect(&gdl, NULL, 2, 0, 2, 1, NULL); gDPSetRenderMode(gdl++, G_RM_AA_XLU_SURF, G_RM_AA_XLU_SURF2); // Render BG gdl = bgScissorToViewport(gdl); for (i = g_BgMinDrawOrder; i <= g_BgMaxDrawOrder; i++) { for (j = 0; j < g_BgNumDrawSlots; j++) { struct drawslot *thing = &g_BgDrawSlots[j]; if (thing->draworder == i) { gdl = bgRenderRoomInXray(gdl, thing->roomnum); } } } // Render props gdl = bgScissorToViewport(gdl); gSPMatrix(gdl++, osVirtualToPhysical(camGetOrthogonalMtxL()), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); if (g_BgMinDrawOrder); \ if (g_BgNumDrawSlots); \ for (i = g_BgMaxDrawOrder; i >= g_BgMinDrawOrder; i--) { for (k = 0; k < g_BgNumDrawSlots; k++) { struct drawslot *thing = &g_BgDrawSlots[k]; if (thing->draworder == i) { gSPMatrix(gdl++, osVirtualToPhysical(camGetOrthogonalMtxL()), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); gdl = bgScissorWithinViewportF(gdl, thing->box.xmin, thing->box.ymin, thing->box.xmax, thing->box.ymax); gSPMatrix(gdl++, osVirtualToPhysical(camGetPerspectiveMtxL()), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); if (debugIsPropRenderingEnabled() && getVar80084040()) { if (thing->roomnum == -1) { gdl = propsRender(gdl, 0, RENDERPASS_XLU, roomnumsbyprop); } gdl = propsRender(gdl, thing->roomnum, RENDERPASS_XLU, roomnumsbyprop); } if (1); } } } gdl = skyRenderSuns(gdl, true); return gdl; } Gfx *bgRenderScene(Gfx *gdl) { s32 stagenum = g_Vars.stagenum; s32 firstroomnum = -1; s32 i; s32 roomnum; RoomNum roomnumsbyprop[200]; struct prop **ptr; struct drawslot *thing; RoomNum *roomnumptr; struct prop *prop; s16 tmp; RoomNum *room; s16 roomorder[60]; RoomNum roomnums[60]; #ifdef PLATFORM_N64 g_NumRoomsWithGlares = 0; #endif if (g_Vars.currentplayer->visionmode == VISIONMODE_XRAY) { gdl = bgRenderSceneInXray(gdl); return gdl; } // Build an array of all room numbers, and a parallel array that contains // their draw order (as defined by portal code). for (roomnum = 0; roomnum < g_BgNumDrawSlots; roomnum++) { roomorder[roomnum] = g_BgDrawSlots[roomnum].draworder; roomnums[roomnum] = roomnum; } // Sort them by distance ascending if (g_BgNumDrawSlots >= 2) { do { i = false; for (roomnum = 0; roomnum < g_BgNumDrawSlots - 1; roomnum++) { if (roomorder[roomnum + 1] < roomorder[roomnum]) { tmp = roomorder[roomnum]; roomorder[roomnum] = roomorder[roomnum + 1]; roomorder[roomnum + 1] = tmp; tmp = roomnums[roomnum]; roomnums[roomnum] = roomnums[roomnum + 1]; roomnums[roomnum + 1] = tmp; i = true; } } } while (i); } gdl = bgScissorToViewport(gdl); // Render special "always on" rooms, such as the Defection moon, // Attack Ship planet, and other sky tricks that are implemented as rooms if (!USINGDEVICE(DEVICE_NIGHTVISION) && !USINGDEVICE(DEVICE_IRSCANNER) && (stagenum == g_Stages[STAGEINDEX_INFILTRATION].id || stagenum == g_Stages[STAGEINDEX_RESCUE].id || stagenum == g_Stages[STAGEINDEX_ESCAPE].id || stagenum == g_Stages[STAGEINDEX_MAIANSOS].id || stagenum == g_Stages[STAGEINDEX_SKEDARRUINS].id || stagenum == g_Stages[STAGEINDEX_WAR].id || stagenum == g_Stages[STAGEINDEX_DEFECTION].id || stagenum == g_Stages[STAGEINDEX_EXTRACTION].id || stagenum == g_Stages[STAGEINDEX_MBR].id || stagenum == g_Stages[STAGEINDEX_TEST_OLD].id || stagenum == g_Stages[STAGEINDEX_ATTACKSHIP].id)) { gdl = envStopFog(gdl); gdl = vi0000ab78(gdl); roomnum = -1; if (stagenum == g_Stages[STAGEINDEX_INFILTRATION].id || stagenum == g_Stages[STAGEINDEX_RESCUE].id || stagenum == g_Stages[STAGEINDEX_ESCAPE].id || stagenum == g_Stages[STAGEINDEX_MAIANSOS].id) { roomnum = 0x0f; } else if (stagenum == g_Stages[STAGEINDEX_SKEDARRUINS].id || stagenum == g_Stages[STAGEINDEX_WAR].id) { roomnum = 0x02; } else if (stagenum == g_Stages[STAGEINDEX_DEFECTION].id || stagenum == g_Stages[STAGEINDEX_EXTRACTION].id || stagenum == g_Stages[STAGEINDEX_MBR].id) { roomnum = 0x01; } else if (stagenum == g_Stages[STAGEINDEX_TEST_OLD].id) { roomnum = 0x01; } else if (stagenum == g_Stages[STAGEINDEX_ATTACKSHIP].id) { roomnum = 0x71; } if (PLAYERCOUNT() == 1 && (stagenum == STAGE_DEFECTION || stagenum == STAGE_EXTRACTION || stagenum == STAGE_TEST_OLD || stagenum == STAGE_INFILTRATION || stagenum == STAGE_ESCAPE || stagenum == STAGE_ATTACKSHIP)) { gdl = text0f153628(gdl); gSPMatrix(gdl++, osVirtualToPhysical(camGetOrthogonalMtxL()), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); gdl = playerLoadMatrix(gdl); gdl = envStopFog(gdl); gdl = starsRender(gdl); gdl = text0f153780(gdl); gdl = vi0000ab78(gdl); } if (roomnum != -1) { if (!g_Rooms[roomnum].loaded240) { bgLoadRoom(roomnum); } gdl = bgRenderRoomOpaque(gdl, roomnum); } gSPPerspNormalize(gdl++, viGetPerspScale()); } gdl = skyRenderSuns(gdl, false); // Build an array of room numbers per onscreen prop. // For each onscreen prop there is exactly one entry in the roomnumsbyprop array. roomnumptr = roomnumsbyprop; \ if (g_BgNumDrawSlots); for (ptr = g_Vars.onscreenprops; ptr < g_Vars.endonscreenprops; ptr++) { *roomnumptr = 0; prop = *ptr; if (prop) { room = prop->rooms; while (*room != -1) { if (g_Rooms[*room].flags & ROOMFLAG_ONSCREEN) { *roomnumptr = *room; break; } room++; } } roomnumptr++; } // Render the opaque passes for (i = 0; i < g_BgNumDrawSlots; i++) { roomnum = roomnums[i]; if (firstroomnum < 0) { firstroomnum = g_BgDrawSlots[roomnum].roomnum; if (firstroomnum); } thing = &g_BgDrawSlots[roomnum]; // Render prop opaque components - pre BG pass gSPMatrix(gdl++, osVirtualToPhysical(camGetPerspectiveMtxL()), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); gdl = envStopFog(gdl); if (debugIsPropRenderingEnabled() && getVar80084040()) { if (firstroomnum == thing->roomnum) { gdl = propsRender(gdl, 0, RENDERPASS_OPA_PREBG, roomnumsbyprop); } gdl = propsRender(gdl, thing->roomnum, RENDERPASS_OPA_PREBG, roomnumsbyprop); } // Render BG opaque components gSPMatrix(gdl++, osVirtualToPhysical(camGetOrthogonalMtxL()), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); gdl = bgScissorWithinViewportF(gdl, thing->box.xmin, thing->box.ymin, thing->box.xmax, thing->box.ymax); gdl = envStartFog(gdl, false); if (debugIsBgRenderingEnabled() && getVar80084040()) { if (g_StageIndex != STAGEINDEX_TEST_OLD) { gdl = bgRenderRoomOpaque(gdl, thing->roomnum); } } // Render prop opaque components - post BG pass gSPMatrix(gdl++, osVirtualToPhysical(camGetPerspectiveMtxL()), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); gdl = envStopFog(gdl); if (debugIsPropRenderingEnabled() && getVar80084040()) { if (firstroomnum == thing->roomnum) { gdl = propsRender(gdl, 0, RENDERPASS_OPA_POSTBG, roomnumsbyprop); } gdl = propsRender(gdl, thing->roomnum, RENDERPASS_OPA_POSTBG, roomnumsbyprop); } } gdl = envStopFog(gdl); gdl = bgScissorToViewport(gdl); // Render wall hits gSPMatrix(gdl++, osVirtualToPhysical(camGetOrthogonalMtxL()), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); if (getVar80084040() && g_Vars.currentplayer->visionmode != VISIONMODE_XRAY) { for (i = 0; i < g_BgNumDrawSlots; i++) { roomnum = roomnums[i]; gdl = wallhitRenderBgHits(g_BgDrawSlots[roomnum].roomnum, gdl); } } if (1); for (i = g_BgNumDrawSlots - 1; i >= 0; i--) { roomnum = roomnums[i]; gSPMatrix(gdl++, osVirtualToPhysical(camGetOrthogonalMtxL()), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); thing = &g_BgDrawSlots[roomnum]; // Render BG translucent components gdl = bgScissorWithinViewportF(gdl, thing->box.xmin, thing->box.ymin, thing->box.xmax, thing->box.ymax); gdl = envStartFog(gdl, true); if (debugIsBgRenderingEnabled() && getVar80084040()) { gdl = bgRenderRoomXlu(gdl, thing->roomnum); } gSPMatrix(gdl++, osVirtualToPhysical(camGetPerspectiveMtxL()), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); gdl = envStopFog(gdl); // Render prop translucent components if (debugIsPropRenderingEnabled() && getVar80084040()) { if (firstroomnum == thing->roomnum) { gdl = propsRender(gdl, 0, RENDERPASS_XLU, roomnumsbyprop); } gdl = propsRender(gdl, thing->roomnum, RENDERPASS_XLU, roomnumsbyprop); } #ifdef PLATFORM_N64 // on PC we do this earlier, before prop matrices turn into garbage if (!g_Vars.mplayerisrunning) { artifactsCalculateGlaresForRoom(thing->roomnum); if (g_NumRoomsWithGlares < 100) { g_GlareRooms[g_NumRoomsWithGlares++] = thing->roomnum; } } #endif } #if VERSION < VERSION_NTSC_1_0 #ifdef DEBUG debug0f119a80nb(); #endif #endif return gdl; } Gfx *bgRenderArtifacts(Gfx *gdl) { s32 i; if (g_Vars.mplayerisrunning == false && g_NumRoomsWithGlares > 0) { gdl = artifactsConfigureForGlares(gdl); for (i = 0; i < g_NumRoomsWithGlares; i++) { gdl = artifactsRenderGlaresForRoom(gdl, g_GlareRooms[i]); } gdl = artifactsUnconfigureForGlares(gdl); } gdl = skyRenderArtifacts(gdl); return gdl; } void bgLoadFile(void *memaddr, u32 offset, u32 len) { if (var8007fc04) { bcopy(var8007fc08 + offset, memaddr, len); } else { fileLoadPartToAddr(g_Stages[g_StageIndex].bgfileid, memaddr, offset, len); } } s32 bgGetStageIndex(s32 stagenum) { s32 index = -1; s32 i; for (i = 0; i != ARRAYCOUNT(g_Stages); i++) { if (g_Stages[i].id == stagenum) { index = i; } } return index; } f32 bgCalculatePortalSurfaceArea(s32 portalnum) { struct portalvertices *pvertices = (struct portalvertices *)((uintptr_t)g_BgPortals + g_BgPortals[portalnum].verticesoffset); s32 count = pvertices->count; s32 i; s32 j; f32 sum = 0.0f; f32 sp90[3]; f32 sp84[3]; f32 sp78[3]; for (i = 2; i < count; i++) { for (j = 0; j < 3; j++) { sp84[j] = pvertices->vertices[i - 1].f[j] - pvertices->vertices[0].f[j]; sp78[j] = pvertices->vertices[i].f[j] - pvertices->vertices[i - 1].f[j]; } sp90[0] = sp84[1] * sp78[2] - sp84[2] * sp78[1]; sp90[1] = -sp84[0] * sp78[2] + sp84[2] * sp78[0]; sp90[2] = sp84[0] * sp78[1] - sp84[1] * sp78[0]; sum += sqrtf(sp90[0] * sp90[0] + sp90[1] * sp90[1] + sp90[2] * sp90[2]) * 0.5f; } return sum; } void bgGetPortalVtxInfo(s32 portalnum, struct coord *a, struct coord *b, struct coord *c, struct coord *normal) { struct portalvertices *pvertices; pvertices = (struct portalvertices *)((uintptr_t)g_BgPortals + g_BgPortals[portalnum].verticesoffset); a->x = pvertices->vertices[0].x; a->y = pvertices->vertices[0].y; a->z = pvertices->vertices[0].z; b->x = pvertices->vertices[1].x - pvertices->vertices[0].x; b->y = pvertices->vertices[1].y - pvertices->vertices[0].y; b->z = pvertices->vertices[1].z - pvertices->vertices[0].z; c->x = pvertices->vertices[1].x - pvertices->vertices[0].x; c->y = pvertices->vertices[1].y - pvertices->vertices[0].y; c->z = pvertices->vertices[1].z - pvertices->vertices[0].z; normal->x = (g_PortalMetrics + portalnum)->normal.x; normal->y = (g_PortalMetrics + portalnum)->normal.y; normal->z = (g_PortalMetrics + portalnum)->normal.z; } /** * We don't know that this is actually alpha. * The value is not used, so it's just a guess. * If may have been used if portals were being rendered. * * A 1 metre square portal would have alpha of 1. * A 1596 metre square portal would have alpha of 255. */ u8 bgCalculatePortalAlpha(s32 portal) { s32 alpha = bgCalculatePortalSurfaceArea(portal) / 10000.0f; if (alpha > 0xff) { alpha = 0xff; } return alpha; } u8 bgGetPortalAlpha(s32 portalnum) { return g_BgPortalAlphas[portalnum]; } #if PIRACYCHECKS u32 bgNot(u32 arg) { return ~arg; } u32 bgXorBabebabe(u32 value) { return value ^ 0xbabebabe; } #endif #if VERSION < VERSION_NTSC_1_0 /** * The following two functions were surely debug code that was accidentally left * in the NTSC beta build, whose purpose is to debug a light data corruption * issue. * * The first function is run on stage start and builds the known good checksums. * The second function runs on every tick, re-sums them and induces a crash if * they've changed. */ void bgBuildReferenceLightSums(void) { s32 i; g_BgNumLightsChecksum = 0; for (i = 0; i < g_Vars.roomcount; i++) { g_BgNumLightsChecksum += g_Rooms[i].numlights; } g_BgLightsOffsetChecksum = 0; for (i = 0; i < g_Vars.roomcount; i++) { g_BgLightsOffsetChecksum += g_Rooms[i].lightindex; } } void bgVerifyLightSums(char *file, s32 line) { s32 i; s32 sum; char message[128]; u32 stack; sum = 0; for (i = 0; i < g_Vars.roomcount; i++) { sum += g_Rooms[i].numlights; } if (sum != g_BgNumLightsChecksum) { sprintf(message, "NumLightsChecksum failed %s %d", file, line); crashSetMessage(message); CRASH(); } sum = 0; for (i = 0; i < g_Vars.roomcount; i++) { sum += g_Rooms[i].lightindex; } if (sum != g_BgLightsOffsetChecksum) { sprintf(message, "LightsOffsetChecksum failed %s %d", file, line); crashSetMessage(message); CRASH(); } } #endif /** * Extracts and inflates primary data (room/portal/light tables) from the * BG file to memory, ensures required textures are loaded, and sets some * global pointers to each table within the primary data. * * -- Overview ----------------------------------------------------------------- * * The structure of a BG File is: * * - Section 1: * - Primary data (compressed): * - Room table, with pointers to their room gfx data * - Portal table * - Portal command list * - Light table * - For each room, a compressed binary containing gfx data * - Section 2 (compressed) - list of texture IDs to load * - Section 3 (compressed) - unknown and not read by this function * * Each section has a header (uncompressed) containing size information: * * Section 1 header is 0x0c bytes long: * - 4 bytes decompressed size of primary data * - 4 bytes compressed size of section 1 in its entirety * - 4 bytes compressed size of primary data * * Section 2 and 3 headers are 0x04 bytes long: * - 2 bytes decompressed size of data (mask with 0x7fff) * - 2 bytes compressed size of data * * -- Primary Data (compressed) ------------------------------------------------ * * Header is 0x18 bytes long: * - 4 bytes null * - 4 bytes pointer to room table * - 4 bytes pointer to portal table * - 4 bytes pointer to portal commands list * - 4 bytes pointer to light table * - 4 bytes null */ void bgReset(s32 stagenum) { u8 *header; u8 headerbuffer[0x50]; u32 numtextures; u16 *section2; s32 j; s32 i; u32 primcompsize; u32 inflatedsize; // used for both primary and section 2 u32 section2compsize; u32 section2start; u32 section1compsize; uintptr_t scratch; var8007fc0c = 8; #if VERSION >= VERSION_NTSC_1_0 if (IS4MB()) { g_BgUnloadDelay240 = 6; g_BgUnloadDelay240_2 = 6; } else { g_BgUnloadDelay240 = 120; g_BgUnloadDelay240_2 = 120; } #else if (IS4MB()) { g_BgUnloadDelay240 = 120; g_BgUnloadDelay240_2 = 120; } else { g_BgUnloadDelay240 = 3600; g_BgUnloadDelay240_2 = 3600; } var800a4bf4 = 0; #endif g_StageIndex = bgGetStageIndex(stagenum); if (g_StageIndex < 0) { g_StageIndex = 0; } // Copy section 1 header to stack and parse into variables header = (u8 *)ALIGN16((uintptr_t)headerbuffer); bgLoadFile(header, 0, 0x40); #ifndef PLATFORM_N64 preprocessBgSection1Header(header, 0x40); #endif inflatedsize = *(u32 *)&header[0]; section1compsize = *(u32 *)&header[4]; primcompsize = *(u32 *)&header[8]; var8007fc54 = inflatedsize - primcompsize; var8007fc54 -= 0xc; #ifdef PLATFORM_64BIT inflatedsize = romdataFileGetEstimatedSize(inflatedsize, FT_BG); #endif inflatedsize = ALIGN16(inflatedsize); // Allocate space for the primary bg data // An extra 0x8000 or so is given as temporary scratch space g_BgPrimaryData = mempAlloc(ALIGN16(inflatedsize + 0x8010), MEMPOOL_STAGE); // Set up pointer to scratch space scratch = (uintptr_t) g_BgPrimaryData + inflatedsize - primcompsize; scratch = ALIGN16(scratch + 0x8000); g_LoadType = LOADTYPE_BG; // Copy section 1 header + compressed primary to scratch space bgLoadFile((u8 *) scratch, 0, ALIGN16(primcompsize + 15)); // Inflate primary data to the start of the buffer scratch += 0xc; bgInflate((u8 *) scratch, g_BgPrimaryData, primcompsize); #ifndef PLATFORM_N64 preprocessBgSection1(g_BgPrimaryData, inflatedsize, 0x0f000000); #endif // Shrink the allocation (ie. free the scratch space) mempRealloc(g_BgPrimaryData, inflatedsize, MEMPOOL_STAGE); // Load the section 2 header section2start = section1compsize + 0xc; bgLoadFile(header, section2start, 0x40); #ifndef PLATFORM_N64 preprocessBgSection2Header(header, 0x40); #endif inflatedsize = (*(u16 *) &header[0] & 0x7fff) - 1; section2compsize = *(u16 *) &header[2]; inflatedsize = (inflatedsize | 0xf) + 1; // Allocate space for the section 2 data (texture ID list). // This is the cause and fix for the Challenge 7 memory corruption bug in // NTSC 1.0. A full writeup about the bug and how the fix works can be found // in the docs folder of this project. #ifdef AVOID_UB section2 = mempAlloc(inflatedsize + section2compsize, MEMPOOL_STAGE); scratch = (uintptr_t) section2 + inflatedsize; #elif VERSION >= VERSION_NTSC_FINAL section2 = mempAlloc(inflatedsize + 0x8000, MEMPOOL_STAGE); scratch = (uintptr_t) section2 + 0x8000; #else section2 = mempAlloc(inflatedsize + 0x800, MEMPOOL_STAGE); scratch = (uintptr_t) section2 + 0x800; #endif // Load compressed data from ROM to scratch bgLoadFile((u8 *) scratch, section2start + 4, ((section2compsize - 1) | 0xf) + 1); // Inflate section 2 to the start of the buffer bgInflate((u8 *) scratch, (u8 *) section2, section2compsize); // Iterate texture IDs and ensure they're loaded inflatedsize = (*(u16 *) &header[0] & 0x7fff) >> 1; #ifndef PLATFORM_N64 preprocessBgSection2((u8 *)section2, inflatedsize); #endif for (i = 0; i ^ inflatedsize; i++) { texLoadFromTextureNum(section2[i] & 0xffff & 0xffff & 0xffff & 0xffff & 0xffff & 0xffff & 0xffff & 0xffff, NULL); } if (1); // Free section 2 mempRealloc(section2, 0, MEMPOOL_STAGE); g_BgSection3 = section2start + section2compsize + 4; #if PIRACYCHECKS { u32 addr = bgNot(PAL ? ~0xb0000340 : ~0xb0000454); u32 actualvalue; u32 expectedvalue = bgXorBabebabe((PAL ? 0x0330c820 : 0x0109082b) ^ 0xbabebabe); osPiReadIo(addr, &actualvalue); if (actualvalue != expectedvalue) { // Copy 0x40 bytes from a random location in ROM to a random // location in RAM. The write address can be anywhere in the // boot segment or in the lib segment up to modelRenderNodeDl. dmaExec((void *)(PHYS_TO_K0(0x1000) + (random() & 0x1fff8)), random() & 0x1fffe, 0x40); } } #endif var800a4920 = *(u32 *)g_BgPrimaryData; if (var800a4920 == 0) { g_BgPrimaryData2 = (uintptr_t*)g_BgPrimaryData; g_BgRooms = (struct bgroom *)(g_BgPrimaryData2[1] + g_BgPrimaryData - 0x0f000000); goto foo; foo:; g_Vars.roomcount = 0; for (j = 1; g_BgRooms[j].unk00 != 0; j++) { g_Vars.roomcount++; } g_BgPortals = (struct bgportal *)(g_BgPrimaryData2[2] + g_BgPrimaryData - 0x0f000000); if (g_BgPrimaryData2[3] == 0) { g_BgCommands = NULL; } else { g_BgCommands = (struct bgcmd *)(g_BgPrimaryData2[3] + g_BgPrimaryData - 0x0f000000); } if (g_BgPrimaryData2[4] == 0) { g_BgLightsFileData = NULL; } else { g_BgLightsFileData = (u8 *)(g_BgPrimaryData2[4] + g_BgPrimaryData - 0x0f000000); } if (g_BgPrimaryData2[5] == 0) { g_BgStanThings = NULL; } else { g_BgStanThings = (f32 *)(g_BgPrimaryData2[5] + g_BgPrimaryData - 0x0f000000); } } } void bgBuildTables(s32 stagenum) { s32 i; s32 j; s32 k; u32 r; u8 *header; s32 numportals; s32 index; f32 divisor; s16 lightindex; s32 candportalnum; s32 numportalsthisroom; bool swap; struct portalvertices *pvertices; struct portalmetric *metric; s32 numvertices; struct portalmetric tmp; u8 *scratch; u8 headerbuffer[0x50]; s32 thisneighbournum; s32 offset; u32 inflatedsize; u32 section3compsize; s16 *bboxptr; u8 *section3; u16 *datalenptr; u8 *numlightsptr; g_Rooms = mempAlloc(ALIGN16(g_Vars.roomcount * sizeof(struct room)), MEMPOOL_STAGE); g_BgDrawSlotsByRoom = mempAlloc(ALIGN16(g_Vars.roomcount * sizeof(struct drawslotpointer)), MEMPOOL_STAGE); for (i = 0; i < g_Vars.roomcount; i++) { g_BgDrawSlotsByRoom[i].updatedframe = 0xffff; g_BgDrawSlotsByRoom[i].slotnum = 0; } if (g_Vars.mplayerisrunning) { g_MpRoomVisibility = mempAlloc(ALIGN16(g_Vars.roomcount), MEMPOOL_STAGE); for (i = 0; i < g_Vars.roomcount; i++) { g_MpRoomVisibility[i] = 0; } } for (i = 0; i < g_Vars.roomcount; i++) { g_Rooms[i].vtxbatches = NULL; g_Rooms[i].numlights = 0; g_Rooms[i].lightindex = 0; g_Rooms[i].flags = 0; g_Rooms[i].unk4d = 0; g_Rooms[i].lightop = 0; g_Rooms[i].unk4e_04 = 0; #ifndef PLATFORM_N64 g_Rooms[i].extra_flags = 0; #endif } bgSetStageTranslationThing(g_Stages[g_StageIndex].unk14); chr0f028490(g_Stages[g_StageIndex].unk14); for (i = 0; i < MAX_PLAYERS; i++) { g_Vars.playerstats[i].scale_bg2gfx = g_Stages[g_StageIndex].unk18; } mtx00016748(1); if (var800a4920 == 0) { numportals = 0; for (i = 0; g_BgPortals[i].verticesoffset != 0; i++) { numportals++; } g_BgNumPortalCameraCacheItems = numportals; g_PortalCameraCache = mempAlloc(ALIGN16(g_BgNumPortalCameraCacheItems * sizeof(struct portalcamcacheitem)), MEMPOOL_STAGE); // Iterate the portals and update their verticesoffset value. In // storage, the g_BgPortals array is followed by vertice data, and each // portal's verticesoffset value is an index into the vertice data. // Here, the unk00 value is being converted to an offset relative to the // start of the g_BgPortals array. Start by initialising offset past the // end of the portal array, which is the start of the vertice data. offset = numportals * sizeof(struct bgportal); offset += sizeof(struct bgportal); // Because each group of vertices is variable length, the portals can't // be iterated in order and have their offset calculated. The vertice // data has to be iterated in storage order, then iterate all portals to // see if any refer to this index. for (i = 1; true; i++) { for (j = 0; j < numportals; j++) { if (g_BgPortals[j].verticesoffset == i) { g_BgPortals[j].verticesoffset = offset; } } pvertices = (struct portalvertices *)((uintptr_t)g_BgPortals + offset); if (pvertices->count <= 0) { break; } offset += pvertices->count * 12; offset += 4; } // Calculate g_RoomPortals: An array of portal numbers, ordered by room // number ascending. Each room struct contains an index into this array // where its portal numbers start. index = 0; g_RoomPortals = mempAlloc(ALIGN16((numportals == 0 ? 1 : numportals) * sizeof(s16 *)), MEMPOOL_STAGE); g_Vars.roomportalrecursionlimit = 0; for (i = 0; i < g_Vars.roomcount; i++) { numportalsthisroom = 0; g_Rooms[i].roomportallistoffset = index; for (j = 0; j < numportals; j++) { if (i == g_BgPortals[j].roomnum1) { g_RoomPortals[index] = j; numportalsthisroom++; index++; } if (i == g_BgPortals[j].roomnum2) { g_RoomPortals[index] = j; numportalsthisroom++; index++; } } g_Rooms[i].numportals = numportalsthisroom; if (numportalsthisroom > g_Vars.roomportalrecursionlimit) { g_Vars.roomportalrecursionlimit = numportalsthisroom; } } // Sort the portal numbers in g_RoomPortals within their room groups. // Sorting is done by neighbouring room number ascending. // // @bug: The k loop doesn't reset to j after doing a swap, which means // some items may not be sorted correctly. This isn't a problem if the // data on ROM is already sorted, or if they actually don't need to be // sorted. for (i = 0; i < g_Vars.roomcount; i++) { for (j = 0; j < g_Rooms[i].numportals; j++) { if (g_BgPortals[g_RoomPortals[g_Rooms[i].roomportallistoffset + j]].roomnum1 == i) { thisneighbournum = g_BgPortals[g_RoomPortals[g_Rooms[i].roomportallistoffset + j]].roomnum2; } else { thisneighbournum = g_BgPortals[g_RoomPortals[g_Rooms[i].roomportallistoffset + j]].roomnum1; } for (k = j; k < g_Rooms[i].numportals; k++) { swap = false; if (i == g_BgPortals[g_RoomPortals[g_Rooms[i].roomportallistoffset + k]].roomnum1) { if (g_BgPortals[g_RoomPortals[g_Rooms[i].roomportallistoffset + k]].roomnum2 < thisneighbournum) { swap = true; } } else { if (g_BgPortals[g_RoomPortals[g_Rooms[i].roomportallistoffset + k]].roomnum1 < thisneighbournum) { swap = true; } } if (swap) { candportalnum = g_RoomPortals[g_Rooms[i].roomportallistoffset + k]; g_RoomPortals[g_Rooms[i].roomportallistoffset + k] = g_RoomPortals[g_Rooms[i].roomportallistoffset + j]; g_RoomPortals[g_Rooms[i].roomportallistoffset + j] = candportalnum; if (g_BgPortals[g_RoomPortals[g_Rooms[i].roomportallistoffset + j]].roomnum1 == i) { thisneighbournum = g_BgPortals[g_RoomPortals[g_Rooms[i].roomportallistoffset + j]].roomnum2; } else { thisneighbournum = g_BgPortals[g_RoomPortals[g_Rooms[i].roomportallistoffset + j]].roomnum1; } } } } } g_BgPortalAlphas = mempAlloc(ALIGN16(numportals == 0 ? 1 : numportals), MEMPOOL_STAGE); for (i = 0; i < numportals; i++) { g_BgPortalAlphas[i] = bgCalculatePortalAlpha(i); } g_PortalMetrics = mempAlloc(ALIGN16(numportals * sizeof(struct portalmetric)), MEMPOOL_STAGE); for (i = 0; i < numportals; i++) { // Clockwise vertices will cause the normal to point towards the viewer tmp.normal.x = 0.0f; tmp.normal.y = 0.0f; tmp.normal.z = 0.0f; pvertices = (struct portalvertices *)((uintptr_t)g_BgPortals + g_BgPortals[i].verticesoffset); for (j = 0; j < pvertices->count; j++) { struct coord *next = &pvertices->vertices[(j + 1) % pvertices->count]; tmp.normal.x += (pvertices->vertices[j].y - next->y) * (pvertices->vertices[j].z + next->z); tmp.normal.y += (pvertices->vertices[j].z - next->z) * (pvertices->vertices[j].x + next->x); tmp.normal.z += (pvertices->vertices[j].x - next->x) * (pvertices->vertices[j].y + next->y); } divisor = -sqrtf(tmp.normal.f[0] * tmp.normal.f[0] + tmp.normal.f[1] * tmp.normal.f[1] + tmp.normal.f[2] * tmp.normal.f[2]); tmp.normal.x /= divisor; tmp.normal.y /= divisor; tmp.normal.z /= divisor; tmp.min = MAXFLOAT; tmp.max = MINFLOAT; for (j = 0; j < pvertices->count; j++) { f32 value = pvertices->vertices[j].f[0] * tmp.normal.f[0] + pvertices->vertices[j].f[1] * tmp.normal.f[1] + pvertices->vertices[j].f[2] * tmp.normal.f[2]; if (value < tmp.min) { tmp.min = value; } if (value > tmp.max) { tmp.max = value; } } metric = &g_PortalMetrics[i]; metric->normal.x = tmp.normal.x; metric->normal.y = tmp.normal.y; metric->normal.z = tmp.normal.z; metric->min = tmp.min; metric->max = tmp.max; } portal0f0b65a8(numportals); if (g_BgCommands != NULL) { for (i = 0; g_BgCommands[i].type != BGCMD_END; i++) { if (g_BgCommands[i].type == BGCMD_PORTALARG) { g_BgCommands[i].param = bgFindPortalByVertices((void *)((intptr_t)g_BgPrimaryData - 0x0f000000 + g_BgCommands[i].param)); } } } for (i = 0; i < g_Vars.roomcount; i++) { g_Rooms[i].flags = 0; g_Rooms[i].snakecount = 0; g_Rooms[i].unk07 = 1; g_Rooms[i].loaded240 = 0; g_Rooms[i].gfxdata = NULL; g_Rooms[i].gfxdatalen = -1; g_Rooms[i].opawallhits = NULL; g_Rooms[i].xluwallhits = NULL; } roomsReset(); g_Rooms[0].bbmin[0] = 0.0f; g_Rooms[0].bbmin[1] = 0.0f; g_Rooms[0].bbmin[2] = 0.0f; g_Rooms[0].bbmax[0] = 0.0f; g_Rooms[0].bbmax[1] = 0.0f; g_Rooms[0].bbmax[2] = 0.0f; dyntexReset(); // Load section 3 of the BG file. To do this, the header of the BG file // must be loaded first as it contains the offset to section 3. Then // section 3 is loaded and inflated. Data is read out of section 3, then // the section 3 allocation is resized to 0, effectively freeing it. // Load and read the header header = (u8 *)ALIGN16((uintptr_t)headerbuffer); bgLoadFile(header, g_BgSection3, 0x40); #ifndef PLATFORM_N64 preprocessBgSection3Header(header, 0x40); #endif inflatedsize = (*(u16 *)&header[0] & 0x7fff) - 1; section3compsize = *(u16 *)&header[2]; inflatedsize = (inflatedsize | 0xf) + 1; // Load and inflate section 3 #ifdef AVOID_UB section3 = mempAlloc(inflatedsize + section3compsize, MEMPOOL_STAGE); scratch = section3 + inflatedsize; #elif VERSION >= VERSION_NTSC_FINAL section3 = mempAlloc(inflatedsize + 0x8000, MEMPOOL_STAGE); scratch = section3 + 0x8000; #else section3 = mempAlloc(inflatedsize + 0x1000, MEMPOOL_STAGE); scratch = section3 + 0x1000; #endif bgLoadFile(scratch, g_BgSection3 + 4, ((section3compsize - 1) | 0xf) + 1); bgInflate(scratch, section3, section3compsize); #ifndef PLATFORM_N64 preprocessBgSection3(section3, section3compsize); #endif // Section 3 starts with a table of room bounding boxes bboxptr = (s16 *) section3; for (r = 1; r < g_Vars.roomcount; r++) { // Calculate bounding box g_Rooms[r].bbmin[0] = *bboxptr + g_BgRooms[r].pos.x; bboxptr++; g_Rooms[r].bbmin[1] = *bboxptr + g_BgRooms[r].pos.y; bboxptr++; g_Rooms[r].bbmin[2] = *bboxptr + g_BgRooms[r].pos.z; bboxptr++; g_Rooms[r].bbmax[0] = *bboxptr + g_BgRooms[r].pos.x; bboxptr++; g_Rooms[r].bbmax[1] = *bboxptr + g_BgRooms[r].pos.y; bboxptr++; g_Rooms[r].bbmax[2] = *bboxptr + g_BgRooms[r].pos.z; bboxptr++; if (1); // Calculate centre g_Rooms[r].centre.x = (g_Rooms[r].bbmin[0] + g_Rooms[r].bbmax[0]) / 2.0f; g_Rooms[r].centre.y = (g_Rooms[r].bbmin[1] + g_Rooms[r].bbmax[1]) / 2.0f; g_Rooms[r].centre.z = (g_Rooms[r].bbmin[2] + g_Rooms[r].bbmax[2]) / 2.0f; if (1); // Calculate radius g_Rooms[r].radius = sqrtf((g_Rooms[r].bbmin[0] - g_Rooms[r].bbmax[0]) * (g_Rooms[r].bbmin[0] - g_Rooms[r].bbmax[0]) + (g_Rooms[r].bbmin[1] - g_Rooms[r].bbmax[1]) * (g_Rooms[r].bbmin[1] - g_Rooms[r].bbmax[1]) + (g_Rooms[r].bbmin[2] - g_Rooms[r].bbmax[2]) * (g_Rooms[r].bbmin[2] - g_Rooms[r].bbmax[2])) / 2.0f; } // The next part of section 3 is a list of roomgfxdata sizes. // There is one per room and the value needs to be multiplied by 0x10. datalenptr = (u16 *) bboxptr; for (r = 1; r < g_Vars.roomcount; r++) { g_Rooms[r].gfxdatalen = ALIGN16(*datalenptr * 0x10 + 0x100); datalenptr++; } // The last part of section 3 is the number of lights per room. // This is calculating the index into the lights file where each room's // lights start. The light data is already ordered by room, so it can do // this easily by adding to the offset of the previous one. lightindex = 0; numlightsptr = (u8 *) datalenptr; for (r = 1; r < g_Vars.roomcount; r++) { g_Rooms[r].numlights = *numlightsptr; if (g_Rooms[r].numlights > 0) { g_Rooms[r].lightindex = lightindex; lightindex += g_Rooms[r].numlights; } else { g_Rooms[r].lightindex = -1; } numlightsptr++; } // Free the section 3 allocation mempRealloc(section3, 0, MEMPOOL_STAGE); for (i = 1; i < g_Vars.roomcount; i++) { roomInitLights(i); } // Initialise a table related to lights. // j is being reused here as a total light count. j = 0; for (i = 1; i < g_Vars.roomcount; i++) { j += g_Rooms[i].numlights; } if (j) { var800a41a0 = mempAlloc(ALIGN16(j * 3), MEMPOOL_STAGE); for (i = 0; i < j; i++) { var800a41a0[i * 3 + 0] = 0; var800a41a0[i * 3 + 1] = 0; var800a41a0[i * 3 + 2] = 0; } } else { var800a41a0 = NULL; } for (i = 0; g_BgPortals[i].verticesoffset != 0; i++) { bgInitPortal(i); } for (i = 1; i < g_Vars.roomcount; i++) { bgInitRoom(i); } for (i = 1; i < g_Vars.roomcount; i++) { bgExpandRoomToPortals(i); } for (i = 0; g_BgPortals[i].verticesoffset != 0; i++) { g_BgPortals[i].flags &= ~PORTALFLAG_CLOSED & 0xff; } } envSetStageNum(stagenum); var8007fc10 = 200; wallhitReset(); func0f002a98(); func0f001c0c(); #if VERSION < VERSION_NTSC_1_0 bgBuildReferenceLightSums(); #endif } void bgStop(void) { bgUnloadAllRooms(); mtx00016748(1); } void bgSetStageTranslationThing(f32 arg0) { // empty } f32 bgGetStageTranslationThing(void) { return g_Stages[g_StageIndex].unk1c / g_Stages[g_StageIndex].unk14; } f32 bgGetScaleBg2Gfx(void) { return g_Vars.currentplayerstats->scale_bg2gfx; } void bgSetScaleBg2Gfx(f32 scale) { g_Vars.currentplayerstats->scale_bg2gfx = g_Stages[g_StageIndex].unk18 * scale; mtx00016748(g_Vars.currentplayerstats->scale_bg2gfx); } /** * The counter is incremented once per frame per player. * * The portal camera cache uses this counter as a last-updated timestamp to keep * track of whether any cache item is current as of this frame/player or whether * it needs to be recalculated. * * When the counter rolls over, all portal camera cache is cleared to avoid any * potential issues with reusing the timestamps. */ void bgTickCounter(void) { g_BgFrameCount++; if (g_BgFrameCount == 0xffff) { g_BgFrameCount = 1; bgClearPortalCameraCache(); } #if PIRACYCHECKS if ((g_BgFrameCount & 0xff) == 0xff) { u32 checksum = 0; s32 *ptr = (s32 *)&menuTickTimers; s32 *end = (s32 *)&menuGetSinOscFrac; while (ptr < end) { checksum ^= ~*ptr; checksum ^= *ptr << 5; checksum ^= *ptr >> 15; ptr++; } if (checksum != CHECKSUM_PLACEHOLDER) { ptr = (s32 *)&bgBuildTables + 20; if (1) { end = &ptr[4]; } while (ptr < end) { *ptr -= 0x24e21; ptr++; } } } #endif } void bgTick(void) { s32 tickmode; g_BgNumForceOnscreenRooms = 0; #if VERSION < VERSION_NTSC_1_0 bgVerifyLightSums("bg.c", 5761); #endif bgTickCounter(); if (g_Vars.currentplayerindex == 0) { bgTickRooms(); } tickmode = g_Vars.tickmode; if (tickmode == TICKMODE_NORMAL) { var8007fc10 = 4; #if VERSION >= VERSION_NTSC_1_0 if (IS8MB() && var8007fc0c) #else if (var8007fc0c) #endif { var8007fc0c--; var8007fc10 = 200; } } else { var8007fc0c = 8; var8007fc10 = 200; } if (g_Vars.currentplayer->visionmode == VISIONMODE_XRAY) { var8007fc10 = 100; } g_CamRoom = g_Vars.currentplayer->cam_room; #if VERSION >= VERSION_NTSC_1_0 bgTickPortals(); #else bgVerifyLightSums("bg.c", 5834); bgTickPortals(); bgVerifyLightSums("bg.c", 5846); #endif } Gfx *bgRender(Gfx *gdl) { gdl = lightsSetDefault(gdl); gSPSegment(gdl++, SPSEGMENT_BG_DL, g_BgPrimaryData); gdl = envStartFog(gdl, false); gdl = bgRenderSceneAndLoadCandidate(gdl); gdl = bgScissorToViewport(gdl); gdl = envStopFog(gdl); gSPMatrix(gdl++, var80092870, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); gdl = playerLoadMatrix(gdl); return gdl; } /** * Leftover from GE. */ f32 bgGetStanThing(s32 roomnum) { return g_BgStanThings[roomnum + 1]; } Gfx *bgScissorToViewport(Gfx *gdl) { return bgScissorWithinViewport(gdl, g_Vars.currentplayer->viewleft, g_Vars.currentplayer->viewtop, g_Vars.currentplayer->viewleft + g_Vars.currentplayer->viewwidth, g_Vars.currentplayer->viewtop + g_Vars.currentplayer->viewheight); } Gfx *bgScissorWithinViewportF(Gfx *gdl, f32 viewleft, f32 viewtop, f32 viewright, f32 viewbottom) { gdl = bgScissorWithinViewport(gdl, viewleft, viewtop, ceiltoint(viewright), ceiltoint(viewbottom)); return gdl; } Gfx *bgScissorWithinViewport(Gfx *gdl, s32 viewleft, s32 viewtop, s32 viewright, s32 viewbottom) { #ifndef PLATFORM_N64 const s32 xmargin = videoGetWidth() / SCREEN_320 - 1; const s32 ymargin = videoGetHeight() / SCREEN_240 - 1; if (xmargin > 0) { viewleft -= xmargin; viewright += xmargin; } if (ymargin > 0) { viewtop -= ymargin; viewbottom += ymargin; } #endif if (viewleft < g_Vars.currentplayer->viewleft) { viewleft = g_Vars.currentplayer->viewleft; } if (viewtop < g_Vars.currentplayer->viewtop) { viewtop = g_Vars.currentplayer->viewtop; } if (viewright > g_Vars.currentplayer->viewleft + g_Vars.currentplayer->viewwidth) { viewright = g_Vars.currentplayer->viewleft + g_Vars.currentplayer->viewwidth; } if (viewbottom > g_Vars.currentplayer->viewtop + g_Vars.currentplayer->viewheight) { viewbottom = g_Vars.currentplayer->viewtop + g_Vars.currentplayer->viewheight; } gDPSetScissor(gdl++, G_SC_NON_INTERLACE, viewleft, viewtop, viewright, viewbottom); return gdl; } void bgClearPortalCameraCache(void) { s32 i; for (i = 0; i < g_BgNumPortalCameraCacheItems; i++) { g_PortalCameraCache[i].bboxisvalid = -1; g_PortalCameraCache[i].updatedframe2 = 0; g_PortalCameraCache[i].updatedframe1 = 0; } } bool bgRoomIntersectsScreenBox(s32 room, struct screenbox *screen) { s32 i; struct coord roomscreenpos; struct coord corner; s32 numbehind = 0; s32 numfar = 0; s32 numleft = 0; s32 numright = 0; s32 numbelow = 0; s32 numabove = 0; for (i = 0; i != 8; i++) { if (i & 1) { corner.x = g_Rooms[room].bbmin[0]; } else { corner.x = g_Rooms[room].bbmax[0]; } if (i & 2) { corner.y = g_Rooms[room].bbmin[1]; } else { corner.y = g_Rooms[room].bbmax[1]; } if (i & 4) { corner.z = g_Rooms[room].bbmin[2]; } else { corner.z = g_Rooms[room].bbmax[2]; } if (bg3dPosTo2dPos(&corner, &roomscreenpos) == 0) { // Corner is behind the camera if (g_BgSnake.zrange.far <= -roomscreenpos.z) { numfar++; } if (roomscreenpos.x > screen->xmin) { numleft++; } if (roomscreenpos.x < screen->xmax) { numright++; } if (roomscreenpos.y > screen->ymin) { numbelow++; } if (roomscreenpos.y < screen->ymax) { numabove++; } numbehind++; } else { // Corner is in front of the camera if (g_BgSnake.zrange.far <= -roomscreenpos.z) { numfar++; } if (roomscreenpos.x < screen->xmin) { numleft++; } else if (roomscreenpos.x > screen->xmax) { numright++; } if (roomscreenpos.y < screen->ymin) { numbelow++; } else if (roomscreenpos.y > screen->ymax) { numabove++; } } } if (numbehind == 8 || numfar == 8 || numleft == 8 || numright == 8 || numbelow == 8 || numabove == 8) { return false; } return true; } bool bg3dPosTo2dPos(struct coord *cornerpos, struct coord *screenpos) { Mtxf *matrix = camGetWorldToScreenMtxf(); screenpos->x = cornerpos->x; screenpos->y = cornerpos->y; screenpos->z = cornerpos->z; mtx4TransformVecInPlace(matrix, screenpos); cam0f0b4d68(screenpos, screenpos->f); if (screenpos->z > 0) { return false; } return true; } bool bgGetPortalScreenBbox(s32 portalnum, struct screenbox *box) { s32 i; s32 j; s32 len; s32 start; s32 numvalid; f32 sp2e4[2]; f32 sp2d4[2][2]; struct portalthing2 *thing; struct portalthing2 things[40]; if (g_PortalCameraCache[portalnum].updatedframe2 == g_BgFrameCount) { box->xmin = g_PortalCameraCache[portalnum].xmin; box->ymin = g_PortalCameraCache[portalnum].ymin; box->xmax = g_PortalCameraCache[portalnum].xmax; box->ymax = g_PortalCameraCache[portalnum].ymax; return g_PortalCameraCache[portalnum].bboxisvalid; } len = portalConvertCoordinates(portalnum, &start, things); numvalid = 0; thing = &things[start]; for (j = 0; j < len; j++) { if (thing->coord.z <= 0.0f) { cam0f0b4d68(&thing->coord, sp2e4); if (numvalid == 0) { sp2d4[0][0] = sp2d4[1][0] = sp2e4[0]; sp2d4[0][1] = sp2d4[1][1] = sp2e4[1]; } else { if (sp2e4[0] < sp2d4[0][0]) { sp2d4[0][0] = sp2e4[0]; } if (sp2d4[1][0] < sp2e4[0]) { sp2d4[1][0] = sp2e4[0]; } if (sp2e4[1] < sp2d4[0][1]) { sp2d4[0][1] = sp2e4[1]; } if (sp2d4[1][1] < sp2e4[1]) { sp2d4[1][1] = sp2e4[1]; } } numvalid++; } thing++; } if (numvalid == 0) { box->xmin = 0; box->xmax = 0; box->ymin = 0; box->ymax = 0; } else if (sp2d4[1][0] < sp2d4[0][0] || sp2d4[1][1] < sp2d4[0][1]) { struct player *player = g_Vars.currentplayer; box->xmin = player->screenxminf; box->ymin = player->screenyminf; box->xmax = player->screenxmaxf; box->ymax = player->screenymaxf; } else { sp2d4[0][0] -= 0.5f; sp2d4[0][1] -= 0.5f; sp2d4[1][0] += 0.5f; sp2d4[1][1] += 0.5f; for (i = 0; i < 2; i++) { for (j = 0; j < 2; j++) { f32 value = sp2d4[i][j]; if (value >= 0.0f) { if (value > 32000.0f) { box->array[i][j] = 32000; } else { box->array[i][j] = value; } } else if (value < -32000.0f) { box->array[i][j] = -32000; } else { box->array[i][j] = value; } } } } g_PortalCameraCache[portalnum].xmin = box->xmin; g_PortalCameraCache[portalnum].ymin = box->ymin; g_PortalCameraCache[portalnum].xmax = box->xmax; g_PortalCameraCache[portalnum].ymax = box->ymax; g_PortalCameraCache[portalnum].bboxisvalid = numvalid; g_PortalCameraCache[portalnum].updatedframe2 = g_BgFrameCount; return numvalid; } Gfx *bgDrawBoxEdge(Gfx *gdl, s32 x1, s32 y1, s32 x2, s32 y2) { gDPFillRectangle(gdl++, x1, y1, x2 + 1, y2 + 1); return gdl; } Gfx *bgDrawBox(Gfx *gdl, s32 x1, s32 y1, s32 x2, s32 y2) { gdl = bgDrawBoxEdge(gdl, x1, y1, x2, y1); // top gdl = bgDrawBoxEdge(gdl, x2, y1, x2, y2); // right gdl = bgDrawBoxEdge(gdl, x1, y2, x2, y2); // bottom gdl = bgDrawBoxEdge(gdl, x1, y1, x1, y2); // left return gdl; } bool bgGetBoxIntersection(struct screenbox *a, struct screenbox *b) { a->xmin = a->xmin > b->xmin ? a->xmin : b->xmin; a->ymin = a->ymin > b->ymin ? a->ymin : b->ymin; a->xmax = b->xmax > a->xmax ? a->xmax : b->xmax; a->ymax = b->ymax > a->ymax ? a->ymax : b->ymax; if (a->xmin >= a->xmax) { a->xmin = a->xmax; return false; } if (a->ymax <= a->ymin) { a->ymin = a->ymax; return false; } return true; } void bgExpandBox(struct screenbox *a, struct screenbox *b) { a->xmin = a->xmin < b->xmin ? a->xmin : b->xmin; a->ymin = a->ymin < b->ymin ? a->ymin : b->ymin; a->xmax = a->xmax > b->xmax ? a->xmax : b->xmax; a->ymax = a->ymax > b->ymax ? a->ymax : b->ymax; } void bgCopyBox(struct screenbox *dst, struct screenbox *src) { dst->xmin = src->xmin; dst->ymin = src->ymin; dst->xmax = src->xmax; dst->ymax = src->ymax; } bool bgRoomIsOnscreen(s32 room) { if (g_Vars.mplayerisrunning) { return (g_MpRoomVisibility[room] & 0xf) != 0; } else { return g_Rooms[room].flags & ROOMFLAG_ONSCREEN; } } bool bgRoomIsStandby(s32 room) { if (g_Vars.mplayerisrunning) { return (g_MpRoomVisibility[room] & 0xf0) != 0; } return g_Rooms[room].flags & ROOMFLAG_STANDBY; } bool bgRoomIsOnPlayerScreen(s32 room, u32 playernum) { if (g_Vars.mplayerisrunning) { return (g_MpRoomVisibility[room] & (1 << playernum)) != 0; } else { return g_Rooms[room].flags & ROOMFLAG_ONSCREEN; } } bool bgRoomIsOnPlayerStandby(s32 room, u32 playernum) { if (g_Vars.mplayerisrunning) { return (g_MpRoomVisibility[room] & (0x10 << playernum)) != 0; } else { return g_Rooms[room].flags & ROOMFLAG_STANDBY; } } s32 bgFindPortalByVertices(struct portalvertices *target) { s32 i; struct bgportal *portal = g_BgPortals; for (i = 0; portal[i].verticesoffset != 0; i++) { struct portalvertices *pvertices = (struct portalvertices *)((uintptr_t)portal + portal[i].verticesoffset); if (pvertices == target) { return i; } } return 0; } /** * Build a string showing the state of all rooms in the stage. * * The string contains "L" if a room is loaded, "." if not, and has line breaks * every 40 characters. * * Nothing is done with the string though. It's likely that debug versions of * the game would send the string to the host computer or display it on the HUD. */ void bgPrintLoadedRooms(void) { if (debugIsRoomStateDebugEnabled()) { u8 string[704]; s32 len = 0; s32 i; for (i = 1; i < g_Vars.roomcount; i++) { if ((i - 1) % 40 == 0) { if (i != 1) { string[len] = '\n'; len++; } } if (g_Rooms[i].loaded240) { string[len] = 'L'; } else { string[len] = '.'; } len++; } string[len] = '\0'; } } u32 bgInflate(u8 *src, u8 *dst, u32 len) { u32 result; u8 scratch[5120]; #if VERSION < VERSION_NTSC_1_0 char message[128]; #endif if (rzipIs1173(src)) { result = rzipInflate(src, dst, &scratch); #if VERSION < VERSION_NTSC_1_0 if (!result) { sprintf(message, "DMA-Crash %s %d Ram: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", "bg.c", 6914, src[0], src[1], src[2], src[3], src[4], src[5], src[6], src[7], src[8], src[9], src[10], src[11], src[12], src[13], src[14], src[15]); crashSetMessage(message); CRASH(); } #endif } else { result = len; bcopy(src, dst, len); } return result; } Gfx *bgGetNextGdlInBlock(struct roomblock *block, Gfx *start, Gfx *end) { \ Gfx *tmp; \ while (true) { if (block == NULL) { return end; } if (start); if (start); if (start); if (start); switch (block->type) { case ROOMBLOCKTYPE_LEAF: if (block->gdl > start && (block->gdl < end || end == NULL)) { end = block->gdl; } block = block->next; break; case ROOMBLOCKTYPE_PARENT: tmp = bgGetNextGdlInBlock(block->child, start, end); block = block->next; end = tmp; break; default: return end; } } return end; } Gfx *bgGetNextGdlInLayer(s32 roomnum, Gfx *start, u32 types) { struct roomblock *opablocks = g_Rooms[roomnum].gfxdata->opablocks; struct roomblock *xlublocks = g_Rooms[roomnum].gfxdata->xlublocks; Gfx *opagdl = NULL; Gfx *xlugdl = NULL; if ((types & VTXBATCHTYPE_OPA) && opablocks) { opagdl = bgGetNextGdlInBlock(opablocks, start, NULL); if (types == VTXBATCHTYPE_OPA) { return opagdl; } } if ((types & VTXBATCHTYPE_XLU) && xlublocks) { xlugdl = bgGetNextGdlInBlock(xlublocks, start, NULL); if (types == VTXBATCHTYPE_XLU) { return xlugdl; } } if (opagdl) { if (xlugdl && xlugdl < opagdl) { return xlugdl; } return opagdl; } return xlugdl; } Vtx *bgFindVerticesForGdl(s32 roomnum, Gfx *gdl) { struct roomblock *block = g_Rooms[roomnum].gfxdata->blocks; uintptr_t end = (uintptr_t)g_Rooms[roomnum].gfxdata->vertices; while ((uintptr_t)(block + 1) <= end) { switch (block->type) { case ROOMBLOCKTYPE_LEAF: if (gdl == block->gdl) { return block->vertices; } break; case ROOMBLOCKTYPE_PARENT: if ((uintptr_t)block->unk0c < end) { end = (uintptr_t)block->unk0c; } break; } block++; } return NULL; } /** * The rough steps to load a room are: * - Allocate some memory out of mema. * - DMA the zipped graphics data from ROM to the right side of the allocation. * - Unzip the data to the left side. * - Replace offsets within the graphics data with pointers. * - Copy the displaylists to the right side of the allocation. * - Scan the right-side displaylists, loading textures and rewriting the * displaylists, overwriting the ones on the left side. These rewritten * displaylists may be longer than the originals. * - Shrink the mema allocation to just the left side. * - Fix pointers due to the resized displaylists. * - Do some find/replaces in the displaylist based on environment settings. * - Find each batch of vertices and build a bbox for each batch. * These are used for hit detection. */ void bgLoadRoom(s32 roomnum) { s32 alloclen; s32 inflatedlen; u8 *allocation; s32 readlen; s32 fileoffset; u8 *itergdl1; u8 *itergdl2; struct roomblock *block1; struct roomblock *block2; u8 *memaddr; u8 *gfxblocks[50]; u8 *vtxblocks[50]; u8 *gdlpointers[50]; s32 numgdls; uintptr_t end1; s32 i; s32 len; #if VERSION < VERSION_NTSC_1_0 s32 stack; #endif uintptr_t end2; s32 prev; #if VERSION < VERSION_NTSC_1_0 bgVerifyLightSums("bg.c", 7076); #endif if (roomnum == 0 || roomnum >= g_Vars.roomcount) { return; } if (g_Rooms[roomnum].loaded240) { return; } // Determine how much memory to allocate. // It must be big enough to fit the biggest of: // 1. The inflated room data and compressed room data // 2. The inflated room data and the displaylists a second time. if (g_Rooms[roomnum].gfxdatalen > 0) { alloclen = g_Rooms[roomnum].gfxdatalen; if (debugIsRoomGfxExtraMemEnabled()) { alloclen += 1024; } } else { #ifdef PLATFORM_N64 alloclen = memaGetLongestFree(); #else // probably never reaches here in practice as all rooms have gfxdatalen // alloc 10k and hope for the best alloclen = 10240; #endif } #ifdef PLATFORM_64BIT alloclen = alloclen * 4; // just to be safe for now, adjust properly later #TODO #endif #ifdef PLATFORM_N64 bgGarbageCollectRooms(alloclen, false); allocation = memaAlloc(alloclen); #else // allocate room data from heap to not take up mema space allocation = sysMemAlloc(alloclen); #endif if (allocation != NULL) { dyntexSetCurrentRoom(roomnum); // Calculate the file offset and read length // of the compressed room data in the BG file readlen = ((g_BgRooms[roomnum + 1].unk00 - g_BgRooms[roomnum].unk00) + 0xf) & ~0xf; fileoffset = (g_BgPrimaryData + g_BgRooms[roomnum].unk00 - g_BgPrimaryData) - 0x0f000000; fileoffset -= var8007fc54; if (readlen > alloclen) { dyntexSetCurrentRoom(-1); return; } // Load the compressed data to the right side of the allocation memaddr = allocation + (alloclen - readlen); bgLoadFile(memaddr, fileoffset, readlen); if (rzipIs1173(memaddr) && readlen + 0x20 > alloclen) { dyntexSetCurrentRoom(-1); return; } // Inflate the data to the left side of the allocation inflatedlen = bgInflate(memaddr, allocation, g_BgRooms[roomnum + 1].unk00 - g_BgRooms[roomnum].unk00); #ifndef PLATFORM_N64 inflatedlen = preprocessBgRoom(allocation, inflatedlen, g_BgRooms[roomnum].unk00); #endif g_Rooms[roomnum].gfxdata = (struct roomgfxdata *)allocation; // Promote offsets to pointers in the gfxdata header if (g_Rooms[roomnum].gfxdata->vertices) { g_Rooms[roomnum].gfxdata->vertices = (Vtx *) (allocation + ((uintptr_t) g_Rooms[roomnum].gfxdata->vertices - g_BgRooms[roomnum].unk00)); } if (g_Rooms[roomnum].gfxdata->colours) { g_Rooms[roomnum].gfxdata->colours = (Col *) (allocation + ((uintptr_t) g_Rooms[roomnum].gfxdata->colours - g_BgRooms[roomnum].unk00)); } if (g_Rooms[roomnum].gfxdata->opablocks) { g_Rooms[roomnum].gfxdata->opablocks = (struct roomblock *) (allocation + ((uintptr_t) g_Rooms[roomnum].gfxdata->opablocks - g_BgRooms[roomnum].unk00)); } if (g_Rooms[roomnum].gfxdata->xlublocks) { g_Rooms[roomnum].gfxdata->xlublocks = (struct roomblock *) (allocation + ((uintptr_t) g_Rooms[roomnum].gfxdata->xlublocks - g_BgRooms[roomnum].unk00)); } // Promote offsets to pointers in each gfxdata block end1 = (uintptr_t)g_Rooms[roomnum].gfxdata->vertices; for (block1 = g_Rooms[roomnum].gfxdata->blocks; (intptr_t) (block1 + 1) <= end1; block1++) { switch (block1->type) { case ROOMBLOCKTYPE_LEAF: if (block1->next != NULL) { block1->next = (struct roomblock *) (allocation + ((uintptr_t) block1->next - g_BgRooms[roomnum].unk00)); } if (block1->gdl != 0) { block1->gdl = (Gfx *) (allocation + ((uintptr_t) block1->gdl - g_BgRooms[roomnum].unk00)); } if (block1->vertices != 0) { block1->vertices = (Vtx *) (allocation + ((uintptr_t) block1->vertices - g_BgRooms[roomnum].unk00)); } if (block1->colours != 0) { block1->colours = (Col *) (allocation + ((uintptr_t) block1->colours - g_BgRooms[roomnum].unk00)); } break; case ROOMBLOCKTYPE_PARENT: if (block1->next != NULL) { block1->next = (struct roomblock *) (allocation + ((uintptr_t) block1->next - g_BgRooms[roomnum].unk00)); } if (block1->gdl != 0) { block1->gdl = (Gfx *) (allocation + ((uintptr_t) block1->gdl - g_BgRooms[roomnum].unk00)); } if (block1->vertices != 0) { block1->vertices = (Vtx *) (allocation + ((uintptr_t) block1->vertices - g_BgRooms[roomnum].unk00)); } if (block1->colours != 0) { block1->colours = (Col *) (allocation + ((uintptr_t) block1->colours - g_BgRooms[roomnum].unk00)); } if ((uintptr_t) block1->vertices < end1) { end1 = (uintptr_t) block1->vertices; } break; } } // Calculate the number of vertices and colours g_Rooms[roomnum].gfxdata->numvertices = ((uintptr_t) g_Rooms[roomnum].gfxdata->colours - (uintptr_t) g_Rooms[roomnum].gfxdata->vertices) / sizeof(Vtx); g_Rooms[roomnum].gfxdata->numcolours = ((uintptr_t) bgGetNextGdlInLayer(roomnum, 0, VTXBATCHTYPE_OPA | VTXBATCHTYPE_XLU) - (uintptr_t) g_Rooms[roomnum].gfxdata->colours) / sizeof(Col); // Build arrays of pointers to gfx blocks and vtx blocks numgdls = 0; itergdl1 = (u8 *) bgGetNextGdlInLayer(roomnum, NULL, VTXBATCHTYPE_OPA | VTXBATCHTYPE_XLU); while (itergdl1) { gfxblocks[numgdls] = (u8 *) itergdl1; vtxblocks[numgdls] = (u8 *) bgFindVerticesForGdl(roomnum, (Gfx *) itergdl1); numgdls++; itergdl1 = (u8 *) bgGetNextGdlInLayer(roomnum, (Gfx *) itergdl1, VTXBATCHTYPE_OPA | VTXBATCHTYPE_XLU); } gfxblocks[numgdls] = allocation + inflatedlen; // Copy gdls to the right-side of the allocation // and build a pointer array to them texCopyGdls((void *) gfxblocks[0], (void *) (allocation + alloclen - (gfxblocks[numgdls] - gfxblocks[0])), (uintptr_t) (gfxblocks[numgdls] - gfxblocks[0])); for (i = 0; i < numgdls + 1; i++) { gdlpointers[i] = gfxblocks[i] + (allocation + alloclen - gfxblocks[numgdls]); } // Load textures by scanning the right-side gdls. // texLoadFromGdl is reading from gdlpointers and writing new GBI commands // to itergdl2, overwriting the GBI commands that were loaded from the // BG file. As these are being processed the gdlpointers pointers are // changed to point to the written GBI. itergdl2 = gfxblocks[0]; for (i = 0; i < numgdls; i++) { s32 byteswritten; len = gfxblocks[i + 1] - gfxblocks[i]; byteswritten = texLoadFromGdl((void *) gdlpointers[i], len, (void *) itergdl2, NULL, vtxblocks[i]); gdlpointers[i] = itergdl2; if (len); itergdl2 = (u8 *) ALIGN8((uintptr_t) (itergdl2 + byteswritten)); } gdlpointers[numgdls] = itergdl2; // Free the right side of the allocation prev = g_Rooms[roomnum].gfxdatalen; g_Rooms[roomnum].gfxdatalen = ALIGN16(gdlpointers[numgdls] - allocation + 0x20); if (g_Rooms[roomnum].gfxdatalen > prev) { #if VERSION < VERSION_NTSC_1_0 crashSetMessage("bg.c: roominf[room].allocsize > calculated!"); CRASH(); #endif } g_Rooms[roomnum].loaded240 = 1; #ifdef PLATFORM_N64 if (g_Rooms[roomnum].gfxdatalen != alloclen) { memaRealloc((intptr_t) allocation, alloclen, g_Rooms[roomnum].gfxdatalen); } #endif // Update gdl pointers in the gfxdata so they point to the ones // that have been processed by textLoadFromGdl. block2 = g_Rooms[roomnum].gfxdata->blocks; end2 = (uintptr_t) g_Rooms[roomnum].gfxdata->vertices; while ((intptr_t) (block2 + 1) <= end2) { switch (block2->type) { case ROOMBLOCKTYPE_LEAF: if (block2->gdl) { for (i = 0; i < numgdls; i++) { memaddr = (u8 *) block2->gdl; // reusing var if (memaddr == gfxblocks[i]) { block2->gdl = (Gfx *) gdlpointers[i]; break; } } } break; case ROOMBLOCKTYPE_PARENT: if ((uintptr_t) block2->unk0c < end2) { end2 = (uintptr_t) block2->unk0c; } break; } block2++; } // Do some find/replaces in the gdls based on environment configuration if (g_FogEnabled) { gfxReplaceGbiCommandsRecursively(g_Rooms[roomnum].gfxdata->opablocks, 1); gfxReplaceGbiCommandsRecursively(g_Rooms[roomnum].gfxdata->xlublocks, 5); } else if (!g_EnvHasTransparency) { gfxReplaceGbiCommandsRecursively(g_Rooms[roomnum].gfxdata->opablocks, 6); gfxReplaceGbiCommandsRecursively(g_Rooms[roomnum].gfxdata->xlublocks, 7); } // Create vertex batches - these are used for hit detection bgFindRoomVtxBatches(roomnum); g_Rooms[roomnum].flags |= ROOMFLAG_LIGHTS_DIRTY; g_Rooms[roomnum].flags |= ROOMFLAG_BRIGHTNESS_DIRTY_PERM; g_Rooms[roomnum].colours = NULL; dyntexSetCurrentRoom(-1); #if VERSION < VERSION_NTSC_1_0 bgVerifyLightSums("bg.c", 7474); #endif } } const char var7f1b7420[] = "Checking Convex Room %d"; const char var7f1b7438[] = " Portal %d %s%s%.1f < %.1f"; const char var7f1b7454[] = ""; const char var7f1b7458[] = ""; const char var7f1b745c[] = " Convex Room Failed (1)"; const char var7f1b7474[] = " Portal %d %s%s%.1f > %.1f"; const char var7f1b7490[] = ""; const char var7f1b7494[] = ""; const char var7f1b7498[] = " Convex Room Failed (0)"; const char var7f1b74b0[] = "Checking Concave Room %d"; const char var7f1b74cc[] = " Checking Portal %d"; const char var7f1b74e0[] = "Reject P:%d (%s%s%.1f %.1f n3=%.1f)"; const char var7f1b7504[] = ""; const char var7f1b7508[] = ""; const char var7f1b750c[] = "Reject P:%d (%s%s%.1f %.1f n4=%.1f)"; const char var7f1b7530[] = ""; const char var7f1b7534[] = ""; const char var7f1b7538[] = " Full %d%s%s %.1f %.1f (%.1f %.1f)"; const char var7f1b755c[] = ""; const char var7f1b7560[] = ""; const char var7f1b7564[] = " Failed 2 - Crossed portal %d"; const char var7f1b7584[] = " Failed 1 - Crossed portal %d"; const char var7f1b75a4[] = " Passed"; void bgUnloadRoom(s32 roomnum) { u32 size; if (g_Rooms[roomnum].vtxbatches) { #ifdef PLATFORM_N64 size = ((g_Rooms[roomnum].numvtxbatches) * sizeof(struct vtxbatch) + 0xf) & ~0xf; memaFree(g_Rooms[roomnum].vtxbatches, size); #else sysMemFree(g_Rooms[roomnum].vtxbatches); #endif g_Rooms[roomnum].vtxbatches = NULL; } if (g_Rooms[roomnum].gfxdatalen > 0) { #ifdef PLATFORM_N64 size = g_Rooms[roomnum].gfxdatalen; memaFree(g_Rooms[roomnum].gfxdata, size); #else sysMemFree(g_Rooms[roomnum].gfxdata); #endif g_Rooms[roomnum].gfxdata = NULL; } g_Rooms[roomnum].loaded240 = 0; } void bgUnloadAllRooms(void) { s32 i; for (i = 1; i < g_Vars.roomcount; i++) { if (g_Rooms[i].loaded240) { bgUnloadRoom(i); } } } /** * Find rooms which were recently visible and not yet unloaded, and unload them * until the given bytesneeded amount is available in mema. * * Rooms are unloaded in order of least recently visible. * * If there's still not enough space after 30 unloads and the desparate argument * is true, do a final iteration through all the rooms and free everything * that's not visible. */ void bgGarbageCollectRooms(s32 bytesneeded, bool desparate) { #ifdef PLATFORM_N64 // don't need this on PC as rooms are allocated from heap s32 bytesfree = memaGetLongestFree(); s32 oldestroom; s32 oldesttimer; s32 count = 0; s32 i; while (bytesfree < bytesneeded) { oldestroom = 0; oldesttimer = 0; for (i = 1; i < g_Vars.roomcount; i++) { if (g_Rooms[i].loaded240 > oldesttimer) { oldestroom = i; oldesttimer = g_Rooms[i].loaded240; } } if (oldestroom != 0) { bgUnloadRoom(oldestroom); memaDefrag(); } bytesfree = memaGetLongestFree(); count++; if (count == 30) { if (desparate == true) { for (i = 1; i < g_Vars.roomcount; i++) { #if VERSION >= VERSION_NTSC_1_0 if (g_Rooms[i].loaded240 > 8) #else if (g_Rooms[i].loaded240) #endif { bgUnloadRoom(i); memaDefrag(); if (memaGetLongestFree() >= bytesneeded) { return; } } } } break; } } #endif } /** * Increase the loaded240 timers for rooms which are no longer visible. * If any rooms have reached the timer limit then unload them, but don't unload * more than 2 rooms per frame. */ void bgTickRooms(void) { s32 numunloaded = 0; s32 i; for (i = 1; i < g_Vars.roomcount; i++) { if (g_Rooms[i].loaded240) { #if VERSION >= VERSION_NTSC_1_0 g_Rooms[i].loaded240++; #else g_Rooms[i].loaded240 += g_Vars.lvupdate240; #endif if (g_Rooms[i].loaded240 >= g_BgUnloadDelay240) { g_Rooms[i].loaded240 = g_BgUnloadDelay240; } if (g_Rooms[i].flags & ROOMFLAG_ONSCREEN) { g_Rooms[i].loaded240 = 1; } if (numunloaded < 2 && g_Rooms[i].loaded240 == g_BgUnloadDelay240_2) { bgUnloadRoom(i); #if VERSION >= VERSION_NTSC_1_0 memaDefrag(); #endif numunloaded++; } } } } Gfx *bgRenderRoomPass(Gfx *gdl, s32 roomnum, struct roomblock *block, bool arg3) { uintptr_t v0; if (block == NULL) { return gdl; } switch (block->type) { case ROOMBLOCKTYPE_LEAF: if (g_Rooms[roomnum].flags & ROOMFLAG_HASDYNTEX) { dyntexTickRoom(roomnum, block->vertices); } gSPSegment(gdl++, SPSEGMENT_BG_VTX, OS_PHYSICAL_TO_K0(block->vertices)); roomHighlight(roomnum); v0 = (uintptr_t)g_Rooms[roomnum].colours; if (v0 != NULL) { #ifdef PLATFORM_N64 s32 addr = ALIGN8((uintptr_t)&g_Rooms[roomnum].gfxdata->vertices[g_Rooms[roomnum].gfxdata->numvertices]); v0 += (((intptr_t)block->colours - addr) >> 2) * 4; #else uintptr_t addr = ALIGN8((uintptr_t)&g_Rooms[roomnum].gfxdata->vertices[g_Rooms[roomnum].gfxdata->numvertices]); v0 += (((uintptr_t)block->colours - addr) >> 2) * 4; #endif } else { v0 = (uintptr_t)block->colours; } gSPSegment(gdl++, SPSEGMENT_BG_COL, OS_PHYSICAL_TO_K0(v0)); gSPDisplayList(gdl++, OS_PHYSICAL_TO_K0(block->gdl)); if (arg3) { gdl = bgRenderRoomPass(gdl, roomnum, block->next, true); } break; case ROOMBLOCKTYPE_PARENT: if (block->child != NULL) { struct roomblock *sp58; struct roomblock *sp54; struct coord *coord; f32 sum; f32 sp40[3]; f32 sp34[3]; u32 stack; sp58 = block->child; sp54 = sp58->next; coord = block->unk0c; sp40[0] = coord[1].f[0]; sp40[1] = coord[1].f[1]; sp40[2] = coord[1].f[2]; sp34[0] = coord[0].f[0] - g_Vars.currentplayer->cam_pos.f[0]; sp34[1] = coord[0].f[1] - g_Vars.currentplayer->cam_pos.f[1]; sp34[2] = coord[0].f[2] - g_Vars.currentplayer->cam_pos.f[2]; sum = sp40[0] * sp34[0] + sp40[1] * sp34[1] + sp40[2] * sp34[2]; if (sum < 0.0f) { gdl = bgRenderRoomPass(gdl, roomnum, sp58, false); gdl = bgRenderRoomPass(gdl, roomnum, sp54, false); } else { gdl = bgRenderRoomPass(gdl, roomnum, sp54, false); gdl = bgRenderRoomPass(gdl, roomnum, sp58, false); } if (arg3) { gdl = bgRenderRoomPass(gdl, roomnum, block->next, true); } } break; } return gdl; } /** * Render the opaque layer of the room. */ Gfx *bgRenderRoomOpaque(Gfx *gdl, s32 roomnum) { if (g_Rooms[roomnum].loaded240 == 0) { return gdl; } gdl = roomApplyMtx(gdl, roomnum); gdl = lightsSetForRoom(gdl, roomnum); gdl = bgRenderRoomPass(gdl, roomnum, g_Rooms[roomnum].gfxdata->opablocks, true); gdl = lightsSetDefault(gdl); g_Rooms[roomnum].loaded240 = 1; return gdl; } /** * Render the transparency layer of the room. */ Gfx *bgRenderRoomXlu(Gfx *gdl, s32 roomnum) { u32 stack; if (roomnum == 0 || roomnum >= g_Vars.roomcount) { return gdl; } if (g_Rooms[roomnum].loaded240) { if (g_Rooms[roomnum].gfxdata->xlublocks == NULL) { return gdl; } roomHighlight(roomnum); if (g_Rooms[roomnum].gfxdata); if (g_Rooms[roomnum].gfxdata); gdl = roomApplyMtx(gdl, roomnum); gdl = bgRenderRoomPass(gdl, roomnum, g_Rooms[roomnum].gfxdata->xlublocks, true); g_Rooms[roomnum].loaded240 = 1; } else { bgLoadRoom(roomnum); } return gdl; } s32 bgPopulateVtxBatchType(s32 roomnum, struct vtxbatch *batches, Gfx *gdl, s32 batchindex, Vtx *vertices, s32 type) { s32 i; s32 j; s32 numvertices; Vtx *batchvertices; for (i = 0; gdl[i].dma.cmd != G_ENDDL; i++) { if (gdl[i].dma.cmd == G_VTX) { batches[batchindex].gdl = gdl; batches[batchindex].gbicmdindex = i; batches[batchindex].type = type; for (j = 0; j < 3; j++) { batches[batchindex].bbmin.f[j] = 32767.0f; batches[batchindex].bbmax.f[j] = -32768.0f; } numvertices = (((u32)gdl[i].bytes[GFX_W0_BYTE(1)] >> 4) & 0xf) + 1; batchvertices = (Vtx *)((uintptr_t)vertices + (UNSEGADDR(gdl[i].words.w1) & 0xffffff)); for (j = 0; j < numvertices; j++) { f32 x = batchvertices[j].x; f32 y = batchvertices[j].y; f32 z = batchvertices[j].z; if (x < batches[batchindex].bbmin.x) { batches[batchindex].bbmin.x = x; } if (y < batches[batchindex].bbmin.y) { batches[batchindex].bbmin.y = y; } if (z < batches[batchindex].bbmin.z) { batches[batchindex].bbmin.z = z; } if (x > batches[batchindex].bbmax.x) { batches[batchindex].bbmax.x = x; } if (y > batches[batchindex].bbmax.y) { batches[batchindex].bbmax.y = y; } if (z > batches[batchindex].bbmax.z) { batches[batchindex].bbmax.z = z; } } if (batches[batchindex].bbmin.x == batches[batchindex].bbmax.x) { batches[batchindex].bbmax.x++; } if (batches[batchindex].bbmin.y == batches[batchindex].bbmax.y) { batches[batchindex].bbmax.y++; } if (batches[batchindex].bbmin.z == batches[batchindex].bbmax.z) { batches[batchindex].bbmax.z++; } batches[batchindex].bbmin.x += g_BgRooms[roomnum].pos.x; batches[batchindex].bbmin.y += g_BgRooms[roomnum].pos.y; batches[batchindex].bbmin.z += g_BgRooms[roomnum].pos.z; batches[batchindex].bbmax.x += g_BgRooms[roomnum].pos.x; batches[batchindex].bbmax.y += g_BgRooms[roomnum].pos.y; batches[batchindex].bbmax.z += g_BgRooms[roomnum].pos.z; batchindex++; } } return batchindex; } void bgFindRoomVtxBatches(s32 roomnum) { s32 i; s32 batchindex = 0; s32 xlucount; Gfx *gdl; struct vtxbatch *batches; if (g_Rooms[roomnum].vtxbatches == NULL) { gdl = bgGetNextGdlInLayer(roomnum, NULL, VTXBATCHTYPE_OPA); if (gdl != NULL) { while (gdl) { for (i = 0; gdl[i].dma.cmd != G_ENDDL; i++) { // if gSPVertex if (gdl[i].dma.cmd == G_VTX) { batchindex++; } } gdl = bgGetNextGdlInLayer(roomnum, gdl, VTXBATCHTYPE_OPA); } xlucount = 0; gdl = bgGetNextGdlInLayer(roomnum, NULL, VTXBATCHTYPE_XLU); while (gdl) { for (i = 0; gdl[i].dma.cmd != G_ENDDL; i++) { // if gSPVertex if (gdl[i].dma.cmd == G_VTX) { xlucount++; } } gdl = bgGetNextGdlInLayer(roomnum, gdl, VTXBATCHTYPE_XLU); } batchindex += xlucount; #ifdef PLATFORM_N64 batches = memaAlloc((batchindex * sizeof(struct vtxbatch) + 0xf) & ~0xf); #else batches = sysMemAlloc((batchindex * sizeof(struct vtxbatch) + 0xf) & ~0xf); #endif if (batches != NULL) { gdl = bgGetNextGdlInLayer(roomnum, NULL, VTXBATCHTYPE_OPA); batchindex = 0; g_Rooms[roomnum].vtxbatches = batches; while (gdl) { Vtx *vertices = bgFindVerticesForGdl(roomnum, gdl); batchindex = bgPopulateVtxBatchType(roomnum, batches, gdl, batchindex, vertices, VTXBATCHTYPE_OPA); gdl = bgGetNextGdlInLayer(roomnum, gdl, VTXBATCHTYPE_OPA); } if (xlucount) { gdl = bgGetNextGdlInLayer(roomnum, NULL, VTXBATCHTYPE_XLU); while (gdl) { Vtx *vertices = bgFindVerticesForGdl(roomnum, gdl); batchindex = bgPopulateVtxBatchType(roomnum, batches, gdl, batchindex, vertices, VTXBATCHTYPE_XLU); gdl = bgGetNextGdlInLayer(roomnum, gdl, VTXBATCHTYPE_XLU); } } g_Rooms[roomnum].numvtxbatches = (s16)batchindex; } } } } bool bgTestLineIntersectsIntBbox(struct coord *arg0, struct coord *arg1, s32 *arg2, s32 *arg3) { struct coord arg2f; struct coord arg3f; arg2f.x = arg2[0]; arg2f.y = arg2[1]; arg2f.z = arg2[2]; arg3f.x = arg3[0]; arg3f.y = arg3[1]; arg3f.z = arg3[2]; return bgTestLineIntersectsBbox(arg0, arg1, &arg2f, &arg3f); } bool bgTestLineIntersectsBbox(struct coord *arg0, struct coord *arg1, struct coord *arg2, struct coord *arg3) { u32 stack[4]; f32 f0; f32 f0_2; f32 f2; f32 f2_2; f32 f6; f32 f10; f32 sp34; f32 sp30; f32 f16; f32 f18; f32 f18_2; f32 sp20; f32 f12; f32 f12_2; f32 f14; f32 f14_2; // x f18 = arg1->x; f16 = arg3->x - arg0->x; f14 = arg2->x - arg0->x; if (f18 < 0.0f) { f18 = -f18; f14 = -f14; f16 = -f16; } if (f14 < 0.0f && f16 < 0.0f) { return false; } if (f16 < f14) { f32 tmp = f14; f14 = f16; f16 = tmp; } // y f12 = arg1->y; f2 = arg3->y - arg0->y; f0 = arg2->y - arg0->y; if (f12 < 0.0f) { f12 = -f12; f0 = -f0; f2 = -f2; } if (f0 < 0.0f && f2 < 0.0f) { return false; } if (f2 < f0) { sp20 = f0; f0 = f2; f2 = sp20; } f6 = f14 * f12; f10 = f0 * f18; if (f10 < f6) { if (f2 * f18 < f6) { return false; } sp34 = f14; sp30 = f18; } else { if (f16 * f12 < f10) { return false; } sp34 = f0; sp30 = f12; } if (f16 * f12 < f2 * f18) { f0_2 = f16; f14_2 = f18; } else { f0_2 = f2; f14_2 = f12; } // z f2_2 = arg1->z; f12_2 = arg3->z - arg0->z; f18_2 = arg2->z - arg0->z; if (f2_2 < 0.0f) { f2_2 = -f2_2; f18_2 = -f18_2; f12_2 = -f12_2; } if (f18_2 < 0.0f && f12_2 < 0.0f) { return false; } if (f12_2 < f18_2) { f32 tmp = f18_2; f18_2 = f12_2; f12_2 = tmp; } if (sp34 * f2_2 < f18_2 * sp30) { if (f0_2 * f2_2 < f18_2 * f14_2) { return false; } } else { if (f12_2 * sp30 < sp34 * f2_2) { return false; } } return true; } bool bgTestHitOnObj(struct coord *arg0, struct coord *arg1, struct coord *arg2, Gfx *gdl, Gfx *gdl2, Vtx *vertices, struct hitthing *hitthing) { s16 stack; s16 triref; s32 trisremaining; bool intersectsbbox; f32 *ptr; f32 tmp; f32 sqdist; bool hit = false; struct coord *point1; struct coord *point2; struct coord *point3; Vtx *vtx; Gfx *imggdl = NULL; s32 texturenum; f32 lowestsqdist = MAXFLOAT; uintptr_t offset; s32 numvertices; Gfx *tri4gdl; s32 count; struct coord min; struct coord max; struct coord sp8c; struct coord sp80; s32 points[3]; while (true) { if (gdl->dma.cmd == G_ENDDL) { imggdl = NULL; if (gdl2 != NULL) { gdl = gdl2; gdl2 = NULL; continue; } break; } else if (gdl->dma.cmd == G_VTX) { ptr = var800a6470; count = gdl->bytes[GFX_W0_BYTE(1)] & 0xf; #ifdef PLATFORM_N64 offset = (UNSEGADDR(gdl->words.w1) & 0xffffff); #else if (gdl->words.w1 & 1) { // segmented address offset = (UNSEGADDR(gdl->words.w1) & 0xffffff); } else { // linear address offset = gdl->words.w1 - (uintptr_t)vertices; } #endif numvertices = (((u32) gdl->bytes[GFX_W0_BYTE(1)] >> 4) & 0xf) + 1; vtx = (Vtx *)((uintptr_t)vertices + offset); vtx -= count; ptr[0] = vtx->x; ptr[1] = vtx->y; ptr[2] = vtx->z; min.x = ptr[0]; max.x = ptr[0]; min.y = ptr[1]; max.y = ptr[1]; min.z = ptr[2]; max.z = ptr[2]; ptr += 3; vtx++; numvertices--; while (numvertices > 0) { ptr[0] = vtx->x; ptr[1] = vtx->y; ptr[2] = vtx->z; if (ptr[0] < min.x) { min.x = ptr[0]; } if (ptr[1] < min.y) { min.y = ptr[1]; } if (ptr[2] < min.z) { min.z = ptr[2]; } if (ptr[0] > max.x) { max.x = ptr[0]; } if (ptr[1] > max.y) { max.y = ptr[1]; } if (ptr[2] > max.z) { max.z = ptr[2]; } vtx++; numvertices--; ptr += 3; } intersectsbbox = true; if (arg0->x < min.x) { if (arg1->x < min.x) { intersectsbbox = false; } } else if (arg0->x > max.x) { if (arg1->x > max.x) { intersectsbbox = false; } } if (arg0->y < min.y) { if (arg1->y < min.y) { intersectsbbox = false; } } else if (arg0->y > max.y) { if (arg1->y > max.y) { intersectsbbox = false; } } if (arg0->z < min.z) { if (arg1->z < min.z) { intersectsbbox = false; } } else if (arg0->z > max.z) { if (arg1->z > max.z) { intersectsbbox = false; } } if (intersectsbbox) { intersectsbbox = bgTestLineIntersectsBbox(arg0, arg2, &min, &max); } } else if (gdl->dma.cmd == (s8)G_SETTIMG) { imggdl = gdl; } else { if (!intersectsbbox) { gdl++; continue; } if (gdl->dma.cmd != G_TRI1 && gdl->dma.cmd != G_TRI4) { gdl++; continue; } if (gdl->dma.cmd == G_TRI1) { trisremaining = 0; triref = 0; points[0] = gdl->tri.tri.v[GFX_TRI_VTX(0)] / 10; points[1] = gdl->tri.tri.v[GFX_TRI_VTX(1)] / 10; points[2] = gdl->tri.tri.v[GFX_TRI_VTX(2)] / 10; } else if (gdl->dma.cmd == G_TRI4) { tri4gdl = gdl; trisremaining = 3; triref = 1; points[0] = gdl->tri4.x1; points[1] = gdl->tri4.y1; points[2] = gdl->tri4.z1; } do { if (points[0] == 0 && points[1] == 0 && points[2] == 0) { break; } point1 = (struct coord *) (var800a6470 + points[0] * 3); point2 = (struct coord *) (var800a6470 + points[1] * 3); point3 = (struct coord *) (var800a6470 + points[2] * 3); min.x = point1->x; max.x = point1->x; if (point2->x < min.x) { min.x = point2->x; } if (point2->x > max.x) { max.x = point2->x; } if (point3->x < min.x) { min.x = point3->x; } if (point3->x > max.x) { max.x = point3->x; } if (!(arg0->x < min.x && arg1->x < min.x) && !(arg0->x > max.x && arg1->x > max.x)) { min.z = point1->z; max.z = point1->z; if (point2->z < min.z) { min.z = point2->z; } if (point2->z > max.z) { max.z = point2->z; } if (point3->z < min.z) { min.z = point3->z; } if (point3->z > max.z) { max.z = point3->z; } if (!(arg0->z < min.z && arg1->z < min.z) && !(arg0->z > max.z && arg1->z > max.z)) { min.y = point1->y; max.y = point1->y; if (point2->y < min.y) { min.y = point2->y; } if (point2->y > max.y) { max.y = point2->y; } if (point3->y < min.y) { min.y = point3->y; } if (point3->y > max.y) { max.y = point3->y; } if (!(arg0->y < min.y && arg1->y < min.y) && !(arg0->y > max.y && arg1->y > max.y)) { if (bgTestLineIntersectsBbox(arg0, arg2, &min, &max) && func0002f560(point1, point2, point3, NULL, arg0, arg1, arg2, &sp8c, &sp80)) { tmp = sp8c.x - arg0->x; sqdist = tmp * tmp; tmp = sp8c.y - arg0->y; sqdist += tmp * tmp; tmp = sp8c.z - arg0->z; sqdist += tmp * tmp; if (sqdist < lowestsqdist) { hit = true; if (imggdl == NULL #ifdef PLATFORM_N64 || (imggdl->words.w1 & 0x0f000000) == 0x0f000000 || (imggdl->words.w1 & 0x05000000) == 0x05000000) { #else // not sure if the above check even works right on N64, but we can test easily for seg addresses || (imggdl->words.w1 & 1)) { #endif texturenum = -1; } else { #ifdef PLATFORM_N64 s32 tmp = PHYS_TO_K0(UNSEGADDR(imggdl->words.w1) - 8); #else uintptr_t tmp = PHYS_TO_K0(UNSEGADDR(imggdl->words.w1) - 8); #endif texturenum = *(s16 *) tmp; } lowestsqdist = sqdist; hitthing->pos.x = sp8c.x; hitthing->pos.y = sp8c.y; hitthing->pos.z = sp8c.z; hitthing->unk0c.x = sp80.x; hitthing->unk0c.y = sp80.y; hitthing->unk0c.z = sp80.z; hitthing->unk18 = &vtx[points[0]]; hitthing->unk1c = &vtx[points[1]]; hitthing->unk20 = &vtx[points[2]]; hitthing->texturenum = texturenum; hitthing->tricmd = gdl; hitthing->unk28 = triref; } } } } } trisremaining--; if (trisremaining == 2) { points[0] = tri4gdl->tri4.x2; points[1] = tri4gdl->tri4.y2; points[2] = tri4gdl->tri4.z2; triref = 2; } else if (trisremaining == 1) { points[0] = tri4gdl->tri4.x3; points[1] = tri4gdl->tri4.y3; points[2] = tri4gdl->tri4.z3; triref = 3; } else if (trisremaining == 0) { points[0] = tri4gdl->tri4.x4; points[1] = tri4gdl->tri4.y4; points[2] = tri4gdl->tri4.z4; triref = 1; } } while (trisremaining >= 0); } gdl++; } return hit; } bool bgTestHitOnChr(struct model *model, struct coord *arg1, struct coord *arg2, struct coord *arg3, Gfx *gdl, Gfx *gdl2, Vtx *vertices, f32 *sqdistptr, struct hitthing *hitthing) { s16 triref; s32 i; bool intersectsbbox; s32 count; s32 spdc; s32 spd8; s32 numvertices; f32 *ptr; bool hit; f32 tmp; f32 sqdist; Vtx *vtx; struct coord *point1; struct coord *point2; struct coord *point3; u32 word; Gfx *tri4gdl; Mtxf *mtx; struct coord min; struct coord max; struct coord sp84; struct coord sp78; s32 points[3]; spdc = 16; spd8 = 0; hit = false; while (true) { if (gdl->dma.cmd == G_ENDDL) { if (gdl2 != NULL) { gdl = gdl2; gdl2 = NULL; continue; } break; } else if (gdl->dma.cmd == G_MTX) { word = UNSEGADDR(gdl->words.w1) & 0xffffff; i = word / sizeof(Mtxf); mtx = &model->matrices[i]; } else if (gdl->dma.cmd == G_VTX) { count = (gdl->bytes[GFX_W0_BYTE(1)] & 0xf); word = UNSEGADDR(gdl->words.w1) & 0xffffff; numvertices = ((u32) gdl->bytes[GFX_W0_BYTE(1)] >> 4) + 1; vtx = (Vtx *)((uintptr_t)vertices + word); if (count < spdc) { spdc = count; } if (numvertices + count > spd8) { spd8 = numvertices + count; } ptr = &var800a6470[count * 3]; while (numvertices > 0) { if (1); ptr[0] = vtx->x; ptr[1] = vtx->y; ptr[2] = vtx->z; mtx4TransformVecInPlace(mtx, (struct coord *) ptr); numvertices--; ptr += 3; vtx++; } ptr = &var800a6470[spdc]; min.x = ptr[0]; max.x = ptr[0]; min.y = ptr[1]; max.y = ptr[1]; min.z = ptr[2]; max.z = ptr[2]; ptr += 3; for (i = spdc; i < spd8; i++) { if (ptr[0] < min.x) { min.x = ptr[0]; } if (ptr[1] < min.y) { min.y = ptr[1]; } if (ptr[2] < min.z) { min.z = ptr[2]; } if (ptr[0] > max.x) { max.x = ptr[0]; } if (ptr[1] > max.y) { max.y = ptr[1]; } if (ptr[2] > max.z) { max.z = ptr[2]; } ptr += 3; } if ((arg1->x < min.x && arg2->x < min.x) || (arg1->x > max.x && arg2->x > max.x) || (arg1->y < min.y && arg2->y < min.y) || (arg1->y > max.y && arg2->y > max.y) || (arg1->z < min.z && arg2->z < min.z) || (arg1->z > max.z && arg2->z > max.z)) { intersectsbbox = false; } else { intersectsbbox = bgTestLineIntersectsBbox(arg1, arg3, &min, &max); } } else { if (!intersectsbbox) { gdl++; continue; } if ((gdl->dma.cmd != G_TRI1 && gdl->dma.cmd != G_TRI4)) { gdl++; continue; } if (gdl->dma.cmd == G_TRI1) { i = 0; triref = 0; points[0] = gdl->tri.tri.v[GFX_TRI_VTX(0)] / 10; points[1] = gdl->tri.tri.v[GFX_TRI_VTX(1)] / 10; points[2] = gdl->tri.tri.v[GFX_TRI_VTX(2)] / 10; } else if (gdl->dma.cmd == G_TRI4) { tri4gdl = gdl; i = 3; triref = 1; points[0] = gdl->tri4.x1; points[1] = gdl->tri4.y1; points[2] = gdl->tri4.z1; } do { if (points[0] == 0 && points[1] == 0 && points[2] == 0) { break; } point1 = (struct coord *) (var800a6470 + points[0] * 3); point2 = (struct coord *) (var800a6470 + points[1] * 3); point3 = (struct coord *) (var800a6470 + points[2] * 3); min.x = point1->x; max.x = point1->x; if (point2->x < min.x) { min.x = point2->x; } if (point2->x > max.x) { max.x = point2->x; } if (point3->x < min.x) { min.x = point3->x; } if (point3->x > max.x) { max.x = point3->x; } if (!(arg1->x < min.x && arg2->x < min.x) && !(arg1->x > max.x && arg2->x > max.x)) { min.z = point1->z; max.z = point1->z; if (point2->z < min.z) { min.z = point2->z; } if (point2->z > max.z) { max.z = point2->z; } if (point3->z < min.z) { min.z = point3->z; } if (point3->z > max.z) { max.z = point3->z; } if (!(arg1->z < min.z && arg2->z < min.z) && !(arg1->z > max.z && arg2->z > max.z)) { min.y = point1->y; max.y = point1->y; if (point2->y < min.y) { min.y = point2->y; } if (point2->y > max.y) { max.y = point2->y; } if (point3->y < min.y) { min.y = point3->y; } if (point3->y > max.y) { max.y = point3->y; } if (!(arg1->y < min.y && arg2->y < min.y) && !(arg1->y > max.y && arg2->y > max.y)) { if (bgTestLineIntersectsBbox(arg1, arg3, &min, &max) && func0002f560(point1, point2, point3, NULL, arg1, arg2, arg3, &sp84, &sp78)) { tmp = sp84.x - arg1->x; sqdist = tmp * tmp; tmp = sp84.y - arg1->y; sqdist += tmp * tmp; tmp = sp84.z - arg1->z; sqdist += tmp * tmp; if (sqdist < *sqdistptr) { hit = true; *sqdistptr = sqdist; hitthing->pos.x = sp84.x; hitthing->pos.y = sp84.y; hitthing->pos.z = sp84.z; hitthing->unk0c.x = sp78.x; hitthing->unk0c.y = sp78.y; hitthing->unk0c.z = sp78.z; hitthing->unk18 = &vtx[points[0]]; hitthing->unk1c = &vtx[points[1]]; hitthing->unk20 = &vtx[points[2]]; hitthing->texturenum = -1; hitthing->tricmd = gdl; hitthing->unk28 = triref; } } } } } i--; if (i == 2) { points[0] = tri4gdl->tri4.x2; points[1] = tri4gdl->tri4.y2; points[2] = tri4gdl->tri4.z2; triref = 2; } else if (i == 1) { points[0] = tri4gdl->tri4.x3; points[1] = tri4gdl->tri4.y3; points[2] = tri4gdl->tri4.z3; triref = 3; } else if (i == 0) { points[0] = tri4gdl->tri4.x4; points[1] = tri4gdl->tri4.y4; points[2] = tri4gdl->tri4.z4; triref = 1; } } while (i >= 0); } gdl++; } return hit; } bool bgTestHitInVtxBatch(struct coord *arg0, struct coord *arg1, struct coord *arg2, struct vtxbatch *batch, s32 roomnum, struct hitthing *hitthing) { s16 stack; s16 triref; s32 trisremaining; Gfx *gdl = batch->gdl; bool hit; s32 points[3]; s32 numvertices; f32 sqdist; f32 lowestsqdist; s32 texturenum; s32 index; struct coord *point1; struct coord *point2; struct coord *point3; struct coord spb0; struct coord spa4; struct coord min; struct coord max; Vtx *vtx; f32 *ptr; Gfx *iter; Gfx *tmpgdl; Gfx *tri4gdl; vtx = bgFindVerticesForGdl(roomnum, gdl); iter = &gdl[batch->gbicmdindex]; vtx = (Vtx *)((UNSEGADDR(iter->words.w1) & 0xffffff) + (uintptr_t)vtx); numvertices = (((u32) iter->bytes[GFX_W0_BYTE(1)] >> 4) & 0xf) + 1; ptr = var800a6470; while (numvertices > 0) { ptr[0] = g_BgRooms[roomnum].pos.x + vtx->x; ptr[1] = g_BgRooms[roomnum].pos.y + vtx->y; ptr[2] = g_BgRooms[roomnum].pos.z + vtx->z; ptr += 3; vtx++; numvertices--; } lowestsqdist = MAXFLOAT; hit = false; iter++; while (iter->dma.cmd != G_VTX && iter->dma.cmd != G_ENDDL) { if (iter->dma.cmd != G_TRI1 && iter->dma.cmd != G_TRI4) { iter++; continue; } if (iter->dma.cmd == G_TRI1) { trisremaining = 0; triref = 0; points[0] = iter->tri.tri.v[GFX_TRI_VTX(0)] / 10; points[1] = iter->tri.tri.v[GFX_TRI_VTX(1)] / 10; points[2] = iter->tri.tri.v[GFX_TRI_VTX(2)] / 10; } else if (iter->dma.cmd == G_TRI4) { tri4gdl = iter; trisremaining = 3; triref = 1; points[0] = tri4gdl->tri4.x1; points[1] = tri4gdl->tri4.y1; points[2] = tri4gdl->tri4.z1; } do { if (points[0] == 0 && points[1] == 0 && points[2] == 0) { break; } point1 = (struct coord *) (var800a6470 + points[0] * 3); point2 = (struct coord *) (var800a6470 + points[1] * 3); point3 = (struct coord *) (var800a6470 + points[2] * 3); min.x = point1->x; if (point2->x < min.x) { min.x = point2->x; } if (point3->x < min.x) { min.x = point3->x; } if (!(arg0->x < min.x && arg1->x < min.x)) { max.x = point1->x; if (point2->x > max.x) { max.x = point2->x; } if (point3->x > max.x) { max.x = point3->x; } if (!(arg0->x > max.x && arg1->x > max.x)) { min.z = point1->z; if (point2->z < min.z) { min.z = point2->z; } if (point3->z < min.z) { min.z = point3->z; } if (!(arg0->z < min.z && arg1->z < min.z)) { max.z = point1->z; if (point2->z > max.z) { max.z = point2->z; } if (point3->z > max.z) { max.z = point3->z; } if (!(arg0->z > max.z && arg1->z > max.z)) { min.y = point1->y; if (point2->y < min.y) { min.y = point2->y; } if (point3->y < min.y) { min.y = point3->y; } if (!(arg0->y < min.y && arg1->y < min.y)) { max.y = point1->y; if (point2->y > max.y) { max.y = point2->y; } if (point3->y > max.y) { max.y = point3->y; } if (!(arg0->y > max.y && arg1->y > max.y)) { if (bgTestLineIntersectsBbox(arg0, arg2, &min, &max) && func0002f560(point1, point2, point3, NULL, arg0, arg1, arg2, &spb0, &spa4)) { f32 tmp; tmp = spb0.x - arg0->x; sqdist = tmp * tmp; tmp = spb0.y - arg0->y; sqdist += tmp * tmp; tmp = spb0.z - arg0->z; sqdist += tmp * tmp; if (sqdist < lowestsqdist) { hit = true; tmpgdl = iter; while (tmpgdl->bytes[GFX_W0_BYTE(0)] != G_SETTIMG && tmpgdl > gdl) { tmpgdl--; } if (tmpgdl == gdl #ifdef PLATFORM_N64 || (tmpgdl->words.w1 & 0x0f000000) == 0x0f000000 || (tmpgdl->words.w1 & 0x05000000) == 0x05000000) { #else // not sure if the above check even works right on N64, but we can test easily for seg addresses || (tmpgdl->words.w1 & 1)) { #endif texturenum = -1; } else { #ifdef PLATFORM_N64 s32 tmp = UNSEGADDR(tmpgdl->words.w1) - 8; #else uintptr_t tmp = UNSEGADDR(tmpgdl->words.w1) - 8; #endif texturenum = *(s16 *) PHYS_TO_K0(tmp); } #ifdef AVOID_UB if (batch->type == VTXBATCHTYPE_XLU && texturenum >= 0 && g_Textures[texturenum].surfacetype == SURFACETYPE_DEFAULT) { #else if (batch->type == VTXBATCHTYPE_XLU && g_Textures[texturenum].surfacetype == SURFACETYPE_DEFAULT) { #endif hit = false; } if (hit) { lowestsqdist = sqdist; hitthing->pos.x = spb0.x; hitthing->pos.y = spb0.y; hitthing->pos.z = spb0.z; hitthing->unk0c.x = spa4.x; hitthing->unk0c.y = spa4.y; hitthing->unk0c.z = spa4.z; hitthing->unk18 = &vtx[points[0]]; hitthing->unk1c = &vtx[points[1]]; hitthing->unk20 = &vtx[points[2]]; hitthing->texturenum = texturenum; hitthing->tricmd = iter; hitthing->unk28 = triref; hitthing->unk2c = batch->type; } } } } } } } } } trisremaining--; if (1); if (1); if (1); if (trisremaining == 2) { points[0] = tri4gdl->tri4.x2; points[1] = tri4gdl->tri4.y2; points[2] = tri4gdl->tri4.z2; triref = 2; } else if (trisremaining == 1) { points[0] = tri4gdl->tri4.x3; points[1] = tri4gdl->tri4.y3; points[2] = tri4gdl->tri4.z3; triref = 3; } else if (trisremaining == 0) { points[0] = tri4gdl->tri4.x4; points[1] = tri4gdl->tri4.y4; points[2] = tri4gdl->tri4.z4; triref = 1; } } while (trisremaining >= 0); iter++; } return hit; } s32 bg0f1612e4(struct coord *bbmin, struct coord *bbmax, struct coord *frompos, struct coord *dist, struct coord *arg4, struct coord *arg5) { s32 i; u8 bail = true; s8 sp48[3]; s32 bestindex; f32 sp38[3]; f32 sp2c[3]; for (i = 0; i < 3; i++) { if (frompos->f[i] < bbmin->f[i]) { sp48[i] = 1; sp38[i] = bbmin->f[i]; bail = false; } else if (frompos->f[i] > bbmax->f[i]) { sp48[i] = 0; sp38[i] = bbmax->f[i]; bail = false; } else { sp48[i] = 2; sp38[i] = 0.0f; } } if (bail) { return -1; } for (i = 0; i < 3; i++) { if (sp48[i] != 2 && dist->f[i] != 0.0f) { sp2c[i] = (sp38[i] - frompos->f[i]) * arg4->f[i]; } else { sp2c[i] = -1.0f; } } bestindex = 0; for (i = 1; i < 3; i++) { if (sp2c[i] > sp2c[bestindex]) { bestindex = i; } } if (sp2c[bestindex] < 0.0f) { return 0; } for (i = 0; i < 3; i++) { if (bestindex != i) { arg5->f[i] = frompos->f[i] + sp2c[bestindex] * dist->f[i]; if (arg5->f[i] < bbmin->f[i] || arg5->f[i] > bbmax->f[i]) { return 0; } } else { arg5->f[i] = sp38[i]; } } return 1; } /** * Figure out which piece of BG geometry is hit in the given room based on a * line intersection from frompos to topos. Populate the hitthing struct with * the details. Props are not considered. Return true if a hit occurred. * * This is used not only for shots, but blood splatters and explosion scorch * marks too. * * Room vertices are already grouped into batches, where each batch has a * precomputed bounding box. */ bool bgTestHitInRoom(struct coord *frompos, struct coord *topos, s32 roomnum, struct hitthing *hitthing) { s32 i; s32 count; f32 f20; s32 a0; s32 numbatches; s32 j; f32 f0; f32 spc8; f32 f2; struct coord from; struct coord to; struct coord dist; struct coord sp94; struct vtxbatch *batch; struct hitthing sp60; s32 tmpindex; count = 0; from.x = frompos->x; from.y = frompos->y; from.z = frompos->z; to.x = topos->x; to.y = topos->y; to.z = topos->z; dist.x = to.x - from.x; dist.y = to.y - from.y; dist.z = to.z - from.z; sp94.x = 1.0f / dist.x; sp94.y = 1.0f / dist.y; sp94.z = 1.0f / dist.z; if (roomnum < 0 || roomnum >= g_Vars.roomcount) { return false; } batch = g_Rooms[roomnum].vtxbatches; if (batch == NULL) { return false; } numbatches = g_Rooms[roomnum].numvtxbatches; for (i = 0; i < numbatches; batch++, i++) { j = bg0f1612e4(&batch->bbmin, &batch->bbmax, &from, &dist, &sp94, &hitthing->pos); if (j == 0) { continue; } if (j == 1) { f0 = from.x - hitthing->pos.x; f20 = f0 * f0; f0 = from.y - hitthing->pos.y; f20 += f0 * f0; f0 = from.z - hitthing->pos.z; f20 += f0 * f0; } else { f20 = -1.0f; } if (count < ARRAYCOUNT(var800a6538)) { a0 = i; for (j = 0; j < count; j++) { f2 = var800a6538[j].unk04; if (f2 > f20) { tmpindex = var800a6538[j].vtxbatchindex; var800a6538[j].vtxbatchindex = a0; a0 = tmpindex; var800a6538[j].unk04 = f20; f20 = f2; } } var800a6538[j].vtxbatchindex = a0; var800a6538[j].unk04 = f20; count++; } else { count = 0; for (j = 0; j < ARRAYCOUNT(var800a6538); j++) { if (bgTestHitInVtxBatch(&from, &to, &dist, &g_Rooms[roomnum].vtxbatches[var800a6538[j].vtxbatchindex], roomnum, hitthing)) { f0 = from.x - hitthing->pos.x; f2 = f0 * f0; f0 = from.y - hitthing->pos.y; f2 += f0 * f0; f0 = from.z - hitthing->pos.z; f2 += f0 * f0; if (count == 0) { var800a6538[0].vtxbatchindex = var800a6538[j].vtxbatchindex; var800a6538[0].unk04 = f2; count = 1; } else if (f2 < var800a6538[0].unk04) { var800a6538[0].vtxbatchindex = var800a6538[j].vtxbatchindex; var800a6538[0].unk04 = f2; count = 1; } } } if (count != 0) { if (f20 < var800a6538[0].unk04) { var800a6538[1].unk04 = var800a6538[0].unk04; var800a6538[0].unk04 = f20; var800a6538[1].vtxbatchindex = var800a6538[0].vtxbatchindex; var800a6538[0].vtxbatchindex = i; } else { var800a6538[1].vtxbatchindex = i; var800a6538[1].unk04 = f20; } count = 2; } else { var800a6538[0].vtxbatchindex = i; var800a6538[0].unk04 = f20; count = 1; } } } if (count == 0) { return false; } batch = g_Rooms[roomnum].vtxbatches; for (i = 0; i < count; i++) { if (bgTestHitInVtxBatch(&from, &to, &dist, &batch[var800a6538[i].vtxbatchindex], roomnum, hitthing)) { i++; if (i < count) { f0 = from.x - hitthing->pos.x; spc8 = f0 * f0; f0 = from.y - hitthing->pos.y; spc8 += f0 * f0; f0 = from.z - hitthing->pos.z; spc8 += f0 * f0; for (; i < count; i++) { if (var800a6538[i].unk04 <= spc8) { if (bgTestHitInVtxBatch(&from, &to, &dist, &batch[var800a6538[i].vtxbatchindex], roomnum, &sp60)) { f0 = from.f[0] - sp60.pos.f[0]; f20 = f0 * f0; f0 = from.f[1] - sp60.pos.f[1]; f20 += f0 * f0; f0 = from.f[2] - sp60.pos.f[2]; f20 += f0 * f0; if (f20 < spc8) { hitthing->pos.x = sp60.pos.x; hitthing->pos.y = sp60.pos.y; hitthing->pos.z = sp60.pos.z; hitthing->unk0c.x = sp60.unk0c.x; hitthing->unk0c.y = sp60.unk0c.y; hitthing->unk0c.z = sp60.unk0c.z; hitthing->unk18 = sp60.unk18; hitthing->unk1c = sp60.unk1c; hitthing->unk20 = sp60.unk20; hitthing->texturenum = sp60.texturenum; hitthing->tricmd = sp60.tricmd; hitthing->unk28 = sp60.unk28; hitthing->unk2c = sp60.unk2c; spc8 = f20; } } } } } return true; } } return false; } bool bgRoomIsLoaded(s32 room) { return g_Rooms[room].loaded240; } bool bgRoomContainsCoord(struct coord *pos, RoomNum roomnum) { struct coord copy; copy.x = pos->x; copy.y = pos->y; copy.z = pos->z; return copy.f[0] >= g_Rooms[roomnum].bbmin[0] && copy.f[0] <= g_Rooms[roomnum].bbmax[0] && copy.f[2] >= g_Rooms[roomnum].bbmin[2] && copy.f[2] <= g_Rooms[roomnum].bbmax[2] && copy.f[1] >= g_Rooms[roomnum].bbmin[1] && copy.f[1] <= g_Rooms[roomnum].bbmax[1]; } /** * Test if a world position is inside a room based on portal checks only. * * It works by iterating the portals for the given room and checking which side * of the portal the position is on. If the position is on the "inside" of all * portals then the position is determined to be in the room. * * Note that: * - Clockwise portal vertices makes the normal face towards the viewer. * - The normal points towards the front of the portal. * - The room on the front side is roomnum2. */ bool bgTestPosInRoomCheap(struct coord *pos, RoomNum roomnum) { s32 i; for (i = 0; i < g_Rooms[roomnum].numportals; i++) { s32 portalnum = g_RoomPortals[g_Rooms[roomnum].roomportallistoffset + i]; struct portalmetric *metric = &g_PortalMetrics[portalnum]; f32 value = metric->normal.f[0] * pos->f[0] + metric->normal.f[1] * pos->f[1] + metric->normal.f[2] * pos->f[2]; if (value < metric->min) { if (roomnum != g_BgPortals[portalnum].roomnum1) { return false; } } else if (value > metric->max) { if (roomnum != g_BgPortals[portalnum].roomnum2) { return false; } } } return true; } bool bgTestPosInRoomExpensive(struct coord *pos, RoomNum roomnum) { s32 t5; struct coord *next; s32 t4; s32 portalnum; struct portalmetric *metric; s32 j; f32 f0; struct portalvertices *pvertices; struct coord sp74; struct coord sp68; struct coord sp5c; f32 sp58[1]; struct coord sp4c; struct coord *cur; f32 f18; s32 i; f32 sum; sp74.f[0] = g_Rooms[roomnum].centre.f[0]; sp74.f[1] = g_Rooms[roomnum].centre.f[1]; sp74.f[2] = g_Rooms[roomnum].centre.f[2]; for (i = 0; i < g_Rooms[roomnum].numportals; i++) { portalnum = g_RoomPortals[g_Rooms[roomnum].roomportallistoffset + i]; pvertices = (struct portalvertices *)((u8 *) g_BgPortals + g_BgPortals[portalnum].verticesoffset); metric = &g_PortalMetrics[portalnum]; f0 = pos->f[0] * metric->normal.f[0] + pos->f[1] * metric->normal.f[1] + pos->f[2] * metric->normal.f[2]; f18 = sp74.f[0] * metric->normal.f[0] + sp74.f[1] * metric->normal.f[1] + sp74.f[2] * metric->normal.f[2]; if (f0 < metric->min) { if (f18 < metric->min) { continue; } } else { if (f0 > metric->max && f18 > metric->max) { continue; } } sp68.f[0] = sp74.f[0] - pos->f[0]; sp68.f[1] = sp74.f[1] - pos->f[1]; sp68.f[2] = sp74.f[2] - pos->f[2]; t4 = 0; t5 = true; cur = &pvertices->vertices[0]; next = &pvertices->vertices[1]; for (j = 0; j < pvertices->count; j++) { if (j + 1 == pvertices->count) { next = &pvertices->vertices[0]; } sp5c.f[0] = next->f[0] - cur->f[0]; sp5c.f[1] = next->f[1] - cur->f[1]; sp5c.f[2] = next->f[2] - cur->f[2]; sp4c.f[0] = sp5c.f[1] * sp68.f[2] - sp5c.f[2] * sp68.f[1]; sp4c.f[1] = sp5c.f[2] * sp68.f[0] - sp5c.f[0] * sp68.f[2]; sp4c.f[2] = sp5c.f[0] * sp68.f[1] - sp5c.f[1] * sp68.f[0]; sum = sp4c.f[0] * sp4c.f[0] + sp4c.f[1] * sp4c.f[1] + sp4c.f[2] * sp4c.f[2]; if (sum == 0.0f) { t5 = false; break; } sp58[0] = sp4c.f[0] * cur->f[0] + sp4c.f[1] * cur->f[1] + sp4c.f[2] * cur->f[2]; sum = sp4c.f[0] * pos->f[0] + sp4c.f[1] * pos->f[1] + sp4c.f[2] * pos->f[2]; if (sum < sp58[0]) { if (t4 == 2) { t5 = false; break; } t4 = 1; } else if (t4 == 1) { t5 = false; break; } else { t4 = 2; } cur++; next++; } if (t5) { if (f0 < metric->min) { if (roomnum == g_BgPortals[portalnum].roomnum2) { return false; } } else if (f0 > metric->max) { if (roomnum == g_BgPortals[portalnum].roomnum1) { return false; } } } } return true; } bool bgTestPosInRoom(struct coord *pos, RoomNum roomnum) { if (g_Rooms[roomnum].flags & ROOMFLAG_COMPLICATEDPORTALS) { return bgTestPosInRoomExpensive(pos, roomnum); } else { return bgTestPosInRoomCheap(pos, roomnum); } } /** * Find rooms near the given pos so the caller can decide which room pos is * actually in. * * inrooms should point to an empty array of room numbers whose length is max + 1. * The function will populate this array with room numbers where the pos is * inside the room's bounding box. * * aboverooms works the same way, but it'll be populated with room numbers where * the pos is above the room's bounding box. * * For both arrays, the function will only consider rooms that have portals. * If this doesn't produce anything for both arrays, the function will try again * but with rooms that have no portals. * * If all of the above produces no results (ie. the pos is out of bounds) and * the bestroom pointer is not NULL, the function then finds the closest room * to pos and writes the room number to the bestroom pointer. The bestroom * pointer is a pointer to a single s16 rather than an array. */ void bgFindRoomsByPos(struct coord *posarg, RoomNum *inrooms, RoomNum *aboverooms, s32 max, RoomNum *bestroom) { s32 inlen = 0; s32 abovelen = 0; s32 closestroomnum = -1; struct coord pos; f32 closestdist = 0.0f; s32 i; s32 j; pos.x = posarg->x; pos.y = posarg->y; pos.z = posarg->z; // Try rooms which have portals for (i = 1; i < g_Vars.roomcount; i++) { if (g_Rooms[i].numportals > 0 && pos.x >= g_Rooms[i].bbmin[0] && pos.x <= g_Rooms[i].bbmax[0] && pos.z >= g_Rooms[i].bbmin[2] && pos.z <= g_Rooms[i].bbmax[2] && pos.y >= g_Rooms[i].bbmin[1]) { if (pos.y <= g_Rooms[i].bbmax[1]) { // Pos is inside the bbox if (inlen < max) { inrooms[inlen] = i; inlen++; } } else { // Pos is above the bbox if (abovelen < max) { aboverooms[abovelen] = i; abovelen++; } } } } // Try again but with rooms that have no portals if (inlen == 0 && abovelen == 0) { for (i = 1; i < g_Vars.roomcount; i++) { if (g_Rooms[i].numportals == 0 && pos.x >= g_Rooms[i].bbmin[0] && pos.x <= g_Rooms[i].bbmax[0] && pos.z >= g_Rooms[i].bbmin[2] && pos.z <= g_Rooms[i].bbmax[2] && pos.y >= g_Rooms[i].bbmin[1]) { if (pos.y <= g_Rooms[i].bbmax[1]) { // Pos is inside the bbox if (inlen < max) { inrooms[inlen] = i; inlen++; } } else { // Pos is above the bbox if (abovelen < max) { aboverooms[abovelen] = i; abovelen++; } } } } } inrooms[inlen] = -1; aboverooms[abovelen] = -1; if (bestroom != NULL) { if (inlen == 0 && abovelen == 0) { for (i = 1; i < g_Vars.roomcount; i++) { f32 dist = 0.0f; for (j = 0; j < 3; j++) { if (pos.f[j] < g_Rooms[i].bbmin[j] || pos.f[j] > g_Rooms[i].bbmax[j]) { f32 dist1 = pos.f[j] - g_Rooms[i].bbmin[j]; f32 dist2 = pos.f[j] - g_Rooms[i].bbmax[j]; if (dist1 < 0.0f) { dist1 = -dist1; } if (dist2 < 0.0f) { dist2 = -dist2; } if (dist2 < dist1) { dist1 = dist2; } dist += dist1; } } if (dist > 0.0f && (closestroomnum < 0 || dist < closestdist)) { closestroomnum = i; closestdist = dist; } } } *bestroom = closestroomnum >= 0 ? closestroomnum : -1; } } bool bgCmdPushValue(bool value) { g_BgCmdStack[g_BgCmdStackIndex] = value; g_BgCmdStackIndex = (g_BgCmdStackIndex + 1) % 20; return value; } bool bgCmdPopValue(void) { bool val = g_BgCmdStack[g_BgCmdStackIndex = (g_BgCmdStackIndex + 19) % 20]; return val; } bool bgCmdGetNthValueFromEnd(s32 n) { return g_BgCmdStack[((g_BgCmdStackIndex - n) + 19) % 20]; } /** * BG files contain bytecode that is used to override the default portal * behaviour. They can be used to check if the camera is in particular rooms * and then force other rooms to show or hide. * * Only six BG files use of this feature. They are Villa, Chicago, Area 51, * Pelagic II, Deep Sea and Skedar Ruins. All other BG files contain a single * "end" instruction in their bytecode. * * The scripting language supports if-statements with an infinite nesting level. * The interpreter maintains a stack of boolean values which can be pushed to * or popped from the end. This can be used to build complex conditions that * combine "AND" and "OR" operations. * * All commands are interpreted in order. There is no support for loops. * * When processing conditional code the function calls itself recursively for * that branch. The execute argument denotes whether the condition passed and * these statements should be executed, or whether the condition failed and * it's just passing over them to get to the endif command. */ struct bgcmd *bgCmdExecuteBranch(struct bgcmd *cmd, bool execute) { s32 i; g_BgCmdThrowing = false; if (!cmd) { return cmd; } while (true) { switch (cmd->type) { case BGCMD_END: return cmd; case BGCMD_PUSH: if (execute) { bgCmdPushValue(cmd->param); } cmd += cmd->len; break; case BGCMD_POP: if (execute) { bgCmdPopValue(); } cmd += cmd->len; break; case BGCMD_AND: if (execute) { bgCmdPushValue(bgCmdPopValue() & bgCmdPopValue()); } cmd += cmd->len; break; case BGCMD_OR: if (execute) { bgCmdPushValue(bgCmdPopValue() | bgCmdPopValue()); } cmd += cmd->len; break; case BGCMD_NOT: if (execute) { bgCmdPushValue(bgCmdPopValue() == 0); } cmd += cmd->len; break; case BGCMD_XOR: if (execute) { bgCmdPushValue(bgCmdPopValue() ^ bgCmdPopValue()); } cmd += cmd->len; break; case BGCMD_PUSH_CAMINROOMRANGE: if (execute) { bgCmdPushValue(g_CamRoom >= cmd[1].param && g_CamRoom <= cmd[2].param); } cmd += cmd->len; break; case BGCMD_SETRESULT_TRUE: if (execute) { g_BgCmdScreenBox.xmin = g_Vars.currentplayer->screenxminf; g_BgCmdScreenBox.ymin = g_Vars.currentplayer->screenyminf; g_BgCmdScreenBox.xmax = g_Vars.currentplayer->screenxmaxf; g_BgCmdScreenBox.ymax = g_Vars.currentplayer->screenymaxf; g_BgCmdResult = BGRESULT_TRUE; } cmd += cmd->len; break; case BGCMD_SETRESULT_IFPORTALINFOV: if (execute) { if (!PORTAL_IS_CLOSED(cmd[1].param)) { if (!bgGetPortalScreenBbox(cmd[1].param, &g_PortalScreenBbox)) { g_BgCmdResult = BGRESULT_FALSE; } else if (bgGetBoxIntersection(&g_BgCmdScreenBox, &g_PortalScreenBbox) == 0) { g_BgCmdResult = BGRESULT_FALSE; } else { g_BgCmdResult = BGRESULT_TRUE; } } } cmd += cmd->len; break; case BGCMD_SETRESULT_TRUEIFTHROUGHPORTAL: if (execute) { struct screenbox portalbox; if (!PORTAL_IS_CLOSED(cmd[1].param)) { if (bgGetPortalScreenBbox(cmd[1].param, &portalbox) && bgGetBoxIntersection(&g_BgCmdScreenBox, &portalbox)) { if (g_BgCmdResult != BGRESULT_TRUE) { bgCopyBox(&g_BgCmdScreenBox, &portalbox); g_BgCmdResult = BGRESULT_TRUE; } else { bgExpandBox(&g_BgCmdScreenBox, &portalbox); } } } } cmd += cmd->len; break; case BGCMD_SETRESULT_FALSEIFNOTTHROUGHPORTAL: if (execute) { if (g_BgCmdResult == BGRESULT_TRUE) { struct screenbox portalbox; if (PORTAL_IS_CLOSED(cmd[1].param)) { g_BgCmdResult = BGRESULT_FALSE; } else if (!bgGetPortalScreenBbox(cmd[1].param, &portalbox)) { g_BgCmdResult = BGRESULT_FALSE; } else if (bgGetBoxIntersection(&portalbox, (struct screenbox *)&g_Vars.currentplayer->screenxminf) == 0) { g_BgCmdResult = BGRESULT_FALSE; } else if (bgGetBoxIntersection(&g_PortalScreenBbox, &portalbox) == 0) { g_BgCmdResult = BGRESULT_FALSE; } } } cmd += cmd->len; break; case BGCMD_IFRESULT_SHOWROOM: if (execute) { if (g_BgCmdResult == BGRESULT_TRUE && bgRoomIntersectsScreenBox(cmd[1].param, &g_BgCmdScreenBox)) { bgSetRoomOnscreen(cmd[1].param, 0, &g_BgCmdScreenBox); g_BgForceOnscreenRooms[g_BgNumForceOnscreenRooms++] = cmd[1].param; } } cmd += cmd->len; break; case BGCMD_DISABLEROOM: if (execute) { g_Rooms[cmd[1].param].flags |= ROOMFLAG_DISABLEDBYSCRIPT; } cmd += cmd->len; break; case BGCMD_DISABLEROOMRANGE: if (execute) { for (i = cmd[1].param; i <= cmd[2].param; i++) { g_Rooms[i].flags |= ROOMFLAG_DISABLEDBYSCRIPT; } } cmd += cmd->len; break; case BGCMD_LOADROOM: cmd += cmd->len; break; case BGCMD_LOADROOMRANGE: cmd += cmd->len; break; case BGCMD_SETROOMTESTSDISABLED: if (execute) { g_BgRoomTestsDisabled = true; } cmd += cmd->len; break; case BGCMD_PUSH_PORTALISOPEN: if (execute) { bgCmdPushValue(!PORTAL_IS_CLOSED(cmd[1].param)); } cmd += cmd->len; break; case BGCMD_2A: if (execute) { g_Rooms[cmd[1].param].unk07 = 0; } cmd += cmd->len; break; case BGCMD_SETRESULT_FALSE: if (execute) { g_BgCmdResult = BGRESULT_FALSE; } cmd += cmd->len; break; case BGCMD_BRANCH: cmd = bgCmdExecuteBranch(cmd + cmd->len, execute); cmd += cmd->len; break; case BGCMD_CATCH: cmd += cmd->len; g_BgCmdThrowing = false; return cmd; case BGCMD_THROW: cmd += cmd->len; if (execute) { g_BgCmdThrowing = true; } execute = false; break; case BGCMD_IF: cmd = bgCmdExecuteBranch(cmd + cmd->len, bgCmdPopValue() & execute); if (g_BgCmdThrowing) { execute = false; } break; case BGCMD_ELSE: /** * Assuming this is indeed an else command, it's not safe to assume * that the execution state can be unconditionally toggled. * For example, given the following portal code: * * if (a false condition) * if (any condition) * branch 1 * else * branch 2 * endif * endif * * ...when reaching the else, execution would be turned on. * * However, this command isn't even used. */ execute ^= 1; cmd += cmd->len; break; case BGCMD_ENDIF: /** * Note the return here rather than break. */ cmd += cmd->len; return cmd; default: if (1); if (1); if (1); if (1); return cmd; } } g_BgCmdThrowing = false; return cmd; } struct bgcmd *bgCmdExecute(struct bgcmd *cmd) { struct player *player = g_Vars.currentplayer; g_BgCmdResult = BGRESULT_TRUE; if (!cmd) { return cmd; } // This may have been used in an osSyncPrintf call bgCmdGetNthValueFromEnd(0); g_BgCmdScreenBox.xmin = player->screenxminf; g_BgCmdScreenBox.ymin = player->screenyminf; g_BgCmdScreenBox.xmax = player->screenxmaxf; g_BgCmdScreenBox.ymax = player->screenymaxf; return bgCmdExecuteBranch(cmd, true); } void bgTickPortalsXray(void) { struct coord vismax; struct coord vismin; struct coord eraserpos; struct coord vismid; struct player *player = g_Vars.currentplayer; s16 ymax; s16 xmax; s16 ymin; s16 xmin; struct stagetableentry *stage; s32 i; struct drawslot *thing; static u32 edist = 400; bgCalculateScreenProperties(); if (g_BgDrawSlots); if (g_BgNumAttemptedDrawSlots > g_BgMostAttemptedDrawSlots) { g_BgMostAttemptedDrawSlots = g_BgNumAttemptedDrawSlots; } xmin = player->screenxminf; ymin = player->screenyminf; xmax = player->screenxmaxf; ymax = player->screenymaxf; if (bgunGetWeaponNum(HAND_RIGHT) == WEAPON_FARSIGHT && player->gunsightoff == 0) { player->eraserdepth = -500.0f / camGetLodScaleZ(); } else { player->eraserdepth = -500.0f; } eraserpos.f[0] = 0.0f; eraserpos.f[1] = 0.0f; eraserpos.f[2] = player->eraserdepth; mtx4TransformVecInPlace(camGetProjectionMtxF(), &eraserpos); player->eraserpos.f[0] = eraserpos.f[0]; player->eraserpos.f[1] = eraserpos.f[1]; player->eraserpos.f[2] = eraserpos.f[2]; mainOverrideVariable("edist", &edist); stage = stageGetCurrent(); player->eraserpropdist = stage->eraserpropdist; player->eraserbgdist = (f32) stage->eraserpropdist + stage->unk30; vismax.f[0] = eraserpos.f[0] + player->eraserbgdist; vismax.f[1] = eraserpos.f[1] + player->eraserbgdist; vismax.f[2] = eraserpos.f[2] + player->eraserbgdist; vismin.f[0] = eraserpos.f[0] - player->eraserbgdist; vismin.f[1] = eraserpos.f[1] - player->eraserbgdist; vismin.f[2] = eraserpos.f[2] - player->eraserbgdist; vismid.f[0] = eraserpos.f[0]; vismid.f[1] = eraserpos.f[1]; vismid.f[2] = eraserpos.f[2]; g_BgNumDrawSlots = 0; g_BgNumAttemptedDrawSlots = 0; g_BgDrawSlots[60].roomnum = -1; g_BgDrawSlots[60].draworder = 255; g_BgDrawSlots[60].box.xmin = xmin; g_BgDrawSlots[60].box.ymin = ymin; g_BgDrawSlots[60].box.xmax = xmax; g_BgDrawSlots[60].box.ymax = ymax; g_BgMaxDrawOrder = 0; g_BgMinDrawOrder = 0x7fff; for (i = 1; i < g_Vars.roomcount; i++) { if (!(vismax.f[0] < g_Rooms[i].bbmin[0]) && !(vismin.f[0] > g_Rooms[i].bbmax[0]) && !(vismax.f[2] < g_Rooms[i].bbmin[2]) && !(vismin.f[2] > g_Rooms[i].bbmax[2]) && !(vismax.f[1] < g_Rooms[i].bbmin[1]) && !(vismin.f[1] > g_Rooms[i].bbmax[1])) { s32 index = g_BgNumDrawSlots; if (xmin); if (g_Rooms[i].bbmax); if (1); if (1); if (1); if (index < 60) { f32 x; f32 y; f32 z; g_Rooms[i].flags |= ROOMFLAG_ONSCREEN; g_BgDrawSlots[index].roomnum = i; bgUnpausePropsInRoom(i, false); x = (g_Rooms[i].bbmin[0] + g_Rooms[i].bbmax[0]) / 2.0f - vismid.f[0]; y = (g_Rooms[i].bbmin[1] + g_Rooms[i].bbmax[1]) / 2.0f - vismid.f[1]; z = (g_Rooms[i].bbmin[2] + g_Rooms[i].bbmax[2]) / 2.0f - vismid.f[2]; g_BgDrawSlots[index].draworder = sqrtf(x * x + y * y + z * z) / 100.0f; if (g_BgDrawSlots[index].draworder > g_BgMaxDrawOrder) { g_BgMaxDrawOrder = g_BgDrawSlots[index].draworder; } if (g_BgDrawSlots[index].draworder < g_BgMinDrawOrder) { g_BgMinDrawOrder = g_BgDrawSlots[index].draworder; } g_BgDrawSlots[index].box.xmin = xmin; g_BgDrawSlots[index].box.ymin = ymin; g_BgDrawSlots[index].box.xmax = xmax; g_BgDrawSlots[index].box.ymax = ymax; g_BgNumDrawSlots++; g_BgNumAttemptedDrawSlots++; g_Rooms[player->cam_room].flags |= ROOMFLAG_ONSCREEN; } else { // empty } } } bgChooseRoomsToLoad(); } void bgAddToSnake(RoomNum fromroomnum, RoomNum roomnum, s16 depth, struct screenbox *box) { struct bgsnakeitem *item; s32 i; s32 j; if (g_Rooms[roomnum].flags & ROOMFLAG_DISABLEDBYSCRIPT) { return; } if (depth >= 2) { if (g_Rooms[roomnum].portalrecursioncount < 255) { g_Rooms[roomnum].portalrecursioncount++; } if (g_Rooms[roomnum].portalrecursioncount > g_Vars.roomportalrecursionlimit) { return; } } if (g_Rooms[roomnum].snakecount != 0 && g_Rooms[roomnum].unk07) { i = g_BgSnake.tailindex; item = &g_BgSnake.items[i]; while (i != g_BgSnake.headindex) { if (item->roomnum == roomnum) { for (j = 0; j < ARRAYCOUNT(item->fromroomnums); j++) { if (item->fromroomnums[j] == -1) { bgExpandBox(&item->screenbox, box); item->fromroomnums[j] = fromroomnum; return; } } } i++; item++; if (i == ARRAYCOUNT(g_BgSnake.items)) { i = 0; item = &g_BgSnake.items[0]; } } } item = &g_BgSnake.items[g_BgSnake.headindex]; item->fromroomnums[0] = fromroomnum; item->roomnum = roomnum; item->depth = depth; item->roomportallistoffset = g_Rooms[roomnum].roomportallistoffset; item->numportals = g_Rooms[roomnum].numportals; item->screenbox.xmin = box->xmin; item->screenbox.ymin = box->ymin; item->screenbox.xmax = box->xmax; item->screenbox.ymax = box->ymax; g_Rooms[roomnum].snakecount++; for (i = 1; i < ARRAYCOUNT(item->fromroomnums); i++) { item->fromroomnums[i] = -1; } g_BgSnake.headindex++; if (g_BgSnake.headindex == 250) { g_BgSnake.headindex = 0; } if (g_BgSnake.headindex == g_BgSnake.tailindex) { g_BgSnake.headindex--; } } /** * Iterate the given item/room's portals and check if each of the neighbouring * rooms should be onscreen or not. * * Those that should be onscreen are added to the draw list and appended to the * snake so its neighbours will be processed recursively. */ void bgConsumeSnakeItem(struct bgsnakeitem *item) { struct coord *campos; s32 i; s16 portalnum; s16 prevvalidcount; RoomNum prevfoundroom; RoomNum newfoundroom; s16 side; RoomNum tmp; bool pass; struct portalmetric *metric; struct screenbox prevbox; struct screenbox newbox; f32 sum; g_Rooms[item->roomnum].snakecount--; g_BgSnake.count++; campos = &g_Vars.currentplayer->cam_pos; prevvalidcount = 0; prevfoundroom = -1; for (i = 0; i < item->numportals; i++) { portalnum = g_RoomPortals[item->roomportallistoffset + i]; // Calculate which side of the portal the camera is on // if we haven't done it on this frame yet. if (g_PortalCameraCache[portalnum].updatedframe1 != g_BgFrameCount) { metric = &g_PortalMetrics[portalnum]; sum = metric->normal.x * campos->f[0] + metric->normal.y * campos->f[1] + metric->normal.z * campos->f[2]; if (sum < metric->min) { g_PortalCameraCache[portalnum].side = 1; } else if (sum > metric->max) { g_PortalCameraCache[portalnum].side = 0; } else { g_PortalCameraCache[portalnum].side = 2; } g_PortalCameraCache[portalnum].updatedframe1 = g_BgFrameCount; } // Swap the rooms if needed, or skip past this portal entirely if the // other room and camera room are on the same side. tmp = g_BgPortals[portalnum].roomnum1; side = g_PortalCameraCache[portalnum].side; if ((u32)tmp == item->roomnum) { if (side == 0) { continue; } newfoundroom = g_BgPortals[portalnum].roomnum2; } else { if (side == 1) { continue; } newfoundroom = tmp; } if (1); // Avoid adding a room twice in a row, which would happen if there are // multiple portals between the same two rooms. if (prevfoundroom != newfoundroom) { if (prevvalidcount) { bgSetRoomOnscreen(prevfoundroom, item->depth, &prevbox); bgAddToSnake(item->roomnum, prevfoundroom, item->depth + 1, &prevbox); } prevvalidcount = 0; prevfoundroom = newfoundroom; } // Not sure why this check isn't done first? if (PORTAL_IS_CLOSED(portalnum)) { continue; } // If this room has already been processed in the reverse direction, // there's no need to add it again. if (newfoundroom == item->fromroomnums[0] || newfoundroom == item->fromroomnums[1] || newfoundroom == item->fromroomnums[2] || newfoundroom == item->fromroomnums[3] || newfoundroom == item->fromroomnums[4]) { continue; } // Reusing the side variable as a bboxisvalid variable if (g_BgPortals[portalnum].flags & PORTALFLAG_02) { newbox.xmin = item->screenbox.xmin; newbox.ymin = item->screenbox.ymin; newbox.xmax = item->screenbox.xmax; newbox.ymax = item->screenbox.ymax; side = true; } else { side = bgGetPortalScreenBbox(portalnum, &newbox); } if (side) { bgGetBoxIntersection(&newbox, &item->screenbox); if (newbox.xmin < newbox.xmax && newbox.ymin < newbox.ymax) { if (prevvalidcount == 0) { prevbox.xmin = newbox.xmin; prevbox.ymin = newbox.ymin; prevbox.xmax = newbox.xmax; prevbox.ymax = newbox.ymax; } else { bgExpandBox(&prevbox, &newbox); } prevvalidcount++; } } } if (prevvalidcount != 0) { bgSetRoomOnscreen(prevfoundroom, item->depth, &prevbox); bgAddToSnake(item->roomnum, prevfoundroom, item->depth + 1, &prevbox); } } /** * The "snake" is a circular array with a head index and tail index. * Items (rooms) are added to the head of the snake and consumed from the tail. * Consuming an item may cause more items to be added to the head. * Eventually the tail catches up to the head and the snake is finished. * * The structure is used for discovering onscreen rooms. */ bool bgTryConsumeSnake(void) { if (g_BgSnake.tailindex == g_BgSnake.headindex) { return false; } bgConsumeSnakeItem(&g_BgSnake.items[g_BgSnake.tailindex]); g_BgSnake.tailindex++; if (g_BgSnake.tailindex == ARRAYCOUNT(g_BgSnake.items)) { g_BgSnake.tailindex = 0; } return true; } /** * Choose which rooms should be placed on standby and which are candidates for * loading. * * All offscreen neighbours of onscreen rooms are placed on standby and are * nominated for loading. Any tinted glass in these rooms will be unpaused. * * If the portal between the onscreen room and the offscreen room is blocked * (ie. a closed door or opaque glass) then nominate the neighbours of the * offscreen room for loading too. This is necessary because opening the door or * destroying the glass may make many rooms visible at once, and only one room * is loaded per tick. */ void bgChooseRoomsToLoad(void) { s32 i; s32 j; u32 stack; g_BgNumRoomLoadCandidates = 0; for (i = 0; g_BgPortals[i].verticesoffset != 0; i++) { if ((g_BgPortals[i].flags & PORTALFLAG_SKIP) == 0) { s32 roomnum1 = g_BgPortals[i].roomnum1; s32 roomnum2 = g_BgPortals[i].roomnum2; s32 portalnum; if ((g_Rooms[roomnum1].flags & ROOMFLAG_ONSCREEN) && (g_Rooms[roomnum2].flags & ROOMFLAG_ONSCREEN) == 0) { // From room1 to room2 g_Rooms[roomnum2].flags |= ROOMFLAG_STANDBY; if (g_Rooms[roomnum2].loaded240 == 0) { g_Rooms[roomnum2].flags |= ROOMFLAG_LOADCANDIDATE; g_BgNumRoomLoadCandidates++; } bgUnpausePropsInRoom(roomnum2, true); if (PORTAL_IS_CLOSED(i)) { for (j = 0; j < g_Rooms[roomnum2].numportals; j++) { portalnum = g_RoomPortals[g_Rooms[roomnum2].roomportallistoffset + j]; if (roomnum2 == g_BgPortals[portalnum].roomnum1) { if (g_Rooms[g_BgPortals[portalnum].roomnum2].loaded240 == 0) { g_Rooms[g_BgPortals[portalnum].roomnum2].flags |= ROOMFLAG_LOADCANDIDATE; g_BgNumRoomLoadCandidates++; } } else { if (g_Rooms[g_BgPortals[portalnum].roomnum1].loaded240 == 0) { g_Rooms[g_BgPortals[portalnum].roomnum1].flags |= ROOMFLAG_LOADCANDIDATE; g_BgNumRoomLoadCandidates++; } } } } } else if ((g_Rooms[roomnum2].flags & ROOMFLAG_ONSCREEN) && (g_Rooms[roomnum1].flags & ROOMFLAG_ONSCREEN) == 0) { // From room2 to room1 g_Rooms[roomnum1].flags |= ROOMFLAG_STANDBY; if (g_Rooms[roomnum1].loaded240 == 0) { g_Rooms[roomnum1].flags |= ROOMFLAG_LOADCANDIDATE; g_BgNumRoomLoadCandidates++; } bgUnpausePropsInRoom(roomnum1, true); if (PORTAL_IS_CLOSED(i)) { for (j = 0; j < g_Rooms[roomnum1].numportals; j++) { portalnum = g_RoomPortals[g_Rooms[roomnum1].roomportallistoffset + j]; if (roomnum1 == g_BgPortals[portalnum].roomnum1) { if (g_Rooms[g_BgPortals[portalnum].roomnum1].loaded240 == 0) { g_Rooms[g_BgPortals[portalnum].roomnum1].flags |= ROOMFLAG_LOADCANDIDATE; g_BgNumRoomLoadCandidates++; } } else { if (g_Rooms[g_BgPortals[portalnum].roomnum1].loaded240 == 0) { g_Rooms[g_BgPortals[portalnum].roomnum2].flags |= ROOMFLAG_LOADCANDIDATE; g_BgNumRoomLoadCandidates++; } } } } } } } // Update visibility per player if (g_Vars.mplayerisrunning) { u8 flag1 = 0x01 << g_Vars.currentplayernum; u8 flag2 = 0x10 << g_Vars.currentplayernum; for (i = 0; i < g_Vars.roomcount; i++) { if (g_Rooms[i].flags & ROOMFLAG_ONSCREEN) { g_MpRoomVisibility[i] |= flag1; } else { g_MpRoomVisibility[i] &= ~flag1; } if (g_Rooms[i].flags & ROOMFLAG_STANDBY) { g_MpRoomVisibility[i] |= flag2; } else { g_MpRoomVisibility[i] &= ~flag2; } } } } void bgTickPortals(void) { s32 i; s32 room; struct screenbox box; struct player *player = g_Vars.currentplayer; bgCalculateScreenProperties(); box.xmin = player->screenxminf; box.ymin = player->screenyminf; box.xmax = player->screenxmaxf; box.ymax = player->screenymaxf; viGetZRange(&g_BgSnake.zrange); g_BgSnake.zrange.far = g_BgSnake.zrange.far / g_Vars.currentplayerstats->scale_bg2gfx; for (i = 0; i < g_Vars.roomcount; i++) { g_Rooms[i].flags &= ~(ROOMFLAG_DISABLEDBYSCRIPT | ROOMFLAG_ONSCREEN | ROOMFLAG_STANDBY | ROOMFLAG_LOADCANDIDATE); g_Rooms[i].portalrecursioncount = 0; g_Rooms[i].snakecount = 0; g_Rooms[i].unk07 = 1; } if (player->visionmode == VISIONMODE_XRAY) { bgTickPortalsXray(); } else { if (g_BgNumAttemptedDrawSlots > g_BgMostAttemptedDrawSlots) { g_BgMostAttemptedDrawSlots = g_BgNumAttemptedDrawSlots; } g_BgNumDrawSlots = 0; g_BgNumAttemptedDrawSlots = 0; g_BgMaxDrawOrder = 0; g_BgMinDrawOrder = 32767; g_BgDrawSlots[60].roomnum = -1; g_BgDrawSlots[60].draworder = 255; g_BgSnake.count = 0; g_BgSnake.headindex = 0; g_BgSnake.tailindex = 0; g_BgRoomTestsDisabled = false; g_BgDrawSlots[60].box.xmin = box.xmin; g_BgDrawSlots[60].box.ymin = box.ymin; g_BgDrawSlots[60].box.xmax = box.xmax; g_BgDrawSlots[60].box.ymax = box.ymax; bgCmdExecute(g_BgCommands); if (!g_BgRoomTestsDisabled) { if (g_BgPortals[0].verticesoffset == 0) { for (room = 1; room < g_Vars.roomcount; room++) { if (bgRoomIntersectsScreenBox(room, &box) && ((g_StageIndex != STAGEINDEX_INFILTRATION && g_StageIndex != STAGEINDEX_RESCUE && g_StageIndex != STAGEINDEX_ESCAPE) || room != 0xf) && (g_StageIndex != STAGEINDEX_SKEDARRUINS || room != 0x02) && ((g_StageIndex != STAGEINDEX_DEFECTION && g_StageIndex != STAGEINDEX_EXTRACTION) || room != 0x01) && (g_StageIndex != STAGEINDEX_ATTACKSHIP || room != 0x71)) { bgSetRoomOnscreen(room, 0, &box); } } } else { bgSetRoomOnscreen(g_CamRoom, 0, &box); g_BgSnake.count = 0; g_BgSnake.headindex = 0; g_BgSnake.tailindex = 0; bgAddToSnake(g_CamRoom, g_CamRoom, 1, &box); while (bgTryConsumeSnake()); } } bgChooseRoomsToLoad(); } } Gfx *bgRenderSceneAndLoadCandidate(Gfx *gdl) { gdl = bgRenderScene(gdl); gdl = bgScissorToViewport(gdl); if (g_Vars.currentplayerindex == 0) { g_BgLoadCandidateTimer240 -= g_Vars.lvupdate240; } if (g_BgLoadCandidateTimer240 < 0) { g_BgLoadCandidateTimer240 = 0; } // Consider loading one room by finding the load candidate that is closest to the player if (g_BgLoadCandidateTimer240 == 0 && var8007fc10 == 4 && g_Vars.tickmode == TICKMODE_NORMAL && IS8MB()) { struct player *player = g_Vars.currentplayer; s32 i; f32 value; struct coord dist; f32 bestvalue = MAXFLOAT; s32 bestroomnum = 0; f32 radius; if (g_BgNumRoomLoadCandidates) { for (i = 1; i < g_Vars.roomcount; i++) { if (!g_Rooms[i].loaded240 && (g_Rooms[i].flags & ROOMFLAG_LOADCANDIDATE)) { dist.x = g_Vars.currentplayer->prop->pos.x - g_Rooms[i].centre.x; dist.y = g_Vars.currentplayer->prop->pos.y - g_Rooms[i].centre.y; dist.z = g_Vars.currentplayer->prop->pos.z - g_Rooms[i].centre.z; value = dist.f[0] * dist.f[0] + dist.f[1] * dist.f[1] + dist.f[2] * dist.f[2]; radius = g_Rooms[i].radius; if (var8009dd6c + radius < player->projectionmtx->m[2][0] * g_Rooms[i].centre.f[0] + player->projectionmtx->m[2][1] * g_Rooms[i].centre.f[1] + player->projectionmtx->m[2][2] * g_Rooms[i].centre.f[2]) { value *= 3.0f; } if (var8009dd4c + radius < var8009dd40.f[0] * g_Rooms[i].centre.f[0] + var8009dd40.f[1] * g_Rooms[i].centre.f[1] + var8009dd40.f[2] * g_Rooms[i].centre.f[2]) { value *= 1.5f; } if (var8009dd5c + radius < var8009dd50.f[0] * g_Rooms[i].centre.f[0] + var8009dd50.f[1] * g_Rooms[i].centre.f[1] + var8009dd50.f[2] * g_Rooms[i].centre.f[2]) { value *= 1.5f; } if (var8009dd2c + radius < var8009dd20.f[0] * g_Rooms[i].centre.f[0] + var8009dd20.f[1] * g_Rooms[i].centre.f[1] + var8009dd20.f[2] * g_Rooms[i].centre.f[2]) { value *= 2.0f; } if (var8009dd3c + radius < var8009dd30.f[0] * g_Rooms[i].centre.f[0] + var8009dd30.f[1] * g_Rooms[i].centre.f[1] + var8009dd30.f[2] * g_Rooms[i].centre.f[2]) { value *= 2.0f; } if (value < bestvalue) { bestvalue = value; bestroomnum = i; } } } } if (bestroomnum != 0) { bgLoadRoom(bestroomnum); g_BgLoadCandidateTimer240 = 64; } } return gdl; } s32 bgGetForceOnscreenRooms(RoomNum *rooms, s32 len) { s32 i; for (i = 0; i < g_BgNumForceOnscreenRooms && i < len; i++) { rooms[i] = g_BgForceOnscreenRooms[i]; } rooms[i] = -1; return i; } s32 bgRoomGetNeighbours(s32 roomnum, RoomNum *dstrooms, s32 len) { s32 count = 0; s32 i; s32 j; for (i = 0; i < g_Rooms[roomnum].numportals; i++) { s32 portalnum = g_RoomPortals[g_Rooms[roomnum].roomportallistoffset + i]; s32 neighbournum = g_BgPortals[portalnum].roomnum1; if (neighbournum == roomnum) { neighbournum = g_BgPortals[portalnum].roomnum2; } for (j = 0; j < count; j++) { if (dstrooms[j] == neighbournum) { goto end; } } dstrooms[count] = neighbournum; count++; if (count >= len) { break; } end: ; } dstrooms[count] = -1; return count; } bool bgRoomsAreNeighbours(s32 roomnum1, s32 roomnum2) { s32 i; for (i = 0; i < g_Rooms[roomnum1].numportals; i++) { s32 portalnum = g_RoomPortals[g_Rooms[roomnum1].roomportallistoffset + i]; if (g_BgPortals[portalnum].roomnum1 == roomnum2 || g_BgPortals[portalnum].roomnum2 == roomnum2) { return true; } } return false; } void bgCalculateScreenProperties(void) { struct player *player = g_Vars.currentplayer; f32 width = viGetWidth(); u32 stack; f32 height = viGetHeight(); u32 stack2; player->screenxminf = viGetViewLeft(); if (player->screenxminf < 0) { player->screenxminf = 0; } if (player->screenxminf > width) { player->screenxminf = width; } player->screenyminf = viGetViewTop(); if (player->screenyminf < 0) { player->screenyminf = 0; } if (player->screenyminf > height) { player->screenyminf = height; } player->screenxmaxf = viGetViewLeft() + viGetViewWidth(); if (player->screenxmaxf < 0) { player->screenxmaxf = 0; } if (player->screenxmaxf > width) { player->screenxmaxf = width; } player->screenymaxf = viGetViewTop() + viGetViewHeight(); if (player->screenymaxf < 0) { player->screenymaxf = 0; } if (player->screenymaxf > height) { player->screenymaxf = height; } } void bgExpandRoomToPortals(s32 roomnum) { s32 i; s32 j; s32 k; s32 count = 0; for (i = 0; i < g_Rooms[roomnum].numportals; i++) { s32 portalnum = g_RoomPortals[g_Rooms[roomnum].roomportallistoffset + i]; struct portalvertices *pvertices = (struct portalvertices *)((uintptr_t)g_BgPortals + g_BgPortals[portalnum].verticesoffset); for (j = 0; j < pvertices->count; j++) { for (k = 0; k < 3; k++) { f32 value = pvertices->vertices[j].f[k]; if (value < g_Rooms[roomnum].bbmin[k]) { g_Rooms[roomnum].bbmin[k] = value; count++; } if (value > g_Rooms[roomnum].bbmax[k]) { g_Rooms[roomnum].bbmax[k] = value; count++; } } } } if (count); } bool bgPortalExists(s32 portalnum) { s32 i; for (i = 0; g_BgPortals[i].verticesoffset != 0; i++) { if (i == portalnum) { return true; } } return false; } void bgPortalSwapRooms(s32 portal) { RoomNum tmp = g_BgPortals[portal].roomnum1; g_BgPortals[portal].roomnum1 = g_BgPortals[portal].roomnum2; g_BgPortals[portal].roomnum2 = tmp; } void bgInitPortal(s32 portalnum) { struct coord room1centre; struct coord room2centre; f32 tmp; struct portalmetric sp28; struct portalmetric *ptr; f32 tmp1; f32 tmp2; bool sp18; s32 roomnum1; s32 roomnum2; roomnum1 = g_BgPortals[portalnum].roomnum1; roomnum2 = g_BgPortals[portalnum].roomnum2; room1centre.x = g_Rooms[roomnum1].centre.x; room1centre.y = g_Rooms[roomnum1].centre.y; room1centre.z = g_Rooms[roomnum1].centre.z; room2centre.x = g_Rooms[roomnum2].centre.x; room2centre.y = g_Rooms[roomnum2].centre.y; room2centre.z = g_Rooms[roomnum2].centre.z; ptr = &g_PortalMetrics[portalnum]; sp28.normal.x = ptr->normal.x; sp28.normal.y = ptr->normal.y; sp28.normal.z = ptr->normal.z; sp28.min = ptr->min; sp28.max = ptr->max; tmp1 = sp28.normal.f[0] * room1centre.f[0] + sp28.normal.f[1] * room1centre.f[1] + sp28.normal.f[2] * room1centre.f[2]; if (tmp1); sp18 = 0; if (tmp1 > sp28.max) { sp18 = 1; bgPortalSwapRooms(portalnum); sp28.normal.x = -sp28.normal.x; sp28.normal.y = -sp28.normal.y; sp28.normal.z = -sp28.normal.z; tmp = sp28.min; sp28.min = -sp28.max; sp28.max = -tmp; } tmp2 = sp28.normal.f[0] * room2centre.f[0] + sp28.normal.f[1] * room2centre.f[1] + sp28.normal.f[2] * room2centre.f[2]; if (tmp2 <= sp28.min && sp18) { bgPortalSwapRooms(portalnum); } if (sp18); } /** * Figure out if the room has complicated portals and set a flag on the room if so. * * Most rooms use a simple portal setup where the portals (doors) exist along the * bounding box of the room. The game engine can easily test if a position is * inside the room by checking which side of the portal's plane it's on. * * A complicated portal setup happens when you have an L-shaped room and there's * a portal (door) on the inside wall of the L. It means you can be inside the * room but your position is on the opposite side of the door's plane. */ void bgInitRoom(s32 roomnum) { struct portalvertices *pvertices; s32 i; s32 j; s32 k; struct portalmetric metric; s16 portalnum; s16 portalnum2; f32 tmp; for (i = 0; i < g_Rooms[roomnum].numportals; i++) { portalnum = g_RoomPortals[g_Rooms[roomnum].roomportallistoffset + i]; metric.normal.f[0] = (g_PortalMetrics + portalnum)->normal.f[0]; metric.normal.f[1] = (g_PortalMetrics + portalnum)->normal.f[1]; metric.normal.f[2] = (g_PortalMetrics + portalnum)->normal.f[2]; metric.min = (g_PortalMetrics + portalnum)->min; metric.max = (g_PortalMetrics + portalnum)->max; if (roomnum == g_BgPortals[portalnum].roomnum1) { metric.normal.f[0] = -metric.normal.f[0]; metric.normal.f[1] = -metric.normal.f[1]; metric.normal.f[2] = -metric.normal.f[2]; tmp = metric.min; metric.min = -metric.max; metric.max = -tmp; } for (j = 0; j < g_Rooms[roomnum].numportals; j++) { portalnum2 = g_RoomPortals[g_Rooms[roomnum].roomportallistoffset + j]; if (portalnum2 == portalnum) { continue; } pvertices = (struct portalvertices *)((uintptr_t)g_BgPortals + g_BgPortals[portalnum2].verticesoffset); for (k = 0; k < pvertices->count; k++) { tmp = metric.normal.f[0] * pvertices->vertices[k].f[0] + metric.normal.f[1] * pvertices->vertices[k].f[1] + metric.normal.f[2] * pvertices->vertices[k].f[2]; if (tmp < metric.min) { g_Rooms[roomnum].flags |= ROOMFLAG_COMPLICATEDPORTALS; return; } } } } } void bgSetPortalOpenState(s32 portal, bool open) { g_BgPortals[portal].flags = (g_BgPortals[portal].flags | PORTALFLAG_CLOSED) ^ (open != false); } Gfx *bgRenderPortals(Gfx *gdl, s32 arg1, s32 arg2) { return gdl; } void bg0f164e80(s32 arg0, s32 arg1) { // empty } f32 var8007fcb4 = 0; s32 bgFindPortalBetweenPositions(struct coord *pos1, struct coord *pos2) { s32 bestportalnum = -1; s32 count = 0; f32 bestthing = MAXFLOAT; f32 thisthing; s32 i; for (i = 0; g_BgPortals[i].verticesoffset; i++) { if (portalCalculateIntersection(i, pos1, pos2) != PORTALINTERSECTION_NONE) { thisthing = var8007fcb4; if (thisthing < 0) { thisthing = -thisthing; } if (thisthing < bestthing) { if (count); if (i); bestportalnum = i; bestthing = thisthing; count++; } } } return bestportalnum; } bool bgIsBboxOverlapping(struct coord *portalbbmin, struct coord *portalbbmax, struct coord *propbbmin, struct coord *propbbmax) { s32 i; for (i = 0; i < 3; i++) { if (propbbmin->f[i] > portalbbmax->f[i] || propbbmax->f[i] < portalbbmin->f[i]) { return false; } } return true; } void bgCalculatePortalBbox(s32 portalnum, struct coord *bbmin, struct coord *bbmax) { struct portalvertices *pvertices; s32 i; s32 j; bbmin->x = MAXFLOAT; bbmin->y = MAXFLOAT; bbmin->z = MAXFLOAT; bbmax->x = MINFLOAT; bbmax->y = MINFLOAT; bbmax->z = MINFLOAT; pvertices = (struct portalvertices *)((uintptr_t)g_BgPortals + g_BgPortals[portalnum].verticesoffset); for (i = 0; i < pvertices->count; i++) { for (j = 0; j < 3; j++) { f32 value = pvertices->vertices[i].f[j]; if (value < bbmin->f[j]) { bbmin->f[j] = value; } if (value > bbmax->f[j]) { bbmax->f[j] = value; } } } } void bgFindEnteredRooms(struct coord *bbmin, struct coord *bbmax, RoomNum *rooms, s32 maxlen, bool arg4) { RoomNum room; RoomNum otherroom; s32 portalnum; struct coord propbbmin; struct coord propbbmax; s32 len; s32 i; s32 j; s32 k; s32 origlen; struct coord portalbbmin; struct coord portalbbmax; propbbmin.x = bbmin->x; propbbmin.y = bbmin->y; propbbmin.z = bbmin->z; propbbmax.x = bbmax->x; propbbmax.y = bbmax->y; propbbmax.z = bbmax->z; i = 0; for (len = 0; rooms[len] != -1; len++); while (true) { origlen = len; for (; i < origlen; i++) { room = rooms[i]; for (j = 0; j < g_Rooms[room].numportals; j++) { portalnum = g_RoomPortals[g_Rooms[room].roomportallistoffset + j]; if (arg4 && PORTAL_IS_CLOSED(portalnum)) { continue; } bgCalculatePortalBbox(portalnum, &portalbbmin, &portalbbmax); if (bgIsBboxOverlapping(&portalbbmin, &portalbbmax, &propbbmin, &propbbmax)) { if (room == g_BgPortals[portalnum].roomnum1) { otherroom = g_BgPortals[portalnum].roomnum2; } else { otherroom = g_BgPortals[portalnum].roomnum1; } for (k = 0; k < len; k++) { if (rooms[k] == otherroom) { break; } } if (k == len) { if (len < maxlen) { rooms[len] = otherroom; len++; } if (len >= maxlen) { goto end; } } } } } if (len == origlen) { break; } } end: rooms[len] = -1; } #ifndef PLATFORM_N64 void bgCalculateGlaresForVisibleRooms(void) { s32 i; g_NumRoomsWithGlares = 0; if (!g_Vars.mplayerisrunning) { for (i = 1; i < g_Vars.roomcount; i++) { if (g_Rooms[i].flags & ROOMFLAG_ONSCREEN) { artifactsCalculateGlaresForRoom(i); if (g_NumRoomsWithGlares < 100) { g_GlareRooms[g_NumRoomsWithGlares++] = i; } } } } } #endif