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

// This example shows the difference between modulation and decal polygon modes.
//
// Modulation mode is what you normally expect. Light affects translucent
// textures by coloring the texture. Alpha values of the texture make the
// texture translucent.
//
// Decal mode makes light stop affecting the color of the texture. Instead, the
// texture is rendered with its base color. The alpha component behaves as if
// there is another texture underneath that is completely white and affected by
// light.

#include <stdio.h>

#include <nds.h>

// Header autogenerated for each PNG file inside GFXDIRS in the Makefile
#include "teapot.h"

#include "teapot_bin.h"

__attribute__((noreturn)) void wait_forever(void)
{
    printf("Press START to exit.");

    while (1)
    {
        swiWaitForVBlank();

        scanKeys();
        if (keysHeld() & KEY_START)
            exit(1);
    }
}

int main(int argc, char **argv)
{
    int textureID;

    // Setup sub screen for the text console
    consoleDemoInit();

    videoSetMode(MODE_0_3D);

    glInit();

    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);

    // The background must be fully opaque and have a unique polygon ID
    // (different from the polygons that are going to be drawn) so that
    // alpha blending works.
    glClearColor(0, 0, 0, 31);
    glClearPolyID(63);

    glClearDepth(0x7FFF);

    glViewport(0, 0, 255, 191);

    // Setup some VRAM as memory for textures and texture palettes
    vramSetBankA(VRAM_A_TEXTURE);
    vramSetBankB(VRAM_B_TEXTURE);
    vramSetBankF(VRAM_F_TEX_PALETTE);

    // Load textures
    glGenTextures(1, &textureID);

    glBindTexture(0, textureID);
    if (glTexImageNtr2D(GL_RGB8_A5, 256, 256,
                        TEXGEN_TEXCOORD | GL_TEXTURE_WRAP_S | GL_TEXTURE_WRAP_T |
                        GL_TEXTURE_FLIP_S | GL_TEXTURE_FLIP_T,
                        teapotBitmap, NULL) == 0)
    {
        printf("Failed to load texture\n");
        wait_forever();
    }
    if (glColorTableNtr(8, teapotPal) == 0)
    {
        printf("Failed to load palette\n");
        wait_forever();
    }

    // Setup some material properties
    glMaterialf(GL_AMBIENT, RGB15(0, 0, 0));
    glMaterialf(GL_DIFFUSE, RGB15(31, 31, 31));
    glMaterialf(GL_SPECULAR, RGB15(0, 0, 0));
    glMaterialf(GL_EMISSION, RGB15(0, 0, 0));

    // Setup lights
    glLight(0, RGB15(0, 31, 0), floattov10(-1), floattov10(0), floattov10(0));
    glLight(1, RGB15(31, 0, 0), floattov10(1), floattov10(0), floattov10(0));
    glLight(2, RGB15(0, 0, 31), floattov10(0), floattov10(-1), floattov10(0));
    glLight(3, RGB15(31, 31, 0), floattov10(0), floattov10(1), floattov10(0));

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

    gluLookAt(0.0, 0.0, 2.25,  // Position
              0.0, 0.0, 0.0,  // Look at
              0.0, 1.0, 0.0); // Up

    int angle_x = 0;
    int angle_y = 0;

    int poly_mode = POLY_MODULATION;

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

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

        consoleClear();

        // Print some controls
        printf("A:      Modulation mode\n");
        printf("B:      Decal mode\n");
        printf("PAD:    Rotate triangle\n");
        printf("\n");
        printf("START:  Exit to loader\n");

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

        scanKeys();

        uint16_t keys = keysHeld();

        if (keys & KEY_LEFT)
            angle_y += 3;
        if (keys & KEY_RIGHT)
            angle_y -= 3;

        if (keys & KEY_UP)
            angle_x += 3;
        if (keys & KEY_DOWN)
            angle_x -= 3;

        if (keys & KEY_A)
            poly_mode = POLY_MODULATION;
        if (keys & KEY_B)
            poly_mode = POLY_DECAL;

        if (keys & KEY_START)
            break;

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

        glMatrixMode(GL_MODELVIEW);

        glPushMatrix();

        glRotateX(angle_x);
        glRotateY(angle_y);

        glBindTexture(0, textureID);

        // We want to see the difference between modulation and decal modes.
        //
        // In modulation mode all textures are translucent as usual, so we need
        // to sort polygons from back to front (we do two passes with front and
        // back culling).
        //
        // With decal mode textures are fully opaque, so we don't need to do
        // the two rendering passes (but let's do it anyway to see that it
        // doesn't change anything.
#if 0
        // This is what you would need in decal mode
        glPolyFmt(POLY_ALPHA(31) | POLY_ID(0) | POLY_CULL_NONE | POLY_DECAL |
                  POLY_FORMAT_LIGHT0 | POLY_FORMAT_LIGHT1 | POLY_FORMAT_LIGHT2 |
                  POLY_FORMAT_LIGHT3);

        glCallList(teapot_bin);
#endif

        glPolyFmt(POLY_ALPHA(31) | POLY_ID(0) | POLY_CULL_FRONT | poly_mode |
                  POLY_FORMAT_LIGHT0 | POLY_FORMAT_LIGHT1 | POLY_FORMAT_LIGHT2 |
                  POLY_FORMAT_LIGHT3);

        glCallList(teapot_bin);

        glPolyFmt(POLY_ALPHA(31) | POLY_ID(1) | POLY_CULL_BACK | poly_mode |
                  POLY_FORMAT_LIGHT0 | POLY_FORMAT_LIGHT1 | POLY_FORMAT_LIGHT2 |
                  POLY_FORMAT_LIGHT3);

        glCallList(teapot_bin);

        glPopMatrix(1);

        glFlush(GL_TRANS_MANUALSORT);
    }

    glDeleteTextures(1, &textureID);

    return 0;
}
