Implement .obj load & rendering

This commit is contained in:
Daniel Ramírez 2025-04-23 02:48:08 +02:00
parent f7da063cfe
commit 6e0d5f92ea
23 changed files with 4273 additions and 90 deletions

2
.gitignore vendored
View File

@ -9,7 +9,7 @@
*.slo
*.lo
*.o
*.obj
build/*.obj
*.elf
*.lst
*.nds

View File

@ -5,6 +5,7 @@ project(xrbDS)
include_directories(
engine
include/xrbds
include/tinyobjloader
)
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS

View File

@ -0,0 +1,5 @@
#include "components/component.h"
PtrUnq<Component> Component::Initialize() {
return PtrUnq<Component>(new Component());
}

View File

@ -0,0 +1,15 @@
#include "components/mesh_filter.h"
#include "resources/resource.h"
#include "utils/file.h"
MeshFilter::MeshFilter(const FString &path) {
// Load the mesh from the file
auto vertices = Utils::File::ReadObjFile(path);
mesh = LoadResource<Mesh>(vertices);
}
PtrUnq<MeshFilter> MeshFilter::Initialize(const FString &path) {
return PtrUnq<MeshFilter>(new MeshFilter(path));
}

View File

@ -12,6 +12,7 @@
#include "graphics/renderer.h"
#include "scene/3d/node_3d.h"
#include "utils/file.h"
#include "scene/3d/mesh_instance_3d.h"
/////////////////////////////////////////////////////////
// Main function
@ -43,24 +44,18 @@ PtrUnq<Engine> &Engine::GetInstance() {
}
void Engine::run() {
// Initialize the main loop
// - VBlank IRQ
irqSet(IRQ_VBLANK, Engine::VblankCallback);
VblankCallback();
// TEST ------------------------
PtrShr<Node3D> node = NewNode<Node3D>();
node->getComponent<Transform>()->position = {1.0f, 2.0f, 3.0f};
FString ahuevo = Utils::File::ReadTextFile("hola.txt");
iprintf("ahuevo: %s\n", ahuevo.c_str());
// Create a MeshInstance3D
PtrShr<MeshInstance3D> meshInstance =
NewNode<MeshInstance3D>("meshes/mococo/mococo.obj");
// -----------------------------
while (pmMainLoop()) {
processInput();
update();
render();
}
}
@ -70,9 +65,22 @@ void Engine::update() {
// Update
}
void Engine::render() { Renderer::GetInstance()->render(); }
void Engine::render() {
if (!ComponentManager::GetInstance())
return;
PtrUnq<Renderer> &renderer = Renderer::GetInstance();
void Engine::VblankCallback() { Engine::GetInstance()->render(); }
renderer->beginFrame();
auto view = ComponentManager::GetInstance()->view<Transform, MeshFilter>();
for (auto &[entity, transform, meshFilter] : view) {
renderer->render(*transform, *meshFilter);
}
renderer->endFrame();
swiWaitForVBlank();
}
Engine::Engine() {
// Initialize nitroFS

View File

@ -1,30 +1,21 @@
#include "renderer.h"
#include "core/types.h"
#include "components/mesh_filter.h"
#include "components/transform.h"
#include "resources/mesh.h"
#include <nds.h>
#include <stdio.h>
std::unique_ptr<Renderer> Renderer::Instance;
PtrUnq<Renderer> Renderer::Instance;
std::unique_ptr<Renderer> &Renderer::GetInstance() {
PtrUnq<Renderer> &Renderer::GetInstance() {
if (!Instance)
Instance = std::unique_ptr<Renderer>(new Renderer());
Instance = PtrUnq<Renderer>(new Renderer());
return Instance;
}
void Renderer::render() {
clearScreen(); // Clear the screen
beginFrame(); // Prepare for rendering
glLoadIdentity(); // Load the identity matrix
// Render scene
glTranslatef(0.f, 0.f, -4.f); // Move the camera back
glRotatef(45, 1, 1, 0); // Rotation
drawCube(1.0f); // Draw a cube
endFrame(); // Finish rendering
}
void Renderer::beginFrame() {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
@ -34,6 +25,30 @@ void Renderer::beginFrame() {
glPushMatrix(); // Save the current matrix state
glPolyFmt(POLY_ALPHA(31) | POLY_CULL_BACK);
glLoadIdentity(); // Load the identity matrix
// Render scene
glTranslatef(0.f, 0.f, -4.f); // Move the camera back
glRotatef(45, 1, 1, 0); // Rotation
}
void Renderer::render(const Transform &transform,
const MeshFilter &meshFilter) {
// drawCube(1.0f); // Draw a cube
glBegin(GL_TRIANGLES);
for (const auto &shapes : meshFilter.getMesh()->getVertices()) {
for (const auto &vertex : shapes) {
glColor3f(1.0, 0.0, 0.0); // Red
glVertex3f(vertex.position.x, vertex.position.y, vertex.position.z);
glNormal3f(vertex.normal.x, vertex.normal.y, vertex.normal.z);
glTexCoord2f(vertex.texCoords.x, vertex.texCoords.y);
}
}
glEnd();
glPopMatrix(1);
}
void Renderer::endFrame() {

View File

@ -23,8 +23,12 @@
#ifndef RENDERER_H
#define RENDERER_H
#include "core/types.h"
#include <memory>
struct Transform;
struct MeshFilter;
/**
* @class Renderer
* @brief A base class for rendering operations.
@ -46,7 +50,14 @@ public:
*
* @return A unique pointer to a Renderer instance.
*/
static std::unique_ptr<Renderer> &GetInstance();
static PtrUnq<Renderer> &GetInstance();
/**
* @brief Prepares the renderer for a new frame.
*
* This method is called at the beginning of the rendering process.
*/
void beginFrame();
/**
* @brief Renders the current frame.
@ -54,15 +65,7 @@ public:
* This method handles the rendering process, including frame management
* and drawing operations.
*/
void render();
private:
/**
* @brief Prepares the renderer for a new frame.
*
* This method is called at the beginning of the rendering process.
*/
void beginFrame();
void render(const Transform &transform, const MeshFilter &meshFilter);
/**
* @brief Finalizes the rendering of the current frame.
@ -71,14 +74,6 @@ private:
*/
void endFrame();
/**
* @brief Clears the screen to prepare for rendering.
*
* This method resets the screen to a default state, typically clearing
* any previous drawings.
*/
void clearScreen();
/**
* @brief Draws a cube of the specified size.
*
@ -86,7 +81,16 @@ private:
*/
void drawCube(float size);
static std::unique_ptr<Renderer> Instance;
private:
static PtrUnq<Renderer> Instance;
/**
* @brief Clears the screen to prepare for rendering.
*
* This method resets the screen to a default state, typically clearing
* any previous drawings.
*/
void clearScreen();
/**
* @brief Constructor for the Renderer class.

View File

@ -1 +1,8 @@
#include "resources/mesh.h"
#include "resources/mesh.h"
Mesh Mesh::Load(const TVector<TVector<FVertex>> &vertices) {
return std::move(Mesh(vertices));
}
Mesh::Mesh(const TVector<TVector<FVertex>> &vertices)
: Super(), vertices(vertices) {}

View File

@ -1,6 +1,3 @@
#include "resources/resource.h"
Resource::Resource(const FString &name, const FString &path)
: name(name), path(path) {
// Constructor implementation
}
Resource Resource::Load() { return std::move(Resource()); }

View File

@ -2,7 +2,7 @@
#include "components/mesh_filter.h"
MeshInstance3D::MeshInstance3D() {
ComponentManager::GetInstance()->addComponent<MeshFilter>(id,
MeshFilter("test"));
MeshInstance3D::MeshInstance3D(const FString &path) {
ComponentManager::GetInstance()->addComponent<MeshFilter>(
id, *MeshFilter::Initialize(path));
}

View File

@ -1,5 +1,8 @@
#include "utils/file.h"
#define TINYOBJLOADER_IMPLEMENTATION
#include "tiny_obj_loader.h"
namespace Utils::File {
FString AssetPath(const FString &path) { return "nitro:/" + path; }
@ -27,4 +30,78 @@ FString ReadTextFile(const FString &path) {
return std::move(result);
}
TVector<TVector<FVertex>> ReadObjFile(const FString &path) {
tinyobj::attrib_t attrib;
TVector<tinyobj::shape_t> shapes;
TVector<tinyobj::material_t> materials;
FString err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err,
AssetPath(path).c_str(),
AssetPath(path + "/..").c_str(), true);
if (ret)
iprintf("Cargado correctamente\n");
else
iprintf("Error al cargar el archivo: %s\n", err.c_str());
iprintf("Numero de materiales: %d\n", materials.size());
TVector<TVector<FVertex>> vertices;
// Loop over shapes
for (size_t s = 0; s < shapes.size(); s++) {
vertices.push_back({});
// Loop over faces(polygon)
size_t index_offset = 0;
for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) {
size_t fv = size_t(shapes[s].mesh.num_face_vertices[f]);
// Loop over vertices in the face.
for (size_t v = 0; v < fv; v++) {
// access to vertex
tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
tinyobj::real_t vx = attrib.vertices[3 * size_t(idx.vertex_index) + 0];
tinyobj::real_t vy = attrib.vertices[3 * size_t(idx.vertex_index) + 1];
tinyobj::real_t vz = attrib.vertices[3 * size_t(idx.vertex_index) + 2];
FVector3 positions(vx, vy, vz);
// Check if `normal_index` is zero or positive. negative = no normal
// data
FVector3 normals(0, 0, 0);
if (idx.normal_index >= 0) {
tinyobj::real_t nx = attrib.normals[3 * size_t(idx.normal_index) + 0];
tinyobj::real_t ny = attrib.normals[3 * size_t(idx.normal_index) + 1];
tinyobj::real_t nz = attrib.normals[3 * size_t(idx.normal_index) + 2];
normals = FVector3(nx, ny, nz);
}
// Check if `texcoord_index` is zero or positive. negative = no texcoord
// data
FVector2 texCoords(0, 0);
if (idx.texcoord_index >= 0) {
tinyobj::real_t tx =
attrib.texcoords[2 * size_t(idx.texcoord_index) + 0];
tinyobj::real_t ty =
attrib.texcoords[2 * size_t(idx.texcoord_index) + 1];
texCoords = FVector2(tx, ty);
}
vertices[s].push_back(FVertex(positions, normals, texCoords));
}
index_offset += fv;
// per-face material
// shapes[s].mesh.material_ids[f];
}
}
return std::move(vertices);
}
} // namespace Utils::File

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,11 @@
#ifndef COMPONENT_H
#define COMPONENT_H
#include "core/types.h"
struct Component {
virtual ~Component() = default;
public:
static PtrUnq<Component> Initialize();
};
#endif // COMPONENT_H

View File

@ -1,7 +0,0 @@
#include "mesh_filter.h"
#include "resources/resource.h"
MeshFilter::MeshFilter(const FString &name) : meshName(name) {
mesh = LoadResource<Mesh>(name);
}

View File

@ -8,11 +8,14 @@ class Mesh;
struct MeshFilter : public Component {
public:
MeshFilter(const FString &name);
static PtrUnq<MeshFilter> Initialize(const FString &path);
PtrShr<Mesh> getMesh() const { return mesh; }
private:
FString meshName;
PtrShr<Mesh> mesh;
MeshFilter(const FString &path);
};
#endif // MESH_FILTER_H

View File

@ -6,32 +6,28 @@
#include <vector>
struct Vertex {
struct FVertex {
FVector3 position;
FVector3 normal;
FVector2 texCoords;
Vertex(const FVector3 &pos, const FVector3 &norm, const FVector2 &tex)
FVertex(const FVector3 &pos, const FVector3 &norm, const FVector2 &tex)
: position(pos), normal(norm), texCoords(tex) {}
};
class Mesh : public Resource {
using Super = Resource;
public:
static PtrShr<Mesh> Load(FString path);
static Mesh Load(const TVector<TVector<FVertex>> &vertices);
const TVector<Vertex> &getVertices() const;
const TVector<unsigned int> &getIndices() const;
void setVertices(const TVector<Vertex> &vertices);
void setIndices(const TVector<unsigned int> &indices);
TVector<TVector<FVertex>> getVertices() const { return vertices; }
private:
TVector<Vertex> m_vertices;
TVector<unsigned int> m_indices;
TVector<TVector<FVertex>> vertices;
Mesh() = delete;
Mesh(const Mesh &) = delete;
Mesh(const TVector<Vertex> &vertices, const TVector<unsigned int> &indices);
Mesh() = default;
Mesh(const TVector<TVector<FVertex>> &vertices);
};
#endif // XRBDS_RESOURCES_MESH_H

View File

@ -4,19 +4,80 @@
#include "core/types.h"
#include <stdio.h>
template <typename T, typename... Args> PtrShr<T> LoadResource(Args &&...args) {
auto ptr = std::make_shared<T>(std::forward<Args>(args)...);
/**
* @struct FResourceData
* @brief Represents resource data with a name and a file path.
*
* This structure is used to store information about a resource, including
* its name and the path to its location.
*
* @var FResourceData::name
* The name of the resource.
*
* @var FResourceData::path
* The file path to the resource.
*
* @param name The name of the resource.
* @param path The file path to the resource.
*/
struct FResourceData {
/**
* @brief Constructs an FResourceData object with the specified name and path.
*
* @param name The name of the resource.
* @param path The file path of the resource.
*/
FResourceData(const FString &name, const FString &path)
: name(name), path(path) {}
return ptr;
}
class Resource {
public:
Resource(const FString &name, const FString &path);
private:
FString name;
FString path;
};
/**
* @class Resource
* @brief Represents a resource that encapsulates data of type FResourceData.
*
* This class is used to manage and store resource data. It provides a
* constructor to initialize the resource with the given data.
*
* @note The class currently only stores the data and does not provide any
* additional functionality or accessors.
*/
class Resource {
public:
static Resource Load();
protected:
Resource() = default;
};
/**
* @brief Loads a resource by creating a shared pointer to an instance of the
* specified type.
*
* @tparam T The type of the resource to be loaded. Must be constructible
with
* the provided arguments.
* @tparam Args The types of the arguments to be forwarded to the constructor
of
* T.
* @param args The arguments to be forwarded to the constructor of T.
* @return PtrShr<T> A shared pointer to the newly created instance of T.
*
* @note This function uses perfect forwarding to pass the arguments to the
* constructor of T. Ensure that the type T is compatible with the provided
* arguments.
*/
template <typename T, typename... Args> PtrShr<T> LoadResource(Args &&...args) {
if constexpr (!std::is_base_of<Resource, T>::value) {
iprintf("Error: Type T must be derived from Resource.\n");
return nullptr;
}
auto ptr = std::make_shared<T>(T::Load(std::forward<Args>(args)...));
return ptr;
}
#endif // RESOURCE_H

View File

@ -9,7 +9,7 @@ public:
using Super = Node3D;
public:
MeshInstance3D();
MeshInstance3D(const FString &path);
};
#endif // XRBDS_SCENE_3D_MESH_INSTANCE_H

View File

@ -2,11 +2,32 @@
#define XRBDS_UTILS_FILE_H
#include "core/types.h"
#include "resources/mesh.h"
namespace Utils::File {
/**
* @brief Reads the contents of a text file and returns it as a string.
*
* @param path The file path to the text file to be read.
* @return FString The contents of the file as a string.
* @throws std::runtime_error If the file cannot be opened or read.
*/
FString ReadTextFile(const FString &path);
/**
* @brief Reads a Wavefront OBJ file and parses its contents into a nested
* vector of vertices.
*
* @param path The file path to the OBJ file as an FString.
* @return A TVector of TVector of FVertex, where each inner vector represents a
* group of vertices.
*
* @note Ensure the file at the specified path exists and is in a valid OBJ
* format.
*/
TVector<TVector<FVertex>> ReadObjFile(const FString &path);
} // namespace Utils::File
#endif // XRBDS_UTILS_FILE_H

View File

@ -0,0 +1,46 @@
# Blender v2.76 (sub 0) OBJ File: ''
# www.blender.org
mtllib cube.mtl
o Cube
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 1.000000 0.333333
vt 1.000000 0.666667
vt 0.666667 0.666667
vt 0.666667 0.333333
vt 0.666667 0.000000
vt 0.000000 0.333333
vt 0.000000 0.000000
vt 0.333333 0.000000
vt 0.333333 1.000000
vt 0.000000 1.000000
vt 0.000000 0.666667
vt 0.333333 0.333333
vt 0.333333 0.666667
vt 1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 1.000000 0.000000 0.000000
vn -0.000000 0.000000 1.000000
vn -1.000000 -0.000000 -0.000000
vn 0.000000 0.000000 -1.000000
usemtl Material
s off
f 2/1/1 3/2/1 4/3/1
f 8/1/2 7/4/2 6/5/2
f 5/6/3 6/7/3 2/8/3
f 6/8/4 7/5/4 3/4/4
f 3/9/5 7/10/5 8/11/5
f 1/12/6 4/13/6 8/11/6
f 1/4/1 2/1/1 4/3/1
f 5/14/2 8/1/2 6/5/2
f 1/12/3 5/6/3 2/8/3
f 2/12/4 6/8/4 3/4/4
f 4/13/5 3/9/5 8/11/5
f 5/6/6 1/12/6 8/11/6

View File

@ -0,0 +1,12 @@
# Blender 4.4.1 MTL File: 'mococo.blend'
# www.blender.org
newmtl mococo_mat
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Ks 0.000000 0.000000 0.000000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
illum 1
map_Kd mococotexture_face1.png
map_d mococotexture_face1.png

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB