// SPDX-License-Identifier: CC0-1.0
//
// SPDX-FileContributor: Antonio Niño Díaz, 2024-2026

// This example shows how to use 3D fog to fade 3D objects over a 2D layer.

#include <stdio.h>

#include <nds.h>

// Headers autogenerated from the files in BINDIRS in the Makefile
#include "sphere_bin.h"

// Headers autogenerated for each PNG file inside GFXDIRS in the Makefile
#include "statue.h"
#include "forest_town.h"

void setup_fog_properties(int shift, int mass, int depth)
{
    // How much depth difference there is between table entries
    glFogShift(shift);

    // Depth at which the fog starts (and the table starts applying)
    glFogOffset(depth);

    // Generate density table

    int density = 0; // Start table at 0

    for (int i = 0; i < 32; i++)
    {
        glFogDensity(i, density);

        density += mass << 1;

        // Entries are 7 bit, so cap the density to 127
        if (density > 127)
            density = 127;
    }
}

int main(int argc, char **argv)
{
    // Enable 3D
    videoSetMode(MODE_0_3D);

    // Setup some VRAM as memory for main engine background, main engine
    // sprites, and 3D textures.
    vramSetPrimaryBanks(VRAM_A_MAIN_BG, VRAM_B_MAIN_SPRITE,
                        VRAM_C_TEXTURE, VRAM_D_TEXTURE);

    // Setup console in the sub screen
    vramSetBankH(VRAM_H_SUB_BG);
    consoleDemoInit();

    // Setup 2D background
    // ===================

    int bg = bgInitHidden(2, BgType_Text8bpp, BgSize_T_256x256, 0, 4);
    bgSetPriority(bg, 2);

    memcpy(bgGetGfxPtr(bg), forest_townTiles, forest_townTilesLen);
    memcpy(bgGetMapPtr(bg), forest_townMap, forest_townMapLen);
    memcpy(BG_PALETTE, forest_townPal, forest_townPalLen);

    bgShow(bg);

    // Setup 2D sprites
    // ================

    oamInit(&oamMain, SpriteMapping_1D_32, false);

    oamEnable(&oamMain);

    // Allocate space for the tiles and copy them there
    u16 *gfxMain = oamAllocateGfx(&oamMain, SpriteSize_32x32, SpriteColorFormat_256Color);
    memcpy(gfxMain, statueTiles, statueTilesLen);

    // Copy palette
    memcpy(SPRITE_PALETTE, statuePal, statuePalLen);

    oamSet(&oamMain, 0,
           30, 50, // X, Y
           0, // Priority
           0, // Palette index
           SpriteSize_64x64, SpriteColorFormat_256Color, // Size, format
           gfxMain,  // Graphics offset
           -1, // Affine index
           false, // Double size
           false, // Hide
           false, false, // H flip, V flip
           false); // Mosaic

    // Setup 3D
    // ========

    glInit();

    glEnable(GL_TEXTURE_2D);
    glEnable(GL_ANTIALIAS);

    // Enable fog and alpha blending so that fog can blend on the 2D layers.
    // Also, set fog to "only alpha" mode to ignore the fog color and only use
    // the alpha value.
    glEnable(GL_FOG);
    glEnable(GL_BLEND);
    glEnable(GL_FOG_ONLY_ALPHA);

    // Don't apply fog to clear plane
    glClearFogEnable(true);

    // Setup color and alpha (color is ignored, only alpha is used)
    glFogColor(0, 0, 0, 0);

    // Fog alpha blending on 2D layers requires the 2D blending registers to be
    // setup to work. We don't need to set a source layer, the 3D layer is set
    // as source layer automatically.
    REG_BLDCNT = BLEND_ALPHA | BLEND_DST_BG2 | BLEND_DST_BACKDROP;
    // The EVA and EVB values are ignored in this case.
    REG_BLDALPHA = BLDALPHA_EVA(31) | BLDALPHA_EVB(31);

    // IMPORTANT: The 3D background must be transparent (alpha = 0) so that the
    // 2D layers behind it can be seen.
    glClearColor(0, 0, 0, 0);
    glClearPolyID(63);

    glClearDepth(0x7FFF);

    glViewport(0, 0, 255, 191);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70, 256.0 / 192.0, 0.1, 40);

    // Setup done
    // ==========

    int rotateX = 0;
    int rotateY = 0;

    int shift = 5;
    int mass = 3;
    int depth = 0x7D00;

    while (1)
    {
        // Synchronize game loop to the screen refresh
        swiWaitForVBlank();

        bgUpdate();

        oamUpdate(&oamMain);

        // Handle user input
        // -----------------

        // Handle key input
        scanKeys();
        u16 keys_held = keysHeld();
        u16 keys_down = keysDown();

        // Rotate balls
        if (keys_held & KEY_UP)
            rotateX += 3 << 5;
        if (keys_held & KEY_DOWN)
            rotateX -= 3 << 5;
        if (keys_held & KEY_LEFT)
            rotateY += 3 << 5;
        if (keys_held & KEY_RIGHT)
            rotateY -= 3 << 5;

        // Modify parameters
        if ((keys_down & KEY_X) && (shift < 8))
            shift++;
        if ((keys_down & KEY_B) && (shift > 0))
            shift--;
        if ((keys_down & KEY_A) && (mass < 6))
            mass++;
        if ((keys_down & KEY_Y) && (mass > 0))
            mass--;
        if ((keys_held & KEY_R) && (depth < 0x7FC0))
            depth += 0x20;
        if ((keys_held & KEY_L) && (depth > 0x4000))
            depth -= 0x20;

        if (keys_held & KEY_START)
            break;

        // Print some text in the demo console
        // -----------------------------------

        consoleClear();

        printf("PAD:   Rotate\n");
        printf("B/X:   Set shift: %d\n", shift);
        printf("A/Y:   Set mass: %d\n", mass);
        printf("L/R:   Set depth: 0x%04X\n", depth);
        printf("\n");
        printf("START: Exit to loader");

        // Render 3D scene
        // ---------------

        // Setup fog properties
        setup_fog_properties(shift, mass, depth);

        glMatrixMode(GL_MODELVIEW);

        // Save the state of the current matrix(the modelview matrix)
        glLoadIdentity();

        // Setup the camera
        gluLookAt(0.0, 3.5, 3.5, // Camera position
                  0.0, 0.0, -3.5, // Look at
                  0.0, 1.0, 0.0); // Up

        glRotateXi(rotateX);
        glRotateYi(rotateY);

        // Draw the scene

        glPolyFmt(POLY_ALPHA(31) | POLY_ID(0) | POLY_CULL_BACK | POLY_FOG);

        glCallList(sphere_bin);

        glTranslatef32(floattof32(0), floattof32(0), floattof32(-3));
        glCallList(sphere_bin);

        glTranslatef32(floattof32(0), floattof32(0), floattof32(-3));
        glCallList(sphere_bin);

        glTranslatef32(floattof32(0), floattof32(0), floattof32(-3));
        glCallList(sphere_bin);

        glFlush(0);
    }

    oamFreeGfx(&oamMain, gfxMain);

    return 0;
}
