Initial commit of Ps1Engine project with basic renderer and OBJ/MTL loading functionality. Added CMake configuration, GLFW integration, and basic cube rendering with textures. Included necessary header files and implemented core rendering logic.
This commit is contained in:
121
Ps1Engine/loader/mesh.cpp
Normal file
121
Ps1Engine/loader/mesh.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "mesh.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
std::map<std::string, Color> load_mtl_colors(const std::string& mtl_path)
|
||||
{
|
||||
std::map<std::string, Color> material_colors;
|
||||
std::ifstream file(mtl_path);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Warning: could not open MTL file: " << mtl_path << std::endl;
|
||||
return material_colors;
|
||||
}
|
||||
|
||||
std::string line, current_mtl;
|
||||
while (std::getline(file, line)) {
|
||||
std::istringstream ss(line);
|
||||
std::string keyword;
|
||||
ss >> keyword;
|
||||
|
||||
if (keyword == "newmtl") {
|
||||
ss >> current_mtl;
|
||||
} else if (keyword == "Kd") {
|
||||
float r, g, b;
|
||||
ss >> r >> g >> b;
|
||||
material_colors[current_mtl] = Color(
|
||||
static_cast<uint8_t>(r * 255),
|
||||
static_cast<uint8_t>(g * 255),
|
||||
static_cast<uint8_t>(b * 255)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return material_colors;
|
||||
}
|
||||
|
||||
std::vector<Triangle> load_obj(const std::string& obj_path)
|
||||
{
|
||||
std::ifstream file(obj_path);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Failed to open OBJ file: " + obj_path);
|
||||
}
|
||||
|
||||
std::vector<Vec3> positions;
|
||||
std::vector<Vec2> texcoords;
|
||||
std::map<std::string, Color> materials;
|
||||
std::vector<Triangle> triangles;
|
||||
|
||||
std::string current_material = "";
|
||||
fs::path base_path = fs::path(obj_path).parent_path();
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
std::istringstream ss(line);
|
||||
std::string keyword;
|
||||
ss >> keyword;
|
||||
|
||||
if (keyword == "v") {
|
||||
float x, y, z;
|
||||
ss >> x >> y >> z;
|
||||
positions.emplace_back((int)(x * 4096), (int)(y * 4096), (int)(z * 4096));
|
||||
} else if (keyword == "vt") {
|
||||
float u, v;
|
||||
ss >> u >> v;
|
||||
texcoords.emplace_back(u, 1.0f - v); // flip v
|
||||
} else if (keyword == "f") {
|
||||
std::vector<std::string> tokens;
|
||||
std::string token;
|
||||
while (ss >> token) tokens.push_back(token);
|
||||
|
||||
std::vector<int> v_idx, t_idx;
|
||||
for (const auto& t : tokens) {
|
||||
std::stringstream ts(t);
|
||||
std::string part;
|
||||
int vi = -1, ti = -1;
|
||||
|
||||
std::getline(ts, part, '/');
|
||||
if (!part.empty()) vi = std::stoi(part) - 1;
|
||||
|
||||
if (std::getline(ts, part, '/') && !part.empty())
|
||||
ti = std::stoi(part) - 1;
|
||||
|
||||
v_idx.push_back(vi);
|
||||
t_idx.push_back(ti);
|
||||
}
|
||||
|
||||
// Triangulate face
|
||||
for (size_t i = 1; i + 1 < v_idx.size(); ++i) {
|
||||
Triangle tri;
|
||||
tri.v0 = positions[v_idx[0]];
|
||||
tri.v1 = positions[v_idx[i]];
|
||||
tri.v2 = positions[v_idx[i + 1]];
|
||||
|
||||
tri.uv0 = (t_idx[0] >= 0 && t_idx[0] < (int)texcoords.size()) ? texcoords[t_idx[0]] : Vec2(0, 0);
|
||||
tri.uv1 = (t_idx[i] >= 0 && t_idx[i] < (int)texcoords.size()) ? texcoords[t_idx[i]] : Vec2(0, 0);
|
||||
tri.uv2 = (t_idx[i + 1] >= 0 && t_idx[i + 1] < (int)texcoords.size()) ? texcoords[t_idx[i + 1]] : Vec2(0, 0);
|
||||
|
||||
if (materials.count(current_material)) {
|
||||
tri.color = materials[current_material];
|
||||
} else {
|
||||
tri.color = Color(255, 255, 255); // fallback white
|
||||
}
|
||||
|
||||
triangles.push_back(tri);
|
||||
}
|
||||
} else if (keyword == "mtllib") {
|
||||
std::string mtl_file;
|
||||
ss >> mtl_file;
|
||||
fs::path mtl_path = base_path / mtl_file;
|
||||
materials = load_mtl_colors(mtl_path.string());
|
||||
} else if (keyword == "usemtl") {
|
||||
ss >> current_material;
|
||||
}
|
||||
}
|
||||
|
||||
return triangles;
|
||||
}
|
||||
9
Ps1Engine/loader/mesh.h
Normal file
9
Ps1Engine/loader/mesh.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "renderer.h" // includes Vec3, Vec2, Triangle, Color
|
||||
|
||||
std::vector<Triangle> load_obj(const std::string& obj_path);
|
||||
std::map<std::string, Color> load_mtl_colors(const std::string& mtl_path);
|
||||
310
Ps1Engine/renderer/renderer.cpp
Normal file
310
Ps1Engine/renderer/renderer.cpp
Normal file
@@ -0,0 +1,310 @@
|
||||
#include "renderer.h"
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
|
||||
#define FIXED_POINT_PRECISION 4096
|
||||
#define FIXED_POINT_SHIFT 12
|
||||
|
||||
Vec3::Vec3(int x_, int y_, int z_) : x(x_), y(y_), z(z_) {}
|
||||
|
||||
Vec2::Vec2(float x_, float y_) : x(x_), y(y_) {}
|
||||
|
||||
Color::Color(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
|
||||
|
||||
Matrix4::Matrix4()
|
||||
{
|
||||
for (int i = 0; i < 16; i++)
|
||||
m[i] = (i % 5 == 0) ? FIXED_POINT_PRECISION : 0;
|
||||
}
|
||||
|
||||
Vec3 matrix_multiply(const Matrix4 &mat, const Vec3 &v)
|
||||
{
|
||||
int x = (mat.m[0] * v.x + mat.m[1] * v.y + mat.m[2] * v.z + mat.m[3] * FIXED_POINT_PRECISION) / FIXED_POINT_PRECISION;
|
||||
int y = (mat.m[4] * v.x + mat.m[5] * v.y + mat.m[6] * v.z + mat.m[7] * FIXED_POINT_PRECISION) / FIXED_POINT_PRECISION;
|
||||
int z = (mat.m[8] * v.x + mat.m[9] * v.y + mat.m[10] * v.z + mat.m[11] * FIXED_POINT_PRECISION) / FIXED_POINT_PRECISION;
|
||||
return Vec3(x, y, z);
|
||||
}
|
||||
|
||||
Matrix4 matrix_multiply(const Matrix4 &a, const Matrix4 &b)
|
||||
{
|
||||
Matrix4 result;
|
||||
|
||||
for (int row = 0; row < 4; ++row)
|
||||
{
|
||||
for (int col = 0; col < 4; ++col)
|
||||
{
|
||||
result.m[row * 4 + col] =
|
||||
(a.m[row * 4 + 0] * b.m[col + 0]) +
|
||||
(a.m[row * 4 + 1] * b.m[col + 4]) +
|
||||
(a.m[row * 4 + 2] * b.m[col + 8]) +
|
||||
(a.m[row * 4 + 3] * b.m[col + 12]);
|
||||
|
||||
result.m[row * 4 + col] /= 4096;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Color convert_color(const Color &color)
|
||||
{
|
||||
|
||||
uint8_t r = (color.r >> 3) << 3;
|
||||
uint8_t g = (color.g >> 3) << 3;
|
||||
uint8_t b = (color.b >> 3) << 3;
|
||||
return Color(r, g, b);
|
||||
}
|
||||
|
||||
Renderer::Renderer(int width_, int height_, const std::string &title_)
|
||||
: width(width_), height(height_), title(title_),
|
||||
internal_width(320), internal_height(240)
|
||||
{
|
||||
|
||||
if (!glfwInit())
|
||||
{
|
||||
throw std::runtime_error("Failed to initialize GLFW");
|
||||
}
|
||||
|
||||
window = glfwCreateWindow(width, height, title.c_str(), NULL, NULL);
|
||||
if (!window)
|
||||
{
|
||||
glfwTerminate();
|
||||
throw std::runtime_error("Failed to create GLFW window");
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent(window);
|
||||
if (glewInit() != GLEW_OK)
|
||||
{
|
||||
throw std::runtime_error("Failed to initialize GLEW");
|
||||
}
|
||||
|
||||
glViewport(0, 0, width, height);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glOrtho(0, width, 0, height, -32768, 32767);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_LEQUAL);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
Renderer::~Renderer()
|
||||
{
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
void Renderer::set_view_matrix(const Matrix4 &view)
|
||||
{
|
||||
view_matrix = view;
|
||||
}
|
||||
|
||||
void Renderer::begin_frame()
|
||||
{
|
||||
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void Renderer::render(const std::vector<Triangle> &triangles, GLuint texture)
|
||||
{
|
||||
std::vector<Triangle> transformed_triangles;
|
||||
|
||||
for (const auto &tri : triangles)
|
||||
{
|
||||
Triangle t;
|
||||
t.v0 = matrix_multiply(view_matrix, tri.v0);
|
||||
t.v1 = matrix_multiply(view_matrix, tri.v1);
|
||||
t.v2 = matrix_multiply(view_matrix, tri.v2);
|
||||
t.uv0 = tri.uv0;
|
||||
t.uv1 = tri.uv1;
|
||||
t.uv2 = tri.uv2;
|
||||
t.color = tri.color;
|
||||
|
||||
Vec3 edge1(t.v1.x - t.v0.x, t.v1.y - t.v0.y, t.v1.z - t.v0.z);
|
||||
Vec3 edge2(t.v2.x - t.v0.x, t.v2.y - t.v0.y, t.v2.z - t.v0.z);
|
||||
Vec3 normal(
|
||||
edge1.y * edge2.z - edge1.z * edge2.y,
|
||||
edge1.z * edge2.x - edge1.x * edge2.z,
|
||||
edge1.x * edge2.y - edge1.y * edge2.x);
|
||||
|
||||
if (normal.z > 0)
|
||||
{
|
||||
transformed_triangles.push_back(t);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(transformed_triangles.begin(), transformed_triangles.end(),
|
||||
[](const Triangle &a, const Triangle &b)
|
||||
{
|
||||
int z_a = (a.v0.z + a.v1.z + a.v2.z) / 3;
|
||||
int z_b = (b.v0.z + b.v1.z + b.v2.z) / 3;
|
||||
return z_a > z_b;
|
||||
});
|
||||
|
||||
std::vector<Triangle> screen_triangles;
|
||||
for (const auto &tri : transformed_triangles)
|
||||
{
|
||||
Triangle screen_tri;
|
||||
|
||||
float scale_x = (float)width / internal_width;
|
||||
float scale_y = (float)height / internal_height;
|
||||
|
||||
screen_tri.v0 = Vec3(
|
||||
((tri.v0.x * internal_width / 32) / FIXED_POINT_PRECISION) * scale_x + width / 2,
|
||||
((tri.v0.y * internal_height / 32) / FIXED_POINT_PRECISION) * scale_y + height / 2,
|
||||
tri.v0.z);
|
||||
screen_tri.v1 = Vec3(
|
||||
((tri.v1.x * internal_width / 32) / FIXED_POINT_PRECISION) * scale_x + width / 2,
|
||||
((tri.v1.y * internal_height / 32) / FIXED_POINT_PRECISION) * scale_y + height / 2,
|
||||
tri.v1.z);
|
||||
screen_tri.v2 = Vec3(
|
||||
((tri.v2.x * internal_width / 32) / FIXED_POINT_PRECISION) * scale_x + width / 2,
|
||||
((tri.v2.y * internal_height / 32) / FIXED_POINT_PRECISION) * scale_y + height / 2,
|
||||
tri.v2.z);
|
||||
|
||||
int snap_factor = 4;
|
||||
screen_tri.v0.x = (screen_tri.v0.x / snap_factor) * snap_factor;
|
||||
screen_tri.v0.y = (screen_tri.v0.y / snap_factor) * snap_factor;
|
||||
screen_tri.v1.x = (screen_tri.v1.x / snap_factor) * snap_factor;
|
||||
screen_tri.v1.y = (screen_tri.v1.y / snap_factor) * snap_factor;
|
||||
screen_tri.v2.x = (screen_tri.v2.x / snap_factor) * snap_factor;
|
||||
screen_tri.v2.y = (screen_tri.v2.y / snap_factor) * snap_factor;
|
||||
|
||||
if (rand() % 10 == 0)
|
||||
{
|
||||
screen_tri.v0.x += (rand() % 3) - 1;
|
||||
screen_tri.v0.y += (rand() % 3) - 1;
|
||||
}
|
||||
|
||||
screen_tri.uv0 = tri.uv0;
|
||||
screen_tri.uv1 = tri.uv1;
|
||||
screen_tri.uv2 = tri.uv2;
|
||||
screen_tri.color = convert_color(tri.color);
|
||||
|
||||
screen_triangles.push_back(screen_tri);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
for (const auto &tri : screen_triangles)
|
||||
{
|
||||
|
||||
glBegin(GL_TRIANGLES);
|
||||
|
||||
Color c = tri.color;
|
||||
glColor3ub(c.r, c.g, c.b);
|
||||
|
||||
glTexCoord2f(tri.uv0.x, tri.uv0.y);
|
||||
glVertex3f(static_cast<float>(tri.v0.x), static_cast<float>(tri.v0.y), static_cast<float>(tri.v0.z) / 32767.0f);
|
||||
|
||||
glTexCoord2f(tri.uv1.x, tri.uv1.y);
|
||||
glVertex3f(static_cast<float>(tri.v1.x), static_cast<float>(tri.v1.y), static_cast<float>(tri.v1.z) / 32767.0f);
|
||||
|
||||
glTexCoord2f(tri.uv2.x, tri.uv2.y);
|
||||
glVertex3f(static_cast<float>(tri.v2.x), static_cast<float>(tri.v2.y), static_cast<float>(tri.v2.z) / 32767.0f);
|
||||
|
||||
glEnd();
|
||||
|
||||
if (rand() % 5 == 0)
|
||||
{
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glColor3f(0.0f, 0.0f, 0.0f);
|
||||
glBegin(GL_LINE_LOOP);
|
||||
glVertex3f(static_cast<float>(tri.v0.x), static_cast<float>(tri.v0.y), static_cast<float>(tri.v0.z) / 32767.0f);
|
||||
glVertex3f(static_cast<float>(tri.v1.x), static_cast<float>(tri.v1.y), static_cast<float>(tri.v1.z) / 32767.0f);
|
||||
glVertex3f(static_cast<float>(tri.v2.x), static_cast<float>(tri.v2.y), static_cast<float>(tri.v2.z) / 32767.0f);
|
||||
glEnd();
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::end_frame()
|
||||
{
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
glfwPollEvents();
|
||||
}
|
||||
|
||||
bool Renderer::should_close()
|
||||
{
|
||||
return glfwWindowShouldClose(window);
|
||||
}
|
||||
|
||||
GLuint Renderer::load_texture(const std::string &filepath)
|
||||
{
|
||||
int width, height, channels;
|
||||
unsigned char *image = stbi_load(filepath.c_str(), &width, &height, &channels, 3);
|
||||
if (!image)
|
||||
{
|
||||
throw std::runtime_error("Failed to load texture: " + filepath);
|
||||
}
|
||||
|
||||
int new_width = std::min(width, 256);
|
||||
int new_height = std::min(height, 256);
|
||||
|
||||
if (new_width > 128)
|
||||
new_width = 256;
|
||||
else if (new_width > 64)
|
||||
new_width = 128;
|
||||
else if (new_width > 32)
|
||||
new_width = 64;
|
||||
else if (new_width > 16)
|
||||
new_width = 32;
|
||||
else
|
||||
new_width = 16;
|
||||
|
||||
if (new_height > 128)
|
||||
new_height = 256;
|
||||
else if (new_height > 64)
|
||||
new_height = 128;
|
||||
else if (new_height > 32)
|
||||
new_height = 64;
|
||||
else if (new_height > 16)
|
||||
new_height = 32;
|
||||
else
|
||||
new_height = 16;
|
||||
|
||||
unsigned char *resized = new unsigned char[new_width * new_height * 3];
|
||||
|
||||
for (int y = 0; y < new_height; y++)
|
||||
{
|
||||
for (int x = 0; x < new_width; x++)
|
||||
{
|
||||
int src_x = x * width / new_width;
|
||||
int src_y = y * height / new_height;
|
||||
int src_idx = (src_y * width + src_x) * 3;
|
||||
int dst_idx = (y * new_width + x) * 3;
|
||||
|
||||
resized[dst_idx] = (image[src_idx] >> 3) << 3;
|
||||
resized[dst_idx + 1] = (image[src_idx + 1] >> 3) << 3;
|
||||
resized[dst_idx + 2] = (image[src_idx + 2] >> 3) << 3;
|
||||
}
|
||||
}
|
||||
|
||||
GLuint texture;
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, new_width, new_height, 0, GL_RGB, GL_UNSIGNED_BYTE, resized);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
stbi_image_free(image);
|
||||
delete[] resized;
|
||||
|
||||
return texture;
|
||||
}
|
||||
69
Ps1Engine/renderer/renderer.h
Normal file
69
Ps1Engine/renderer/renderer.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef RENDERER_H
|
||||
#define RENDERER_H
|
||||
|
||||
#include <GL/glew.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
struct Vec3
|
||||
{
|
||||
int x, y, z;
|
||||
Vec3(int x_ = 0, int y_ = 0, int z_ = 0);
|
||||
};
|
||||
|
||||
struct Vec2
|
||||
{
|
||||
float x, y;
|
||||
Vec2(float x_ = 0.0f, float y_ = 0.0f);
|
||||
};
|
||||
|
||||
struct Color
|
||||
{
|
||||
uint8_t r, g, b;
|
||||
Color(uint8_t r_ = 255, uint8_t g_ = 255, uint8_t b_ = 255);
|
||||
};
|
||||
|
||||
struct Matrix4
|
||||
{
|
||||
int m[16];
|
||||
Matrix4();
|
||||
};
|
||||
|
||||
struct Triangle
|
||||
{
|
||||
Vec3 v0, v1, v2;
|
||||
Vec2 uv0, uv1, uv2;
|
||||
Color color;
|
||||
};
|
||||
|
||||
Vec3 matrix_multiply(const Matrix4 &mat, const Vec3 &v);
|
||||
|
||||
Matrix4 matrix_multiply(const Matrix4 &a, const Matrix4 &b);
|
||||
|
||||
Color convert_color(const Color &color);
|
||||
|
||||
class Renderer
|
||||
{
|
||||
private:
|
||||
GLFWwindow *window;
|
||||
Matrix4 view_matrix;
|
||||
int width;
|
||||
int height;
|
||||
int internal_width;
|
||||
int internal_height;
|
||||
std::string title;
|
||||
|
||||
public:
|
||||
Renderer(int width_, int height_, const std::string &title_);
|
||||
~Renderer();
|
||||
void set_view_matrix(const Matrix4 &view);
|
||||
void begin_frame();
|
||||
void render(const std::vector<Triangle> &triangles, GLuint texture);
|
||||
void end_frame();
|
||||
bool should_close();
|
||||
GLuint load_texture(const std::string &filepath);
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user