#include "koopatlas/map.h" #include "koopatlas/camera.h" //#define TILE_DEBUGGING //#define BOUND_DEBUGGING //#define DOODAD_DEBUGGING #ifdef TILE_DEBUGGING #define TileReport OSReport #else #define TileReport(...) #endif #ifdef BOUND_DEBUGGING #define BoundReport OSReport #else #define BoundReport(...) #endif #ifdef DOODAD_DEBUGGING #define DoodadReport OSReport #else #define DoodadReport(...) #endif dWMMap_c *dWMMap_c::instance = 0; dWMMap_c *dWMMap_c::build() { void *buffer = AllocFromGameHeap1(sizeof(dWMMap_c)); dWMMap_c *c = new(buffer) dWMMap_c; instance = c; return c; } dWMMap_c::dWMMap_c() { } int dWMMap_c::onCreate() { renderer.allocator.setup(GameHeaps[0], 0x20); bool result = renderer.setup(&renderer.allocator); bgMatrix.translation(1140.0f, -2910.0f, -500.0f); s16 rot = 0x4000; bgMatrix.applyRotationX(&rot); allocator.link(-1, GameHeaps[0], 0, 0x20); nw4r::g3d::ResFile rf(dScKoopatlas_c::instance->mapData.bgLoader.buffer); rf.CheckRevision(); rf.Init(); rf.Bind(rf); // Todo: Check flags. 0x32C definitely supports texsrt nw4r::g3d::ResMdl modelRes = rf.GetResMdl("KpBG"); nw4r::g3d::ResAnmTexSrt anmRes = rf.GetResAnmTexSrt("KpBG"); bgModel.setup(modelRes, &allocator, 0x32C, 1, 0); bgSrtAnm.setup(modelRes, anmRes, &allocator, 0, 1); bgSrtAnm.bindEntry(&bgModel, anmRes, 0, 0); bgModel.bindAnim(&bgSrtAnm, 0.0f); allocator.unlink(); return true; } int dWMMap_c::onDelete() { return true; } int dWMMap_c::onExecute() { bgSrtAnm.process(); bgModel.setDrawMatrix(bgMatrix); bgModel.setScale(1.0f, 1.0f, 1.0f); bgModel.calcWorld(false); return true; } int dWMMap_c::onDraw() { renderer.scheduleForDrawing(); bgModel.scheduleForDrawing(); return true; } void dWMMap_c::renderer_c::drawOpa() { //drawLayers(); } void dWMMap_c::renderer_c::drawXlu() { drawLayers(); } void dWMMap_c::renderer_c::drawLayers() { dKPMapData_c *dataCls = &dScKoopatlas_c::instance->mapData; dKPMapFile_s *data = dataCls->data; baseZ = -100.0f - (2 * data->layerCount); beginRendering(); for (int iLayer = data->layerCount - 1; iLayer >= 0; iLayer--) { dKPLayer_s *layer = data->layers[iLayer]; renderMtx[2][3] += 2.0f; TileReport("Checking layer %d with type %d\n", iLayer, layer->type); if (layer->type == dKPLayer_s::OBJECTS) renderTileLayer(layer, data->sectors); else if (layer->type == dKPLayer_s::DOODADS) renderDoodadLayer(layer); else if (layer->type == dKPLayer_s::PATHS) renderPathLayer(layer); } endRendering(); } void dWMMap_c::renderer_c::beginRendering() { currentTexture = 0; nw4r::g3d::Camera cam3d(GetCameraByID(0)); cam3d.GetCameraMtx(&renderMtx); MTXTransApply(renderMtx, renderMtx, 0, 0, baseZ); GXSetCurrentMtx(GX_PNMTX0); dWorldCamera_c *camObj = dWorldCamera_c::instance; minX = ((int)camObj->screenLeft) / 24; minY = ((int)(-camObj->screenTop) - 23) / 24; maxX = (((int)(camObj->screenLeft + camObj->screenWidth)) + 23) / 24; maxY = ((int)(-camObj->screenTop + camObj->screenHeight)) / 24; GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT); // Tiles GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); // Doodads GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_POS, GX_POS_XY, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_TEX0, GX_TEX_ST, GX_U8, 8); GXSetNumIndStages(0); for (int i = 0; i < 0x10; i++) GXSetTevDirect((GXTevStageID)i); GXSetNumChans(0); GXSetNumTexGens(1); GXSetTexCoordGen2(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, GX_FALSE, GX_PTIDENTITY); GXSetNumTevStages(1); GXSetNumIndStages(0); GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_C1, GX_CC_C0, GX_CC_TEXC, GX_CC_ZERO); GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_A0, GX_CA_TEXA, GX_CA_ZERO); GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GXSetZCompLoc(GX_FALSE); GXSetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_SET); GXSetZMode(GX_TRUE, GX_LEQUAL, GX_FALSE); GXSetAlphaCompare(GX_GREATER, 0, GX_AOP_OR, GX_GREATER, 0); GXSetFog(GX_FOG_NONE, 0, 0, 0, 0, (GXColor){0,0,0,0}); GXSetFogRangeAdj(GX_FALSE, 0, 0); GXSetCullMode(GX_CULL_NONE); GXSetDither(GX_TRUE); GXSetTevColor(GX_TEVREG0, (GXColor){255,255,255,255}); GXSetTevColor(GX_TEVREG1, (GXColor){0,0,0,255}); } void dWMMap_c::renderer_c::renderTileLayer(dKPLayer_s *layer, dKPLayer_s::sector_s *sectors) { //TileReport("Rendering layer %p\n", layer); // don't render it if we don't need to if (maxX < layer->left || minX > layer->right) return; if (maxY < layer->top || minY > layer->bottom) return; // set up loadCamera(); loadTexture(layer->tileset); // figure out -what- to render BoundReport("Regular render area: %d,%d to %d,%d\n", minX, minY, maxX, maxY); BoundReport("Layer bounds: %d,%d to %d,%d\n", layer->left, layer->top, layer->right, layer->bottom); int toRenderMinX = max(minX, layer->left); int toRenderMinY = max(minY, layer->top); int toRenderMaxX = min(maxX, layer->right); int toRenderMaxY = min(maxY, layer->bottom); int sectorBaseX = layer->sectorLeft; int sectorBaseY = layer->sectorTop; int sectorIndexStride = (layer->sectorRight - layer->sectorLeft + 1); int sectorMinX = toRenderMinX / 16; int sectorMinY = toRenderMinY / 16; int sectorMaxX = toRenderMaxX / 16; int sectorMaxY = toRenderMaxY / 16; BoundReport("To render: %d,%d to %d,%d\n", toRenderMinX, toRenderMinY, toRenderMaxX, toRenderMaxY); BoundReport("sectors %d,%d to %d,%d\n", sectorMinX, sectorMinY, sectorMaxX, sectorMaxY); for (int sectorY = sectorMinY; sectorY <= sectorMaxY; sectorY++) { int baseIndex = (sectorY - sectorBaseY) * sectorIndexStride; int iMinY = (sectorY == sectorMinY) ? (toRenderMinY & 0xF) : 0; int iMaxY = (sectorY == sectorMaxY) ? (toRenderMaxY & 0xF) : 15; int worldSectorY = sectorY << 4; for (int sectorX = sectorMinX; sectorX <= sectorMaxX; sectorX++) { u16 index = layer->indices[baseIndex + sectorX - sectorBaseX]; TileReport("Sector index @ %d,%d: %d\n", sectorX, sectorY, index); if (index == 0xFFFF) continue; dKPLayer_s::sector_s *sector = §ors[index]; int iMinX = (sectorX == sectorMinX) ? (toRenderMinX & 0xF) : 0; int iMaxX = (sectorX == sectorMaxX) ? (toRenderMaxX & 0xF) : 15; TileReport("Min/Max: X: %d,%d Y: %d,%d\n", iMinX, iMaxX, iMinY, iMaxY); int worldSectorX = sectorX << 4; for (int inY = iMinY; inY <= iMaxY; inY++) { for (int inX = iMinX; inX <= iMaxX; inX++) { u16 tileID = (*sector)[inY][inX]; if (tileID == 0xFFFF) continue; s16 worldX = (worldSectorX | inX) * 24; s16 worldY = -((worldSectorY | inY) * 24); TileReport("Drawing tile %d at %d,%d\n", tileID, worldX, worldY); float tileX = (float)((tileID & 0x1F) * 28); float tileY = (float)(((tileID & 0x1E0) >> 5) * 28); float xMult = (1.0f / 896.0f); float yMult = (1.0f / 448.0f); float coordX1 = xMult * (tileX + 2.0f); float coordX2 = xMult * (tileX + 26.0f); float coordY1 = yMult * (tileY + 2.0f); float coordY2 = yMult * (tileY + 26.0f); GXBegin(GX_QUADS, GX_VTXFMT0, 4); GXPosition2s16(worldX + 24, worldY - 24); GXTexCoord2f32(coordX2, coordY2); GXPosition2s16(worldX + 24, worldY); GXTexCoord2f32(coordX2, coordY1); GXPosition2s16(worldX, worldY); GXTexCoord2f32(coordX1, coordY1); GXPosition2s16(worldX, worldY - 24); GXTexCoord2f32(coordX1, coordY2); GXEnd(); } } TileReport("Sector complete\n"); } } //TileReport("Layer complete\n"); } void dWMMap_c::renderer_c::renderDoodadLayer(dKPLayer_s *layer) { for (int i = 0; i < layer->doodadCount; i++) { dKPDoodad_s *doodad = layer->doodads[i]; DoodadReport("Doodad @ %f,%f sized %f,%f with angle %f\n", doodad->x, doodad->y, doodad->width, doodad->height, doodad->angle); // ANIMATE THE FUCKER float effectiveX = doodad->x, effectiveY = doodad->y; float effectiveWidth = doodad->width, effectiveHeight = doodad->height; float effectiveAngle = doodad->angle; if (doodad->animationCount > 0) { for (int j = 0; j < doodad->animationCount; j++) { dKPDoodad_s::animation_s *anim = &doodad->animations[j]; if (anim->delayOffset == 0) { u32 baseTick = anim->baseTick; if (baseTick == 0) { anim->baseTick = baseTick = GlobalTickCount; } u32 elapsed = GlobalTickCount - baseTick; u32 elapsedAdjusted = elapsed; if (anim->isReversed) elapsed = anim->frameCount - 1 - elapsed; if (elapsed >= anim->frameCount) { if (elapsed >= (anim->frameCount + delay)) { // we've reached the end switch (anim->loop) { case dKPDoodad_s::animation_s::CONTIGUOUS: // Stop here elapsed = anim->frameCount - 1; break; case dKPDoodad_s::animation_s::LOOP: // Start over elapsed = 0; anim->baseTick = GlobalTickCount; break; case dKPDoodad_s::animation_s::REVERSE_LOOP: // Change direction anim->isReversed = !anim->isReversed; elapsed = (anim->isReversed) ? (anim->frameCount - 1) : 0; anim->baseTick = GlobalTickCount; break; } } elapsedAdjusted = anim->frameCount; } // now calculate the thing float progress = elapsedAdjusted / (float)anim->frameCount; float value; switch (anim->curve) { case dKPDoodad_s::animation_s::LINEAR: value = progress; break; case dKPDoodad_s::animation_s::SIN: value = (sin(((progress * M_PI * 2)) - M_PI_2) + 1) / 2; break; case dKPDoodad_s::animation_s::COS: value = (cos(((progress * M_PI * 2)) - M_PI_2) + 1) / 2; break; } float delta = anim->end - anim->start; float frame; if (anim->isReversed) frame = anim->start + ceil(delta * value); else frame = anim->start + (delta * value); // and apply it! switch (anim->type) { case dKPDoodad_s::animation_s::X_POS: effectiveX += frame; break; case dKPDoodad_s::animation_s::Y_POS: effectiveY += frame; break; case dKPDoodad_s::animation_s::ANGLE: effectiveAngle += frame; break; case dKPDoodad_s::animation_s::X_SCALE: effectiveWidth = (effectiveWidth * frame / 100.0); break; case dKPDoodad_s::animation_s::Y_SCALE: effectiveHeight = (effectiveHeight * frame / 100.0); break; case dKPDoodad_s::animation_s::OPACITY: // TODO break; } } else { anim->delayOffset -= 1; } } } float halfW = effectiveWidth * 0.5f, halfH = effectiveHeight * 0.5f; Mtx doodadMtx; MTXTransApply(renderMtx, doodadMtx, effectiveX + halfW, -effectiveY - halfH, 0); if (effectiveAngle != 0) { Mtx rotMtx; MTXRotDeg(rotMtx, 'z', -effectiveAngle); MTXConcat(doodadMtx, rotMtx, doodadMtx); } loadCamera(doodadMtx); loadTexture(doodad->texObj); GXBegin(GX_QUADS, GX_VTXFMT1, 4); GXPosition2f32(halfW, -halfH); GXTexCoord2u8(255, 255); GXPosition2f32(halfW, halfH); GXTexCoord2u8(255, 0); GXPosition2f32(-halfW, halfH); GXTexCoord2u8(0, 0); GXPosition2f32(-halfW, -halfH); GXTexCoord2u8(0, 255); GXEnd(); } } void dWMMap_c::renderer_c::renderPathLayer(dKPLayer_s *layer) { for (int i = 0; i < layer->nodeCount; i++) { dKPNode_s **node = layer->nodes[i]; int world = node->levelNumber[0]; int level = node->levelNumber[1]; SaveBlock *save = GetSaveFile()->GetBlock(-1); u32 conds = save->GetLevelCondition(world-1, level-1); bool isUnlocked = true; bool exitComplete = false; bool secretComplete = false; if (conds & 0x10) exitComplete = true; if (conds & 0x20) secretComplete = true; // Is it unlocked? if (!isUnlocked) node->color.setCurrentFrame(0); // Black // Is it complete? else if ((exitComplete) || (secretComplete)) // Does it have two exits? if ((node->hasSecret) && ((!exitComplete) || (!secretComplete))) node->color.setCurrentFrame(2); // Yellow // All exits are complete else node->color.setCurrentFrame(1); // Blue // Not complete after all else node->color.setCurrentFrame(3); // Red matrix.translation(node->x, node->y, 500.0); } matrix.applyRotationYXZ(&rot.x, &rot.y, &rot.z); node->model.setDrawMatrix(matrix); node->model.setScale(1.0f, 1.0f, 1.0f); node->model.calcWorld(false); node->model.scheduleForDrawing(); } } void dWMMap_c::renderer_c::endRendering() { } void dWMMap_c::renderer_c::loadTexture(GXTexObj *obj) { if (currentTexture == obj) return; GXLoadTexObj(obj, GX_TEXMAP0); currentTexture = obj; } void dWMMap_c::renderer_c::loadCamera() { GXLoadPosMtxImm(renderMtx, GX_PNMTX0); } void dWMMap_c::renderer_c::loadCamera(Mtx m) { GXLoadPosMtxImm(m, GX_PNMTX0); }