Better Wavefront OBJ loader.
All checks were successful
Run ReCI Build Test / Explore-Gitea-Actions (push) Successful in 5m35s

This commit is contained in:
2024-12-20 10:12:11 -05:00
parent 6cbd369d51
commit 0823730e82
14 changed files with 285 additions and 19 deletions

44
assets/models/cube.obj Normal file
View File

@@ -0,0 +1,44 @@
# Blender 3.6.19
# www.blender.org
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 -1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 1.000000
vn -0.0000 1.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -1.0000 -0.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vt 0.875000 0.500000
vt 0.625000 0.750000
vt 0.625000 0.500000
vt 0.375000 1.000000
vt 0.375000 0.750000
vt 0.625000 0.000000
vt 0.375000 0.250000
vt 0.375000 0.000000
vt 0.375000 0.500000
vt 0.125000 0.750000
vt 0.125000 0.500000
vt 0.625000 0.250000
vt 0.875000 0.750000
vt 0.625000 1.000000
s 0
f 5/1/1 3/2/1 1/3/1
f 3/2/2 8/4/2 4/5/2
f 7/6/3 6/7/3 8/8/3
f 2/9/4 8/10/4 6/11/4
f 1/3/5 4/5/5 2/9/5
f 5/12/6 2/9/6 6/7/6
f 5/1/1 7/13/1 3/2/1
f 3/2/2 7/14/2 8/4/2
f 7/6/3 5/12/3 6/7/3
f 2/9/4 4/5/4 8/10/4
f 1/3/5 3/2/5 4/5/5
f 5/12/6 1/3/6 2/9/6

View File

@@ -90,9 +90,12 @@ public:
/// Vertices are required, Everything else is optional.
explicit VertexArray(const std::vector<Vertex>& vertex_positions, const std::vector<unsigned int>& vertex_indices = {},
const std::vector<Normal>& vertex_normals = {}, const std::vector<TextureCoordinate>& texture_coordinates = {});
static VertexArray LoadWavefrontOBJ(const std::string& file_text);
};
using namespace JGL;
static VertexArray Animate(int animation_id, float animation_time);
static VertexArray Animate(const AnimationState& anim_state);

View File

@@ -6,6 +6,7 @@
#include <JGL/logger/logger.h>
#include <J3ML/Geometry/AABB.hpp>
#include <rewindow/logger/logger.h>
#include <JGL/types/VertexArray.h>
using J3ML::LinearAlgebra::Vector2;
using namespace JGL::Fonts;
@@ -166,7 +167,7 @@ public:
camera->render();
// All 3D elements of the scene and JGL elements *must* be rendered before the 2D stuff
/* if rendering to screen space directly. */
auto test_light = PointLight({2,1,2}, {pulse,pulse,pulse, 255}, {0, 0, 0, 255}, {0,0,0}, 1);
auto test_light = PointLight({2,1,2}, {pulse,pulse,pulse, 255}, {pulse, pulse, pulse, 255}, {0,0,0}, 1, 0.1, 0.01);
// If a 3D object has transparency. The things you'd like to see through it must be drawn before.
J3D::Begin();
J3D::DrawLine(Colors::Red, {-0.33,-0.125,1}, {-1,-0.125,1});
@@ -174,9 +175,9 @@ public:
J3D::DrawString(Colors::Red, "JGL Sample Text", {-0.33, -0.1, 1.0f}, 1.f, 32, FreeSans, textAngle, true);
//J3D::WireframeSphere(Colors::Green, {0,0,0.5f}, 0.25f, 1, 128, 128);
Sphere sphere = {{0,0, 0.5f}, 0.2125};
J3D::BatchWireframeRevoSphere(Colors::Green, &sphere, 1, 1, 16, 16, true);
J3D::BatchWireframeRevoSphere(Colors::Green, &sphere, 1, 1, 8, 8, true);
J3D::RequiredLight(&test_light);
J3D::FillAABB(Colors::Whites::AliceBlue, {0,0,0.5f}, {0.1f, 0.1f, 0.1f});
J3D::FillAABB(Colors::Whites::AliceBlue, {0,0,0.5f}, {0.05f, 0.05f, 0.05f});
J3D::WireframeAABB(Colors::Gray, {0,0,0.5f}, {0.11f, 0.06f, 0.11f});
AABB boxes[1] = {{Vector3(-0.2125, -0.2125,0.28750), Vector3(0.2125,0.2125,0.7125)}};
@@ -308,12 +309,25 @@ public:
};
int main(int argc, char** argv) {
auto* window = new JGLDemoWindow("JGL Demo Window", 1280, 720);
window->SetRenderer(RenderingAPI::OPENGL);
window->Open();
window->initGL();
window->SetResizable(true);
window->SetVsyncEnabled(true);
std::ifstream file("assets/models/cube.obj");
if (!file.is_open())
return -1;
std::stringstream buffer;
buffer << file.rdbuf();
std::string file_text = buffer.str();
file.close();
//std::cout << "File contents:\n" << file_text << std::endl;
auto result = VertexArray::LoadWavefrontOBJ(file_text);
ReWindow::Logger::Error.EnableConsole(false);
ReWindow::Logger::Warning.EnableConsole(false);
@@ -322,4 +336,7 @@ int main(int argc, char** argv) {
while (window->IsAlive())
window->ManagedRefresh();
return 0;
//for (const auto& v3 : result)
//std::cout << v3.x << " " << v3.y << " " << v3.z << std::endl;
}

View File

@@ -5,7 +5,7 @@
#include <glad/glad.h>
#include <JGL/JGL.h>
#include <JGL/logger/logger.h>
#include "internals/internals.h"
#include "internals/include/internals.h"
namespace JGL {
using namespace J3ML;

View File

@@ -0,0 +1,9 @@
#pragma once
#include <string>
namespace JGL {
class VertexArray;
VertexArray LoadWavefrontOBJ(const std::string& file_text);
}

View File

@@ -1,13 +1,13 @@
/// Things used in J2D and J3D that should not be exposed to the user of the library.
#pragma once
#include <glad/glad.h>
#include "glad/glad.h"
#include <array>
#include <vector>
#include <J3ML/LinearAlgebra/Vector2.hpp>
#include <JGL/types/RenderTarget.h>
#include <JGL/types/Light.h>
#include <JGL/logger/logger.h>
#include "J3ML/LinearAlgebra/Vector2.hpp"
#include "JGL/types/RenderTarget.h"
#include "JGL/types/Light.h"
#include "JGL/logger/logger.h"
namespace JGL {
inline constexpr std::array<const LightBase*, 8> empty_light_array = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };

View File

@@ -1,4 +1,4 @@
#include <JGL/JGL.h>
#include "JGL/JGL.h"
namespace JGL::Data {
unsigned char Jupiteroid_Data[] = {

View File

@@ -1,5 +1,4 @@
#include <JGL/JGL.h>
#include "internals.h"
void JGL::ShapeCache::Init() {
std::array<Vector3, 8> vertices = {

View File

@@ -0,0 +1,188 @@
#include "../include/WavefrontOBJ.h"
#include "JGL/types/VertexArray.h"
#include "JGL/logger/logger.h"
std::pair<float, unsigned long> ParseNumber(const std::string& file_text, const unsigned long& offset) {
std::string number;
unsigned long new_offset = offset;
bool decimal_used = false;
if (offset >= file_text.size())
return {0.0f, offset};
for (; new_offset < file_text.size(); new_offset++) {
if (file_text[new_offset] == '-') {
if (number.empty()) {
number.push_back(file_text[new_offset]);
continue;
}
Logger::Error("Error while parsing number, Index: " + std::to_string(new_offset) + " Extra negative sign.");
return {0.0f, offset};
}
else if (file_text[new_offset] == '.') {
if (!decimal_used) {
number.push_back(file_text[new_offset]);
decimal_used = true;
continue;
}
Logger::Error("Error parsing number at: " + std::to_string(new_offset) + " Extra decimal point.");
return {0.0f, offset};
}
else if (isdigit(file_text[new_offset]))
number.push_back(file_text[new_offset]);
else
break;
}
return {std::stof(number), new_offset};
}
std::pair<Vector2, unsigned long> ParseVector2(const std::string& file_text, const unsigned long& offset) {
auto x_result = ParseNumber(file_text, offset);
auto y_result = ParseNumber(file_text, x_result.second + 1);
// If the new offset is the same as the offset we passed in then we know it didn't work.
if (x_result.second == offset || y_result.second == x_result.second)
return {Vector2(0, 0), offset};
return {Vector2(x_result.first, y_result.first), y_result.second + 1};
}
std::pair<Vector3, unsigned long> ParseVector3(const std::string& file_text, const unsigned long& offset) {
auto x_result = ParseNumber(file_text, offset);
auto y_result = ParseNumber(file_text, x_result.second + 1);
auto z_result = ParseNumber(file_text, y_result.second + 1);
// If the new offset is the same as the offset we passed in then we know it didn't work.
if (x_result.second == offset || y_result.second == x_result.second || z_result.second == y_result.second)
return {Vector3(0,0,0), offset};
return {Vector3(x_result.first, y_result.first, z_result.first), z_result.second + 1};
}
std::pair<std::array<Vector3, 3>, unsigned long> ParseFaceData(const std::string& file_text, const unsigned long& offset) {
unsigned long new_offset = offset;
std::array<Vector3, 3> face_data;
for (unsigned int i = 0; i < 3; i++) {
Vector3 vertex = {-1, -1, -1};
// Vertex
if (std::isdigit(file_text[new_offset])) {
auto v_result = ParseNumber(file_text, new_offset);
vertex.x = v_result.first;
new_offset = v_result.second;
}
// UV
if (new_offset < file_text.size() && file_text[new_offset] == '/') {
new_offset++;
if (new_offset < file_text.size() && (std::isdigit(file_text[new_offset]))) {
auto vt_result = ParseNumber(file_text, new_offset);
vertex.y = vt_result.first;
new_offset = vt_result.second;
}
}
// Normal
if (new_offset < file_text.size() && file_text[new_offset] == '/') {
new_offset++;
if (new_offset < file_text.size() && (std::isdigit(file_text[new_offset]))) {
auto vn_result = ParseNumber(file_text, new_offset);
vertex.z = vn_result.first;
new_offset = vn_result.second;
}
}
face_data[i] = vertex;
new_offset++;
}
return {face_data, new_offset};
}
VertexArray JGL::LoadWavefrontOBJ(const std::string &file_text) {
std::vector<std::array<Vector3, 3>> faceline_data;
std::vector<TextureCoordinate> temp_uvs;
std::vector<Normal> temp_normals;
std::vector<Vertex> vertices;
std::vector<Normal> normals;
std::vector<TextureCoordinate> uvs;
std::vector<unsigned int> indices;
unsigned long offset = 0;
while (offset < file_text.size()) {
char c = file_text[offset];
// Skip comments and empty lines
if (c == '#' || c == '\n' || c == '\r') {
offset++;
continue;
}
// Vertices
if (c == 'v' && offset + 1 < file_text.size() && file_text[offset + 1] == ' ') {
offset += 2;
auto vertex_result = ParseVector3(file_text, offset);
if (vertex_result.second != offset) {
vertices.push_back(vertex_result.first);
offset = vertex_result.second;
}
}
// Normals.
else if (c == 'v' && offset + 2 < file_text.size() && file_text[offset + 1] == 'n' && file_text[offset + 2] == ' ') {
offset += 3;
auto normal_result = ParseVector3(file_text, offset);
if (normal_result.second != offset) {
temp_normals.push_back(normal_result.first);
offset = normal_result.second;
}
}
// Texture Coordinates
else if (c == 'v' && offset + 2 < file_text.size() && file_text[offset + 1] == 't' && file_text[offset + 2] == ' ') {
offset += 3;
auto uv_result = ParseVector2(file_text, offset);
if (uv_result.second != offset) {
temp_uvs.push_back(uv_result.first);
offset = uv_result.second;
}
}
// Face lines.
else if (c == 'f' && offset + 1 < file_text.size() && file_text[offset + 1] == ' ') {
offset += 2;
auto faceline_result = ParseFaceData(file_text, offset);
if (faceline_result.second != offset) {
faceline_data.push_back(faceline_result.first);
offset = faceline_result.second;
}
}
else
offset++;
}
// Pick everything out of the face lines how OpenGL expects it to be.
for (const auto& face : faceline_data) {
for (const auto& vp : face){
if (vp.x != -1)
indices.push_back(vp.x -1);
if (vp.y != -1)
uvs.push_back(temp_uvs[vp.y -1]);
if (vp.z != -1)
normals.push_back(temp_normals[vp.z -1]);
}
}
return VertexArray(vertices.data(), vertices.size(), indices.data(), indices.size(), normals.data(), normals.size(), uvs.data(), uvs.size());
}

View File

@@ -1,6 +1,6 @@
#include "internals.h"
#include <J3ML/LinearAlgebra/Vector4.hpp>
#include <JGL/types/Light.h>
#include "../include/internals.h"
#include "J3ML/LinearAlgebra/Vector4.hpp"
#include "JGL/types/Light.h"
// TODO handle the case that a required light is in the list of optional lights.
void JGL::J3D::SelectLights(const Vector3& position) {

View File

@@ -1,7 +1,7 @@
#include <JGL/JGL.h>
#include <JGL/logger/logger.h>
#include <J3ML/Algorithm/Bezier.hpp>
#include "../internals/internals.h"
#include "../internals/include/internals.h"
void JGL::J2D::Begin(RenderTarget* rt, bool clear_buffers) {
GLfloat old_clear_color[4];

View File

@@ -4,7 +4,7 @@
#include <J3ML/Geometry/OBB.hpp>
#include <J3ML/Algorithm/Bezier.hpp>
#include <JGL/types/Light.h>
#include "../internals/internals.h"
#include "../internals/include/internals.h"
std::array<GLfloat, 16> JGL::OpenGLPerspectiveProjectionRH(float fovY, float aspect, float z_near, float z_far) {

View File

@@ -1,5 +1,5 @@
#include <JGL/JGL.h>
#include "../internals/internals.h"
#include "../internals/include/internals.h"
#if __linux__

View File

@@ -1,4 +1,6 @@
#include <JGL/types/VertexArray.h>
#include <JGL/logger/logger.h>
#include "../internals/include/WavefrontOBJ.h"
using namespace JGL;
@@ -159,7 +161,7 @@ VertexArray::VertexArray(const Vector3* vertex_positions, const long& vp_length,
if (texture_coordinates && vt_length) {
this->texture_coordinates = VRamList(texture_coordinates, vt_length);
local_normals.resize(vt_length);
local_texture_coordinates.resize(vt_length);
memcpy(local_texture_coordinates.data(), texture_coordinates, sizeof(TextureCoordinate) * vt_length);
}
@@ -189,3 +191,7 @@ VertexArray::VertexArray(const std::vector<Vertex>& vertex_positions, const std:
bool VertexArray::Static() {
return animations.empty();
}
VertexArray VertexArray::LoadWavefrontOBJ(const std::string& file_text) {
return JGL::LoadWavefrontOBJ(file_text);
}