A WIP 3D game engine in C++ using OpenGL
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

344 lines
9.2 KiB

#include "level.h"
#include "entity.h"
#include "player.h"
#include <glm/ext/vector_float3.hpp>
#include "collision.h"
#include <optional>
#define DIRECTIONAL_LIGHT 0
#define POINT_LIGHT 1
#define SPOT_LIGHT 2
std::optional<physics_model> level::physics_from_json(json p) noexcept {
if (!(p.count("position") && p.count("orientation"))) {
return {};
}
try {
// anonymous physics obj
glm::vec3 position {p["position"][0], p["position"][1], p["position"][2]};
glm::mat4 orientation = glm::mat4(1.0); // identity matrix
orientation = glm::rotate(orientation, (float)p["orientation"][0], glm::vec3(1,0,0));
orientation = glm::rotate(orientation, (float)p["orientation"][1], glm::vec3(0,1,0));
orientation = glm::rotate(orientation, (float)p["orientation"][2], glm::vec3(0,0,1));
std::optional<glm::vec2> aabb {};
std::vector<collision::Collider> colliders {};
if (p.count("collision")) {
for (json c : p["collision"]) {
std::optional<collision::Collider> res = collision::Collider::from_json(c);
if (res) {
fmt::print("Added collision shape: {}\n", res->get_name());
colliders.push_back(*res);
}
}
}
return physics_model {.pos = position, .orientation = orientation, .colliders=colliders};
} catch (std::exception &e) {
return {};
}
};
auto LIGHT_TYPES = std::unordered_map<std::string, unsigned int> {{"directional", 0}, {"point", 1}, {"spot", 2}};
void update_lights(Shader * shader, json &j) {
int i = 0;
for (auto &l: j["lights"]) {
if (i >= 9) {
A_ERROR("More than max lights defined");
}
glCheckError();
shader->setInt(fmt::format("lights[{}].type", i), LIGHT_TYPES.at(l["type"]));
glCheckError();
shader->setVec3(fmt::format("lights[{}].position", i),
glm::vec3(l["position"][0],l["position"][1], l["position"][2]));
glCheckError();
shader->setVec3(fmt::format("lights[{}].ambient", i),
glm::vec3(l["ambient"][0],l["ambient"][1], l["ambient"][2]));
glCheckError();
shader->setVec3(fmt::format("lights[{}].diffuse", i),
glm::vec3(l["diffuse"][0],l["diffuse"][1], l["diffuse"][2]));
glCheckError();
shader->setVec3(fmt::format("lights[{}].specular", i),
glm::vec3(l["specular"][0],l["specular"][1], l["specular"][2]));
glCheckError();
shader->setBool(fmt::format("lights[{}].attenuate", i), l["attenuate"]);
glCheckError();
shader->setVec3(fmt::format("lights[{}].attenuation", i),
glm::vec3(l["attenuation"][0],l["attenuation"][1], l["attenuation"][2]));
glCheckError();
i++;
}
shader->setInt("num_lights", i);
shader->setVec3("material.specular", glm::vec3(0.7));
shader->setVec3("material.diffuse", glm::vec3(1.0, 0.7, 0.8));
shader->setVec3("material.ambient", glm::vec3(0.2));
shader->setFloat("material.shininess", 32);
glCheckError();
}
void level::load_physics(json j) {
for (auto &p: j["physics"]) {
auto id = phys_meshes.size();
auto id_slug = fmt::format("{}", id);
if (j.count("id")) {
id_slug = j.at("id");
}
if (phys_ids.count(id_slug)) {
fmt::print("warn::level::load_physics overwritten physics mesh: '{}'\n", id_slug);
}
auto model = physics_from_json(p);
if (model) {
phys_meshes.push_back(*model);
phys_ids.insert({id_slug, id});
} else {
fmt::print("warn::level::load_physics failed to load physics {}\n", id_slug);
}
}
}
void level::load_entities(json j) {
int i = 0;
for (auto &m: j["entities"]) {
entity e {};
e.id = i++;
bool model = false;
bool physics = false;
std::string entity_id = fmt::format("{}", i);
if (m.count("id")) {
entity_id = m.at("id");
}
if (m.count("draw")) {
model = true;
if (m["draw"].count("model")) {
e.model = model_ids.at(m["draw"].at("model"));
e.type = entity_type::STATIC_MESH;
} else if (m["draw"].count("billboardmodel")) {
e.type = entity_type::BILLBOARD;
e.model = model_ids.at(m["draw"].at("billboardmodel"));
}
}
if (m.count("physics")) {
auto p = m["physics"];
physics = true;
physics_model entity_physics;
if (p.count("id")) {
e.physics = phys_ids[p.at("id")];
} else {
auto ph = physics_from_json(p);
if (ph) {
entity_physics = *ph;
int model_id = phys_meshes.size();
phys_meshes.emplace_back(entity_physics);
e.physics = model_id;
auto physics_id = fmt::format("{}", entity_id);
phys_ids.insert({physics_id, model_id});
} else {
throw std::exception {};
}
}
}
if (! (model && physics)) {
fmt::print("warn::level invisible entity");
}
entities.push_back(e);
}
}
level::level(const std::string &json_source)
{
json l;
json_source_file = json_source;
std::ifstream inp (json_source);
inp >> l;
fmt::print("loading level{}", l["name"]);
if (l == nullptr) {
A_ERROR("level | failed to parse json");
}
// main shader should be first
for (auto &s : l["shaders"]) {
if (s.size() > 1) {
shaders.emplace_back(fmt::format("{}/{}",SHADER_DIR , s[0]), fmt::format("{}/{}", SHADER_DIR , s[1]));
} else if (s.size() > 0) {
shaders.emplace_back(fmt::format("{}/{}",SHADER_DIR , s[0])) ;
}
}
if (l.size() <= 0) {
A_ERROR("level | no shader");
}
shader = &shaders[0];
shader->use();
int mid = 0;
for (auto & [key, v] : l["staticmodels"].items()) {
stbi_set_flip_vertically_on_load(false);
if (v.count("fliptextures")) {
stbi_set_flip_vertically_on_load(v.at("fliptextures"));
}
models.emplace_back(new Model(fmt::format("{}/{}/{}", OBJ_DIR, key, v["object"])));
model_ids.insert({key, mid});
mid++;
}
stbi_set_flip_vertically_on_load(false);
load_physics(l);
load_entities(l);
update_lights(shader, l);
// shader->setVec3("material.specular", glm::vec3(0.7));
// glCheckError();
// shader->setVec3("material.diffuse", glm::vec3(1.0, 0.7, 0.8));
// glCheckError();
// shader->setVec3("material.ambient", glm::vec3(0.2));
// glCheckError();
// shader->setFloat("material.shininess", 32.0);
// glCheckError();
}
void level::draw(const glm::mat4 view, const glm::mat4 projection) {
// std::multimap<float, int> order {};
struct job {
Mesh *m;
physics_model phys;
entity e;
};
std::multimap<float, job> order {};
auto camera = player::get_camera();
auto camright = glm::vec3(view[0][0], view[1][0], view[2][0]);
auto camup = camera->up;
#ifdef SHOW_DRAW_ORDER
static int prev = 0;
static int ticks = 0;
int timen = SDL_GetTicks() / 300;
if (timen > ticks) {
ticks = timen;
prev++;
}
#endif
for (const entity &e: entities) {
for (int i = 0; i < models[e.model]->meshes.size(); i++) {
Mesh *m = &models[e.model]->meshes[0] + i;
glm::vec3 pos = phys_meshes[e.physics].pos + m->average_position;
pos = camera->pos - pos;
float depth = glm::length(pos);
order.insert({depth,
job {m, phys_meshes[e.physics], e}
});
}
}
int i = 0;
for (auto iter = order.rbegin(); iter != order.rend(); ++iter) {
#ifdef SHOW_DRAW_ORDER
if (i++ >= prev % order.size() + 1) {
break;
}
#endif
auto k = iter->first;
job v = iter->second;
auto model = v.phys.orientation;
if (v.e.type == entity_type::BILLBOARD) {
// particle billboarding
// http://www.opengl-tutorial.org/intermediate-tutorials/billboards-particles/billboards/
model = glm::mat4(glm::mat3(glm::inverse(view)));
}
setup_shader(shader, view, projection, v.phys.pos, model);
v.m->draw(shader);
// glm::mat4 draw_view = view;
// auto phys = phys_meshes[entities[v].physics];
// }
// draw_objects(shader, {phys}, models[entities[v].model], draw_view, projection);
}
}
void level::update() {
fmt::print("Reloading level\n");
for (auto &s: shaders) {
s.reload();
}
json l;
try {
std::ifstream inp (json_source_file);
inp >> l;
} catch (std::exception &e) {
A_WARN("Failed to reload");
return;
}
shader->use();
update_lights(shader, l);
entities.clear();
phys_meshes.clear();
phys_ids.clear();
load_physics(l);
load_entities(l);
}