// SPDX-License-Identifier: MIT // // Copyright (c) 2008-2022 Antonio Niño Díaz // // This file is part of Nitro Engine #include "NEMain.h" /// @file NE2D.c static NE_Sprite **NE_spritepointers = NULL; static int NE_MAX_SPRITES; static bool ne_sprite_system_inited = false; NE_Sprite *NE_SpriteCreate(void) { if (!ne_sprite_system_inited) { NE_DebugPrint("System not initialized"); return NULL; } for (int i = 0; i < NE_MAX_SPRITES; i++) { if (NE_spritepointers[i] != NULL) continue; NE_Sprite *sprite = calloc(1, sizeof(NE_Sprite)); if (sprite == NULL) { NE_DebugPrint("Not enough memory"); return NULL; } sprite->visible = true; sprite->scale = inttof32(1); sprite->color = NE_White; sprite->mat = NULL; sprite->alpha = 31; NE_spritepointers[i] = sprite; return sprite; } NE_DebugPrint("No free slots"); return NULL; } void NE_SpriteSetPos(NE_Sprite *sprite, int x, int y) { NE_AssertPointer(sprite, "NULL pointer"); sprite->x = x; sprite->y = y; } void NE_SpriteSetSize(NE_Sprite *sprite, int w, int h) { NE_AssertPointer(sprite, "NULL pointer"); sprite->w = w; sprite->h = h; } void NE_SpriteSetRot(NE_Sprite *sprite, int angle) { NE_AssertPointer(sprite, "NULL pointer"); sprite->rot_angle = angle; } void NE_SpriteSetScaleI(NE_Sprite *sprite, int scale) { NE_AssertPointer(sprite, "NULL pointer"); sprite->scale = scale; } void NE_SpriteSetMaterial(NE_Sprite *sprite, NE_Material *mat) { NE_AssertPointer(sprite, "NULL sprite pointer"); NE_AssertPointer(mat, "NULL material pointer"); sprite->mat = mat; int mat_w = NE_TextureGetSizeX(mat); int mat_h = NE_TextureGetSizeY(mat); sprite->w = mat_w; sprite->h = mat_h; sprite->tl = 0; sprite->tr = mat_w; sprite->tt = 0; sprite->tb = mat_h; } void NE_SpriteSetMaterialCanvas(NE_Sprite *sprite, int tl, int tt, int tr, int tb) { NE_AssertPointer(sprite, "NULL sprite pointer"); NE_AssertPointer(sprite->mat, "Sprite doesn't have a material"); sprite->tl = tl; sprite->tr = tr; sprite->tt = tt; sprite->tb = tb; } void NE_SpriteSetPriority(NE_Sprite *sprite, int priority) { NE_AssertPointer(sprite, "NULL pointer"); sprite->priority = priority; } void NE_SpriteVisible(NE_Sprite *sprite, bool visible) { NE_AssertPointer(sprite, "NULL pointer"); sprite->visible = visible; } void NE_SpriteSetParams(NE_Sprite *sprite, u8 alpha, u8 id, u32 color) { NE_AssertPointer(sprite, "NULL pointer"); NE_AssertMinMax(0, alpha, 31, "Invalid alpha value %d", alpha); NE_AssertMinMax(0, id, 63, "Invalid polygon ID %d", id); sprite->alpha = alpha; sprite->id = id; sprite->color = color; } void NE_SpriteDelete(NE_Sprite *sprite) { if (!ne_sprite_system_inited) return; NE_AssertPointer(sprite, "NULL pointer"); for (int i = 0; i < NE_MAX_SPRITES; i++) { if (NE_spritepointers[i] != sprite) continue; NE_spritepointers[i] = NULL; free((void *)sprite); return; } NE_DebugPrint("Object not found"); return; } void NE_SpriteDeleteAll(void) { if (!ne_sprite_system_inited) return; for (int i = 0; i < NE_MAX_SPRITES; i++) NE_SpriteDelete(NE_spritepointers[i]); } int NE_SpriteSystemReset(int max_sprites) { if (ne_sprite_system_inited) NE_SpriteSystemEnd(); if (max_sprites < 1) NE_MAX_SPRITES = NE_DEFAULT_SPRITES; else NE_MAX_SPRITES = max_sprites; NE_spritepointers = calloc(NE_MAX_SPRITES, sizeof(NE_spritepointers)); if (NE_spritepointers == NULL) { NE_DebugPrint("Not enough memory"); return -1; } ne_sprite_system_inited = true; return 0; } void NE_SpriteSystemEnd(void) { if (!ne_sprite_system_inited) return; NE_SpriteDeleteAll(); free(NE_spritepointers); ne_sprite_system_inited = false; } void NE_SpriteDraw(const NE_Sprite *sprite) { if (!ne_sprite_system_inited) return; NE_AssertPointer(sprite, "NULL pointer"); if (!sprite->visible) return; if (sprite->rot_angle) { MATRIX_PUSH = 0; NE_2DViewRotateScaleByPositionI(sprite->x + (sprite->w >> 1), sprite->y + (sprite->h >> 1), sprite->rot_angle, sprite->scale); } else { NE_2DViewScaleByPositionI(sprite->x + (sprite->w >> 1), sprite->y + (sprite->h >> 1), sprite->scale); } GFX_POLY_FORMAT = POLY_ALPHA(sprite->alpha) | POLY_ID(sprite->id) | NE_CULL_NONE; NE_2DDrawTexturedQuadColorCanvas(sprite->x, sprite->y, sprite->x + sprite->w, sprite->y + sprite->h, sprite->priority, sprite->tl, sprite->tt, sprite->tr, sprite->tb, sprite->mat, sprite->color); if (sprite->rot_angle) MATRIX_POP = 1; } void NE_SpriteDrawAll(void) { if (!ne_sprite_system_inited) return; for (int i = 0; i < NE_MAX_SPRITES; i++) { if (NE_spritepointers[i] == NULL) continue; NE_Sprite *sprite = NE_spritepointers[i]; if (!sprite->visible) continue; if (sprite->rot_angle) { MATRIX_PUSH = 0; NE_2DViewRotateScaleByPositionI(sprite->x + (sprite->w >> 1), sprite->y + (sprite->h >> 1), sprite->rot_angle, sprite->scale); } else { NE_2DViewScaleByPositionI(sprite->x + (sprite->w >> 1), sprite->y + (sprite->h >> 1), sprite->scale); } GFX_POLY_FORMAT = POLY_ALPHA(sprite->alpha) | POLY_ID(sprite->id) | NE_CULL_NONE; NE_2DDrawTexturedQuadColorCanvas(sprite->x, sprite->y, sprite->x + sprite->w, sprite->y + sprite->h, sprite->priority, sprite->tl, sprite->tt, sprite->tr, sprite->tb, sprite->mat, sprite->color); if (sprite->rot_angle) MATRIX_POP = 1; } } //---------------------------------------------------------- // // Functions to draw freely in 2D. // //---------------------------------------------------------- // Internal use. See NETexture.c int __NE_TextureGetRawX(const NE_Material *tex); int __NE_TextureGetRawY(const NE_Material *tex); //-------------------------------------------- void NE_2DViewInit(void) { GFX_VIEWPORT = 0 | (0 << 8) | (255 << 16) | (191 << 24); // The projection matrix actually thinks that the size of the DS is // (256 << factor) x (192 << factor). After this, we scale the MODELVIEW // matrix to match this scale factor. // // This way, it is possible to draw on the screen by using numbers up to 256 // x 192, but internally the DS has more digits when it does transformations // like a rotation. Not having this factor results in noticeable flickering, // specially in some emulators. // // Unfortunately, applying this factor reduces the accuracy of the Y // coordinate a lot (nothing is noticeable in the X coordinate). Any factor // over 4 starts showing a noticeable accuracy loss: some sprites start // being slightly distorted, with missing some horizontal lines as the // height is reduced. When the number is higher, like 12, the Y coordinate // is significantly compressed. When the number is even higher, like 18, the // polygons disappear because too much accuracy has been lost. // // The current solution is to compromise, and use a factor of 2, which // doesn't cause any distortion, and solves most of the flickering. Ideally // we would use 0 to simplify the calculations, but we want to reduce the // flickering. // // On hardware, the difference in flickering between 0 and 2 isn't too // noticeable, but it is noticeable. In DeSmuMe it is very noticeable. // In my tests, Y axis distortion starts to happen with a factor of 4, so a // factor of 2 should be safe and reduce enough flickering. MATRIX_CONTROL = GL_PROJECTION; MATRIX_IDENTITY = 0; int factor = 2; glOrthof32(0, 256 << factor, 192 << factor, 0, inttof32(1), inttof32(-1)); MATRIX_CONTROL = GL_MODELVIEW; MATRIX_IDENTITY = 0; MATRIX_SCALE = inttof32(1 << factor); MATRIX_SCALE = inttof32(1 << factor); MATRIX_SCALE = inttof32(1); NE_PolyFormat(31, 0, 0, NE_CULL_NONE, 0); } void NE_2DViewRotateScaleByPositionI(int x, int y, int rotz, int scale) { NE_ViewMoveI(x, y, 0); MATRIX_SCALE = scale; MATRIX_SCALE = scale; MATRIX_SCALE = inttof32(1); glRotateZi(rotz << 6); NE_ViewMoveI(-x, -y, 0); } void NE_2DViewRotateByPosition(int x, int y, int rotz) { NE_ViewMoveI(x, y, 0); glRotateZi(rotz << 6); NE_ViewMoveI(-x, -y, 0); } void NE_2DViewScaleByPositionI(int x, int y, int scale) { NE_ViewMoveI(x, y, 0); MATRIX_SCALE = scale; MATRIX_SCALE = scale; MATRIX_SCALE = inttof32(1); NE_ViewMoveI(-x, -y, 0); } void NE_2DDrawQuad(s16 x1, s16 y1, s16 x2, s16 y2, s16 z, u32 color) { GFX_BEGIN = GL_QUADS; GFX_TEX_FORMAT = 0; GFX_COLOR = color; GFX_VERTEX16 = (y1 << 16) | (x1 & 0xFFFF); // Up-left GFX_VERTEX16 = z; GFX_VERTEX_XY = (y2 << 16) | (x1 & 0xFFFF); // Down-left GFX_VERTEX_XY = (y2 << 16) | (x2 & 0xFFFF); // Down-right GFX_VERTEX_XY = (y1 << 16) | (x2 & 0xFFFF); // Up-right } void NE_2DDrawQuadGradient(s16 x1, s16 y1, s16 x2, s16 y2, s16 z, u32 color1, u32 color2, u32 color3, u32 color4) { GFX_BEGIN = GL_QUADS; GFX_TEX_FORMAT = 0; GFX_COLOR = color1; GFX_VERTEX16 = (y1 << 16) | (x1 & 0xFFFF); // Up-left GFX_VERTEX16 = z; GFX_COLOR = color4; GFX_VERTEX_XY = (y2 << 16) | (x1 & 0xFFFF); // Down-left GFX_COLOR = color3; GFX_VERTEX_XY = (y2 << 16) | (x2 & 0xFFFF); // Down-right GFX_COLOR = color2; GFX_VERTEX_XY = (y1 << 16) | (x2 & 0xFFFF); // Up-right } void NE_2DDrawTexturedQuad(s16 x1, s16 y1, s16 x2, s16 y2, s16 z, const NE_Material *mat) { NE_AssertPointer(mat, "NULL pointer"); NE_Assert(mat->texindex != NE_NO_TEXTURE, "No texture"); int x = NE_TextureGetSizeX(mat), y = NE_TextureGetSizeY(mat); NE_MaterialUse(mat); GFX_BEGIN = GL_QUADS; GFX_TEX_COORD = TEXTURE_PACK(0, 0); GFX_VERTEX16 = (y1 << 16) | (x1 & 0xFFFF); // Up-left GFX_VERTEX16 = z; GFX_TEX_COORD = TEXTURE_PACK(0, inttot16(y)); GFX_VERTEX_XY = (y2 << 16) | (x1 & 0xFFFF); // Down-left GFX_TEX_COORD = TEXTURE_PACK(inttot16(x), inttot16(y)); GFX_VERTEX_XY = (y2 << 16) | (x2 & 0xFFFF); // Down-right GFX_TEX_COORD = TEXTURE_PACK(inttot16(x), 0); GFX_VERTEX_XY = (y1 << 16) | (x2 & 0xFFFF); // Up-right } void NE_2DDrawTexturedQuadColor(s16 x1, s16 y1, s16 x2, s16 y2, s16 z, const NE_Material *mat, u32 color) { NE_AssertPointer(mat, "NULL pointer"); NE_Assert(mat->texindex != NE_NO_TEXTURE, "No texture"); int x = NE_TextureGetSizeX(mat), y = NE_TextureGetSizeY(mat); NE_MaterialUse(mat); GFX_COLOR = color; GFX_BEGIN = GL_QUADS; GFX_TEX_COORD = TEXTURE_PACK(0, 0); GFX_VERTEX16 = (y1 << 16) | (x1 & 0xFFFF); // Up-left GFX_VERTEX16 = z; GFX_TEX_COORD = TEXTURE_PACK(0, inttot16(y)); GFX_VERTEX_XY = (y2 << 16) | (x1 & 0xFFFF); // Down-left GFX_TEX_COORD = TEXTURE_PACK(inttot16(x), inttot16(y)); GFX_VERTEX_XY = (y2 << 16) | (x2 & 0xFFFF); // Down-right GFX_TEX_COORD = TEXTURE_PACK(inttot16(x), 0); GFX_VERTEX_XY = (y1 << 16) | (x2 & 0xFFFF); // Up-right } void NE_2DDrawTexturedQuadGradient(s16 x1, s16 y1, s16 x2, s16 y2, s16 z, const NE_Material *mat, u32 color1, u32 color2, u32 color3, u32 color4) { NE_AssertPointer(mat, "NULL pointer"); NE_Assert(mat->texindex != NE_NO_TEXTURE, "No texture"); int x = NE_TextureGetSizeX(mat), y = NE_TextureGetSizeY(mat); NE_MaterialUse(mat); GFX_BEGIN = GL_QUADS; GFX_COLOR = color1; GFX_TEX_COORD = TEXTURE_PACK(0, 0); GFX_VERTEX16 = (y1 << 16) | (x1 & 0xFFFF); // Up-left GFX_VERTEX16 = z; GFX_COLOR = color4; GFX_TEX_COORD = TEXTURE_PACK(0, inttot16(y)); GFX_VERTEX_XY = (y2 << 16) | (x1 & 0xFFFF); // Down-left GFX_COLOR = color3; GFX_TEX_COORD = TEXTURE_PACK(inttot16(x), inttot16(y)); GFX_VERTEX_XY = (y2 << 16) | (x2 & 0xFFFF); // Down-right GFX_COLOR = color2; GFX_TEX_COORD = TEXTURE_PACK(inttot16(x), 0); GFX_VERTEX_XY = (y1 << 16) | (x2 & 0xFFFF); // Up-right } void NE_2DDrawTexturedQuadColorCanvas(s16 x1, s16 y1, s16 x2, s16 y2, s16 z, int tl, int tt, int tr, int tb, const NE_Material *mat, u32 color) { NE_AssertPointer(mat, "NULL pointer"); NE_Assert(mat->texindex != NE_NO_TEXTURE, "No texture"); NE_MaterialUse(mat); GFX_COLOR = color; GFX_BEGIN = GL_QUADS; GFX_TEX_COORD = TEXTURE_PACK(inttot16(tl), inttot16(tt)); GFX_VERTEX16 = (y1 << 16) | (x1 & 0xFFFF); // Up-left GFX_VERTEX16 = z; GFX_TEX_COORD = TEXTURE_PACK(inttot16(tl), inttot16(tb)); GFX_VERTEX_XY = (y2 << 16) | (x1 & 0xFFFF); // Down-left GFX_TEX_COORD = TEXTURE_PACK(inttot16(tr), inttot16(tb)); GFX_VERTEX_XY = (y2 << 16) | (x2 & 0xFFFF); // Down-right GFX_TEX_COORD = TEXTURE_PACK(inttot16(tr), inttot16(tt)); GFX_VERTEX_XY = (y1 << 16) | (x2 & 0xFFFF); // Up-right }