nitro-engine/source/NEModel.c
Antonio Niño Díaz 4642bdf982 library: Allow users to specify model transformation matrices
A manual matrix overrides scale, translation and rotation. It can be
removed to switch back to individual scale, translation and rotation.
2024-10-11 13:33:21 +02:00

649 lines
16 KiB
C

// SPDX-License-Identifier: MIT
//
// Copyright (c) 2008-2022 Antonio Niño Díaz
//
// This file is part of Nitro Engine
#include <nds/arm9/postest.h>
#include "dsma/dsma.h"
#include "NEMain.h"
/// @file NEModel.c
typedef struct {
void *address;
int uses; // Number of models that use this mesh
bool has_to_free;
} ne_mesh_info_t;
static ne_mesh_info_t *NE_Mesh = NULL;
static NE_Model **NE_ModelPointers;
static int NE_MAX_MODELS;
static bool ne_model_system_inited = false;
static void ne_mesh_delete(int mesh_index)
{
int slot = mesh_index;
// A mesh may be used by several models
NE_Mesh[slot].uses--;
// If the number of users is zero, delete it.
if (NE_Mesh[slot].uses == 0)
{
if (NE_Mesh[slot].has_to_free)
free(NE_Mesh[slot].address);
NE_Mesh[slot].address = NULL;
}
}
static int ne_model_get_free_mesh_slot(void)
{
// Get free slot
for (int i = 0; i < NE_MAX_MODELS; i++)
{
if (NE_Mesh[i].address == NULL)
return i;
}
NE_DebugPrint("No free slots");
return NE_NO_MESH;
}
static int ne_model_load_ram_common(NE_Model *model, const void *pointer)
{
NE_AssertPointer(model, "NULL model pointer");
NE_AssertPointer(pointer, "NULL data pointer");
// Check if a mesh exists
if (model->meshindex != NE_NO_MESH)
ne_mesh_delete(model->meshindex);
int slot = ne_model_get_free_mesh_slot();
if (slot == NE_NO_MESH)
return 0;
model->meshindex = slot;
ne_mesh_info_t *mesh = &NE_Mesh[slot];
mesh->address = (void *)pointer;
mesh->has_to_free = false;
mesh->uses = 1;
return 1;
}
static int ne_model_load_filesystem_common(NE_Model *model, const char *path)
{
NE_AssertPointer(model, "NULL model pointer");
NE_AssertPointer(path, "NULL path pointer");
// Check if a mesh exists
if (model->meshindex != NE_NO_MESH)
ne_mesh_delete(model->meshindex);
int slot = ne_model_get_free_mesh_slot();
if (slot == NE_NO_MESH)
return 0;
void *pointer = NE_FATLoadData(path);
if (pointer == NULL)
return 0;
model->meshindex = slot;
ne_mesh_info_t *mesh = &NE_Mesh[slot];
mesh->address = pointer;
mesh->has_to_free = true;
mesh->uses = 1;
return 1;
}
//--------------------------------------------------------------------------
NE_Model *NE_ModelCreate(NE_ModelType type)
{
if (!ne_model_system_inited)
{
NE_DebugPrint("System not initialized");
return NULL;
}
NE_Model *model = calloc(1, sizeof(NE_Model));
if (model == NULL)
{
NE_DebugPrint("Not enough memory");
return NULL;
}
int i = 0;
while (1)
{
if (NE_ModelPointers[i] == NULL)
{
NE_ModelPointers[i] = model;
break;
}
i++;
if (i == NE_MAX_MODELS)
{
NE_DebugPrint("No free slots");
free(model);
return NULL;
}
}
model->sx = model->sy = model->sz = inttof32(1);
model->mat = NULL;
model->modeltype = type;
model->meshindex = NE_NO_MESH;
if (type == NE_Animated)
{
for (int i = 0; i < 2; i++)
{
model->animinfo[i] = calloc(sizeof(NE_AnimInfo), 1);
NE_AssertPointer(model->animinfo[i],
"Couldn't allocate animation info");
}
}
return model;
}
void NE_ModelDelete(NE_Model *model)
{
if (!ne_model_system_inited)
return;
NE_AssertPointer(model, "NULL pointer");
int i = 0;
while (1)
{
if (i == NE_MAX_MODELS)
{
NE_DebugPrint("Model not found");
return;
}
if (NE_ModelPointers[i] == model)
{
NE_ModelPointers[i] = NULL;
break;
}
i++;
}
if (model->modeltype == NE_Animated)
{
for (int i = 0; i < 2; i++)
free(model->animinfo[i]);
}
if (model->mat != NULL)
free(model->mat);
// If there is an asigned mesh
if (model->meshindex != NE_NO_MESH)
ne_mesh_delete(model->meshindex);
free(model);
}
int NE_ModelLoadStaticMeshFAT(NE_Model *model, const char *path)
{
if (!ne_model_system_inited)
return 0;
NE_Assert(model->modeltype == NE_Static, "Not a static model");
return ne_model_load_filesystem_common(model, path);
}
int NE_ModelLoadStaticMesh(NE_Model *model, const void *pointer)
{
if (!ne_model_system_inited)
return 0;
NE_Assert(model->modeltype == NE_Static, "Not a static model");
return ne_model_load_ram_common(model, pointer);
}
void NE_ModelFreeMeshWhenDeleted(NE_Model *model)
{
NE_AssertPointer(model, "NULL model pointer");
if (model->meshindex != NE_NO_MESH)
{
ne_mesh_info_t *mesh = &NE_Mesh[model->meshindex];
mesh->has_to_free = true;
}
}
void NE_ModelSetMaterial(NE_Model *model, NE_Material *material)
{
NE_AssertPointer(model, "NULL model pointer");
NE_AssertPointer(material, "NULL material pointer");
model->texture = material;
}
void NE_ModelSetAnimation(NE_Model *model, NE_Animation *anim)
{
NE_AssertPointer(model, "NULL model pointer");
NE_AssertPointer(anim, "NULL animation pointer");
NE_Assert(model->modeltype == NE_Animated, "Not an animated model");
model->animinfo[0]->animation = anim;
uint32_t frames = DSMA_GetNumFrames(anim->data);
model->animinfo[0]->numframes = frames;
}
void NE_ModelSetAnimationSecondary(NE_Model *model, NE_Animation *anim)
{
NE_AssertPointer(model, "NULL model pointer");
NE_AssertPointer(anim, "NULL animation pointer");
NE_Assert(model->modeltype == NE_Animated, "Not an animated model");
model->animinfo[1]->animation = anim;
uint32_t frames = DSMA_GetNumFrames(anim->data);
model->animinfo[1]->numframes = frames;
}
//---------------------------------------------------------
// Internal use... see below
extern bool NE_TestTouch;
void NE_ModelDraw(const NE_Model *model)
{
NE_AssertPointer(model, "NULL pointer");
if (model->meshindex == NE_NO_MESH)
return;
if (model->modeltype == NE_Animated)
{
// The base animation must always be present. The secondary animation
// isn't required to draw the model.
if (model->animinfo[0]->animation == NULL)
return;
}
MATRIX_PUSH = 0;
if (model->mat != NULL)
{
glMultMatrix4x3(model->mat);
}
else
{
MATRIX_TRANSLATE = model->x;
MATRIX_TRANSLATE = model->y;
MATRIX_TRANSLATE = model->z;
if (model->rx != 0)
glRotateXi(model->rx << 6);
if (model->ry != 0)
glRotateYi(model->ry << 6);
if (model->rz != 0)
glRotateZi(model->rz << 6);
MATRIX_SCALE = model->sx;
MATRIX_SCALE = model->sy;
MATRIX_SCALE = model->sz;
}
if (NE_TestTouch)
{
PosTest_Asynch(0, 0, 0);
}
else
{
// If the texture pointer is NULL, this will set GFX_TEX_FORMAT
// to 0 and GFX_COLOR to white
NE_MaterialUse(model->texture);
}
ne_mesh_info_t *mesh = &NE_Mesh[model->meshindex];
const void *meshdata = mesh->address;
if (model->modeltype == NE_Static)
{
NE_DisplayListDrawDefault(meshdata);
}
else // if(model->modeltype == NE_Animated)
{
if (model->animinfo[0]->animation && model->animinfo[1]->animation)
{
int ret = DSMA_DrawModelBlendAnimation(meshdata,
model->animinfo[0]->animation->data,
model->animinfo[0]->currframe,
model->animinfo[1]->animation->data,
model->animinfo[1]->currframe,
model->anim_blend);
NE_Assert(ret == DSMA_SUCCESS, "Failed to draw animated model");
}
else // if (model->animinfo[0]->animation)
{
int ret = DSMA_DrawModel(meshdata,
model->animinfo[0]->animation->data,
model->animinfo[0]->currframe);
NE_Assert(ret == DSMA_SUCCESS, "Failed to draw animated model");
}
}
MATRIX_POP = 1;
}
void NE_ModelClone(NE_Model *dest, NE_Model *source)
{
NE_AssertPointer(dest, "NULL dest pointer");
NE_AssertPointer(source, "NULL source pointer");
NE_Assert(dest->modeltype == source->modeltype,
"Different model types");
if (dest->modeltype == NE_Animated)
{
memcpy(dest->animinfo[0], source->animinfo[0], sizeof(NE_AnimInfo));
memcpy(dest->animinfo[1], source->animinfo[1], sizeof(NE_AnimInfo));
dest->anim_blend = source->anim_blend;
}
dest->x = source->x;
dest->y = source->y;
dest->z = source->z;
dest->rx = source->rx;
dest->ry = source->ry;
dest->rz = source->rz;
dest->sx = source->sx;
dest->sy = source->sy;
dest->sz = source->sz;
dest->texture = source->texture;
dest->meshindex = source->meshindex;
// If the model has a mesh (which is the normal situation), increase the
// count of users of that mesh.
if (dest->meshindex != NE_NO_MESH)
{
ne_mesh_info_t *mesh = &NE_Mesh[dest->meshindex];
mesh->uses++;
}
}
void NE_ModelScaleI(NE_Model *model, int x, int y, int z)
{
NE_AssertPointer(model, "NULL pointer");
model->sx = x;
model->sy = y;
model->sz = z;
}
void NE_ModelTranslateI(NE_Model *model, int x, int y, int z)
{
NE_AssertPointer(model, "NULL pointer");
model->x += x;
model->y += y;
model->z += z;
}
void NE_ModelSetCoordI(NE_Model *model, int x, int y, int z)
{
NE_AssertPointer(model, "NULL pointer");
model->x = x;
model->y = y;
model->z = z;
}
void NE_ModelRotate(NE_Model *model, int rx, int ry, int rz)
{
NE_AssertPointer(model, "NULL pointer");
model->rx = (model->rx + rx + 512) & 0x1FF;
model->ry = (model->ry + ry + 512) & 0x1FF;
model->rz = (model->rz + rz + 512) & 0x1FF;
}
void NE_ModelSetRot(NE_Model *model, int rx, int ry, int rz)
{
NE_AssertPointer(model, "NULL pointer");
model->rx = rx;
model->ry = ry;
model->rz = rz;
}
int NE_ModelSetMatrix(NE_Model *model, m4x3 *mat)
{
NE_AssertPointer(model, "NULL model pointer");
NE_AssertPointer(mat, "NULL matrix pointer");
if (model->mat == NULL)
{
model->mat = malloc(sizeof(m4x3));
if (model->mat == NULL)
return 0;
}
memcpy(model->mat, mat, sizeof(m4x3));
return 1;
}
void NE_ModelClearMatrix(NE_Model *model)
{
NE_AssertPointer(model, "NULL pointer");
if (model->mat == NULL)
return;
free(model->mat);
}
void NE_ModelAnimateAll(void)
{
if (!ne_model_system_inited)
return;
for (int i = 0; i < NE_MAX_MODELS; i++)
{
if (NE_ModelPointers[i] == NULL)
continue;
if (NE_ModelPointers[i]->modeltype != NE_Animated)
continue;
for (int j = 0; j < 2; j++)
{
NE_AnimInfo *animinfo = NE_ModelPointers[i]->animinfo[j];
animinfo->currframe += animinfo->speed;
if (animinfo->type == NE_ANIM_LOOP)
{
int32_t endval = inttof32(animinfo->numframes);
if (animinfo->currframe >= endval)
animinfo->currframe -= endval;
else if (animinfo->currframe < 0)
animinfo->currframe += endval;
}
else if (animinfo->type == NE_ANIM_ONESHOT)
{
int32_t endval = inttof32(animinfo->numframes - 1);
if (animinfo->currframe > endval)
{
animinfo->currframe = endval;
animinfo->speed = 0;
}
else if (animinfo->currframe < 0)
{
animinfo->currframe = 0;
animinfo->speed = 0;
}
}
}
}
}
void NE_ModelAnimStart(NE_Model *model, NE_AnimationType type, int32_t speed)
{
NE_AssertPointer(model, "NULL pointer");
NE_Assert(model->modeltype == NE_Animated, "Not an animated model");
model->animinfo[0]->type = type;
model->animinfo[0]->speed = speed;
model->animinfo[0]->currframe = 0;
}
void NE_ModelAnimSecondaryStart(NE_Model *model, NE_AnimationType type,
int32_t speed)
{
NE_AssertPointer(model, "NULL pointer");
NE_Assert(model->modeltype == NE_Animated, "Not an animated model");
model->animinfo[1]->type = type;
model->animinfo[1]->speed = speed;
model->animinfo[1]->currframe = 0;
model->anim_blend = 0;
}
void NE_ModelAnimSetSpeed(NE_Model *model, int32_t speed)
{
NE_AssertPointer(model, "NULL pointer");
NE_Assert(model->modeltype == NE_Animated, "Not an animated model");
model->animinfo[0]->speed = speed;
}
void NE_ModelAnimSecondarySetSpeed(NE_Model *model, int32_t speed)
{
NE_AssertPointer(model, "NULL pointer");
NE_Assert(model->modeltype == NE_Animated, "Not an animated model");
model->animinfo[1]->speed = speed;
}
int32_t NE_ModelAnimGetFrame(const NE_Model *model)
{
NE_AssertPointer(model, "NULL pointer");
NE_Assert(model->modeltype == NE_Animated, "Not an animated model");
return model->animinfo[0]->currframe;
}
int32_t NE_ModelAnimSecondaryGetFrame(const NE_Model *model)
{
NE_AssertPointer(model, "NULL pointer");
NE_Assert(model->modeltype == NE_Animated, "Not an animated model");
return model->animinfo[1]->currframe;
}
void NE_ModelAnimSetFrame(NE_Model *model, int32_t frame)
{
NE_AssertPointer(model, "NULL pointer");
NE_Assert(model->modeltype == NE_Animated, "Not an animated model");
// TODO: Check if it is off bounds
model->animinfo[0]->currframe = frame;
}
void NE_ModelAnimSecondarySetFrame(NE_Model *model, int32_t frame)
{
NE_AssertPointer(model, "NULL pointer");
NE_Assert(model->modeltype == NE_Animated, "Not an animated model");
// TODO: Check if it is off bounds
model->animinfo[1]->currframe = frame;
}
void NE_ModelAnimSecondarySetFactor(NE_Model *model, int32_t factor)
{
NE_AssertPointer(model, "NULL pointer");
NE_Assert(model->modeltype == NE_Animated, "Not an animated model");
if (factor < 0)
factor = 0;
if (factor > inttof32(1))
factor = inttof32(1);
model->anim_blend = factor;
}
void NE_ModelAnimSecondaryClear(NE_Model *model, bool replace_base_anim)
{
NE_AssertPointer(model, "NULL pointer");
NE_Assert(model->modeltype == NE_Animated, "Not an animated model");
// Return if there is no animation to remove
if (model->animinfo[1]->animation == NULL)
return;
if (replace_base_anim)
memcpy(model->animinfo[0], model->animinfo[1], sizeof(NE_AnimInfo));
memset(model->animinfo[1], 0, sizeof(NE_AnimInfo));
}
int NE_ModelLoadDSMFAT(NE_Model *model, const char *path)
{
if (!ne_model_system_inited)
return 0;
NE_Assert(model->modeltype == NE_Animated, "Not an animated model");
return ne_model_load_filesystem_common(model, path);
}
int NE_ModelLoadDSM(NE_Model *model, const void *pointer)
{
if (!ne_model_system_inited)
return 0;
NE_Assert(model->modeltype == NE_Animated, "Not an animated model");
return ne_model_load_ram_common(model, pointer);
}
void NE_ModelDeleteAll(void)
{
if (!ne_model_system_inited)
return;
for (int i = 0; i < NE_MAX_MODELS; i++)
{
if (NE_ModelPointers[i] != NULL)
NE_ModelDelete(NE_ModelPointers[i]);
}
}
int NE_ModelSystemReset(int max_models)
{
if (ne_model_system_inited)
NE_ModelSystemEnd();
if (max_models < 1)
NE_MAX_MODELS = NE_DEFAULT_MODELS;
else
NE_MAX_MODELS = max_models;
NE_Mesh = calloc(NE_MAX_MODELS, sizeof(ne_mesh_info_t));
NE_ModelPointers = calloc(NE_MAX_MODELS, sizeof(NE_ModelPointers));
if ((NE_Mesh == NULL) || (NE_ModelPointers == NULL))
{
free(NE_Mesh);
free(NE_ModelPointers);
NE_DebugPrint("Not enough memory");
return -1;
}
ne_model_system_inited = true;
return 0;
}
void NE_ModelSystemEnd(void)
{
if (!ne_model_system_inited)
return;
NE_ModelDeleteAll();
free(NE_Mesh);
free(NE_ModelPointers);
ne_model_system_inited = false;
}