cleanup build. add demo project. add timing features to sync. switch to more std::string and std::vector usage over custom, hacky Array template

This commit is contained in:
2025-09-01 21:56:43 -05:00
parent b4e90dce4d
commit 75e72ba9ca
25 changed files with 663 additions and 1759 deletions

View File

@@ -1,9 +1,15 @@
#include <cassert>
#include <glad/glad.h>
#include <map>
#include <string>
#include <optional>
#include "util.hpp"
#include "body.hpp"
#include "shaders.hpp"
using namespace std;
enum class ParserState {
PREFIX,
VERTEX,
@@ -11,72 +17,29 @@ enum class ParserState {
FACE_SKIP,
};
static map<string, pair<vector<float>, vector<int>>> cache;
optional<pair<vector<float>, vector<int>>> _parse_obj(const char* obj_filepath);
bool load_body(Body* out_body, const char* obj_filepath) {
Array<char> source;
if (!read_file(&source, obj_filepath)) {
vector<float> verts;
vector<int> faces;
if (auto it = cache.find(obj_filepath); it != cache.end()) {
verts = it->second.first;
faces = it->second.second;
} else if (auto pair = _parse_obj(obj_filepath); pair.has_value()) {
verts = pair.value().first;
faces = pair.value().second;
cache[obj_filepath] = pair.value();
} else {
return false;
}
size_t num_verts = 0;
size_t num_faces = 0;
for (int i = 1; i < source.len; i++) {
if (source[i] == ' ' && source[i - 1] == 'v') {
num_verts++;
} else if (source[i] == ' ' && source[i - 1] == 'f') {
num_faces++;
}
}
Array<float> verts = { (float*)malloc(sizeof(float) * num_verts * 3), num_verts * 3 };
Array<int> faces = { (int*)malloc(sizeof(int) * num_faces * 3), num_faces*3 };
// Get ready for the parsing loop
ParserState state = ParserState::PREFIX;
int vert_i = 0;
int face_i = 0;
int start = 0;
for (int i = 1; i < source.len; i++) {
switch (state) {
case ParserState::PREFIX:
if (source[i - 1] == 'v' && source[i] == ' ') {
state = ParserState::VERTEX;
start = i + 1;
} else if (i > 0 && source[i - 1] == 'f' && source[i] == ' ') {
state = ParserState::FACE;
start = i + 1;
}
break;
case ParserState::VERTEX:
if (iswspace(source[i])) {
verts[vert_i] = atof(&source[start]);
vert_i++;
start = i + 1;
}
break;
case ParserState::FACE:
if (source[i] == '/') {
state = ParserState::FACE_SKIP;
faces[face_i] = atoi(&source[start]) - 1;
face_i++;
}
break;
case ParserState::FACE_SKIP:
if (iswspace(source[i])) {
state = ParserState::FACE;
start = i + 1;
}
}
if (source[i] == '\n' || source[i] == '\r') {
state = ParserState::PREFIX;
continue;
}
}
// Need a good validation check here. This is a stand in.
if (verts.len == 0 || faces.len == 0) {
if (verts.size() == 0 || faces.size() == 0) {
printf(
"Obj file appears incomplete or corrupted. Num verts %zu. Num Faces %zu.\n", verts.len, faces.len);
"Obj file appears incomplete or corrupted. Num verts %zu. Num Faces %zu.\n", verts.size(), faces.size());
return false;
}
@@ -92,10 +55,10 @@ bool load_body(Body* out_body, const char* obj_filepath) {
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, verts.len * sizeof(float), verts.data, GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(float), &verts[0], GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, faces.len * sizeof(int), faces.data, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, faces.size() * sizeof(int), &faces[0], GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
@@ -116,10 +79,69 @@ void draw_body(const Body& b) {
set_uniform(b.shader, "global_t", glm::scale(b.pose, glm::vec3(b.scale)));
set_uniform(b.shader, "color", b.color);
glBindVertexArray(b.vao);
glDrawElements(GL_TRIANGLES, b.faces.len, GL_UNSIGNED_INT, 0);
glDrawElements(GL_TRIANGLES, b.faces.size(), GL_UNSIGNED_INT, 0);
}
void create_new_sphere(Body* b, float scale) {
assert(load_body(b, "Icosphere.obj"));
b->scale = scale;
}
optional<pair<vector<float>, vector<int>>> _parse_obj(const char* obj_filepath) {
string source;
if (!read_file(&source, obj_filepath)) {
return {};
}
size_t num_verts = 0;
size_t num_faces = 0;
for (int i = 1; i < source.size(); i++) {
if (source[i] == ' ' && source[i - 1] == 'v') {
num_verts++;
} else if (source[i] == ' ' && source[i - 1] == 'f') {
num_faces++;
}
}
vector<float> verts;
vector<int> faces;
// Get ready for the parsing loop
ParserState state = ParserState::PREFIX;
int start = 0;
for (int i = 1; i < source.size(); i++) {
switch (state) {
case ParserState::PREFIX:
if (source[i - 1] == 'v' && source[i] == ' ') {
state = ParserState::VERTEX;
start = i + 1;
} else if (i > 0 && source[i - 1] == 'f' && source[i] == ' ') {
state = ParserState::FACE;
start = i + 1;
}
break;
case ParserState::VERTEX:
if (iswspace(source[i])) {
verts.push_back(atof(&source[start]));
start = i + 1;
}
break;
case ParserState::FACE:
if (source[i] == '/') {
state = ParserState::FACE_SKIP;
faces.push_back(atoi(&source[start]) - 1);
}
break;
case ParserState::FACE_SKIP:
if (iswspace(source[i])) {
state = ParserState::FACE;
start = i + 1;
}
}
if (source[i] == '\n' || source[i] == '\r') {
state = ParserState::PREFIX;
continue;
}
}
}

View File

@@ -1,62 +0,0 @@
#include <body.hpp>
#include <stdio.h>
#include <stdlib.h>
#include <glm/glm.hpp>
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/ext/vector_float3.hpp>
#include <glm/matrix.hpp>
#include <util.hpp>
#include "camera_poses.hpp"
#define LINE_BUF_SIZE 256
#define NUM_SPHERES_PER_AXE 25
#define SPACE_PER_SPHERE 10.0f
bool parse_poses(Array<glm::mat2x3>* locs_out, const char* filepath) {
FILE* fp;
if (fopen_s(&fp, filepath, "rb")) {
printf("Error parsing %s\n", filepath);
return false;
}
char delim = ',';
char line[LINE_BUF_SIZE];
// because clang refuses to cooperate with my append implementation and generate the function code...
*locs_out = { (glm::mat2x3*)malloc(sizeof(glm::mat2x3) * 3 * NUM_SPHERES_PER_AXE * 14),
3 * NUM_SPHERES_PER_AXE * 14 };
// read in header
fgets(line, LINE_BUF_SIZE, fp);
fgets(line, LINE_BUF_SIZE, fp);
for (int camera_i = 0; !feof(fp); camera_i++) {
Array<char*> words = split_str(line, delim);
float x = atof(words[0]);
float y = atof(words[1]);
float z = atof(words[2]);
float w = atof(words[3]);
float i = atof(words[4]);
float j = atof(words[5]);
float k = atof(words[6]);
glm::mat4 pose = quat_to_mat4(glm::quat(w, i, j, k));
pose[3] = glm::vec4(x, y, z, 1);
// Generate axis spheres
for (int m = 0; m < 3; m++) {
for (int n = 0; n < NUM_SPHERES_PER_AXE; n++) {
glm::mat2x3 loc_color = glm::mat2x3(0.0);
loc_color[0][m] = n * SPACE_PER_SPHERE;
loc_color[0] = pose * glm::vec4(loc_color[0], 1);
loc_color[1] = glm::vec3(0);
loc_color[1][m] = 1.0f;
locs_out->data[camera_i*3*NUM_SPHERES_PER_AXE + m*NUM_SPHERES_PER_AXE + n] = loc_color;
}
}
fgets(line, LINE_BUF_SIZE, fp);
}
return true;
}

101
src/demo.cpp Normal file
View File

@@ -0,0 +1,101 @@
#include <iostream>
#include <string>
#include <map>
#include <stdio.h>
#include <stdlib.h>
#include <glm/glm.hpp>
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/ext/vector_float3.hpp>
#include <glm/matrix.hpp>
#include "util.hpp"
#include "live_plotter.hpp"
#pragma comment(lib, "LivePlotter.lib")
using namespace std;
static map<string, pointid> name_to_id;
#define LINE_BUF_SIZE 256
#define NUM_SPHERES_PER_AXE 25
#define SPACE_PER_SPHERE 10.0f
bool parse_poses(std::vector<glm::mat2x3>& locs_out, const char* filepath) {
ifstream file(filepath);
if (!file.is_open()) {
return false;
}
string line;
getline(file, line);
while (getline(file, line)) {
std::vector<std::string> words = split_str(line, ',');
float x = stof(words[0]);
float y = stof(words[1]);
float z = stof(words[2]);
float w = stof(words[3]);
float i = stof(words[4]);
float j = stof(words[5]);
float k = stof(words[6]);
glm::mat4 pose = quat_to_mat4(glm::quat(w, i, j, k));
pose[3] = glm::vec4(x, y, z, 1);
// Generate axis spheres
for (int m = 0; m < 3; m++) {
for (int n = 0; n < NUM_SPHERES_PER_AXE; n++) {
glm::mat2x3 loc_color = glm::mat2x3(0.0);
loc_color[0][m] = n * SPACE_PER_SPHERE;
loc_color[0] = pose * glm::vec4(loc_color[0], 1);
loc_color[1] = glm::vec3(0);
loc_color[1][m] = 1.0f;
locs_out.push_back(loc_color);
}
}
}
return true;
}
int main() {
vector<glm::mat2x3> camera_pose_axes;
if (!parse_poses(camera_pose_axes, "poses.csv")) {
return -1;
}
start(800, 800);
while (true)
;
//for (int i = 0; i < camera_pose_axes.size(); i++) {
// glm::vec3 p = camera_pose_axes[i][0];
// glm::vec3 color = camera_pose_axes[i][1];
// pointid id = create_point(p.x, p.y, p.z);
// set_color(id, color.x, color.y, color.z);
// set_scale(id, 10);
//}
//while (true) {
// string line;
// getline(cin, line);
// vector<string> words = split_str(line, ' ');
// if (!words.size() == 4)
// return NULL;
// printf("Received: %s, %s, %s, %s\n", words[0], words[1], words[2], words[3]); // echo for debugging
// string name = words[0];
// float x = stof(words[1]);
// float y = stof(words[2]);
// float z = stof(words[3]);
// if (auto it = name_to_id.find(name); it != name_to_id.end()) {
// update_point(it->second, x, y, z);
// } else {
// name_to_id[name] = create_point(x, y, z);
// set_lifetime(name_to_id[name], 0.2);
// set_scale(name_to_id[name], 15);
// }
//}
}

View File

@@ -20,6 +20,7 @@
#include <winnt.h>
#include <algorithm>
#include "sync.hpp"
#include "util.hpp"
#include "shaders.hpp"
#include "body.hpp"
@@ -29,6 +30,8 @@
#define LERP_MOVE_PERIOD_S 0.05
#define STOP_WAIT_TIME_MS 500
using namespace sync;
struct Point {
bool initialized;
glm::vec3 startloc;
@@ -44,10 +47,9 @@ static double prev_cursor_x, prev_cursor_y;
static bool mouse_pressed, scroll_pressed, stop_flag, running;
static Camera camera;
static Viewport viewport;
static Thread t;
static Mutex m;
static uint shader;
static CRITICAL_SECTION cs;
static HANDLE win_thread_h;
static DWORD win_thread_id;
static LARGE_INTEGER fr;
static pointid next_id = 0;
static std::map<uint, Point*> id_to_point; // Use a pool allocator possibly later
@@ -68,14 +70,13 @@ void _refresh_win();
double _time();
extern DllExport bool __cdecl start(int win_w, int win_h) {
if (!InitializeCriticalSectionAndSpinCount(&cs, 0x00000400))
return false;
if (running) {
printf("Already running! Call stop before calling stop again\n");
return false;
}
m = make_mutex();
if (!QueryPerformanceFrequency(&fr)) {
printf("Failed to get the performance counter frequency.\n");
return false;
@@ -84,9 +85,9 @@ extern DllExport bool __cdecl start(int win_w, int win_h) {
glm::vec2* winsize = (glm::vec2*)malloc(sizeof(glm::vec2));
*winsize = glm::vec2(win_w, win_h);
stop_flag = false;
win_thread_h = CreateThread(NULL, 0, _win_thread, winsize, 0, &win_thread_id);
t = make_thread(_win_thread, winsize);
running = win_thread_h != NULL;
running = t != NULL;
if (!running)
printf("Error while creating the live plotter window thread.\n");
return running;
@@ -94,18 +95,17 @@ extern DllExport bool __cdecl start(int win_w, int win_h) {
extern DllExport bool __cdecl stop() {
stop_flag = true;
DWORD res = WaitForSingleObject(win_thread_h, STOP_WAIT_TIME_MS);
DWORD res = WaitForSingleObject(t, STOP_WAIT_TIME_MS);
bool success = res == WAIT_OBJECT_0;
running = !success; // Consider the app still running if we didn't shut down properly...?
// TODO: Please wrap away win api synch stuff. thank you
if (!running) DeleteCriticalSection(&cs);
if (!running) dispose(m);
return success;
}
extern DllExport pointid __cdecl create_point(float x, float y, float z) {
EnterCriticalSection(&cs);
lock(m);
Point* p = (Point*)malloc(sizeof(Point));
*p = {
.initialized = false,
@@ -120,48 +120,48 @@ extern DllExport pointid __cdecl create_point(float x, float y, float z) {
pointid id = next_id++;
id_to_point[id] = p;
point_to_id[p] = id;
LeaveCriticalSection(&cs);
unlock(m);
return id;
}
extern DllExport void __cdecl set_color(pointid id, float r, float g, float b) {
EnterCriticalSection(&cs);
lock(m);
Point* p = id_to_point[id];
p->b.color = { r, g, b, p->b.color[3] };
LeaveCriticalSection(&cs);
unlock(m);
}
extern DllExport void __cdecl set_scale(pointid id, float scale) {
EnterCriticalSection(&cs);
lock(m);
Point* p = id_to_point[id];
p->b.scale = scale;
LeaveCriticalSection(&cs);
unlock(m);
}
extern DllExport void __cdecl update_point(pointid id, float x, float y, float z) {
EnterCriticalSection(&cs);
lock(m);
Point* p = id_to_point[id];
p->targetloc = { x, y, z };
p->startloc = p->b.pose[3];
p->start_s = _time();
LeaveCriticalSection(&cs);
unlock(m);
}
extern DllExport void __cdecl set_lifetime(pointid id, float new_lifetime_s) {
EnterCriticalSection(&cs);
lock(m);
Point* p = id_to_point[id];
p->start_s = _time();
p->lifetime_s = new_lifetime_s;
LeaveCriticalSection(&cs);
unlock(m);
}
extern DllExport void __cdecl clear_point(pointid id) {
EnterCriticalSection(&cs);
lock(m);
Point* p = id_to_point[id];
id_to_point.erase(id);
point_to_id.erase(p);
free(p);
LeaveCriticalSection(&cs);
unlock(m);
}
void _resize_cb(GLFWwindow* win, int w, int h) {
@@ -254,7 +254,7 @@ void _refresh_win() {
set_uniform(shader, "camera_t", world_to_camera(camera));
set_uniform(shader, "projection_t", camera_to_projection(viewport));
if (TryEnterCriticalSection(&cs)) {
if (trylock(m)) {
for (const auto& [id, p] : id_to_point) {
// Initialize if not done so yet
if (!p->initialized) {
@@ -276,7 +276,7 @@ void _refresh_win() {
p->b.pose[3] = glm::vec4(lerp(p->startloc, p->targetloc, t), 1);
draw_body(p->b);
}
LeaveCriticalSection(&cs);
unlock(m);
}
glfwSwapBuffers(win);
glfwPollEvents();

11
src/logger.cpp Normal file
View File

@@ -0,0 +1,11 @@
#include "sync.hpp"
#include "logger.hpp"
static Mutex m;
static Thread t;
static std::vector<string> backlog;
void log(const char* msg) {
}

View File

@@ -1,46 +0,0 @@
//#include <iostream>
//#include <string>
//#include <map>
//
//#include "camera_poses.hpp"
//#include "live_plotter.hpp"
//
//static std::map<std::string, pointid> name_to_id;
//
//int main() {
// Array<glm::mat2x3> camera_pose_axes = { NULL, 0 };
// if (!parse_poses(&camera_pose_axes, "poses.csv")) {
// return -1;
// }
//
// start(800, 800);
//
// for (int i = 0; i < camera_pose_axes.len; i++) {
// glm::vec3 p = camera_pose_axes[i][0];
// glm::vec3 color = camera_pose_axes[i][1];
// pointid id = create_point(p.x, p.y, p.z);
// set_color(id, color.x, color.y, color.z);
// set_scale(id, 10);
// }
//
// while (true) {
// std::string line;
// std::getline(std::cin, line);
// Array<char*> words = split_str(line.c_str());
// if (!words.len == 4)
// return NULL;
// printf("Received: %s, %s, %s, %s\n", words[0], words[1], words[2], words[3]); // echo for debugging
// std::string name = std::string(words[0]);
// float x = atof(words[1]);
// float y = atof(words[2]);
// float z = atof(words[3]);
//
// if (auto it = name_to_id.find(name); it != name_to_id.end()) {
// update_point(it->second, x, y, z);
// } else {
// name_to_id[name] = create_point(x, y, z);
// set_lifetime(name_to_id[name], 0.2);
// set_scale(name_to_id[name], 15);
// }
// }
//}

View File

@@ -68,23 +68,22 @@ void set_uniform(uint id, const char* name, uniform_variant value) {
}
bool _compile_shader(uint *out_id, const char* filepath, int shader_type) {
Array<char> source;
std::string source;
if (!read_file(&source, filepath))
return false;
uint id = glCreateShader(shader_type);
glShaderSource(id, 1, &source.data, (int*)&source.len);
int sz = source.size();
const char* data = source.c_str();
glShaderSource(id, 1, &data, &sz);
glCompileShader(id);
free(source.data);
int status;
glGetShaderiv(id, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
char err_msg[MAX_ERR_MSG_LEN];
glGetShaderInfoLog(id, MAX_ERR_MSG_LEN, NULL, err_msg);
printf("Error compiling shader %s\n", filepath);
printf("%.*s\n", (int)source.len, source.data);
printf("Error msg: %s\n", err_msg);
}
*out_id = id;

View File

@@ -1,94 +1,25 @@
#include <cstdio>
#include <assert.h>
#include <cstring>
#include <cwctype>
#include <stdlib.h>
#include <glm/ext/quaternion_float.hpp>
#include <glm/mat4x4.hpp>
#include <string>
#include <fstream>
#include <sstream>
#include "util.hpp"
#include "body.hpp"
Array<char*> _split_str_inner(const char* s, char delim, bool just_check_ws);
using namespace std;
template<class T> void append(Array<T>& a, T el) {
if (a.len == a.cap) {
resize(a, std::max((size_t)8, a.cap * 2));
}
a[a.len] = el;
a.len++;
}
template<class T> T pop(Array<T>& a) {
assert(a.len >= 1);
a.len--;
return a.data[a.len + 1];
}
template<class T> void resize(Array<T>& a, size_t new_cap) {
T* new_data = (T*)malloc(sizeof(T) * new_cap);
if (a.len > 0) {
memcpy(new_data, a.data, std::min(a.len, new_cap));
}
free(a.data);
a.data = new_data;
a.len = std::min(a.len, new_cap);
a.cap = new_cap;
}
template<class T> void clear(Array<T>& a) {
a.len = 0;
}
bool read_file(Array<char>* out, const char* filepath) {
FILE* fp = NULL;
if (fopen_s(&fp, filepath, "rb") != 0) {
printf("ERROR Failed to open file %s\n", filepath);
bool read_file(string* s, const char* filepath) {
ifstream file(filepath);
if (!file.is_open()) {
return false;
}
fseek(fp, 0L, SEEK_END);
size_t sz = ftell(fp);
fseek(fp, 0, SEEK_SET);
char* data = (char*)malloc(sizeof(char) * sz);
fread(data, sizeof(char), sz, fp);
fclose(fp);
out->data = data;
out->len = sz;
return true;
*s = { istreambuf_iterator<char>(file), istreambuf_iterator<char>() };
}
Array<char*> split_str(const char* s, char delimiter) { return _split_str_inner(s, delimiter, false); }
Array<char*> split_str(const char* s) { return _split_str_inner(s, ' ', true); }
Array<char*> _split_str_inner(const char* s, char delim, bool just_check_ws) {
Array<char*> res = { NULL, 0, 0 };
char c;
int i = 0;
Array<char> cur_word = { NULL, 0, 0 };
while (true) {
c = s[i++];
bool is_delim = just_check_ws ? iswspace(c) : c == delim;
if ((is_delim || c == '\0') && cur_word.len > 0) {
char* word = (char*)malloc(sizeof(char) * cur_word.len + 1);
memcpy(word, cur_word.data, cur_word.len);
word[cur_word.len] = '\0';
append(res, word);
clear(cur_word);
if (c == '\0') {
break;
}
continue;
} else {
append(cur_word, c);
}
if (c == '\0') {
break;
}
vector<string> split_str(string s, char delim) {
vector<string> res;
string cur_word;
istringstream ss(s);
while (getline(ss, cur_word, delim)) {
res.push_back(cur_word);
}
return res;
}
@@ -109,6 +40,4 @@ glm::mat4 quat_to_mat4(glm::quat q) {
return glm::mat4(col0, col1, col2, col3);
}
float randf() {
return (float)rand() / (float)RAND_MAX;
}
float randf() { return (float)rand() / (float)RAND_MAX; }