Commit c2535f0f authored by TheNumbat's avatar TheNumbat
Browse files

upstream changes

parent 8b8ee1f1
#pragma once
#include "../platform/gl.h"
#include "../geometry/halfedge.h"
#include "../platform/gl.h"
#include "../rays/shapes.h"
#include "pose.h"
#include "material.h"
#include "pose.h"
#include "skeleton.h"
using Scene_ID = unsigned int;
class Scene_Object {
public:
Scene_Object() = default;
Scene_Object(Scene_ID id, Pose pose, GL::Mesh&& mesh, std::string n = {});
Scene_Object(Scene_ID id, Pose pose, Halfedge_Mesh&& mesh, std::string n = {});
Scene_Object(const Scene_Object& src) = delete;
Scene_Object(Scene_Object&& src) = default;
~Scene_Object() = default;
void operator=(const Scene_Object& src) = delete;
Scene_Object& operator=(Scene_Object&& src) = default;
Scene_ID id() const {return _id;}
void sync_mesh();
void sync_anim_mesh();
void set_time(float time);
const GL::Mesh& mesh() {sync_mesh(); return _mesh;}
const GL::Mesh& posed_mesh();
void render(const Mat4& view, bool solid = false, bool depth_only = false, bool posed = true, bool anim = true);
Halfedge_Mesh& get_mesh();
const Halfedge_Mesh& get_mesh() const;
void copy_mesh(Halfedge_Mesh& out);
void take_mesh(Halfedge_Mesh&& in);
void set_mesh(Halfedge_Mesh& in);
Halfedge_Mesh::ElementRef set_mesh(Halfedge_Mesh& in, unsigned int eid);
BBox bbox();
bool is_editable() const;
bool is_shape() const;
void try_make_editable(PT::Shape_Type prev = PT::Shape_Type::none);
void set_mesh_dirty();
void set_skel_dirty();
void set_pose_dirty();
static const inline int max_name_len = 256;
struct Options {
char name[max_name_len] = {};
bool wireframe = false;
bool smooth_normals = false;
PT::Shape_Type shape_type = PT::Shape_Type::none;
PT::Shape shape;
};
Options opt;
Pose pose;
Anim_Pose anim;
Skeleton armature;
Material material;
mutable bool rig_dirty = false;
Scene_Object() = default;
Scene_Object(Scene_ID id, Pose pose, GL::Mesh &&mesh, std::string n = {});
Scene_Object(Scene_ID id, Pose pose, Halfedge_Mesh &&mesh, std::string n = {});
Scene_Object(const Scene_Object &src) = delete;
Scene_Object(Scene_Object &&src) = default;
~Scene_Object() = default;
void operator=(const Scene_Object &src) = delete;
Scene_Object &operator=(Scene_Object &&src) = default;
Scene_ID id() const { return _id; }
void sync_mesh();
void sync_anim_mesh();
void set_time(float time);
const GL::Mesh &mesh() {
sync_mesh();
return _mesh;
}
const GL::Mesh &posed_mesh();
void render(const Mat4 &view, bool solid = false, bool depth_only = false, bool posed = true,
bool anim = true);
Halfedge_Mesh &get_mesh();
const Halfedge_Mesh &get_mesh() const;
void copy_mesh(Halfedge_Mesh &out);
void take_mesh(Halfedge_Mesh &&in);
void set_mesh(Halfedge_Mesh &in);
Halfedge_Mesh::ElementRef set_mesh(Halfedge_Mesh &in, unsigned int eid);
BBox bbox();
bool is_editable() const;
bool is_shape() const;
void try_make_editable(PT::Shape_Type prev = PT::Shape_Type::none);
void set_mesh_dirty();
void set_skel_dirty();
void set_pose_dirty();
static const inline int max_name_len = 256;
struct Options {
char name[max_name_len] = {};
bool wireframe = false;
bool smooth_normals = false;
PT::Shape_Type shape_type = PT::Shape_Type::none;
PT::Shape shape;
};
Options opt;
Pose pose;
Anim_Pose anim;
Skeleton armature;
Material material;
mutable bool rig_dirty = false;
private:
Scene_ID _id = 0;
Halfedge_Mesh halfedge;
mutable GL::Mesh _mesh, _anim_mesh;
mutable std::unordered_map<unsigned int, std::vector<Joint*>> vertex_joints;
mutable bool editable = true;
mutable bool mesh_dirty = false;
mutable bool skel_dirty = false, pose_dirty = false;
Scene_ID _id = 0;
Halfedge_Mesh halfedge;
mutable GL::Mesh _mesh, _anim_mesh;
mutable std::unordered_map<unsigned int, std::vector<Joint *>> vertex_joints;
mutable bool editable = true;
mutable bool mesh_dirty = false;
mutable bool skel_dirty = false, pose_dirty = false;
};
bool operator!=(const Scene_Object::Options& l, const Scene_Object::Options& r);
bool operator!=(const Scene_Object::Options &l, const Scene_Object::Options &r);
#include "pose.h"
Mat4 Pose::transform() const {
return Mat4::translate(pos) *
rotation_mat() *
Mat4::scale(scale);
}
Mat4 Pose::transform() const { return Mat4::translate(pos) * rotation_mat() * Mat4::scale(scale); }
Mat4 Pose::rotation_mat() const {
return Mat4::euler(euler);
}
Mat4 Pose::rotation_mat() const { return Mat4::euler(euler); }
Quat Pose::rotation_quat() const {
return Quat::euler(euler);
}
Quat Pose::rotation_quat() const { return Quat::euler(euler); }
bool Pose::valid() const {
return pos.valid() && euler.valid() && scale.valid();
}
bool Pose::valid() const { return pos.valid() && euler.valid() && scale.valid(); }
void Pose::clamp_euler() {
if(!valid()) return;
while(euler.x < 0) euler.x += 360.0f;
while(euler.x >= 360.0f) euler.x -= 360.0f;
while(euler.y < 0) euler.y += 360.0f;
while(euler.y >= 360.0f) euler.y -= 360.0f;
while(euler.z < 0) euler.z += 360.0f;
while(euler.z >= 360.0f) euler.z -= 360.0f;
if (!valid())
return;
while (euler.x < 0)
euler.x += 360.0f;
while (euler.x >= 360.0f)
euler.x -= 360.0f;
while (euler.y < 0)
euler.y += 360.0f;
while (euler.y >= 360.0f)
euler.y -= 360.0f;
while (euler.z < 0)
euler.z += 360.0f;
while (euler.z >= 360.0f)
euler.z -= 360.0f;
}
Pose Pose::rotated(Vec3 angles) {
return Pose{Vec3{}, angles, Vec3{1.0f, 1.0f, 1.0f}};
}
Pose Pose::rotated(Vec3 angles) { return Pose{Vec3{}, angles, Vec3{1.0f, 1.0f, 1.0f}}; }
Pose Pose::moved(Vec3 t) {
return Pose{t, Vec3{}, Vec3{1.0f, 1.0f, 1.0f}};
}
Pose Pose::moved(Vec3 t) { return Pose{t, Vec3{}, Vec3{1.0f, 1.0f, 1.0f}}; }
Pose Pose::scaled(Vec3 s) {
return Pose{Vec3{}, Vec3{}, s};
}
Pose Pose::scaled(Vec3 s) { return Pose{Vec3{}, Vec3{}, s}; }
Pose Pose::id() {
return Pose{Vec3{},Vec3{},Vec3{1.0f, 1.0f, 1.0f}};
}
Pose Pose::id() { return Pose{Vec3{}, Vec3{}, Vec3{1.0f, 1.0f, 1.0f}}; }
bool operator==(const Pose& l, const Pose& r) {
return l.pos == r.pos && l.euler == r.euler && l.scale == r.scale;
bool operator==(const Pose &l, const Pose &r) {
return l.pos == r.pos && l.euler == r.euler && l.scale == r.scale;
}
bool operator!=(const Pose& l, const Pose& r) {
return l.pos != r.pos || l.euler != r.euler || l.scale != r.scale;
bool operator!=(const Pose &l, const Pose &r) {
return l.pos != r.pos || l.euler != r.euler || l.scale != r.scale;
}
Pose Anim_Pose::at(float t) const {
auto [p, r, s] = splines.at(t);
return Pose{p, r.to_euler(), s};
}
void Anim_Pose::set(float t, Pose p) {
splines.set(t, p.pos, Quat::euler(p.euler), p.scale);
auto [p, r, s] = splines.at(t);
return Pose{p, r.to_euler(), s};
}
void Anim_Pose::set(float t, Pose p) { splines.set(t, p.pos, Quat::euler(p.euler), p.scale); }
#pragma once
#include <set>
#include "../lib/mathlib.h"
#include "../geometry/spline.h"
#include "../lib/mathlib.h"
#include <set>
struct Pose {
Vec3 pos;
Vec3 euler;
Vec3 scale = Vec3{1.0f};
Vec3 pos;
Vec3 euler;
Vec3 scale = Vec3{1.0f};
Mat4 transform() const;
Mat4 rotation_mat() const;
Quat rotation_quat() const;
Mat4 transform() const;
Mat4 rotation_mat() const;
Quat rotation_quat() const;
void clamp_euler();
bool valid() const;
void clamp_euler();
bool valid() const;
static Pose rotated(Vec3 angles);
static Pose moved(Vec3 t);
static Pose scaled(Vec3 s);
static Pose id();
static Pose rotated(Vec3 angles);
static Pose moved(Vec3 t);
static Pose scaled(Vec3 s);
static Pose id();
};
bool operator==(const Pose& l, const Pose& r);
bool operator!=(const Pose& l, const Pose& r);
bool operator==(const Pose &l, const Pose &r);
bool operator!=(const Pose &l, const Pose &r);
struct Anim_Pose {
Pose at(float t) const;
void set(float t, Pose p);
Splines<Vec3, Quat, Vec3> splines;
Pose at(float t) const;
void set(float t, Pose p);
Splines<Vec3, Quat, Vec3> splines;
};
#include <imgui/imgui.h>
#include "../geometry/util.h"
#include "../gui/manager.h"
#include "../lib/mathlib.h"
#include "../geometry/util.h"
#include "renderer.h"
#include "scene.h"
static const int DEFAULT_SAMPLES = 4;
Renderer::Renderer(Vec2 dim) :
framebuffer(2, dim, DEFAULT_SAMPLES, true),
id_resolve(1, dim, 1, false),
save_buffer(1, dim, DEFAULT_SAMPLES, true),
save_output(1, dim, 1, false),
mesh_shader(GL::Shaders::mesh_v, GL::Shaders::mesh_f),
line_shader(GL::Shaders::line_v, GL::Shaders::line_f),
inst_shader(GL::Shaders::inst_v, GL::Shaders::mesh_f),
dome_shader(GL::Shaders::dome_v, GL::Shaders::dome_f),
_sphere(Util::sphere_mesh(1.0f, 3)),
_cyl(Util::cyl_mesh(1.0f, 1.0f, 64, false)),
_hemi(Util::hemi_mesh(1.0f)),
samples(DEFAULT_SAMPLES),
window_dim(dim),
id_buffer(new GLubyte[(int)dim.x * (int)dim.y * 4])
{}
Renderer::Renderer(Vec2 dim)
: framebuffer(2, dim, DEFAULT_SAMPLES, true), id_resolve(1, dim, 1, false),
save_buffer(1, dim, DEFAULT_SAMPLES, true), save_output(1, dim, 1, false),
mesh_shader(GL::Shaders::mesh_v, GL::Shaders::mesh_f),
line_shader(GL::Shaders::line_v, GL::Shaders::line_f),
inst_shader(GL::Shaders::inst_v, GL::Shaders::mesh_f),
dome_shader(GL::Shaders::dome_v, GL::Shaders::dome_f), _sphere(Util::sphere_mesh(1.0f, 3)),
_cyl(Util::cyl_mesh(1.0f, 1.0f, 64, false)), _hemi(Util::hemi_mesh(1.0f)),
samples(DEFAULT_SAMPLES), window_dim(dim),
id_buffer(new GLubyte[(int)dim.x * (int)dim.y * 4]) {}
Renderer::~Renderer() {
delete[] id_buffer;
id_buffer = nullptr;
delete[] id_buffer;
id_buffer = nullptr;
}
Renderer& Renderer::get() {
assert(data);
return *data;
Renderer &Renderer::get() {
assert(data);
return *data;
}
void Renderer::setup(Vec2 dim) {
data = new Renderer(dim);
}
void Renderer::setup(Vec2 dim) { data = new Renderer(dim); }
void Renderer::update_dim(Vec2 dim) {
window_dim = dim;
delete[] id_buffer;
id_buffer = new GLubyte[(int)dim.x * (int)dim.y * 4]();
framebuffer.resize(dim, samples);
save_buffer.resize(dim, save_buffer.samples());
id_resolve.resize(dim);
save_output.resize(dim);
window_dim = dim;
delete[] id_buffer;
id_buffer = new GLubyte[(int)dim.x * (int)dim.y * 4]();
framebuffer.resize(dim, samples);
save_buffer.resize(dim, save_buffer.samples());
id_resolve.resize(dim);
save_output.resize(dim);
}
void Renderer::shutdown() {
delete data;
data = nullptr;
delete data;
data = nullptr;
}
void Renderer::proj(const Mat4& proj) {
_proj = proj;
}
void Renderer::proj(const Mat4 &proj) { _proj = proj; }
void Renderer::complete() {
framebuffer.blit_to(1, id_resolve, false);
if(!id_resolve.can_read_at())
id_resolve.read(0, id_buffer);
framebuffer.blit_to(1, id_resolve, false);
if (!id_resolve.can_read_at())
id_resolve.read(0, id_buffer);
framebuffer.blit_to_screen(0, window_dim);
framebuffer.blit_to_screen(0, window_dim);
}
void Renderer::begin() {
framebuffer.clear(0, Vec4(Gui::Color::background, 1.0f));
framebuffer.clear(1, Vec4{0.0f, 0.0f, 0.0f, 1.0f});
framebuffer.clear_d();
framebuffer.bind();
GL::viewport(window_dim);
framebuffer.clear(0, Vec4(Gui::Color::background, 1.0f));
framebuffer.clear(1, Vec4{0.0f, 0.0f, 0.0f, 1.0f});
framebuffer.clear_d();
framebuffer.bind();
GL::viewport(window_dim);
}
void Renderer::save(Scene& scene, const Camera& cam, int w, int h, int s) {
Vec2 dim((float)w, (float)h);
void Renderer::save(Scene &scene, const Camera &cam, int w, int h, int s) {
Vec2 dim((float)w, (float)h);
save_buffer.resize(dim, s);
save_output.resize(dim);
save_buffer.clear(0, Vec4{0.0f, 0.0f, 0.0f, 1.0f});
save_buffer.bind();
GL::viewport(dim);
save_buffer.resize(dim, s);
save_output.resize(dim);
save_buffer.clear(0, Vec4{0.0f, 0.0f, 0.0f, 1.0f});
save_buffer.bind();
GL::viewport(dim);
Mat4 view = cam.get_view();
scene.for_items([&](Scene_Item& item) {
if(item.is<Scene_Light>()) return;
item.render(view);
});
Mat4 view = cam.get_view();
scene.for_items([&](Scene_Item &item) {
if (item.is<Scene_Light>())
return;
item.render(view);
});
save_buffer.blit_to(0, save_output, true);
save_buffer.blit_to(0, save_output, true);
framebuffer.bind();
GL::viewport(window_dim);
framebuffer.bind();
GL::viewport(window_dim);
}
void Renderer::saved(std::vector<unsigned char>& out) const {
save_output.flush();
out.resize(save_output.bytes());
save_output.read(0, out.data());
void Renderer::saved(std::vector<unsigned char> &out) const {
save_output.flush();
out.resize(save_output.bytes());
save_output.read(0, out.data());
}
GLuint Renderer::saved() const {
save_output.flush();
return save_output.get_output(0);
save_output.flush();
return save_output.get_output(0);
}
void Renderer::lines(const GL::Lines& lines, const Mat4& view, const Mat4& model, float alpha) {
void Renderer::lines(const GL::Lines &lines, const Mat4 &view, const Mat4 &model, float alpha) {
Mat4 mvp = _proj * view * model;
line_shader.bind();
line_shader.uniform("mvp", mvp);
line_shader.uniform("alpha", alpha);
lines.render(framebuffer.is_multisampled());
Mat4 mvp = _proj * view * model;
line_shader.bind();
line_shader.uniform("mvp", mvp);
line_shader.uniform("alpha", alpha);
lines.render(framebuffer.is_multisampled());
}
void Renderer::skydome(const Mat4& rotation, Vec3 color, float cosine, const GL::Tex2D& tex) {
void Renderer::skydome(const Mat4 &rotation, Vec3 color, float cosine, const GL::Tex2D &tex) {
tex.bind();
dome_shader.bind();
dome_shader.uniform("tex", 0);
dome_shader.uniform("use_texture", true);
dome_shader.uniform("color", color);
dome_shader.uniform("cosine", cosine);
dome_shader.uniform("transform", _proj * rotation);
_sphere.render();
tex.bind();
dome_shader.bind();
dome_shader.uniform("tex", 0);
dome_shader.uniform("use_texture", true);
dome_shader.uniform("color", color);
dome_shader.uniform("cosine", cosine);
dome_shader.uniform("transform", _proj * rotation);
_sphere.render();
}
void Renderer::skydome(const Mat4& rotation, Vec3 color, float cosine) {
void Renderer::skydome(const Mat4 &rotation, Vec3 color, float cosine) {
dome_shader.bind();
dome_shader.uniform("use_texture", false);
dome_shader.uniform("color", color);
dome_shader.uniform("cosine", cosine);
dome_shader.uniform("transform", _proj * rotation);
_sphere.render();
dome_shader.bind();
dome_shader.uniform("use_texture", false);
dome_shader.uniform("color", color);
dome_shader.uniform("cosine", cosine);
dome_shader.uniform("transform", _proj * rotation);
_sphere.render();
}
void Renderer::sphere(MeshOpt opt) {
mesh(_sphere, opt);
}
void Renderer::sphere(MeshOpt opt) { mesh(_sphere, opt); }
void Renderer::capsule(MeshOpt opt, const Mat4& mdl, float height, float rad, BBox& box) {
void Renderer::capsule(MeshOpt opt, const Mat4 &mdl, float height, float rad, BBox &box) {
Mat4 T = opt.modelview;
Mat4 cyl = mdl * Mat4::scale(Vec3{rad, height, rad});
Mat4 bot = mdl * Mat4::scale(Vec3{rad});
Mat4 top = mdl * Mat4::translate(Vec3{0.0f, height, 0.0f}) * Mat4::euler(Vec3{180.0f, 0.0f, 0.0f}) * Mat4::scale(Vec3{rad});
Mat4 T = opt.modelview;
Mat4 cyl = mdl * Mat4::scale(Vec3{rad, height, rad});
Mat4 bot = mdl * Mat4::scale(Vec3{rad});
Mat4 top = mdl * Mat4::translate(Vec3{0.0f, height, 0.0f}) *
Mat4::euler(Vec3{180.0f, 0.0f, 0.0f}) * Mat4::scale(Vec3{rad});
opt.modelview = T * cyl;
mesh(_cyl, opt);
opt.modelview = T * bot;
mesh(_hemi, opt);
opt.modelview = T * top;
mesh(_hemi, opt);
BBox b = _cyl.bbox();
b.transform(cyl);
box.enclose(b);
b = _hemi.bbox();
b.transform(bot);
box.enclose(b);
b = _hemi.bbox();
b.transform(top);
box.enclose(b);
mesh(_cyl, opt);
opt.modelview = T * bot;
mesh(_hemi, opt);
opt.modelview = T * top;
mesh(_hemi, opt);
BBox b = _cyl.bbox();
b.transform(cyl);
box.enclose(b);
b = _hemi.bbox();
b.transform(bot);
box.enclose(b);
b = _hemi.bbox();
b.transform(top);
box.enclose(b);
}
void Renderer::capsule(MeshOpt opt, float height, float rad) {
BBox box;
capsule(opt, Mat4::I, height, rad, box);
BBox box;
capsule(opt, Mat4::I, height, rad, box);
}
void Renderer::mesh(GL::Mesh& mesh, Renderer::MeshOpt opt) {
void Renderer::mesh(GL::Mesh &mesh, Renderer::MeshOpt opt) {
mesh_shader.bind();
mesh_shader.uniform("use_v_id", opt.per_vert_id);
mesh_shader.uniform("id", opt.id);
mesh_shader.uniform("alpha", opt.alpha);
mesh_shader.uniform("mvp", _proj * opt.modelview);
mesh_shader.uniform("normal", Mat4::transpose(Mat4::inverse(opt.modelview)));
mesh_shader.uniform("solid", opt.solid_color);
mesh_shader.uniform("sel_color", opt.sel_color);
mesh_shader.uniform("sel_id", opt.sel_id);
mesh_shader.uniform("hov_color", opt.hov_color);
mesh_shader.uniform("hov_id", opt.hov_id);
if(opt.depth_only) GL::color_mask(false);
if(opt.wireframe) {
mesh_shader.uniform("color", Vec3());
GL::enable(GL::Opt::wireframe);
mesh.render();
GL::disable(GL::Opt::wireframe);
}
mesh_shader.uniform("color", opt.color);
mesh.render();
if(opt.depth_only) GL::color_mask(true);
mesh_shader.uniform("use_v_id", opt.per_vert_id);
mesh_shader.uniform("id", opt.id);
mesh_shader.uniform("alpha", opt.alpha);
mesh_shader.uniform("mvp", _proj * opt.modelview);
mesh_shader.uniform("normal", Mat4::transpose(Mat4::inverse(opt.modelview)));
mesh_shader.uniform("solid", opt.solid_color);
mesh_shader.uniform("sel_color", opt.sel_color);
mesh_shader.uniform("sel_id", opt.sel_id);
mesh_shader.uniform("hov_color", opt.hov_color);
mesh_shader.uniform("hov_id", opt.hov_id);
if (opt.depth_only)
GL::color_mask(false);
if (opt.wireframe) {
mesh_shader.uniform("color", Vec3());
GL::enable(GL::Opt::wireframe);
mesh.render();
GL::disable(GL::Opt::wireframe);
}
mesh_shader.uniform("color", opt.color);
mesh.render();
if (opt.depth_only)
GL::color_mask(true);
}
void Renderer::set_samples(int s) {
samples = s;
framebuffer.resize(window_dim, samples);
samples = s;
framebuffer.resize(window_dim, samples);
}
Scene_ID Renderer::read_id(Vec2 pos) {
int x = (int)pos.x;
int y = (int)(window_dim.y - pos.y - 1);
int x = (int)pos.x;
int y = (int)(window_dim.y - pos.y - 1);
if(id_resolve.can_read_at()) {
if (id_resolve.can_read_at()) {
GLubyte read[4] = {};
id_resolve.read_at(0, x, y, read);
return (int)read[0] | (int)read[1] << 8 | (int)read[2] << 16;
GLubyte read[4] = {};
id_resolve.read_at(0, x, y, read);
return (int)read[0] | (int)read[1] << 8 | (int)read[2] << 16;
} else {
int idx = y * (int)window_dim.x * 4 + x * 4;
assert(id_buffer && idx > 0 && idx <= window_dim.x * window_dim.y * 4);
int a = id_buffer[idx];
int b = id_buffer[idx + 1];
int c = id_buffer[idx + 2];
} else {
return a | b << 8 | c << 16;
}
}
int idx = y * (int)window_dim.x * 4 + x * 4;
assert(id_buffer && idx > 0 && idx <= window_dim.x * window_dim.y * 4);
void Renderer::reset_depth() {
framebuffer.clear_d();
}
int a = id_buffer[idx];
int b = id_buffer[idx + 1];
int c = id_buffer[idx + 2];
void Renderer::begin_outline() {
framebuffer.clear_d();
return a | b << 8 | c << 16;
}
}
void Renderer::end_outline(const Mat4& view, BBox box) {
void Renderer::reset_depth() { framebuffer.clear_d(); }
void Renderer::begin_outline() { framebuffer.clear_d(); }
void Renderer::end_outline(const Mat4 &view, BBox box) {
Mat4 viewproj = _proj * view;
Vec2 min, max;
box.screen_rect(viewproj, min, max);
Mat4 viewproj = _proj * view;
Vec2 min, max;
box.screen_rect(viewproj, min, max);
Vec2 thickness = Vec2(3.0f / window_dim.x, 3.0f / window_dim.y);
GL::Effects::outline(framebuffer, framebuffer, Gui::Color::outline,
min - thickness, max + thickness);
Vec2 thickness = Vec2(3.0f / window_dim.x, 3.0f / window_dim.y);
GL::Effects::outline(framebuffer, framebuffer, Gui::Color::outline, min - thickness,
max + thickness);
}
void Renderer::outline(const Mat4& view, Scene_Item& obj) {
void Renderer::outline(const Mat4 &view, Scene_Item &obj) {
Mat4 viewproj = _proj * view;
Mat4 viewproj = _proj * view;
framebuffer.clear_d();
obj.render(view, false, true);
framebuffer.clear_d();
obj.render(view, false, true);
Vec2 min, max;
obj.bbox().screen_rect(viewproj, min, max);
Vec2 min, max;
obj.bbox().screen_rect(viewproj, min, max);
Vec2 thickness = Vec2(3.0f / window_dim.x, 3.0f / window_dim.y);
GL::Effects::outline(framebuffer, framebuffer, Gui::Color::outline,
min - thickness, max + thickness);
Vec2 thickness = Vec2(3.0f / window_dim.x, 3.0f / window_dim.y);
GL::Effects::outline(framebuffer, framebuffer, Gui::Color::outline, min - thickness,
max + thickness);
}
void Renderer::halfedge_editor(Renderer::HalfedgeOpt opt) {
auto [faces, spheres, cylinders, arrows] = opt.editor.shapes();
MeshOpt fopt = MeshOpt();
fopt.modelview = opt.modelview;
fopt.color = opt.color;
fopt.per_vert_id = true;
fopt.sel_color = Gui::Color::outline;
fopt.sel_id = opt.editor.select_id();
fopt.hov_color = Gui::Color::hover;
fopt.hov_id = opt.editor.hover_id();
Renderer::mesh(faces, fopt);
inst_shader.bind();
inst_shader.uniform("use_v_id", true);
inst_shader.uniform("use_i_id", true);
inst_shader.uniform("solid", false);
inst_shader.uniform("proj", _proj);
inst_shader.uniform("modelview", opt.modelview);
inst_shader.uniform("color", opt.color);
inst_shader.uniform("alpha", fopt.alpha);
inst_shader.uniform("sel_color", Gui::Color::outline);
inst_shader.uniform("hov_color", Gui::Color::hover);
inst_shader.uniform("sel_id", fopt.sel_id);
inst_shader.uniform("hov_id", fopt.hov_id);
spheres.render();
cylinders.render();
arrows.render();
auto [faces, spheres, cylinders, arrows] = opt.editor.shapes();
MeshOpt fopt = MeshOpt();
fopt.modelview = opt.modelview;
fopt.color = opt.color;
fopt.per_vert_id = true;
fopt.sel_color = Gui::Color::outline;
fopt.sel_id = opt.editor.select_id();
fopt.hov_color = Gui::Color::hover;
fopt.hov_id = opt.editor.hover_id();
Renderer::mesh(faces, fopt);
inst_shader.bind();
inst_shader.uniform("use_v_id", true);
inst_shader.uniform("use_i_id", true);
inst_shader.uniform("solid", false);
inst_shader.uniform("proj", _proj);
inst_shader.uniform("modelview", opt.modelview);
inst_shader.uniform("color", opt.color);
inst_shader.uniform("alpha", fopt.alpha);
inst_shader.uniform("sel_color", Gui::Color::outline);
inst_shader.uniform("hov_color", Gui::Color::hover);
inst_shader.uniform("sel_id", fopt.sel_id);
inst_shader.uniform("hov_id", fopt.hov_id);
spheres.render();
cylinders.render();
arrows.render();
}
......@@ -3,26 +3,28 @@
#include <variant>
#include "scene.h"
#include "../platform/gl.h"
#include "../lib/bbox.h"
#include "../platform/gl.h"
#include "scene.h"
namespace Gui { class Model; }
namespace Gui {
class Model;
}
// Singleton
class Renderer {
public:
static void setup(Vec2 dim);
static void shutdown();
static Renderer& get();
static Renderer &get();
void begin();
void complete();
void reset_depth();
void proj(const Mat4& proj);
void proj(const Mat4 &proj);
void update_dim(Vec2 dim);
void settings_gui(bool* open);
void settings_gui(bool *open);
void set_samples(int samples);
unsigned int read_id(Vec2 pos);
......@@ -36,47 +38,48 @@ public:
bool solid_color = false;
bool depth_only = false;
bool per_vert_id = false;
};
};
struct HalfedgeOpt {
HalfedgeOpt(Gui::Model& e) : editor(e) {}
Gui::Model& editor;
HalfedgeOpt(Gui::Model &e) : editor(e) {}
Gui::Model &editor;
Mat4 modelview;
Vec3 color;
};
// NOTE(max): updates & uses the indices in mesh for selection/traversal
void halfedge_editor(HalfedgeOpt opt);
void mesh(GL::Mesh& mesh, MeshOpt opt);
void lines(const GL::Lines& lines, const Mat4& view, const Mat4& model = Mat4::I, float alpha = 1.0f);
void outline(const Mat4& view, Scene_Item& obj);
void mesh(GL::Mesh &mesh, MeshOpt opt);
void lines(const GL::Lines &lines, const Mat4 &view, const Mat4 &model = Mat4::I,
float alpha = 1.0f);
void outline(const Mat4 &view, Scene_Item &obj);
void begin_outline();
void end_outline(const Mat4& view, BBox box);
void skydome(const Mat4& rotation, Vec3 color, float cosine);
void skydome(const Mat4& rotation, Vec3 color, float cosine, const GL::Tex2D& tex);
void end_outline(const Mat4 &view, BBox box);
void skydome(const Mat4 &rotation, Vec3 color, float cosine);
void skydome(const Mat4 &rotation, Vec3 color, float cosine, const GL::Tex2D &tex);
void sphere(MeshOpt opt);
void capsule(MeshOpt opt, float height, float rad);
void capsule(MeshOpt opt, const Mat4& mdl, float height, float rad, BBox& box);
void capsule(MeshOpt opt, const Mat4 &mdl, float height, float rad, BBox &box);
GLuint saved() const;
void saved(std::vector<unsigned char>& data) const;
void save(Scene& scene, const Camera& cam, int w, int h, int samples);
void saved(std::vector<unsigned char> &data) const;
void save(Scene &scene, const Camera &cam, int w, int h, int samples);
private:
Renderer(Vec2 dim);
~Renderer();
static inline Renderer* data = nullptr;
static inline Renderer *data = nullptr;
GL::Framebuffer framebuffer, id_resolve, save_buffer, save_output;
GL::Shader mesh_shader, line_shader, inst_shader, dome_shader;
GL::Framebuffer framebuffer, id_resolve, save_buffer, save_output;
GL::Shader mesh_shader, line_shader, inst_shader, dome_shader;
GL::Mesh _sphere, _cyl, _hemi;
int samples;
Vec2 window_dim;
GLubyte* id_buffer;
GLubyte *id_buffer;
Mat4 _proj;
};
#include <sstream>
#include <assimp/Importer.hpp>
#include <assimp/Exporter.hpp>
#include <assimp/Importer.hpp>
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#include <sstream>
#include "../lib/log.h"
#include "../gui/manager.h"
#include "../gui/render.h"
#include "../lib/log.h"
#include "scene.h"
#include "renderer.h"
#include "scene.h"
#include "undo.h"
namespace std {
template<typename T1, typename T2>
struct hash<pair<T1, T2>> {
uint64_t operator()(const pair<T1,T2>& p) const {
static const hash<T1> h1;
static const hash<T2> h2;
return h1(p.first) ^ h2(p.second);
}
};
template <typename T1, typename T2> struct hash<pair<T1, T2>> {
uint64_t operator()(const pair<T1, T2> &p) const {
static const hash<T1> h1;
static const hash<T2> h2;
return h1(p.first) ^ h2(p.second);
}
};
}; // namespace std
Scene_Item::Scene_Item(Scene_Object&& obj) :
data(std::move(obj))
{}
Scene_Item::Scene_Item(Scene_Object &&obj) : data(std::move(obj)) {}
Scene_Item::Scene_Item(Scene_Light&& light) :
data(std::move(light))
{}
Scene_Item::Scene_Item(Scene_Light &&light) : data(std::move(light)) {}
Scene_Item::Scene_Item(Scene_Item&& src) : data(std::move(src.data))
{}
Scene_Item::Scene_Item(Scene_Item &&src) : data(std::move(src.data)) {}
Scene_Item& Scene_Item::operator=(Scene_Item&& src) {
data = std::move(src.data);
return *this;
Scene_Item &Scene_Item::operator=(Scene_Item &&src) {
data = std::move(src.data);
return *this;
}
void Scene_Item::set_time(float time) {
return std::visit(overloaded {
[time](Scene_Object& obj) {
obj.set_time(time);
},
[time](Scene_Light& light) {
light.set_time(time);
}
}, data);
return std::visit(overloaded{[time](Scene_Object &obj) { obj.set_time(time); },
[time](Scene_Light &light) { light.set_time(time); }},
data);
}
BBox Scene_Item::bbox() {
return std::visit(overloaded {
[](Scene_Object& obj) {
return obj.bbox();
},
[](Scene_Light& light) {
return light.bbox();
}
}, data);
return std::visit(overloaded{[](Scene_Object &obj) { return obj.bbox(); },
[](Scene_Light &light) { return light.bbox(); }},
data);
}
void Scene_Item::render(const Mat4& view, bool solid, bool depth_only, bool posed) {
std::visit(overloaded {
[&](Scene_Object& obj) {
obj.render(view, solid, depth_only, posed);
},
[&](Scene_Light& light) {
light.render(view, depth_only, posed);
}
}, data);
void Scene_Item::render(const Mat4 &view, bool solid, bool depth_only, bool posed) {
std::visit(overloaded{[&](Scene_Object &obj) { obj.render(view, solid, depth_only, posed); },
[&](Scene_Light &light) { light.render(view, depth_only, posed); }},
data);
}
Scene_ID Scene_Item::id() const {
return std::visit(overloaded {
[](const Scene_Object& obj) {
return obj.id();
},
[](const Scene_Light& light) {
return light.id();
}
}, data);
return std::visit(overloaded{[](const Scene_Object &obj) { return obj.id(); },
[](const Scene_Light &light) { return light.id(); }},
data);
}
Anim_Pose& Scene_Item::animation() {
Anim_Pose &Scene_Item::animation() {
Scene_Object* o = std::get_if<Scene_Object>(&data);
if(o) return o->anim;
Scene_Light* l = std::get_if<Scene_Light>(&data);
if(l) return l->anim;
Scene_Object *o = std::get_if<Scene_Object>(&data);
if (o)
return o->anim;
Scene_Light *l = std::get_if<Scene_Light>(&data);
if (l)
return l->anim;
assert(false);
return l->anim;
assert(false);
return l->anim;
}
const Anim_Pose& Scene_Item::animation() const {
const Anim_Pose &Scene_Item::animation() const {
const Scene_Object* o = std::get_if<Scene_Object>(&data);
if(o) return o->anim;
const Scene_Light* l = std::get_if<Scene_Light>(&data);
if(l) return l->anim;
const Scene_Object *o = std::get_if<Scene_Object>(&data);
if (o)
return o->anim;
const Scene_Light *l = std::get_if<Scene_Light>(&data);
if (l)
return l->anim;
assert(false);
return l->anim;
assert(false);
return l->anim;
}
Pose& Scene_Item::pose() {
Pose &Scene_Item::pose() {
Scene_Object* o = std::get_if<Scene_Object>(&data);
if(o) return o->pose;
Scene_Light* l = std::get_if<Scene_Light>(&data);
if(l) return l->pose;
Scene_Object *o = std::get_if<Scene_Object>(&data);
if (o)
return o->pose;
Scene_Light *l = std::get_if<Scene_Light>(&data);
if (l)
return l->pose;
assert(false);
return l->pose;
assert(false);
return l->pose;
}
const Pose& Scene_Item::pose() const {
const Pose &Scene_Item::pose() const {
const Scene_Object* o = std::get_if<Scene_Object>(&data);
if(o) return o->pose;
const Scene_Light* l = std::get_if<Scene_Light>(&data);
if(l) return l->pose;
const Scene_Object *o = std::get_if<Scene_Object>(&data);
if (o)
return o->pose;
const Scene_Light *l = std::get_if<Scene_Light>(&data);
if (l)
return l->pose;
assert(false);
return l->pose;
assert(false);
return l->pose;
}
std::pair<char*,int> Scene_Item::name() {
Scene_Object* o = std::get_if<Scene_Object>(&data);
if(o) return {o->opt.name, Scene_Object::max_name_len};
Scene_Light* l = std::get_if<Scene_Light>(&data);
if(l) return {l->opt.name, Scene_Light::max_name_len};
std::pair<char *, int> Scene_Item::name() {
Scene_Object *o = std::get_if<Scene_Object>(&data);
if (o)
return {o->opt.name, Scene_Object::max_name_len};
Scene_Light *l = std::get_if<Scene_Light>(&data);
if (l)
return {l->opt.name, Scene_Light::max_name_len};
assert(false);
return {l->opt.name, Scene_Object::max_name_len};
assert(false);
return {l->opt.name, Scene_Object::max_name_len};
}
std::string Scene_Item::name() const {
const Scene_Object* o = std::get_if<Scene_Object>(&data);
if(o) return std::string(o->opt.name);
const Scene_Light* l = std::get_if<Scene_Light>(&data);
if(l) return std::string(l->opt.name);
const Scene_Object *o = std::get_if<Scene_Object>(&data);
if (o)
return std::string(o->opt.name);
const Scene_Light *l = std::get_if<Scene_Light>(&data);
if (l)
return std::string(l->opt.name);
assert(false);
return std::string(l->opt.name);
assert(false);
return std::string(l->opt.name);
}
Scene::Scene(Scene_ID start) :
next_id(start),
first_id(start) {
}
Scene::Scene(Scene_ID start) : next_id(start), first_id(start) {}
Scene_ID Scene::used_ids() {
return next_id;
}
Scene_ID Scene::used_ids() { return next_id; }
Scene_ID Scene::reserve_id() {
return next_id++;
}
Scene_ID Scene::reserve_id() { return next_id++; }
Scene_ID Scene::add(Pose pose, Halfedge_Mesh&& mesh, std::string n, Scene_ID id) {
if(!id) id = next_id++;
assert(objs.find(id) == objs.end());
objs.emplace(std::make_pair(id, Scene_Object(id, pose, std::move(mesh), n)));
return id;
Scene_ID Scene::add(Pose pose, Halfedge_Mesh &&mesh, std::string n, Scene_ID id) {
if (!id)
id = next_id++;
assert(objs.find(id) == objs.end());
objs.emplace(std::make_pair(id, Scene_Object(id, pose, std::move(mesh), n)));
return id;
}
Scene_ID Scene::add(Pose pose, GL::Mesh&& mesh, std::string n, Scene_ID id) {
if(!id) id = next_id++;
assert(objs.find(id) == objs.end());
objs.emplace(std::make_pair(id, Scene_Object(id, pose, std::move(mesh), n)));
return id;
Scene_ID Scene::add(Pose pose, GL::Mesh &&mesh, std::string n, Scene_ID id) {
if (!id)
id = next_id++;
assert(objs.find(id) == objs.end());
objs.emplace(std::make_pair(id, Scene_Object(id, pose, std::move(mesh), n)));
return id;
}
std::string Scene::set_env_map(std::string file) {
Scene_ID id = 0;
for_items([&id](const Scene_Item& item) {
if(item.is<Scene_Light>() && item.get<Scene_Light>().is_env())
id = item.id();
});
Scene_Light l(Light_Type::sphere, reserve_id(), {}, "env_map");
std::string err = l.emissive_load(file);
if(err.empty()) {
if(id) erase(id);
add(std::move(l));
}
return err;
Scene_ID id = 0;
for_items([&id](const Scene_Item &item) {
if (item.is<Scene_Light>() && item.get<Scene_Light>().is_env())
id = item.id();
});
Scene_Light l(Light_Type::sphere, reserve_id(), {}, "env_map");
std::string err = l.emissive_load(file);
if (err.empty()) {
if (id)
erase(id);
add(std::move(l));
}
return err;
}
bool Scene::has_env_light() const {
bool ret = false;
for_items([&ret](const Scene_Item& item) {
ret = ret || (item.is<Scene_Light>() && item.get<Scene_Light>().is_env());
});
return ret;
bool ret = false;
for_items([&ret](const Scene_Item &item) {
ret = ret || (item.is<Scene_Light>() && item.get<Scene_Light>().is_env());
});
return ret;
}
Scene_ID Scene::add(Scene_Light&& obj) {
assert(objs.find(obj.id()) == objs.end());
objs.emplace(std::make_pair(obj.id(), std::move(obj)));
return obj.id();
Scene_ID Scene::add(Scene_Light &&obj) {
assert(objs.find(obj.id()) == objs.end());
objs.emplace(std::make_pair(obj.id(), std::move(obj)));
return obj.id();
}
Scene_ID Scene::add(Scene_Object&& obj) {
assert(objs.find(obj.id()) == objs.end());
objs.emplace(std::make_pair(obj.id(), std::move(obj)));
return obj.id();
Scene_ID Scene::add(Scene_Object &&obj) {
assert(objs.find(obj.id()) == objs.end());
objs.emplace(std::make_pair(obj.id(), std::move(obj)));
return obj.id();
}
void Scene::restore(Scene_ID id) {
if(objs.find(id) != objs.end()) return;
assert(erased.find(id) != erased.end());
objs.insert({id, std::move(erased[id])});
erased.erase(id);
if (objs.find(id) != objs.end())
return;
assert(erased.find(id) != erased.end());
objs.insert({id, std::move(erased[id])});
erased.erase(id);
}
void Scene::erase(Scene_ID id) {
assert(erased.find(id) == erased.end());
assert(objs.find(id) != objs.end());
assert(erased.find(id) == erased.end());
assert(objs.find(id) != objs.end());
erased.insert({id, std::move(objs[id])});
objs.erase(id);
erased.insert({id, std::move(objs[id])});
objs.erase(id);
}
void Scene::for_items(std::function<void(const Scene_Item&)> func) const {
for(const auto& obj : objs) {
func(obj.second);
}
void Scene::for_items(std::function<void(const Scene_Item &)> func) const {
for (const auto &obj : objs) {
func(obj.second);
}
}
void Scene::for_items(std::function<void(Scene_Item&)> func) {
for(auto& obj : objs) {
func(obj.second);
}
void Scene::for_items(std::function<void(Scene_Item &)> func) {
for (auto &obj : objs) {
func(obj.second);
}
}
size_t Scene::size() {
return objs.size();
}
size_t Scene::size() { return objs.size(); }
bool Scene::empty() {
return objs.size() == 0;
}
bool Scene::empty() { return objs.size() == 0; }
Scene_Maybe Scene::get(Scene_ID id) {
auto entry = objs.find(id);
if(entry == objs.end()) return std::nullopt;
return entry->second;
auto entry = objs.find(id);
if (entry == objs.end())
return std::nullopt;
return entry->second;
}
Scene_Light& Scene::get_light(Scene_ID id) {
auto entry = objs.find(id);
assert(entry != objs.end());
assert(entry->second.is<Scene_Light>());
return entry->second.get<Scene_Light>();
Scene_Light &Scene::get_light(Scene_ID id) {
auto entry = objs.find(id);
assert(entry != objs.end());
assert(entry->second.is<Scene_Light>());
return entry->second.get<Scene_Light>();
}
Scene_Object& Scene::get_obj(Scene_ID id) {
auto entry = objs.find(id);
assert(entry != objs.end());
assert(entry->second.is<Scene_Object>());
return entry->second.get<Scene_Object>();
Scene_Object &Scene::get_obj(Scene_ID id) {
auto entry = objs.find(id);
assert(entry != objs.end());
assert(entry->second.is<Scene_Object>());
return entry->second.get<Scene_Object>();
}
void Scene::clear(Undo& undo) {
next_id = first_id;
objs.clear();
erased.clear();
undo.reset();
void Scene::clear(Undo &undo) {
next_id = first_id;
objs.clear();
erased.clear();
undo.reset();
}
static const std::string FAKE_NAME = "FAKE-S3D-FAKE-MESH";
static aiVector3D vecVec(Vec3 v) {
return aiVector3D(v.x, v.y, v.z);
}
static aiVector3D vecVec(Vec3 v) { return aiVector3D(v.x, v.y, v.z); }
static Quat aiQuat(aiQuaternion aiv) {
return Quat(aiv.x, aiv.y, aiv.z, aiv.w);
}
static Quat aiQuat(aiQuaternion aiv) { return Quat(aiv.x, aiv.y, aiv.z, aiv.w); }
static Vec3 aiVec(aiVector3D aiv) {
return Vec3(aiv.x, aiv.y, aiv.z);
}
static Vec3 aiVec(aiVector3D aiv) { return Vec3(aiv.x, aiv.y, aiv.z); }
static Spectrum aiSpec(aiColor3D aiv) {
return Spectrum(aiv.r, aiv.g, aiv.b);
}
static Spectrum aiSpec(aiColor3D aiv) { return Spectrum(aiv.r, aiv.g, aiv.b); }
static aiMatrix4x4 matMat(const Mat4& T) {
return {T[0][0], T[1][0], T[2][0], T[3][0],
T[0][1], T[1][1], T[2][1], T[3][1],
T[0][2], T[1][2], T[2][2], T[3][2],
T[0][3], T[1][3], T[2][3], T[3][3]};
static aiMatrix4x4 matMat(const Mat4 &T) {
return {T[0][0], T[1][0], T[2][0], T[3][0], T[0][1], T[1][1], T[2][1], T[3][1],
T[0][2], T[1][2], T[2][2], T[3][2], T[0][3], T[1][3], T[2][3], T[3][3]};
}
static Mat4 aiMat(aiMatrix4x4 T) {
return Mat4{Vec4{T[0][0], T[1][0], T[2][0], T[3][0]},
Vec4{T[0][1], T[1][1], T[2][1], T[3][1]},
Vec4{T[0][2], T[1][2], T[2][2], T[3][2]},
Vec4{T[0][3], T[1][3], T[2][3], T[3][3]}};
return Mat4{Vec4{T[0][0], T[1][0], T[2][0], T[3][0]}, Vec4{T[0][1], T[1][1], T[2][1], T[3][1]},
Vec4{T[0][2], T[1][2], T[2][2], T[3][2]}, Vec4{T[0][3], T[1][3], T[2][3], T[3][3]}};
}
static aiMatrix4x4 node_transform(const aiNode* node) {
aiMatrix4x4 T;
while(node) {
T = T * node->mTransformation;
node = node->mParent;
}
return T;
static aiMatrix4x4 node_transform(const aiNode *node) {
aiMatrix4x4 T;
while (node) {
T = T * node->mTransformation;
node = node->mParent;
}
return T;
}
static void load_node(Scene& scobj, std::vector<std::string>& errors,
std::unordered_map<aiNode*, Scene_ID>& node_to_obj,
std::unordered_map<aiNode*, Joint*>& node_to_bone,
const aiScene* scene, aiNode* node, aiMatrix4x4 transform) {
transform = transform * node->mTransformation;
for (unsigned int i = 0; i < node->mNumMeshes; i++) {
const aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
std::string name;
bool do_flip = false, do_smooth = false;
if(mesh->mName.length) {
name = std::string(mesh->mName.C_Str());
if(name.find(FAKE_NAME) != std::string::npos) continue;
size_t special = name.find("-S3D-");
if(special != std::string::npos) {
if(name.find("FLIPPED") != std::string::npos) do_flip = true;
if(name.find("SMOOTHED") != std::string::npos) do_smooth = true;
name = name.substr(0, special);
std::replace(name.begin(), name.end(), '_', ' ');
}
}
if(!mesh->HasNormals()) {
errors.push_back("Mesh has no normals.");
continue;
}
std::vector<Vec3> verts;
for (unsigned int j = 0; j < mesh->mNumVertices; j++) {
const aiVector3D& pos = mesh->mVertices[j];
verts.push_back(Vec3(pos.x, pos.y, pos.z));
}
std::vector<std::vector<Halfedge_Mesh::Index>> polys;
for (unsigned int j = 0; j < mesh->mNumFaces; j++) {
const aiFace& face = mesh->mFaces[j];
std::vector<Halfedge_Mesh::Index> poly;
for(unsigned int k = 0; k < face.mNumIndices; k++) {
poly.push_back(face.mIndices[k]);
}
polys.push_back(poly);
}
aiVector3D ascale, arot, apos;
transform.Decompose(ascale, arot, apos);
Vec3 pos = aiVec(apos);
Vec3 rot = aiVec(arot);
Vec3 scale = aiVec(ascale);
Pose p = {pos, Degrees(rot).range(0.0f, 360.0f), scale};
Material::Options material;
const aiMaterial& ai_mat = *scene->mMaterials[mesh->mMaterialIndex];
aiColor3D albedo;
ai_mat.Get(AI_MATKEY_COLOR_DIFFUSE, albedo);
material.albedo = aiSpec(albedo);
aiColor3D emissive;
ai_mat.Get(AI_MATKEY_COLOR_EMISSIVE, emissive);
material.emissive = aiSpec(emissive);
aiColor3D reflectance;
ai_mat.Get(AI_MATKEY_COLOR_REFLECTIVE, reflectance);
material.reflectance = aiSpec(reflectance);
aiColor3D transmittance;
ai_mat.Get(AI_MATKEY_COLOR_TRANSPARENT, transmittance);
material.transmittance = aiSpec(transmittance);
ai_mat.Get(AI_MATKEY_REFRACTI, material.ior);
ai_mat.Get(AI_MATKEY_SHININESS, material.intensity);
aiString ai_type;
ai_mat.Get(AI_MATKEY_NAME, ai_type);
std::string type(ai_type.C_Str());
if(type.find("lambertian") != std::string::npos) {
material.type = Material_Type::lambertian;
} else if(type.find("mirror") != std::string::npos) {
material.type = Material_Type::mirror;
} else if(type.find("refract") != std::string::npos) {
material.type = Material_Type::refract;
} else if(type.find("glass") != std::string::npos) {
material.type = Material_Type::glass;
} else if(type.find("diffuse_light") != std::string::npos) {
material.type = Material_Type::diffuse_light;
} else {
material = Material::Options();
}
material.emissive *= 1.0f / material.intensity;
Scene_Object new_obj;
// Horrible hack
if(type.find("SPHERESHAPE") != std::string::npos) {
aiColor3D specular;
ai_mat.Get(AI_MATKEY_COLOR_SPECULAR, specular);
Scene_Object obj(scobj.reserve_id(), p, GL::Mesh());
obj.material.opt = material;
obj.opt.shape_type = PT::Shape_Type::sphere;
obj.opt.shape = PT::Shape(PT::Sphere(specular.r));
new_obj = std::move(obj);
} else {
Halfedge_Mesh hemesh;
std::string err = hemesh.from_poly(polys, verts);
if(!err.empty()) {
std::vector<GL::Mesh::Vert> mesh_verts;
std::vector<GL::Mesh::Index> mesh_inds;
for (unsigned int j = 0; j < mesh->mNumVertices; j++) {
const aiVector3D& vpos = mesh->mVertices[j];
const aiVector3D& vnorm = mesh->mNormals[j];
mesh_verts.push_back({aiVec(vpos), aiVec(vnorm), 0});
}
for (unsigned int j = 0; j < mesh->mNumFaces; j++) {
const aiFace& face = mesh->mFaces[j];
unsigned int start = face.mIndices[0];
for(size_t k = 1; k <= face.mNumIndices - 2; k++) {
mesh_inds.push_back(start);
mesh_inds.push_back(face.mIndices[k]);
mesh_inds.push_back(face.mIndices[k+1]);
}
}
errors.push_back(err);
Scene_Object obj(scobj.reserve_id(), p, GL::Mesh(std::move(mesh_verts), std::move(mesh_inds)), name);
obj.material.opt = material;
new_obj = std::move(obj);
} else {
if(do_flip) hemesh.flip();
Scene_Object obj(scobj.reserve_id(), p, std::move(hemesh), name);
obj.material.opt = material;
obj.opt.smooth_normals = do_smooth;
new_obj = std::move(obj);
}
}
if(mesh->mNumBones) {
Skeleton& skeleton = new_obj.armature;
aiNode* arm_node = mesh->mBones[0]->mArmature;
{
aiVector3D t, r, s;
arm_node->mTransformation.Decompose(s, r, t);
skeleton.base() = aiVec(t);
}
std::unordered_map<aiNode*, aiBone*> node_to_aibone;
for(unsigned int j = 0; j < mesh->mNumBones; j++) {
node_to_aibone[mesh->mBones[j]->mNode] = mesh->mBones[j];
}
std::function<void(Joint*, aiNode*)> build_tree;
build_tree = [&](Joint* p, aiNode* node) {
aiBone* bone = node_to_aibone[node];
aiVector3D t, r, s;
bone->mOffsetMatrix.Decompose(s, r, t);
Joint* c = skeleton.add_child(p, aiVec(t));
node_to_bone[node] = c;
c->pose = aiVec(r);
c->radius = bone->mWeights[0].mWeight;
for(unsigned int j = 0; j < node->mNumChildren; j++)
build_tree(c, node->mChildren[j]);
};
for(unsigned int j = 0; j < arm_node->mNumChildren; j++) {
aiNode* root_node = arm_node->mChildren[j];
aiBone* root_bone = node_to_aibone[root_node];
aiVector3D t, r, s;
root_bone->mOffsetMatrix.Decompose(s, r, t);
Joint* root = skeleton.add_root(aiVec(t));
node_to_bone[root_node] = root;
root->pose = aiVec(r);
root->radius = root_bone->mWeights[0].mWeight;
for(unsigned int k = 0; k < root_node->mNumChildren; k++)
build_tree(root, root_node->mChildren[k]);
}
}
node_to_obj[node] = new_obj.id();
scobj.add(std::move(new_obj));
}
for(unsigned int i = 0; i < node->mNumChildren; i++) {
load_node(scobj, errors, node_to_obj, node_to_bone, scene, node->mChildren[i], transform);
}
static void load_node(Scene &scobj, std::vector<std::string> &errors,
std::unordered_map<aiNode *, Scene_ID> &node_to_obj,
std::unordered_map<aiNode *, Joint *> &node_to_bone, const aiScene *scene,
aiNode *node, aiMatrix4x4 transform) {
transform = transform * node->mTransformation;
for (unsigned int i = 0; i < node->mNumMeshes; i++) {
const aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
std::string name;
bool do_flip = false, do_smooth = false;
if (mesh->mName.length) {
name = std::string(mesh->mName.C_Str());
if (name.find(FAKE_NAME) != std::string::npos)
continue;
size_t special = name.find("-S3D-");
if (special != std::string::npos) {
if (name.find("FLIPPED") != std::string::npos)
do_flip = true;
if (name.find("SMOOTHED") != std::string::npos)
do_smooth = true;
name = name.substr(0, special);
std::replace(name.begin(), name.end(), '_', ' ');
}
}
if (!mesh->HasNormals()) {
errors.push_back("Mesh has no normals.");
continue;
}
std::vector<Vec3> verts;
for (unsigned int j = 0; j < mesh->mNumVertices; j++) {
const aiVector3D &pos = mesh->mVertices[j];
verts.push_back(Vec3(pos.x, pos.y, pos.z));
}
std::vector<std::vector<Halfedge_Mesh::Index>> polys;
for (unsigned int j = 0; j < mesh->mNumFaces; j++) {
const aiFace &face = mesh->mFaces[j];
std::vector<Halfedge_Mesh::Index> poly;
for (unsigned int k = 0; k < face.mNumIndices; k++) {
poly.push_back(face.mIndices[k]);
}
polys.push_back(poly);
}
aiVector3D ascale, arot, apos;
transform.Decompose(ascale, arot, apos);
Vec3 pos = aiVec(apos);
Vec3 rot = aiVec(arot);
Vec3 scale = aiVec(ascale);
Pose p = {pos, Degrees(rot).range(0.0f, 360.0f), scale};
Material::Options material;
const aiMaterial &ai_mat = *scene->mMaterials[mesh->mMaterialIndex];
aiColor3D albedo;
ai_mat.Get(AI_MATKEY_COLOR_DIFFUSE, albedo);
material.albedo = aiSpec(albedo);
aiColor3D emissive;
ai_mat.Get(AI_MATKEY_COLOR_EMISSIVE, emissive);
material.emissive = aiSpec(emissive);
aiColor3D reflectance;
ai_mat.Get(AI_MATKEY_COLOR_REFLECTIVE, reflectance);
material.reflectance = aiSpec(reflectance);
aiColor3D transmittance;
ai_mat.Get(AI_MATKEY_COLOR_TRANSPARENT, transmittance);
material.transmittance = aiSpec(transmittance);
ai_mat.Get(AI_MATKEY_REFRACTI, material.ior);
ai_mat.Get(AI_MATKEY_SHININESS, material.intensity);
aiString ai_type;
ai_mat.Get(AI_MATKEY_NAME, ai_type);
std::string type(ai_type.C_Str());
if (type.find("lambertian") != std::string::npos) {
material.type = Material_Type::lambertian;
} else if (type.find("mirror") != std::string::npos) {
material.type = Material_Type::mirror;
} else if (type.find("refract") != std::string::npos) {
material.type = Material_Type::refract;
} else if (type.find("glass") != std::string::npos) {
material.type = Material_Type::glass;
} else if (type.find("diffuse_light") != std::string::npos) {
material.type = Material_Type::diffuse_light;
} else {
material = Material::Options();
}
material.emissive *= 1.0f / material.intensity;
Scene_Object new_obj;
// Horrible hack
if (type.find("SPHERESHAPE") != std::string::npos) {
aiColor3D specular;
ai_mat.Get(AI_MATKEY_COLOR_SPECULAR, specular);
Scene_Object obj(scobj.reserve_id(), p, GL::Mesh());
obj.material.opt = material;
obj.opt.shape_type = PT::Shape_Type::sphere;
obj.opt.shape = PT::Shape(PT::Sphere(specular.r));
new_obj = std::move(obj);
} else {
Halfedge_Mesh hemesh;
std::string err = hemesh.from_poly(polys, verts);
if (!err.empty()) {
std::vector<GL::Mesh::Vert> mesh_verts;
std::vector<GL::Mesh::Index> mesh_inds;
for (unsigned int j = 0; j < mesh->mNumVertices; j++) {
const aiVector3D &vpos = mesh->mVertices[j];
const aiVector3D &vnorm = mesh->mNormals[j];
mesh_verts.push_back({aiVec(vpos), aiVec(vnorm), 0});
}
for (unsigned int j = 0; j < mesh->mNumFaces; j++) {
const aiFace &face = mesh->mFaces[j];
unsigned int start = face.mIndices[0];
for (size_t k = 1; k <= face.mNumIndices - 2; k++) {
mesh_inds.push_back(start);
mesh_inds.push_back(face.mIndices[k]);
mesh_inds.push_back(face.mIndices[k + 1]);
}
}
errors.push_back(err);
Scene_Object obj(scobj.reserve_id(), p,
GL::Mesh(std::move(mesh_verts), std::move(mesh_inds)), name);
obj.material.opt = material;
new_obj = std::move(obj);
} else {
if (do_flip)
hemesh.flip();
Scene_Object obj(scobj.reserve_id(), p, std::move(hemesh), name);
obj.material.opt = material;
obj.opt.smooth_normals = do_smooth;
new_obj = std::move(obj);
}
}
if (mesh->mNumBones) {
Skeleton &skeleton = new_obj.armature;
aiNode *arm_node = mesh->mBones[0]->mArmature;
{
aiVector3D t, r, s;
arm_node->mTransformation.Decompose(s, r, t);
skeleton.base() = aiVec(t);
}
std::unordered_map<aiNode *, aiBone *> node_to_aibone;
for (unsigned int j = 0; j < mesh->mNumBones; j++) {
node_to_aibone[mesh->mBones[j]->mNode] = mesh->mBones[j];
}
std::function<void(Joint *, aiNode *)> build_tree;
build_tree = [&](Joint *p, aiNode *node) {
aiBone *bone = node_to_aibone[node];
aiVector3D t, r, s;
bone->mOffsetMatrix.Decompose(s, r, t);
Joint *c = skeleton.add_child(p, aiVec(t));
node_to_bone[node] = c;
c->pose = aiVec(r);
c->radius = bone->mWeights[0].mWeight;
for (unsigned int j = 0; j < node->mNumChildren; j++)
build_tree(c, node->mChildren[j]);
};
for (unsigned int j = 0; j < arm_node->mNumChildren; j++) {
aiNode *root_node = arm_node->mChildren[j];
aiBone *root_bone = node_to_aibone[root_node];
aiVector3D t, r, s;
root_bone->mOffsetMatrix.Decompose(s, r, t);
Joint *root = skeleton.add_root(aiVec(t));
node_to_bone[root_node] = root;
root->pose = aiVec(r);
root->radius = root_bone->mWeights[0].mWeight;
for (unsigned int k = 0; k < root_node->mNumChildren; k++)
build_tree(root, root_node->mChildren[k]);
}
}
node_to_obj[node] = new_obj.id();
scobj.add(std::move(new_obj));
}
for (unsigned int i = 0; i < node->mNumChildren; i++) {
load_node(scobj, errors, node_to_obj, node_to_bone, scene, node->mChildren[i], transform);
}
}
std::string Scene::load(bool new_scene, Undo& undo, Gui::Manager& gui, std::string file) {
if(new_scene) {
clear(undo);
gui.get_animate().clear();
gui.get_rig().clear();
}
Assimp::Importer importer;
const aiScene* scene = importer.ReadFile(file.c_str(),
aiProcess_GenSmoothNormals |
aiProcess_PopulateArmatureData |
aiProcess_ValidateDataStructure |
aiProcess_OptimizeMeshes |
aiProcess_FindInstances |
aiProcess_FindDegenerates |
aiProcess_JoinIdenticalVertices |
aiProcess_FindInvalidData);
if (!scene) {
return "Parsing scene " + file + ": " + std::string(importer.GetErrorString());
}
std::vector<std::string> errors;
std::unordered_map<aiNode*, Scene_ID> node_to_obj;
std::unordered_map<aiNode*, Joint*> node_to_bone;
scene->mRootNode->mTransformation = aiMatrix4x4();
// Load objects
load_node(*this, errors, node_to_obj, node_to_bone, scene, scene->mRootNode, aiMatrix4x4());
// Load camera
if(new_scene && scene->mNumCameras) {
const aiCamera& aiCam = *scene->mCameras[0];
Mat4 cam_transform = aiMat(node_transform(scene->mRootNode->FindNode(aiCam.mName)));
Vec3 pos = cam_transform * aiVec(aiCam.mPosition);
Vec3 center = cam_transform * aiVec(aiCam.mLookAt);
gui.get_render().load_cam(pos, center, aiCam.mAspect, aiCam.mHorizontalFOV);
}
// Load lights
for(unsigned int i = 0; i < scene->mNumLights; i++) {
const aiLight& ailight = *scene->mLights[i];
const aiNode* node = scene->mRootNode->FindNode(ailight.mName);
Mat4 trans = aiMat(node_transform(node));
Vec3 pos = trans * aiVec(ailight.mPosition);
Vec3 dir = trans.rotate(Vec3{0.0f, 1.0f, 0.0f});
Pose p;
p.pos = pos;
p.euler = Mat4::rotate_to(dir).to_euler();
bool was_exported_from_s3d = false;
float export_power = 1.0f;
bool is_sphere = false, is_hemisphere = false, is_area = false;
std::string name = std::string(node->mName.C_Str());
size_t special = name.find("-S3D-");
if(special != std::string::npos) {
was_exported_from_s3d = true;
export_power = ailight.mAttenuationQuadratic;
std::string left = name.substr(special + 4);
name = name.substr(0, special);
std::replace(name.begin(), name.end(), '_', ' ');
if(left.find("HEMISPHERE") != std::string::npos) is_hemisphere = true;
else if(left.find("SPHERE") != std::string::npos) is_sphere = true;
else if(left.find("AREA") != std::string::npos) is_area = true;
}
aiColor3D color;
Scene_Light light(Light_Type::point, reserve_id(), p, name);
switch(ailight.mType) {
case aiLightSource_DIRECTIONAL: {
light.opt.type = Light_Type::directional;
color = ailight.mColorDiffuse;
} break;
case aiLightSource_POINT: {
light.opt.type = Light_Type::point;
color = ailight.mColorDiffuse;
} break;
case aiLightSource_SPOT: {
light.opt.type = Light_Type::spot;
light.opt.angle_bounds.x = ailight.mAngleInnerCone;
light.opt.angle_bounds.y = ailight.mAngleOuterCone;
color = ailight.mColorDiffuse;
} break;
case aiLightSource_AMBIENT: {
if(is_hemisphere) light.opt.type = Light_Type::hemisphere;
else if(is_sphere) {
light.opt.type = Light_Type::sphere;
if(ailight.mEnvMap.length) {
light.opt.has_emissive_map = true;
light.emissive_load(std::string(ailight.mEnvMap.C_Str()));
}
} else if(is_area) {
light.opt.type = Light_Type::rectangle;
light.opt.size.x = ailight.mAttenuationConstant;
light.opt.size.y = ailight.mAttenuationLinear;
}
color = ailight.mColorAmbient;
} break;
case aiLightSource_AREA: {
light.opt.type = Light_Type::rectangle;
light.opt.size.x = ailight.mSize.x;
light.opt.size.y = ailight.mSize.y;
color = ailight.mColorDiffuse;
} break;
default: continue;
}
float power = std::max(color.r, std::max(color.g, color.b));
light.opt.spectrum = Spectrum(color.r, color.g, color.b) * (1.0f / power);
light.opt.intensity = power;
if(was_exported_from_s3d) {
float factor = power / export_power;
light.opt.spectrum *= factor;
light.opt.intensity = export_power;
}
if(!light.is_env() || !has_env_light()) {
node_to_obj[(aiNode*)node] = light.id();
std::string anim_name = std::string(node->mName.C_Str()) + "-LIGHT_ANIM_NODE";
aiNode* anim_node = scene->mRootNode->FindNode(aiString(anim_name));
node_to_obj[anim_node] = light.id();
add(std::move(light));
}
}
// animations
for(unsigned int i = 0; i < scene->mNumAnimations; i++) {
aiAnimation& anim = *scene->mAnimations[i];
for(unsigned int j = 0; j < anim.mNumChannels; j++) {
aiNodeAnim& node_anim = *anim.mChannels[j];
if(node_anim.mNodeName == aiString("camera_node")) {
Gui::Anim_Camera& cam = gui.get_animate().camera();
unsigned int keys = std::min(node_anim.mNumPositionKeys,
std::min(node_anim.mNumRotationKeys, node_anim.mNumScalingKeys));
for(unsigned int k = 0; k < keys; k++) {
float t = (float)node_anim.mPositionKeys[k].mTime;
Vec3 pos = aiVec(node_anim.mPositionKeys[k].mValue);
Quat rot = aiQuat(node_anim.mRotationKeys[k].mValue);
Vec3 v = aiVec(node_anim.mScalingKeys[k].mValue);
cam.splines.set(t, pos, rot, v.x, v.y);
}
} else if(std::string(node_anim.mNodeName.C_Str()).find("LIGHT_ANIM_NODE") != std::string::npos) {
aiNode* node = scene->mRootNode->FindNode(node_anim.mNodeName);
auto entry = node_to_obj.find(node);
if(entry != node_to_obj.end()) {
Scene_Light& l = get_light(entry->second);
Scene_Light::Anim_Light& light = l.lanim;
unsigned int keys = std::min(node_anim.mNumPositionKeys,
std::min(node_anim.mNumRotationKeys, node_anim.mNumScalingKeys));
for(unsigned int k = 0; k < keys; k++) {
float t = (float)node_anim.mPositionKeys[k].mTime;
Vec3 spec = aiVec(node_anim.mPositionKeys[k].mValue);
Vec3 angle = aiQuat(node_anim.mRotationKeys[k].mValue).to_euler();
Vec3 inten_sz = aiVec(node_anim.mScalingKeys[k].mValue);
light.splines.set(t, Spectrum{spec.x, spec.y, spec.z}, inten_sz.x - 1.0f,
Vec2{angle.x, angle.z}, Vec2{inten_sz.y - 1.0f, inten_sz.z - 1.0f});
}
}
} else {
aiNode* node = scene->mRootNode->FindNode(node_anim.mNodeName);
auto jentry = node_to_bone.find(node);
if(jentry != node_to_bone.end()) {
Joint* jt = jentry->second;
unsigned int keys = node_anim.mNumRotationKeys;
for(unsigned int k = 0; k < keys; k++) {
float t = (float)node_anim.mRotationKeys[k].mTime;
Quat rot = aiQuat(node_anim.mRotationKeys[k].mValue);
jt->anim.set(t, rot);
}
} else {
auto entry = node_to_obj.find(node);
if(entry != node_to_obj.end()) {
Scene_Item& item = *get(entry->second);
Anim_Pose& pose = item.animation();
unsigned int keys = std::min(node_anim.mNumPositionKeys,
std::min(node_anim.mNumRotationKeys, node_anim.mNumScalingKeys));
for(unsigned int k = 0; k < keys; k++) {
float t = (float)node_anim.mPositionKeys[k].mTime;
Vec3 pos = aiVec(node_anim.mPositionKeys[k].mValue);
Quat rot = aiQuat(node_anim.mRotationKeys[k].mValue);
Vec3 scl = aiVec(node_anim.mScalingKeys[k].mValue);
pose.splines.set(t, pos, rot, scl);
}
}
}
}
}
if(anim.mDuration > 0.0f) {
gui.get_animate().set((int)std::ceil(anim.mDuration), (int)std::round(anim.mTicksPerSecond));
}
gui.get_animate().refresh(*this);
}
std::stringstream stream;
if(errors.size()) {
stream << "Meshes with errors may not be edit-able in the model mode." << std::endl << std::endl;
}
for(size_t i = 0; i < errors.size(); i++) {
stream << "Loading mesh " << i << ": " << errors[i] << std::endl;
}
return stream.str();
std::string Scene::load(bool new_scene, Undo &undo, Gui::Manager &gui, std::string file) {
if (new_scene) {
clear(undo);
gui.get_animate().clear();
gui.get_rig().clear();
}
Assimp::Importer importer;
const aiScene *scene = importer.ReadFile(
file.c_str(), aiProcess_GenSmoothNormals | aiProcess_PopulateArmatureData |
aiProcess_ValidateDataStructure | aiProcess_OptimizeMeshes |
aiProcess_FindInstances | aiProcess_FindDegenerates |
aiProcess_JoinIdenticalVertices | aiProcess_FindInvalidData);
if (!scene) {
return "Parsing scene " + file + ": " + std::string(importer.GetErrorString());
}
std::vector<std::string> errors;
std::unordered_map<aiNode *, Scene_ID> node_to_obj;
std::unordered_map<aiNode *, Joint *> node_to_bone;
scene->mRootNode->mTransformation = aiMatrix4x4();
// Load objects
load_node(*this, errors, node_to_obj, node_to_bone, scene, scene->mRootNode, aiMatrix4x4());
// Load camera
if (new_scene && scene->mNumCameras) {
const aiCamera &aiCam = *scene->mCameras[0];
Mat4 cam_transform = aiMat(node_transform(scene->mRootNode->FindNode(aiCam.mName)));
Vec3 pos = cam_transform * aiVec(aiCam.mPosition);
Vec3 center = cam_transform * aiVec(aiCam.mLookAt);
gui.get_render().load_cam(pos, center, aiCam.mAspect, aiCam.mHorizontalFOV);
}
// Load lights
for (unsigned int i = 0; i < scene->mNumLights; i++) {
const aiLight &ailight = *scene->mLights[i];
const aiNode *node = scene->mRootNode->FindNode(ailight.mName);
Mat4 trans = aiMat(node_transform(node));
Vec3 pos = trans * aiVec(ailight.mPosition);
Vec3 dir = trans.rotate(Vec3{0.0f, 1.0f, 0.0f});
Pose p;
p.pos = pos;
p.euler = Mat4::rotate_to(dir).to_euler();
bool was_exported_from_s3d = false;
float export_power = 1.0f;
bool is_sphere = false, is_hemisphere = false, is_area = false;
std::string name = std::string(node->mName.C_Str());
size_t special = name.find("-S3D-");
if (special != std::string::npos) {
was_exported_from_s3d = true;
export_power = ailight.mAttenuationQuadratic;
std::string left = name.substr(special + 4);
name = name.substr(0, special);
std::replace(name.begin(), name.end(), '_', ' ');
if (left.find("HEMISPHERE") != std::string::npos)
is_hemisphere = true;
else if (left.find("SPHERE") != std::string::npos)
is_sphere = true;
else if (left.find("AREA") != std::string::npos)
is_area = true;
}
aiColor3D color;
Scene_Light light(Light_Type::point, reserve_id(), p, name);
switch (ailight.mType) {
case aiLightSource_DIRECTIONAL: {
light.opt.type = Light_Type::directional;
color = ailight.mColorDiffuse;
} break;
case aiLightSource_POINT: {
light.opt.type = Light_Type::point;
color = ailight.mColorDiffuse;
} break;
case aiLightSource_SPOT: {
light.opt.type = Light_Type::spot;
light.opt.angle_bounds.x = ailight.mAngleInnerCone;
light.opt.angle_bounds.y = ailight.mAngleOuterCone;
color = ailight.mColorDiffuse;
} break;
case aiLightSource_AMBIENT: {
if (is_hemisphere)
light.opt.type = Light_Type::hemisphere;
else if (is_sphere) {
light.opt.type = Light_Type::sphere;
if (ailight.mEnvMap.length) {
light.opt.has_emissive_map = true;
light.emissive_load(std::string(ailight.mEnvMap.C_Str()));
}
} else if (is_area) {
light.opt.type = Light_Type::rectangle;
light.opt.size.x = ailight.mAttenuationConstant;
light.opt.size.y = ailight.mAttenuationLinear;
}
color = ailight.mColorAmbient;
} break;
case aiLightSource_AREA: {
light.opt.type = Light_Type::rectangle;
light.opt.size.x = ailight.mSize.x;
light.opt.size.y = ailight.mSize.y;
color = ailight.mColorDiffuse;
} break;
default:
continue;
}
float power = std::max(color.r, std::max(color.g, color.b));
light.opt.spectrum = Spectrum(color.r, color.g, color.b) * (1.0f / power);
light.opt.intensity = power;
if (was_exported_from_s3d) {
float factor = power / export_power;
light.opt.spectrum *= factor;
light.opt.intensity = export_power;
}
if (!light.is_env() || !has_env_light()) {
node_to_obj[(aiNode *)node] = light.id();
std::string anim_name = std::string(node->mName.C_Str()) + "-LIGHT_ANIM_NODE";
aiNode *anim_node = scene->mRootNode->FindNode(aiString(anim_name));
node_to_obj[anim_node] = light.id();
add(std::move(light));
}
}
// animations
for (unsigned int i = 0; i < scene->mNumAnimations; i++) {
aiAnimation &anim = *scene->mAnimations[i];
for (unsigned int j = 0; j < anim.mNumChannels; j++) {
aiNodeAnim &node_anim = *anim.mChannels[j];
if (node_anim.mNodeName == aiString("camera_node")) {
Gui::Anim_Camera &cam = gui.get_animate().camera();
unsigned int keys =
std::min(node_anim.mNumPositionKeys,
std::min(node_anim.mNumRotationKeys, node_anim.mNumScalingKeys));
for (unsigned int k = 0; k < keys; k++) {
float t = (float)node_anim.mPositionKeys[k].mTime;
Vec3 pos = aiVec(node_anim.mPositionKeys[k].mValue);
Quat rot = aiQuat(node_anim.mRotationKeys[k].mValue);
Vec3 v = aiVec(node_anim.mScalingKeys[k].mValue);
cam.splines.set(t, pos, rot, v.x, v.y);
}
} else if (std::string(node_anim.mNodeName.C_Str()).find("LIGHT_ANIM_NODE") !=
std::string::npos) {
aiNode *node = scene->mRootNode->FindNode(node_anim.mNodeName);
auto entry = node_to_obj.find(node);
if (entry != node_to_obj.end()) {
Scene_Light &l = get_light(entry->second);
Scene_Light::Anim_Light &light = l.lanim;
unsigned int keys =
std::min(node_anim.mNumPositionKeys,
std::min(node_anim.mNumRotationKeys, node_anim.mNumScalingKeys));
for (unsigned int k = 0; k < keys; k++) {
float t = (float)node_anim.mPositionKeys[k].mTime;
Vec3 spec = aiVec(node_anim.mPositionKeys[k].mValue);
Vec3 angle = aiQuat(node_anim.mRotationKeys[k].mValue).to_euler();
Vec3 inten_sz = aiVec(node_anim.mScalingKeys[k].mValue);
light.splines.set(t, Spectrum{spec.x, spec.y, spec.z}, inten_sz.x - 1.0f,
Vec2{angle.x, angle.z},
Vec2{inten_sz.y - 1.0f, inten_sz.z - 1.0f});
}
}
} else {
aiNode *node = scene->mRootNode->FindNode(node_anim.mNodeName);
auto jentry = node_to_bone.find(node);
if (jentry != node_to_bone.end()) {
Joint *jt = jentry->second;
unsigned int keys = node_anim.mNumRotationKeys;
for (unsigned int k = 0; k < keys; k++) {
float t = (float)node_anim.mRotationKeys[k].mTime;
Quat rot = aiQuat(node_anim.mRotationKeys[k].mValue);
jt->anim.set(t, rot);
}
} else {
auto entry = node_to_obj.find(node);
if (entry != node_to_obj.end()) {
Scene_Item &item = *get(entry->second);
Anim_Pose &pose = item.animation();
unsigned int keys = std::min(
node_anim.mNumPositionKeys,
std::min(node_anim.mNumRotationKeys, node_anim.mNumScalingKeys));
for (unsigned int k = 0; k < keys; k++) {
float t = (float)node_anim.mPositionKeys[k].mTime;
Vec3 pos = aiVec(node_anim.mPositionKeys[k].mValue);
Quat rot = aiQuat(node_anim.mRotationKeys[k].mValue);
Vec3 scl = aiVec(node_anim.mScalingKeys[k].mValue);
pose.splines.set(t, pos, rot, scl);
}
}
}
}
}
if (anim.mDuration > 0.0f) {
gui.get_animate().set((int)std::ceil(anim.mDuration),
(int)std::round(anim.mTicksPerSecond));
}
gui.get_animate().refresh(*this);
}
std::stringstream stream;
if (errors.size()) {
stream << "Meshes with errors may not be edit-able in the model mode." << std::endl
<< std::endl;
}
for (size_t i = 0; i < errors.size(); i++) {
stream << "Loading mesh " << i << ": " << errors[i] << std::endl;
}
return stream.str();
}
std::string Scene::write(std::string file, const Camera& render_cam, const Gui::Animate& animation) {
bool no_real_meshes = false;
size_t n_meshes = 0, n_lights = 0, n_anims = 0;
size_t n_armatures = 0;
for_items([&](Scene_Item& item) {
if(item.is<Scene_Object>()) {
Scene_Object& obj = item.get<Scene_Object>();
n_meshes++;
if(obj.anim.splines.any()) n_anims++;
if(obj.armature.has_bones()) n_armatures++;
obj.armature.for_joints([&](Joint* j) {
if(j->anim.any()) n_anims++;
});
} else if(item.is<Scene_Light>()) {
if(item.animation().splines.any()) n_anims++;
if(item.get<Scene_Light>().lanim.splines.any()) n_anims++;
n_lights++;
}
});
size_t camera_anim = n_anims;
if(animation.camera().splines.any()) n_anims++;
if(!n_meshes) {
no_real_meshes = true;
n_meshes = 1;
}
aiScene scene;
scene.mRootNode = new aiNode();
// materials
scene.mMaterials = new aiMaterial*[n_meshes]();
scene.mNumMaterials = (unsigned int)n_meshes;
// camera
const Gui::Anim_Camera& anim_cam = animation.camera();
Camera cam = render_cam;
if(anim_cam.splines.any()) {
cam = animation.camera().at(0.0f);
}
scene.mCameras = new aiCamera*[1]();
scene.mCameras[0] = new aiCamera();
scene.mCameras[0]->mAspect = cam.get_ar();
scene.mCameras[0]->mClipPlaneNear = cam.get_near();
scene.mCameras[0]->mClipPlaneFar = 100.0f;
scene.mCameras[0]->mLookAt = aiVector3D(0.0f, 0.0f, -1.0f);
scene.mCameras[0]->mHorizontalFOV = Radians(cam.get_h_fov());
scene.mCameras[0]->mName = aiString("camera_node");
scene.mNumCameras = 1;
// nodes
size_t cam_idx = n_meshes + n_armatures + n_lights * 2;
size_t n_nodes = cam_idx + 1;
scene.mRootNode->mNumMeshes = 0;
scene.mRootNode->mNumChildren = (unsigned int)(n_nodes);
scene.mRootNode->mChildren = new aiNode*[n_nodes]();
// camera node
Mat4 view = cam.get_view().inverse();
scene.mRootNode->mChildren[cam_idx] = new aiNode();
scene.mRootNode->mChildren[cam_idx]->mNumMeshes = 0;
scene.mRootNode->mChildren[cam_idx]->mName = aiString("camera_node");
scene.mRootNode->mChildren[cam_idx]->mTransformation = matMat(view);
// meshes
scene.mMeshes = new aiMesh*[n_meshes]();
scene.mNumMeshes = (unsigned int)n_meshes;
// lights
scene.mLights = new aiLight*[n_lights]();
scene.mNumLights = (unsigned int)n_lights;
size_t mesh_idx = 0, light_idx = 0, node_idx = 0;
if(no_real_meshes) {
aiMesh* ai_mesh = new aiMesh();
aiNode* ai_node = new aiNode();
scene.mMeshes[mesh_idx++] = ai_mesh;
scene.mRootNode->mChildren[node_idx++] = ai_node;
ai_node->mNumMeshes = 1;
ai_node->mMeshes = new unsigned int((unsigned int)0);
ai_mesh->mVertices = new aiVector3D[3];
ai_mesh->mNormals = new aiVector3D[3];
ai_mesh->mNumVertices = (unsigned int)3;
ai_mesh->mVertices[0] = vecVec(Vec3{1.0f, 0.0f, 0.0f});
ai_mesh->mVertices[1] = vecVec(Vec3{0.0f, 1.0f, 0.0f});
ai_mesh->mVertices[2] = vecVec(Vec3{0.0f, 0.0f, 1.0f});
ai_mesh->mNormals[0] = vecVec(Vec3{0.0f, 1.0f, 0.0f});
ai_mesh->mNormals[1] = vecVec(Vec3{0.0f, 1.0f, 0.0f});
ai_mesh->mNormals[2] = vecVec(Vec3{0.0f, 1.0f, 0.0f});
ai_mesh->mFaces = new aiFace[1];
ai_mesh->mNumFaces = 1u;
ai_mesh->mFaces[0].mIndices = new unsigned int[3];
ai_mesh->mFaces[0].mNumIndices = 3;
ai_mesh->mFaces[0].mIndices[0] = 0;
ai_mesh->mFaces[0].mIndices[1] = 1;
ai_mesh->mFaces[0].mIndices[2] = 2;
ai_mesh->mName = aiString(FAKE_NAME);
ai_node->mName = aiString(FAKE_NAME);
scene.mMaterials[0] = new aiMaterial();
}
std::unordered_map<Scene_ID, aiNode*> item_nodes;
std::unordered_map<std::pair<Scene_ID, Scene_ID>, aiNode*> bone_nodes;
for(auto& entry : objs) {
if(entry.second.is<Scene_Object>()) {
Scene_Object& obj = entry.second.get<Scene_Object>();
if(obj.is_shape()) {
obj.try_make_editable(obj.opt.shape_type);
}
aiMesh* ai_mesh = new aiMesh();
aiNode* ai_node = new aiNode();
aiMaterial* ai_mat = new aiMaterial();
size_t idx = mesh_idx++;
scene.mMaterials[idx] = ai_mat;
scene.mMeshes[idx] = ai_mesh;
scene.mRootNode->mChildren[node_idx++] = ai_node;
ai_mesh->mMaterialIndex = (unsigned int)idx;
ai_node->mNumMeshes = 1;
ai_node->mMeshes = new unsigned int((unsigned int)idx);
if(obj.is_editable()) {
Halfedge_Mesh& mesh = obj.get_mesh();
size_t n_verts = mesh.n_vertices();
size_t n_faces = mesh.n_faces();
ai_mesh->mVertices = new aiVector3D[n_verts];
ai_mesh->mNormals = new aiVector3D[n_verts];
ai_mesh->mNumVertices = (unsigned int)n_verts;
ai_mesh->mFaces = new aiFace[n_faces];
ai_mesh->mNumFaces = (unsigned int)(n_faces);
std::unordered_map<size_t, size_t> id_to_idx;
size_t vert_idx = 0;
for(auto v = mesh.vertices_begin(); v != mesh.vertices_end(); v++) {
id_to_idx[v->id()] = vert_idx;
Vec3 n = mesh.flipped() ? -v->normal() : v->normal();
ai_mesh->mVertices[vert_idx] = vecVec(v->pos);
ai_mesh->mNormals[vert_idx] = vecVec(n);
vert_idx++;
}
size_t face_idx = 0;
for(auto f = mesh.faces_begin(); f != mesh.faces_end(); f++) {
aiFace& face = ai_mesh->mFaces[face_idx];
face.mIndices = new unsigned int[f->degree()];
face.mNumIndices = f->degree();
size_t i_idx = 0;
auto h = f->halfedge();
do {
face.mIndices[i_idx] = (unsigned int)id_to_idx[h->vertex()->id()];
h = h->next();
i_idx++;
} while(h != f->halfedge());
face_idx++;
}
} else {
const auto& verts = obj.mesh().verts();
const auto& elems = obj.mesh().indices();
ai_mesh->mVertices = new aiVector3D[verts.size()];
ai_mesh->mNormals = new aiVector3D[verts.size()];
ai_mesh->mNumVertices = (unsigned int)verts.size();
int j = 0;
for(GL::Mesh::Vert v : verts) {
ai_mesh->mVertices[j] = vecVec(v.pos);
ai_mesh->mNormals[j] = vecVec(v.norm);
j++;
}
ai_mesh->mFaces = new aiFace[elems.size() / 3];
ai_mesh->mNumFaces = (unsigned int)(elems.size() / 3);
for(size_t i = 0; i < elems.size(); i += 3) {
aiFace& face = ai_mesh->mFaces[i / 3];
face.mIndices = new unsigned int[3];
face.mNumIndices = 3;
face.mIndices[0] = elems[i];
face.mIndices[1] = elems[i+1];
face.mIndices[2] = elems[i+2];
}
}
std::string name(obj.opt.name);
std::replace(name.begin(), name.end(), ' ', '_');
name += "-S3D-" + std::to_string(obj.id());
if(obj.get_mesh().flipped()) name += "-FLIPPED";
if(obj.opt.smooth_normals) name += "-SMOOTHED";
ai_mesh->mName = aiString(name);
Mat4 trans = obj.pose.transform();
item_nodes[obj.id()] = ai_node;
ai_node->mName = aiString(name);
ai_node->mTransformation = matMat(trans);
const Material::Options& opt = obj.material.opt;
std::string mat_name;
switch(opt.type) {
case Material_Type::lambertian: {
mat_name = "lambertian";
} break;
case Material_Type::mirror: {
mat_name = "mirror";
} break;
case Material_Type::refract: {
mat_name = "refract";
} break;
case Material_Type::glass: {
mat_name = "glass";
} break;
case Material_Type::diffuse_light: {
mat_name = "diffuse_light";
} break;
default: break;
}
// Horrible hack
if(obj.opt.shape_type == PT::Shape_Type::sphere) {
mat_name += "-SPHERESHAPE";
ai_mat->AddProperty(new aiColor3D(obj.opt.shape.get<PT::Sphere>().radius), 1, AI_MATKEY_COLOR_SPECULAR);
}
Spectrum emissive = obj.material.emissive();
// diffuse -> albedo
// reflective -> reflectance
// transparent -> transmittance
// emissive -> emissive
// refracti -> index of refraction
// shininess -> intensity
ai_mat->AddProperty(new aiString(mat_name), AI_MATKEY_NAME);
ai_mat->AddProperty(new aiColor3D(opt.albedo.r, opt.albedo.g, opt.albedo.b), 1, AI_MATKEY_COLOR_DIFFUSE);
ai_mat->AddProperty(new aiColor3D(opt.reflectance.r, opt.reflectance.g, opt.reflectance.b), 1, AI_MATKEY_COLOR_REFLECTIVE);
ai_mat->AddProperty(new aiColor3D(opt.transmittance.r, opt.transmittance.g, opt.transmittance.b), 1, AI_MATKEY_COLOR_TRANSPARENT);
ai_mat->AddProperty(new aiColor3D(emissive.r, emissive.g, emissive.b), 1, AI_MATKEY_COLOR_EMISSIVE);
ai_mat->AddProperty(new float(opt.ior), 1, AI_MATKEY_REFRACTI);
ai_mat->AddProperty(new float(opt.intensity), 1, AI_MATKEY_SHININESS);
if(obj.armature.has_bones()) {
ai_mesh->mNumBones = obj.armature.n_bones();
ai_mesh->mBones = new aiBone*[ai_mesh->mNumBones];
size_t bone_idx = 0;
std::string prefix = "S3D-joint-" + std::to_string(obj.id()) + "-";
aiNode* arm_node = new aiNode();
scene.mRootNode->mChildren[node_idx++] = arm_node;
arm_node->mName = aiString(prefix + "armature");
arm_node->mTransformation = matMat(Mat4::translate(obj.armature.base_pos));
arm_node->mNumChildren = (unsigned int)obj.armature.roots.size();
arm_node->mChildren = new aiNode*[obj.armature.roots.size()];
std::unordered_map<Joint*, aiNode*> j_to_node;
std::function<void(std::string, aiNode*, Joint*)> joint_tree;
joint_tree = [&joint_tree, &j_to_node](std::string n, aiNode* node, Joint* j) {
std::string name = n + std::to_string(j->_id);
node->mName = aiString(name);
j_to_node[j] = node;
if(j->children.size()) {
node->mNumChildren = (unsigned int)j->children.size();
node->mChildren = new aiNode*[j->children.size()];
size_t i = 0;
for(Joint* c : j->children) {
node->mChildren[i] = new aiNode();
joint_tree(n, node->mChildren[i], c);
i++;
}
}
};
size_t i = 0;
for(Joint* j : obj.armature.roots) {
aiNode* root_node = new aiNode();
arm_node->mChildren[i] = root_node;
j_to_node[j] = root_node;
joint_tree(prefix, root_node, j);
i++;
}
for(auto [j,n] : j_to_node) {
bone_nodes.insert({{obj.id(), j->_id}, n});
}
obj.armature.for_joints([&](Joint* j) {
aiBone* bone = new aiBone();
ai_mesh->mBones[bone_idx++] = bone;
bone->mOffsetMatrix = matMat(Mat4::translate(j->extent) * Mat4::euler(j->pose));
bone->mNode = j_to_node[j];
std::string name = prefix + std::to_string(j->_id);
bone->mName = aiString(name);
bone->mNumWeights = 1;
bone->mWeights = new aiVertexWeight[1];
bone->mWeights[0].mWeight = j->radius;
});
}
} else if(entry.second.is<Scene_Light>()) {
const Scene_Light& light = entry.second.get<Scene_Light>();
std::string name(light.opt.name);
std::replace(name.begin(), name.end(), ' ', '_');
aiLight* ai_light = new aiLight();
aiNode* ai_node = new aiNode();
aiNode* ai_node_light = new aiNode();
scene.mLights[light_idx++] = ai_light;
scene.mRootNode->mChildren[node_idx++] = ai_node;
scene.mRootNode->mChildren[node_idx++] = ai_node_light;
switch(light.opt.type) {
case Light_Type::directional: {
ai_light->mType = aiLightSource_DIRECTIONAL;
name += "-S3D-" + std::to_string(light.id());
} break;
case Light_Type::sphere: {
ai_light->mType = aiLightSource_AMBIENT;
name += "-S3D-SPHERE-" + std::to_string(light.id());
if(light.opt.has_emissive_map) {
ai_light->mEnvMap = aiString(light.emissive_loaded());
}
} break;
case Light_Type::hemisphere: {
ai_light->mType = aiLightSource_AMBIENT;
name += "-S3D-HEMISPHERE-" + std::to_string(light.id());
} break;
case Light_Type::point: {
ai_light->mType = aiLightSource_POINT;
name += "-S3D-" + std::to_string(light.id());
} break;
case Light_Type::spot: {
ai_light->mType = aiLightSource_SPOT;
name += "-S3D-" + std::to_string(light.id());
} break;
case Light_Type::rectangle: {
// the collada exporter literally just ignores area lights ????????
ai_light->mType = aiLightSource_AMBIENT;
name += "-S3D-AREA-" + std::to_string(light.id());
ai_light->mAttenuationConstant = light.opt.size.x;
ai_light->mAttenuationLinear = light.opt.size.y;
} break;
default: break;
}
Spectrum r = light.radiance();
ai_light->mName = aiString(name);
ai_light->mPosition = aiVector3D(0.0f, 0.0f, 0.0f);
ai_light->mDirection = aiVector3D(0.0f, 1.0f, 0.0f);
ai_light->mUp = aiVector3D(0.0f, 1.0f, 0.0f);
ai_light->mSize = aiVector2D(light.opt.size.x, light.opt.size.y);
ai_light->mAngleInnerCone = light.opt.angle_bounds.x;
ai_light->mAngleOuterCone = light.opt.angle_bounds.y;
ai_light->mColorDiffuse = aiColor3D(r.r, r.g, r.b);
ai_light->mColorAmbient = aiColor3D(r.r, r.g, r.b);
ai_light->mColorDiffuse = aiColor3D(r.r, r.g, r.b);
ai_light->mAttenuationQuadratic = light.opt.intensity;
Mat4 T = light.pose.transform();
item_nodes[light.id()] = ai_node;
ai_node->mName = aiString(name);
ai_node->mNumMeshes = 0;
ai_node->mMeshes = nullptr;
ai_node->mTransformation = matMat(T);
ai_node_light->mName = aiString(name + "-LIGHT_ANIM_NODE");
ai_node_light->mNumMeshes = 0;
ai_node_light->mMeshes = nullptr;
ai_node_light->mTransformation = aiMatrix4x4();
}
}
{
scene.mNumAnimations = 1;
scene.mAnimations = new aiAnimation*[1];
scene.mAnimations[0] = new aiAnimation();
aiAnimation& ai_anim = *scene.mAnimations[0];
ai_anim.mName = aiString("Scotty3D-animate");
ai_anim.mDuration = (double)animation.n_frames();
ai_anim.mTicksPerSecond = (double)animation.fps();
ai_anim.mNumChannels = (unsigned int)n_anims;
ai_anim.mChannels = new aiNodeAnim*[n_anims];
auto write_keyframes = [](aiNodeAnim* node, std::string name, auto pt, auto rot, auto scl) {
node->mNodeName = aiString(name);
node->mNumPositionKeys = (unsigned int)pt.size();
node->mNumRotationKeys = (unsigned int)rot.size();
node->mNumScalingKeys = (unsigned int)scl.size();
node->mPositionKeys = new aiVectorKey[pt.size()];
node->mRotationKeys = new aiQuatKey[rot.size()];
node->mScalingKeys = new aiVectorKey[scl.size()];
size_t i = 0;
for(auto& e : pt) {
node->mPositionKeys[i] = aiVectorKey(e.first, vecVec(e.second));
i++;
}
i = 0;
for(auto& e : rot) {
node->mRotationKeys[i] = aiQuatKey(e.first, aiQuaternion(e.second.w, e.second.x, e.second.y, e.second.z));
i++;
}
i = 0;
for(auto& e : scl) {
node->mScalingKeys[i] = aiVectorKey(e.first, vecVec(e.second));
i++;
}
};
if(anim_cam.splines.any()) {
ai_anim.mChannels[camera_anim] = new aiNodeAnim();
std::set<float> keys = anim_cam.splines.keys();
std::unordered_map<float, Vec3> points, scales;
std::unordered_map<float, Quat> rotations;
for(float k : keys) {
auto [p, r, fov, ar] = anim_cam.splines.at(k);
points[k] = p;
rotations[k] = r;
scales[k] = Vec3{fov, ar, 1.0f};
}
write_keyframes(ai_anim.mChannels[camera_anim], "camera_node", points, rotations, scales);
}
size_t anim_idx = 0;
for(auto& entry : objs) {
Scene_Item& item = entry.second;
const Anim_Pose& pose = item.animation();
if(pose.splines.any()) {
std::set<float> keys = pose.splines.keys();
std::unordered_map<float, Vec3> points, scales;
std::unordered_map<float, Quat> rotations;
for(float k : keys) {
auto [p, r, s] = pose.splines.at(k);
points[k] = p;
rotations[k] = r;
scales[k] = s;
}
aiNode* node = item_nodes[item.id()];
ai_anim.mChannels[anim_idx] = new aiNodeAnim();
write_keyframes(ai_anim.mChannels[anim_idx], std::string(node->mName.C_Str()),
points, rotations, scales);
anim_idx++;
}
if(item.is<Scene_Object>()) {
Skeleton& skel = item.get<Scene_Object>().armature;
if(skel.has_keyframes()) {
skel.for_joints([&](Joint* j) {
std::set<float> keys = j->anim.keys();
std::unordered_map<float, Vec3> points, scales;
std::unordered_map<float, Quat> rotations;
for(float k : keys) {
points[k] = Vec3{0.0f};
rotations[k] = j->anim.at(k);
scales[k] = Vec3{1.0f};
}
aiNode* node = bone_nodes[{item.id(), j->_id}];
ai_anim.mChannels[anim_idx] = new aiNodeAnim();
write_keyframes(ai_anim.mChannels[anim_idx], std::string(node->mName.C_Str()),
points, rotations, scales);
anim_idx++;
});
}
} else if(item.is<Scene_Light>()) {
aiNode* node = item_nodes[item.id()];
std::string name(node->mName.C_Str());
name += "-LIGHT_ANIM_NODE";
const Scene_Light::Anim_Light& light = item.get<Scene_Light>().lanim;
if(light.splines.any()) {
std::unordered_map<float, Vec3> points, scales;
std::unordered_map<float, Quat> rotations;
std::set<float> keys = light.splines.keys();
for(float k : keys) {
auto [spec, inten, angle, size] = light.splines.at(k);
points[k] = Vec3{spec.r, spec.g, spec.b};
rotations[k] = Quat::euler(Vec3{angle.x, 0.0f, angle.y});
scales[k] = Vec3{inten + 1.0f, size.x + 1.0f, size.y + 1.0f};
}
ai_anim.mChannels[anim_idx] = new aiNodeAnim();
write_keyframes(ai_anim.mChannels[anim_idx], name, points, rotations, scales);
anim_idx++;
}
}
}
}
// Note: exporter/scene destructor should free everything
Assimp::Exporter exporter;
if(exporter.Export(&scene, "collada", file.c_str())) {
return std::string(exporter.GetErrorString());
}
return {};
std::string Scene::write(std::string file, const Camera &render_cam,
const Gui::Animate &animation) {
bool no_real_meshes = false;
size_t n_meshes = 0, n_lights = 0, n_anims = 0;
size_t n_armatures = 0;
for_items([&](Scene_Item &item) {
if (item.is<Scene_Object>()) {
Scene_Object &obj = item.get<Scene_Object>();
n_meshes++;
if (obj.anim.splines.any())
n_anims++;
if (obj.armature.has_bones())
n_armatures++;
obj.armature.for_joints([&](Joint *j) {
if (j->anim.any())
n_anims++;
});
} else if (item.is<Scene_Light>()) {
if (item.animation().splines.any())
n_anims++;
if (item.get<Scene_Light>().lanim.splines.any())
n_anims++;
n_lights++;
}
});
size_t camera_anim = n_anims;
if (animation.camera().splines.any())
n_anims++;
if (!n_meshes) {
no_real_meshes = true;
n_meshes = 1;
}
aiScene scene;
scene.mRootNode = new aiNode();
// materials
scene.mMaterials = new aiMaterial *[n_meshes]();
scene.mNumMaterials = (unsigned int)n_meshes;
// camera
const Gui::Anim_Camera &anim_cam = animation.camera();
Camera cam = render_cam;
if (anim_cam.splines.any()) {
cam = animation.camera().at(0.0f);
}
scene.mCameras = new aiCamera *[1]();
scene.mCameras[0] = new aiCamera();
scene.mCameras[0]->mAspect = cam.get_ar();
scene.mCameras[0]->mClipPlaneNear = cam.get_near();
scene.mCameras[0]->mClipPlaneFar = 100.0f;
scene.mCameras[0]->mLookAt = aiVector3D(0.0f, 0.0f, -1.0f);
scene.mCameras[0]->mHorizontalFOV = Radians(cam.get_h_fov());
scene.mCameras[0]->mName = aiString("camera_node");
scene.mNumCameras = 1;
// nodes
size_t cam_idx = n_meshes + n_armatures + n_lights * 2;
size_t n_nodes = cam_idx + 1;
scene.mRootNode->mNumMeshes = 0;
scene.mRootNode->mNumChildren = (unsigned int)(n_nodes);
scene.mRootNode->mChildren = new aiNode *[n_nodes]();
// camera node
Mat4 view = cam.get_view().inverse();
scene.mRootNode->mChildren[cam_idx] = new aiNode();
scene.mRootNode->mChildren[cam_idx]->mNumMeshes = 0;
scene.mRootNode->mChildren[cam_idx]->mName = aiString("camera_node");
scene.mRootNode->mChildren[cam_idx]->mTransformation = matMat(view);
// meshes
scene.mMeshes = new aiMesh *[n_meshes]();
scene.mNumMeshes = (unsigned int)n_meshes;
// lights
scene.mLights = new aiLight *[n_lights]();
scene.mNumLights = (unsigned int)n_lights;
size_t mesh_idx = 0, light_idx = 0, node_idx = 0;
if (no_real_meshes) {
aiMesh *ai_mesh = new aiMesh();
aiNode *ai_node = new aiNode();
scene.mMeshes[mesh_idx++] = ai_mesh;
scene.mRootNode->mChildren[node_idx++] = ai_node;
ai_node->mNumMeshes = 1;
ai_node->mMeshes = new unsigned int((unsigned int)0);
ai_mesh->mVertices = new aiVector3D[3];
ai_mesh->mNormals = new aiVector3D[3];
ai_mesh->mNumVertices = (unsigned int)3;
ai_mesh->mVertices[0] = vecVec(Vec3{1.0f, 0.0f, 0.0f});
ai_mesh->mVertices[1] = vecVec(Vec3{0.0f, 1.0f, 0.0f});
ai_mesh->mVertices[2] = vecVec(Vec3{0.0f, 0.0f, 1.0f});
ai_mesh->mNormals[0] = vecVec(Vec3{0.0f, 1.0f, 0.0f});
ai_mesh->mNormals[1] = vecVec(Vec3{0.0f, 1.0f, 0.0f});
ai_mesh->mNormals[2] = vecVec(Vec3{0.0f, 1.0f, 0.0f});
ai_mesh->mFaces = new aiFace[1];
ai_mesh->mNumFaces = 1u;
ai_mesh->mFaces[0].mIndices = new unsigned int[3];
ai_mesh->mFaces[0].mNumIndices = 3;
ai_mesh->mFaces[0].mIndices[0] = 0;
ai_mesh->mFaces[0].mIndices[1] = 1;
ai_mesh->mFaces[0].mIndices[2] = 2;
ai_mesh->mName = aiString(FAKE_NAME);
ai_node->mName = aiString(FAKE_NAME);
scene.mMaterials[0] = new aiMaterial();
}
std::unordered_map<Scene_ID, aiNode *> item_nodes;
std::unordered_map<std::pair<Scene_ID, Scene_ID>, aiNode *> bone_nodes;
for (auto &entry : objs) {
if (entry.second.is<Scene_Object>()) {
Scene_Object &obj = entry.second.get<Scene_Object>();
if (obj.is_shape()) {
obj.try_make_editable(obj.opt.shape_type);
}
aiMesh *ai_mesh = new aiMesh();
aiNode *ai_node = new aiNode();
aiMaterial *ai_mat = new aiMaterial();
size_t idx = mesh_idx++;
scene.mMaterials[idx] = ai_mat;
scene.mMeshes[idx] = ai_mesh;
scene.mRootNode->mChildren[node_idx++] = ai_node;
ai_mesh->mMaterialIndex = (unsigned int)idx;
ai_node->mNumMeshes = 1;
ai_node->mMeshes = new unsigned int((unsigned int)idx);
if (obj.is_editable()) {
Halfedge_Mesh &mesh = obj.get_mesh();
size_t n_verts = mesh.n_vertices();
size_t n_faces = mesh.n_faces();
ai_mesh->mVertices = new aiVector3D[n_verts];
ai_mesh->mNormals = new aiVector3D[n_verts];
ai_mesh->mNumVertices = (unsigned int)n_verts;
ai_mesh->mFaces = new aiFace[n_faces];
ai_mesh->mNumFaces = (unsigned int)(n_faces);
std::unordered_map<size_t, size_t> id_to_idx;
size_t vert_idx = 0;
for (auto v = mesh.vertices_begin(); v != mesh.vertices_end(); v++) {
id_to_idx[v->id()] = vert_idx;
Vec3 n = mesh.flipped() ? -v->normal() : v->normal();
ai_mesh->mVertices[vert_idx] = vecVec(v->pos);
ai_mesh->mNormals[vert_idx] = vecVec(n);
vert_idx++;
}
size_t face_idx = 0;
for (auto f = mesh.faces_begin(); f != mesh.faces_end(); f++) {
aiFace &face = ai_mesh->mFaces[face_idx];
face.mIndices = new unsigned int[f->degree()];
face.mNumIndices = f->degree();
size_t i_idx = 0;
auto h = f->halfedge();
do {
face.mIndices[i_idx] = (unsigned int)id_to_idx[h->vertex()->id()];
h = h->next();
i_idx++;
} while (h != f->halfedge());
face_idx++;
}
} else {
const auto &verts = obj.mesh().verts();
const auto &elems = obj.mesh().indices();
ai_mesh->mVertices = new aiVector3D[verts.size()];
ai_mesh->mNormals = new aiVector3D[verts.size()];
ai_mesh->mNumVertices = (unsigned int)verts.size();
int j = 0;
for (GL::Mesh::Vert v : verts) {
ai_mesh->mVertices[j] = vecVec(v.pos);
ai_mesh->mNormals[j] = vecVec(v.norm);
j++;
}
ai_mesh->mFaces = new aiFace[elems.size() / 3];
ai_mesh->mNumFaces = (unsigned int)(elems.size() / 3);
for (size_t i = 0; i < elems.size(); i += 3) {
aiFace &face = ai_mesh->mFaces[i / 3];
face.mIndices = new unsigned int[3];
face.mNumIndices = 3;
face.mIndices[0] = elems[i];
face.mIndices[1] = elems[i + 1];
face.mIndices[2] = elems[i + 2];
}
}
std::string name(obj.opt.name);
std::replace(name.begin(), name.end(), ' ', '_');
name += "-S3D-" + std::to_string(obj.id());
if (obj.get_mesh().flipped())
name += "-FLIPPED";
if (obj.opt.smooth_normals)
name += "-SMOOTHED";
ai_mesh->mName = aiString(name);
Mat4 trans = obj.pose.transform();
item_nodes[obj.id()] = ai_node;
ai_node->mName = aiString(name);
ai_node->mTransformation = matMat(trans);
const Material::Options &opt = obj.material.opt;
std::string mat_name;
switch (opt.type) {
case Material_Type::lambertian: {
mat_name = "lambertian";
} break;
case Material_Type::mirror: {
mat_name = "mirror";
} break;
case Material_Type::refract: {
mat_name = "refract";
} break;
case Material_Type::glass: {
mat_name = "glass";
} break;
case Material_Type::diffuse_light: {
mat_name = "diffuse_light";
} break;
default:
break;
}
// Horrible hack
if (obj.opt.shape_type == PT::Shape_Type::sphere) {
mat_name += "-SPHERESHAPE";
ai_mat->AddProperty(new aiColor3D(obj.opt.shape.get<PT::Sphere>().radius), 1,
AI_MATKEY_COLOR_SPECULAR);
}
Spectrum emissive = obj.material.emissive();
// diffuse -> albedo
// reflective -> reflectance
// transparent -> transmittance
// emissive -> emissive
// refracti -> index of refraction
// shininess -> intensity
ai_mat->AddProperty(new aiString(mat_name), AI_MATKEY_NAME);
ai_mat->AddProperty(new aiColor3D(opt.albedo.r, opt.albedo.g, opt.albedo.b), 1,
AI_MATKEY_COLOR_DIFFUSE);
ai_mat->AddProperty(
new aiColor3D(opt.reflectance.r, opt.reflectance.g, opt.reflectance.b), 1,
AI_MATKEY_COLOR_REFLECTIVE);
ai_mat->AddProperty(
new aiColor3D(opt.transmittance.r, opt.transmittance.g, opt.transmittance.b), 1,
AI_MATKEY_COLOR_TRANSPARENT);
ai_mat->AddProperty(new aiColor3D(emissive.r, emissive.g, emissive.b), 1,
AI_MATKEY_COLOR_EMISSIVE);
ai_mat->AddProperty(new float(opt.ior), 1, AI_MATKEY_REFRACTI);
ai_mat->AddProperty(new float(opt.intensity), 1, AI_MATKEY_SHININESS);
if (obj.armature.has_bones()) {
ai_mesh->mNumBones = obj.armature.n_bones();
ai_mesh->mBones = new aiBone *[ai_mesh->mNumBones];
size_t bone_idx = 0;
std::string prefix = "S3D-joint-" + std::to_string(obj.id()) + "-";
aiNode *arm_node = new aiNode();
scene.mRootNode->mChildren[node_idx++] = arm_node;
arm_node->mName = aiString(prefix + "armature");
arm_node->mTransformation = matMat(Mat4::translate(obj.armature.base_pos));
arm_node->mNumChildren = (unsigned int)obj.armature.roots.size();
arm_node->mChildren = new aiNode *[obj.armature.roots.size()];
std::unordered_map<Joint *, aiNode *> j_to_node;
std::function<void(std::string, aiNode *, Joint *)> joint_tree;
joint_tree = [&joint_tree, &j_to_node](std::string n, aiNode *node, Joint *j) {
std::string name = n + std::to_string(j->_id);
node->mName = aiString(name);
j_to_node[j] = node;
if (j->children.size()) {
node->mNumChildren = (unsigned int)j->children.size();
node->mChildren = new aiNode *[j->children.size()];
size_t i = 0;
for (Joint *c : j->children) {
node->mChildren[i] = new aiNode();
joint_tree(n, node->mChildren[i], c);
i++;
}
}
};
size_t i = 0;
for (Joint *j : obj.armature.roots) {
aiNode *root_node = new aiNode();
arm_node->mChildren[i] = root_node;
j_to_node[j] = root_node;
joint_tree(prefix, root_node, j);
i++;
}
for (auto [j, n] : j_to_node) {
bone_nodes.insert({{obj.id(), j->_id}, n});
}
obj.armature.for_joints([&](Joint *j) {
aiBone *bone = new aiBone();
ai_mesh->mBones[bone_idx++] = bone;
bone->mOffsetMatrix = matMat(Mat4::translate(j->extent) * Mat4::euler(j->pose));
bone->mNode = j_to_node[j];
std::string name = prefix + std::to_string(j->_id);
bone->mName = aiString(name);
bone->mNumWeights = 1;
bone->mWeights = new aiVertexWeight[1];
bone->mWeights[0].mWeight = j->radius;
});
}
} else if (entry.second.is<Scene_Light>()) {
const Scene_Light &light = entry.second.get<Scene_Light>();
std::string name(light.opt.name);
std::replace(name.begin(), name.end(), ' ', '_');
aiLight *ai_light = new aiLight();
aiNode *ai_node = new aiNode();
aiNode *ai_node_light = new aiNode();
scene.mLights[light_idx++] = ai_light;
scene.mRootNode->mChildren[node_idx++] = ai_node;
scene.mRootNode->mChildren[node_idx++] = ai_node_light;
switch (light.opt.type) {
case Light_Type::directional: {
ai_light->mType = aiLightSource_DIRECTIONAL;
name += "-S3D-" + std::to_string(light.id());
} break;
case Light_Type::sphere: {
ai_light->mType = aiLightSource_AMBIENT;
name += "-S3D-SPHERE-" + std::to_string(light.id());
if (light.opt.has_emissive_map) {
ai_light->mEnvMap = aiString(light.emissive_loaded());
}
} break;
case Light_Type::hemisphere: {
ai_light->mType = aiLightSource_AMBIENT;
name += "-S3D-HEMISPHERE-" + std::to_string(light.id());
} break;
case Light_Type::point: {
ai_light->mType = aiLightSource_POINT;
name += "-S3D-" + std::to_string(light.id());
} break;
case Light_Type::spot: {
ai_light->mType = aiLightSource_SPOT;
name += "-S3D-" + std::to_string(light.id());
} break;
case Light_Type::rectangle: {
// the collada exporter literally just ignores area lights ????????
ai_light->mType = aiLightSource_AMBIENT;
name += "-S3D-AREA-" + std::to_string(light.id());
ai_light->mAttenuationConstant = light.opt.size.x;
ai_light->mAttenuationLinear = light.opt.size.y;
} break;
default:
break;
}
Spectrum r = light.radiance();
ai_light->mName = aiString(name);
ai_light->mPosition = aiVector3D(0.0f, 0.0f, 0.0f);
ai_light->mDirection = aiVector3D(0.0f, 1.0f, 0.0f);
ai_light->mUp = aiVector3D(0.0f, 1.0f, 0.0f);
ai_light->mSize = aiVector2D(light.opt.size.x, light.opt.size.y);
ai_light->mAngleInnerCone = light.opt.angle_bounds.x;
ai_light->mAngleOuterCone = light.opt.angle_bounds.y;
ai_light->mColorDiffuse = aiColor3D(r.r, r.g, r.b);
ai_light->mColorAmbient = aiColor3D(r.r, r.g, r.b);
ai_light->mColorDiffuse = aiColor3D(r.r, r.g, r.b);
ai_light->mAttenuationQuadratic = light.opt.intensity;
Mat4 T = light.pose.transform();
item_nodes[light.id()] = ai_node;
ai_node->mName = aiString(name);
ai_node->mNumMeshes = 0;
ai_node->mMeshes = nullptr;
ai_node->mTransformation = matMat(T);
ai_node_light->mName = aiString(name + "-LIGHT_ANIM_NODE");
ai_node_light->mNumMeshes = 0;
ai_node_light->mMeshes = nullptr;
ai_node_light->mTransformation = aiMatrix4x4();
}
}
{
scene.mNumAnimations = 1;
scene.mAnimations = new aiAnimation *[1];
scene.mAnimations[0] = new aiAnimation();
aiAnimation &ai_anim = *scene.mAnimations[0];
ai_anim.mName = aiString("Scotty3D-animate");
ai_anim.mDuration = (double)animation.n_frames();
ai_anim.mTicksPerSecond = (double)animation.fps();
ai_anim.mNumChannels = (unsigned int)n_anims;
ai_anim.mChannels = new aiNodeAnim *[n_anims];
auto write_keyframes = [](aiNodeAnim *node, std::string name, auto pt, auto rot, auto scl) {
node->mNodeName = aiString(name);
node->mNumPositionKeys = (unsigned int)pt.size();
node->mNumRotationKeys = (unsigned int)rot.size();
node->mNumScalingKeys = (unsigned int)scl.size();
node->mPositionKeys = new aiVectorKey[pt.size()];
node->mRotationKeys = new aiQuatKey[rot.size()];
node->mScalingKeys = new aiVectorKey[scl.size()];
size_t i = 0;
for (auto &e : pt) {
node->mPositionKeys[i] = aiVectorKey(e.first, vecVec(e.second));
i++;
}
i = 0;
for (auto &e : rot) {
node->mRotationKeys[i] = aiQuatKey(
e.first, aiQuaternion(e.second.w, e.second.x, e.second.y, e.second.z));
i++;
}
i = 0;
for (auto &e : scl) {
node->mScalingKeys[i] = aiVectorKey(e.first, vecVec(e.second));
i++;
}
};
if (anim_cam.splines.any()) {
ai_anim.mChannels[camera_anim] = new aiNodeAnim();
std::set<float> keys = anim_cam.splines.keys();
std::unordered_map<float, Vec3> points, scales;
std::unordered_map<float, Quat> rotations;
for (float k : keys) {
auto [p, r, fov, ar] = anim_cam.splines.at(k);
points[k] = p;
rotations[k] = r;
scales[k] = Vec3{fov, ar, 1.0f};
}
write_keyframes(ai_anim.mChannels[camera_anim], "camera_node", points, rotations,
scales);
}
size_t anim_idx = 0;
for (auto &entry : objs) {
Scene_Item &item = entry.second;
const Anim_Pose &pose = item.animation();
if (pose.splines.any()) {
std::set<float> keys = pose.splines.keys();
std::unordered_map<float, Vec3> points, scales;
std::unordered_map<float, Quat> rotations;
for (float k : keys) {
auto [p, r, s] = pose.splines.at(k);
points[k] = p;
rotations[k] = r;
scales[k] = s;
}
aiNode *node = item_nodes[item.id()];
ai_anim.mChannels[anim_idx] = new aiNodeAnim();
write_keyframes(ai_anim.mChannels[anim_idx], std::string(node->mName.C_Str()),
points, rotations, scales);
anim_idx++;
}
if (item.is<Scene_Object>()) {
Skeleton &skel = item.get<Scene_Object>().armature;
if (skel.has_keyframes()) {
skel.for_joints([&](Joint *j) {
std::set<float> keys = j->anim.keys();
std::unordered_map<float, Vec3> points, scales;
std::unordered_map<float, Quat> rotations;
for (float k : keys) {
points[k] = Vec3{0.0f};
rotations[k] = j->anim.at(k);
scales[k] = Vec3{1.0f};
}
aiNode *node = bone_nodes[{item.id(), j->_id}];
ai_anim.mChannels[anim_idx] = new aiNodeAnim();
write_keyframes(ai_anim.mChannels[anim_idx],
std::string(node->mName.C_Str()), points, rotations,
scales);
anim_idx++;
});
}
} else if (item.is<Scene_Light>()) {
aiNode *node = item_nodes[item.id()];
std::string name(node->mName.C_Str());
name += "-LIGHT_ANIM_NODE";
const Scene_Light::Anim_Light &light = item.get<Scene_Light>().lanim;
if (light.splines.any()) {
std::unordered_map<float, Vec3> points, scales;
std::unordered_map<float, Quat> rotations;
std::set<float> keys = light.splines.keys();
for (float k : keys) {
auto [spec, inten, angle, size] = light.splines.at(k);
points[k] = Vec3{spec.r, spec.g, spec.b};
rotations[k] = Quat::euler(Vec3{angle.x, 0.0f, angle.y});
scales[k] = Vec3{inten + 1.0f, size.x + 1.0f, size.y + 1.0f};
}
ai_anim.mChannels[anim_idx] = new aiNodeAnim();
write_keyframes(ai_anim.mChannels[anim_idx], name, points, rotations, scales);
anim_idx++;
}
}
}
}
// Note: exporter/scene destructor should free everything
Assimp::Exporter exporter;
if (exporter.Export(&scene, "collada", file.c_str())) {
return std::string(exporter.GetErrorString());
}
return {};
}
#pragma once
#include <functional>
#include <map>
#include <optional>
#include <functional>
#include "../geometry/halfedge.h"
#include "../lib/mathlib.h"
#include "../util/camera.h"
#include "../platform/gl.h"
#include "../geometry/halfedge.h"
#include "../util/camera.h"
#include "light.h"
#include "object.h"
class Undo;
class Halfedge_Editor;
namespace Gui { class Manager; class Animate; }
namespace Gui {
class Manager;
class Animate;
} // namespace Gui
class Scene_Item {
public:
Scene_Item() = default;
Scene_Item(Scene_Object&& obj);
Scene_Item(Scene_Light&& light);
Scene_Item(Scene_Item&& src);
Scene_Item(const Scene_Item& src) = delete;
Scene_Item& operator=(Scene_Item&& src);
Scene_Item& operator=(const Scene_Item& src) = delete;
BBox bbox();
void render(const Mat4& view, bool solid = false, bool depth_only = false, bool posed = true);
Scene_ID id() const;
Pose& pose();
const Pose& pose() const;
Anim_Pose& animation();
const Anim_Pose& animation() const;
void set_time(float time);
std::string name() const;
std::pair<char*,int> name();
template<typename T>
bool is() const {
return std::holds_alternative<T>(data);
}
template<typename T>
T& get() {
return std::get<T>(data);
}
template<typename T>
const T& get() const {
return std::get<T>(data);
}
Scene_Item() = default;
Scene_Item(Scene_Object &&obj);
Scene_Item(Scene_Light &&light);
Scene_Item(Scene_Item &&src);
Scene_Item(const Scene_Item &src) = delete;
Scene_Item &operator=(Scene_Item &&src);
Scene_Item &operator=(const Scene_Item &src) = delete;
BBox bbox();
void render(const Mat4 &view, bool solid = false, bool depth_only = false, bool posed = true);
Scene_ID id() const;
Pose &pose();
const Pose &pose() const;
Anim_Pose &animation();
const Anim_Pose &animation() const;
void set_time(float time);
std::string name() const;
std::pair<char *, int> name();
template <typename T> bool is() const { return std::holds_alternative<T>(data); }
template <typename T> T &get() { return std::get<T>(data); }
template <typename T> const T &get() const { return std::get<T>(data); }
private:
std::variant<Scene_Object, Scene_Light> data;
std::variant<Scene_Object, Scene_Light> data;
};
using Scene_Maybe = std::optional<std::reference_wrapper<Scene_Item>>;
......@@ -66,34 +60,34 @@ public:
Scene(Scene_ID start);
~Scene() = default;
std::string write(std::string file, const Camera& cam, const Gui::Animate& animation);
std::string load(bool new_scene, Undo& undo, Gui::Manager& gui, std::string file);
void clear(Undo& undo);
std::string write(std::string file, const Camera &cam, const Gui::Animate &animation);
std::string load(bool new_scene, Undo &undo, Gui::Manager &gui, std::string file);
void clear(Undo &undo);
bool empty();
size_t size();
Scene_ID add(Scene_Object&& obj);
Scene_ID add(Scene_Light&& obj);
Scene_ID add(Pose pose, GL::Mesh&& mesh, std::string n = {}, Scene_ID id = 0);
Scene_ID add(Pose pose, Halfedge_Mesh&& mesh, std::string n = {}, Scene_ID id = 0);
Scene_ID reserve_id();
Scene_ID used_ids();
void erase(Scene_ID id);
void restore(Scene_ID id);
void for_items(std::function<void(Scene_Item&)> func);
void for_items(std::function<void(const Scene_Item&)> func) const;
Scene_ID add(Scene_Object &&obj);
Scene_ID add(Scene_Light &&obj);
Scene_ID add(Pose pose, GL::Mesh &&mesh, std::string n = {}, Scene_ID id = 0);
Scene_ID add(Pose pose, Halfedge_Mesh &&mesh, std::string n = {}, Scene_ID id = 0);
Scene_ID reserve_id();
Scene_ID used_ids();
void erase(Scene_ID id);
void restore(Scene_ID id);
void for_items(std::function<void(Scene_Item &)> func);
void for_items(std::function<void(const Scene_Item &)> func) const;
Scene_Maybe get(Scene_ID id);
Scene_Object& get_obj(Scene_ID id);
Scene_Light& get_light(Scene_ID id);
std::string set_env_map(std::string file);
Scene_Object &get_obj(Scene_ID id);
Scene_Light &get_light(Scene_ID id);
std::string set_env_map(std::string file);
bool has_env_light() const;
bool has_env_light() const;
private:
std::map<Scene_ID, Scene_Item> objs;
std::map<Scene_ID, Scene_Item> erased;
Scene_ID next_id, first_id;
std::map<Scene_ID, Scene_Item> objs;
std::map<Scene_ID, Scene_Item> erased;
Scene_ID next_id, first_id;
};
#include "skeleton.h"
#include "renderer.h"
#include "../gui/manager.h"
#include "renderer.h"
bool Joint::is_root() const {
return !parent;
}
bool Joint::is_root() const { return !parent; }
void Joint::for_joints(std::function<void(Joint*)> func) {
void Joint::for_joints(std::function<void(Joint *)> func) {
func(this);
for(Joint* j : children) j->for_joints(func);
for (Joint *j : children)
j->for_joints(func);
}
Skeleton::Skeleton() {
Skeleton::Skeleton() {
root_id = Gui::n_Widget_IDs;
next_id = Gui::n_Widget_IDs + 1;
}
Skeleton::Skeleton(unsigned int obj_id) {
Skeleton::Skeleton(unsigned int obj_id) {
root_id = obj_id + 1;
next_id = root_id + 1;
}
Skeleton::~Skeleton() {
for(Joint* j : roots) delete j;
for(Joint* j : erased) delete j;
Skeleton::~Skeleton() {
for (Joint *j : roots)
delete j;
for (Joint *j : erased)
delete j;
}
bool Skeleton::set_time(float time) {
bool ret = false;
for_joints([&ret, time](Joint* j) {
if(j->anim.any()) {
for_joints([&ret, time](Joint *j) {
if (j->anim.any()) {
j->pose = j->anim.at(time).to_euler();
ret = true;
}
......@@ -38,13 +39,14 @@ bool Skeleton::set_time(float time) {
return ret;
}
void Skeleton::for_joints(std::function<void(Joint*)> func) {
for(Joint* r : roots) r->for_joints(func);
void Skeleton::for_joints(std::function<void(Joint *)> func) {
for (Joint *r : roots)
r->for_joints(func);
}
Joint* Skeleton::add_root(Vec3 extent) {
Joint* j = new Joint(next_id++, nullptr, extent);
for(float f : keys()) {
Joint *Skeleton::add_root(Vec3 extent) {
Joint *j = new Joint(next_id++, nullptr, extent);
for (float f : keys()) {
j->anim.set(f, Quat{});
}
roots.insert(j);
......@@ -52,40 +54,39 @@ Joint* Skeleton::add_root(Vec3 extent) {
}
void Skeleton::crop(float t) {
for_joints([t](Joint* j) {
j->anim.crop(t);
});
for_joints([t](Joint *j) { j->anim.crop(t); });
}
Joint* Skeleton::get_joint(unsigned int id) {
Joint* j = nullptr;
for_joints([&](Joint* jt) {
if(jt->_id == id) j = jt;
Joint *Skeleton::get_joint(unsigned int id) {
Joint *j = nullptr;
for_joints([&](Joint *jt) {
if (jt->_id == id)
j = jt;
});
return j;
}
void Skeleton::render(const Mat4& view, Joint* select, bool root, bool posed, unsigned int offset) {
void Skeleton::render(const Mat4 &view, Joint *select, bool root, bool posed, unsigned int offset) {
Renderer& R = Renderer::get();
Renderer &R = Renderer::get();
Mat4 V = view * Mat4::translate(base_pos);
for_joints([&](Joint* j) {
for_joints([&](Joint *j) {
Renderer::MeshOpt opt;
opt.modelview = V * (posed ? j->joint_to_posed() : j->joint_to_bind()) *
Mat4::rotate_to(j->extent);
opt.modelview =
V * (posed ? j->joint_to_posed() : j->joint_to_bind()) * Mat4::rotate_to(j->extent);
opt.id = j->_id + offset;
opt.alpha = 0.8f;
opt.color = Gui::Color::hover;
R.capsule(opt, j->extent.norm(), j->radius);
});
if(select) {
if (select) {
R.begin_outline();
Mat4 model = Mat4::translate(base_pos) *
(posed ? select->joint_to_posed() : select->joint_to_bind()) *
Mat4::rotate_to(select->extent);
Mat4 model = Mat4::translate(base_pos) *
(posed ? select->joint_to_posed() : select->joint_to_bind()) *
Mat4::rotate_to(select->extent);
Renderer::MeshOpt opt;
opt.modelview = view;
......@@ -104,24 +105,25 @@ void Skeleton::render(const Mat4& view, Joint* select, bool root, bool posed, un
opt.color = root ? Gui::Color::outline : Gui::Color::hover;
R.sphere(opt);
for_joints([&](Joint* j) {
for_joints([&](Joint *j) {
Renderer::MeshOpt opt;
opt.modelview = V * (posed ? j->joint_to_posed() : j->joint_to_bind()) *
Mat4::translate(j->extent) * Mat4::scale(Vec3{j->radius * 0.25f});
opt.modelview = V * (posed ? j->joint_to_posed() : j->joint_to_bind()) *
Mat4::translate(j->extent) * Mat4::scale(Vec3{j->radius * 0.25f});
opt.id = j->_id + offset;
opt.color = select == j ? Gui::Color::outline : Gui::Color::hover;
R.sphere(opt);
});
}
void Skeleton::outline(const Mat4& view, Joint* select, bool root, bool posed, BBox& box, unsigned int offset) {
Renderer& R = Renderer::get();
void Skeleton::outline(const Mat4 &view, Joint *select, bool root, bool posed, BBox &box,
unsigned int offset) {
Renderer &R = Renderer::get();
Mat4 base_t = Mat4::translate(base_pos);
for_joints([&](Joint* j) {
Mat4 model = base_t * (posed ? j->joint_to_posed() : j->joint_to_bind()) * Mat4::rotate_to(j->extent);
for_joints([&](Joint *j) {
Mat4 model = base_t * (posed ? j->joint_to_posed() : j->joint_to_bind()) *
Mat4::rotate_to(j->extent);
Renderer::MeshOpt opt;
opt.modelview = view;
opt.id = j->_id + offset;
......@@ -131,39 +133,31 @@ void Skeleton::outline(const Mat4& view, Joint* select, bool root, bool posed, B
});
}
bool Skeleton::is_root_id(unsigned int id) {
return id == root_id;
}
bool Skeleton::is_root_id(unsigned int id) { return id == root_id; }
Joint* Skeleton::parent(Joint* j) {
return j->parent;
}
Joint *Skeleton::parent(Joint *j) { return j->parent; }
bool Skeleton::has_bones() const {
return !roots.empty();
}
bool Skeleton::has_bones() const { return !roots.empty(); }
unsigned int Skeleton::n_bones() {
unsigned int n = 0;
for_joints([&n](Joint*) { n++; });
for_joints([&n](Joint *) { n++; });
return n;
}
Vec3& Skeleton::base() {
return base_pos;
}
Vec3 &Skeleton::base() { return base_pos; }
Joint* Skeleton::add_child(Joint* j, Vec3 e) {
Joint* c = new Joint(next_id++, j, e);
for(float f : keys()) {
Joint *Skeleton::add_child(Joint *j, Vec3 e) {
Joint *c = new Joint(next_id++, j, e);
for (float f : keys()) {
c->anim.set(f, Quat{});
}
j->children.insert(c);
return c;
}
void Skeleton::restore(Joint* j) {
if(j->parent) {
void Skeleton::restore(Joint *j) {
if (j->parent) {
j->parent->children.insert(j);
} else {
roots.insert(j);
......@@ -171,8 +165,8 @@ void Skeleton::restore(Joint* j) {
erased.erase(j);
}
void Skeleton::erase(Joint* j) {
if(j->parent) {
void Skeleton::erase(Joint *j) {
if (j->parent) {
j->parent->children.erase(j);
} else {
roots.erase(j);
......@@ -180,45 +174,33 @@ void Skeleton::erase(Joint* j) {
erased.insert(j);
}
Vec3 Skeleton::posed_base_of(Joint* j) {
return j->is_root() ? base() : posed_end_of(parent(j));
}
Vec3 Skeleton::posed_base_of(Joint *j) { return j->is_root() ? base() : posed_end_of(parent(j)); }
Vec3 Skeleton::base_of(Joint* j) {
return j->is_root() ? base() : end_of(parent(j));
}
Vec3 Skeleton::base_of(Joint *j) { return j->is_root() ? base() : end_of(parent(j)); }
void Skeleton::set(float t) {
for_joints([t](Joint* j) {
j->anim.set(t, Quat::euler(j->pose));
});
for_joints([t](Joint *j) { j->anim.set(t, Quat::euler(j->pose)); });
}
void Skeleton::erase(float t) {
for_joints([t](Joint* j) {
j->anim.erase(t);
});
for_joints([t](Joint *j) { j->anim.erase(t); });
}
bool Skeleton::has_keyframes() {
bool frame = false;
for_joints([&frame](Joint* j) {
frame = frame || j->anim.any();
});
for_joints([&frame](Joint *j) { frame = frame || j->anim.any(); });
return frame;
}
std::unordered_map<unsigned int, Vec3> Skeleton::now() {
std::unordered_map<unsigned int, Vec3> ret;
for_joints([&ret](Joint* j) {
ret[j->_id] = j->pose;
});
for_joints([&ret](Joint *j) { ret[j->_id] = j->pose; });
return ret;
}
std::set<float> Skeleton::keys() {
std::set<float> ret;
for_joints([&ret](Joint* j) {
for_joints([&ret](Joint *j) {
std::set<float> k = j->anim.keys();
ret.insert(k.begin(), k.end());
});
......@@ -227,14 +209,10 @@ std::set<float> Skeleton::keys() {
std::unordered_map<unsigned int, Vec3> Skeleton::at(float t) {
std::unordered_map<unsigned int, Vec3> ret;
for_joints([&ret, t](Joint* j) {
ret[j->_id] = j->anim.at(t).to_euler();
});
for_joints([&ret, t](Joint *j) { ret[j->_id] = j->anim.at(t).to_euler(); });
return ret;
}
void Skeleton::set(float t, const std::unordered_map<unsigned int, Vec3>& data) {
for_joints([&data, t](Joint* j) {
j->anim.set(t, Quat::euler(data.at(j->_id)));
});
void Skeleton::set(float t, const std::unordered_map<unsigned int, Vec3> &data) {
for_joints([&data, t](Joint *j) { j->anim.set(t, Quat::euler(data.at(j->_id))); });
}
#pragma once
#include <functional>
#include <set>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <functional>
#include <vector>
#include "../lib/mathlib.h"
#include "../geometry/spline.h"
#include "../lib/mathlib.h"
#include "../platform/gl.h"
class Joint {
public:
Joint(unsigned int id) : _id(id) {}
Joint(unsigned int id, Joint* parent, Vec3 extent) : _id(id), parent(parent), extent(extent) {}
~Joint() { for(Joint* j : children) delete j; }
Joint(unsigned int id, Joint *parent, Vec3 extent) : _id(id), parent(parent), extent(extent) {}
~Joint() {
for (Joint *j : children)
delete j;
}
Joint(const Joint& src) = delete;
Joint(Joint&& src) = default;
Joint(const Joint &src) = delete;
Joint(Joint &&src) = default;
void operator=(const Joint& src) = delete;
Joint& operator=(Joint&& src) = default;
void operator=(const Joint &src) = delete;
Joint &operator=(Joint &&src) = default;
unsigned int id() const { return _id; }
// Checks if this joint is a root node
bool is_root() const;
// Euler angles representing the current joint rotation
Vec3 pose;
// The vector representing the direction and length of the bone.
// This is specified in Joint space, and defines the origin of child bones.
// This is specified in Joint space, and defines the origin of child bones.
Vec3 extent = Vec3(0.0f, 1.0f, 0.0f);
// The distance at which the joint segment should stop effecting vertices
float radius = 0.25f;
......@@ -51,12 +54,12 @@ private:
Mat4 joint_to_posed() const;
// Pointer to parent joint in the joint heirarchy
Joint* parent = nullptr;
Joint *parent = nullptr;
// Set of child joints - owned by this joint (could be shared_ptr and everything else weak_ptr)
std::unordered_set<Joint*> children;
std::unordered_set<Joint *> children;
void for_joints(std::function<void(Joint*)> func);
void for_joints(std::function<void(Joint *)> func);
unsigned int _id = 0;
Spline<Quat> anim;
......@@ -71,39 +74,42 @@ public:
Skeleton(unsigned int obj_id);
~Skeleton();
Skeleton(const Skeleton& src) = delete;
Skeleton(Skeleton&& src) = default;
Skeleton(const Skeleton &src) = delete;
Skeleton(Skeleton &&src) = default;
void operator=(const Skeleton& src) = delete;
Skeleton& operator=(Skeleton&& src) = default;
void operator=(const Skeleton &src) = delete;
Skeleton &operator=(Skeleton &&src) = default;
Vec3& base();
Vec3 &base();
bool has_bones() const;
unsigned int n_bones();
Joint* parent(Joint* j);
Joint* get_joint(unsigned int id);
void erase(Joint* j);
void restore(Joint* j);
Vec3 end_of(Joint* j);
Vec3 base_of(Joint* j);
Vec3 posed_end_of(Joint* j);
Vec3 posed_base_of(Joint* j);
void for_joints(std::function<void(Joint*)> func);
Mat4 joint_to_bind(const Joint* j) const;
Mat4 joint_to_posed(const Joint* j) const;
Joint* add_root(Vec3 extent);
Joint* add_child(Joint* j, Vec3 extent);
Joint *parent(Joint *j);
Joint *get_joint(unsigned int id);
void erase(Joint *j);
void restore(Joint *j);
Vec3 end_of(Joint *j);
Vec3 base_of(Joint *j);
Vec3 posed_end_of(Joint *j);
Vec3 posed_base_of(Joint *j);
void for_joints(std::function<void(Joint *)> func);
Mat4 joint_to_bind(const Joint *j) const;
Mat4 joint_to_posed(const Joint *j) const;
Joint *add_root(Vec3 extent);
Joint *add_child(Joint *j, Vec3 extent);
bool is_root_id(unsigned int id);
bool set_time(float time);
void render(const Mat4& view, Joint* select, bool root, bool posed, unsigned int offset = 0);
void outline(const Mat4& view, Joint* select, bool root, bool posed, BBox& box, unsigned int offset = 0);
void find_joints(const GL::Mesh& src, std::unordered_map<unsigned int, std::vector<Joint*>>& map);
void skin(const GL::Mesh& input, GL::Mesh& output, const std::unordered_map<unsigned int, std::vector<Joint*>>& map);
void render(const Mat4 &view, Joint *select, bool root, bool posed, unsigned int offset = 0);
void outline(const Mat4 &view, Joint *select, bool root, bool posed, BBox &box,
unsigned int offset = 0);
void find_joints(const GL::Mesh &src,
std::unordered_map<unsigned int, std::vector<Joint *>> &map);
void skin(const GL::Mesh &input, GL::Mesh &output,
const std::unordered_map<unsigned int, std::vector<Joint *>> &map);
void set(float t);
void crop(float t);
......@@ -112,11 +118,11 @@ public:
std::set<float> keys();
std::unordered_map<unsigned int, Vec3> now();
std::unordered_map<unsigned int, Vec3> at(float t);
void set(float t, const std::unordered_map<unsigned int, Vec3>& data);
void set(float t, const std::unordered_map<unsigned int, Vec3> &data);
private:
Vec3 base_pos;
unsigned int root_id, next_id;
std::unordered_set<Joint*> roots, erased;
std::unordered_set<Joint *> roots, erased;
friend class Scene;
};
#include "undo.h"
#include "../gui/manager.h"
#include "../gui/animate.h"
#include "../gui/manager.h"
#include "../gui/rig.h"
Undo::Undo(Scene& sc, Gui::Manager& man)
: scene(sc), gui(man) {}
Undo::Undo(Scene &sc, Gui::Manager &man) : scene(sc), gui(man) {}
void Undo::reset() {
undos = {};
redos = {};
}
template<typename R, typename U>
class Action : public Action_Base {
template <typename R, typename U> class Action : public Action_Base {
public:
Action(R&& r, U&& u) : _undo(std::forward<decltype(u)>(u)), _redo(std::forward<decltype(r)>(r)) {};
Action(R &&r, U &&u)
: _undo(std::forward<decltype(u)>(u)), _redo(std::forward<decltype(r)>(r)){};
~Action() {}
private:
U _undo;
R _redo;
void undo() {_undo();}
void redo() {_redo();}
void undo() { _undo(); }
void redo() { _redo(); }
};
template<typename R, typename U>
void Undo::action(R&& redo, U&& undo) {
action(std::make_unique<Action<R,U>>(std::move(redo), std::move(undo)));
template <typename R, typename U> void Undo::action(R &&redo, U &&undo) {
action(std::make_unique<Action<R, U>>(std::move(redo), std::move(undo)));
}
void Undo::update_mesh_full(Scene_ID id, Halfedge_Mesh&& old_mesh) {
Scene_Object& obj = scene.get_obj(id);
void Undo::update_mesh_full(Scene_ID id, Halfedge_Mesh &&old_mesh) {
Scene_Object &obj = scene.get_obj(id);
Halfedge_Mesh new_mesh;
obj.copy_mesh(new_mesh);
action([id, this, nm=std::move(new_mesh)]() mutable {
Scene_Object& obj = scene.get_obj(id);
obj.set_mesh(nm);
}, [id, this, om=std::move(old_mesh)]() mutable {
Scene_Object& obj = scene.get_obj(id);
obj.set_mesh(om);
});
action(
[id, this, nm = std::move(new_mesh)]() mutable {
Scene_Object &obj = scene.get_obj(id);
obj.set_mesh(nm);
},
[id, this, om = std::move(old_mesh)]() mutable {
Scene_Object &obj = scene.get_obj(id);
obj.set_mesh(om);
});
}
void Undo::move_root(Scene_ID id, Vec3 old) {
Scene_Object& obj = scene.get_obj(id);
Scene_Object &obj = scene.get_obj(id);
Vec3 np = obj.armature.base();
action([this, id, np]() {
Scene_Object& obj = scene.get_obj(id);
obj.armature.base() = np;
obj.set_skel_dirty();
}, [this, id, op=old](){
Scene_Object& obj = scene.get_obj(id);
obj.armature.base() = op;
obj.set_skel_dirty();
});
action(
[this, id, np]() {
Scene_Object &obj = scene.get_obj(id);
obj.armature.base() = np;
obj.set_skel_dirty();
},
[this, id, op = old]() {
Scene_Object &obj = scene.get_obj(id);
obj.armature.base() = op;
obj.set_skel_dirty();
});
}
void Undo::del_bone(Scene_ID id, Joint* j) {
Scene_Object& obj = scene.get_obj(id);
void Undo::del_bone(Scene_ID id, Joint *j) {
Scene_Object &obj = scene.get_obj(id);
obj.armature.erase(j);
obj.set_skel_dirty();
action([this, id, j]() {
Scene_Object& obj = scene.get_obj(id);
obj.armature.erase(j);
gui.get_rig().invalidate(j);
obj.set_skel_dirty();
}, [this, id, j](){
Scene_Object& obj = scene.get_obj(id);
obj.armature.restore(j);
obj.set_skel_dirty();
});
action(
[this, id, j]() {
Scene_Object &obj = scene.get_obj(id);
obj.armature.erase(j);
gui.get_rig().invalidate(j);
obj.set_skel_dirty();
},
[this, id, j]() {
Scene_Object &obj = scene.get_obj(id);
obj.armature.restore(j);
obj.set_skel_dirty();
});
}
void Undo::move_bone(Scene_ID id, Joint* j, Vec3 old) {
action([this, id, j, ne=j->extent](){
Scene_Object& obj = scene.get_obj(id);
j->extent = ne;
obj.set_skel_dirty();
}, [this, id, j, oe=old](){
Scene_Object& obj = scene.get_obj(id);
j->extent = oe;
obj.set_skel_dirty();
});
void Undo::move_bone(Scene_ID id, Joint *j, Vec3 old) {
action(
[this, id, j, ne = j->extent]() {
Scene_Object &obj = scene.get_obj(id);
j->extent = ne;
obj.set_skel_dirty();
},
[this, id, j, oe = old]() {
Scene_Object &obj = scene.get_obj(id);
j->extent = oe;
obj.set_skel_dirty();
});
}
void Undo::pose_bone(Scene_ID id, Joint* j, Vec3 old) {
action([this, id, j, ne=j->pose](){
Scene_Object& obj = scene.get_obj(id);
j->pose = ne;
obj.set_pose_dirty();
}, [this, id, j, oe=old](){
Scene_Object& obj = scene.get_obj(id);
j->pose = oe;
obj.set_pose_dirty();
});
void Undo::pose_bone(Scene_ID id, Joint *j, Vec3 old) {
action(
[this, id, j, ne = j->pose]() {
Scene_Object &obj = scene.get_obj(id);
j->pose = ne;
obj.set_pose_dirty();
},
[this, id, j, oe = old]() {
Scene_Object &obj = scene.get_obj(id);
j->pose = oe;
obj.set_pose_dirty();
});
}
void Undo::add_bone(Scene_ID id, Joint* j) {
action([this, id, j](){
Scene_Object& obj = scene.get_obj(id);
obj.armature.restore(j);
obj.set_skel_dirty();
}, [this, id, j]() {
Scene_Object& obj = scene.get_obj(id);
obj.armature.erase(j);
gui.get_rig().invalidate(j);
obj.set_skel_dirty();
});
void Undo::add_bone(Scene_ID id, Joint *j) {
action(
[this, id, j]() {
Scene_Object &obj = scene.get_obj(id);
obj.armature.restore(j);
obj.set_skel_dirty();
},
[this, id, j]() {
Scene_Object &obj = scene.get_obj(id);
obj.armature.erase(j);
gui.get_rig().invalidate(j);
obj.set_skel_dirty();
});
}
void Undo::del_obj(Scene_ID id) {
scene.erase(id);
gui.invalidate_obj(id);
action([id, this](){
scene.erase(id);
gui.invalidate_obj(id);
}, [id, this](){
scene.restore(id);
});
action(
[id, this]() {
scene.erase(id);
gui.invalidate_obj(id);
},
[id, this]() { scene.restore(id); });
}
Scene_Object& Undo::add_obj(GL::Mesh&& mesh, std::string name) {
Scene_Object &Undo::add_obj(GL::Mesh &&mesh, std::string name) {
Scene_ID id = scene.add({}, std::move(mesh), name);
scene.restore(id);
action([id, this](){
scene.restore(id);
}, [id, this](){
scene.erase(id);
gui.invalidate_obj(id);
});
action([id, this]() { scene.restore(id); },
[id, this]() {
scene.erase(id);
gui.invalidate_obj(id);
});
return scene.get_obj(id);
}
Scene_Object& Undo::add_obj(Halfedge_Mesh&& mesh, std::string name) {
Scene_Object &Undo::add_obj(Halfedge_Mesh &&mesh, std::string name) {
Scene_ID id = scene.add({}, std::move(mesh), name);
scene.restore(id);
action([id, this](){
scene.restore(id);
}, [id, this](){
scene.erase(id);
gui.invalidate_obj(id);
});
action([id, this]() { scene.restore(id); },
[id, this]() {
scene.erase(id);
gui.invalidate_obj(id);
});
return scene.get_obj(id);
}
void Undo::add_light(Scene_Light&& light) {
void Undo::add_light(Scene_Light &&light) {
Scene_ID id = scene.add(std::move(light));
scene.restore(id);
action([id, this](){
scene.restore(id);
}, [id, this](){
scene.erase(id);
gui.invalidate_obj(id);
});
action([id, this]() { scene.restore(id); },
[id, this]() {
scene.erase(id);
gui.invalidate_obj(id);
});
}
void Undo::update_light(Scene_ID id, Scene_Light::Options old) {
Scene_Light& light = scene.get_light(id);
action([id, this, no=light.opt](){
Scene_Light& light = scene.get_light(id);
light.opt = no;
light.dirty();
}, [id, this, oo=old](){
Scene_Light& light = scene.get_light(id);
light.opt = oo;
light.dirty();
});
Scene_Light &light = scene.get_light(id);
action(
[id, this, no = light.opt]() {
Scene_Light &light = scene.get_light(id);
light.opt = no;
light.dirty();
},
[id, this, oo = old]() {
Scene_Light &light = scene.get_light(id);
light.opt = oo;
light.dirty();
});
}
void Undo::update_material(Scene_ID id, Material::Options old) {
Scene_Object& obj = scene.get_obj(id);
Scene_Object &obj = scene.get_obj(id);
action([id, this, nm=obj.material.opt](){
Scene_Object& obj = scene.get_obj(id);
obj.material.opt = nm;
}, [id, this, om=old](){
Scene_Object& obj = scene.get_obj(id);
obj.material.opt = om;
});
action(
[id, this, nm = obj.material.opt]() {
Scene_Object &obj = scene.get_obj(id);
obj.material.opt = nm;
},
[id, this, om = old]() {
Scene_Object &obj = scene.get_obj(id);
obj.material.opt = om;
});
}
void Undo::update_object(Scene_ID id, Scene_Object::Options old) {
Scene_Object& obj = scene.get_obj(id);
Scene_Object &obj = scene.get_obj(id);
if (obj.opt.shape_type != PT::Shape_Type::none && old.shape_type == PT::Shape_Type::none) {
if(obj.opt.shape_type != PT::Shape_Type::none &&
old.shape_type == PT::Shape_Type::none) {
Halfedge_Mesh old_mesh;
obj.copy_mesh(old_mesh);
action([id, this, no=obj.opt](){
Scene_Object& obj = scene.get_obj(id);
obj.opt = no;
obj.set_mesh_dirty();
}, [id, this, oo=old, om=std::move(old_mesh)]() mutable {
Scene_Object& obj = scene.get_obj(id);
obj.opt = oo;
obj.set_mesh(om);
});
action(
[id, this, no = obj.opt]() {
Scene_Object &obj = scene.get_obj(id);
obj.opt = no;
obj.set_mesh_dirty();
},
[id, this, oo = old, om = std::move(old_mesh)]() mutable {
Scene_Object &obj = scene.get_obj(id);
obj.opt = oo;
obj.set_mesh(om);
});
return;
}
if(obj.opt.shape_type == PT::Shape_Type::none &&
old.shape_type != PT::Shape_Type::none) {
if (obj.opt.shape_type == PT::Shape_Type::none && old.shape_type != PT::Shape_Type::none) {
action([id, this, no=obj.opt, ot=old.shape_type](){
Scene_Object& obj = scene.get_obj(id);
obj.opt = no;
obj.try_make_editable(ot);
}, [id, this, oo=old]() {
Scene_Object& obj = scene.get_obj(id);
obj.opt = oo;
obj.set_mesh_dirty();
});
action(
[id, this, no = obj.opt, ot = old.shape_type]() {
Scene_Object &obj = scene.get_obj(id);
obj.opt = no;
obj.try_make_editable(ot);
},
[id, this, oo = old]() {
Scene_Object &obj = scene.get_obj(id);
obj.opt = oo;
obj.set_mesh_dirty();
});
return;
}
action([id, this, no=obj.opt](){
Scene_Object& obj = scene.get_obj(id);
obj.opt = no;
obj.set_mesh_dirty();
}, [id, this, oo=old](){
Scene_Object& obj = scene.get_obj(id);
obj.opt = oo;
obj.set_mesh_dirty();
});
action(
[id, this, no = obj.opt]() {
Scene_Object &obj = scene.get_obj(id);
obj.opt = no;
obj.set_mesh_dirty();
},
[id, this, oo = old]() {
Scene_Object &obj = scene.get_obj(id);
obj.opt = oo;
obj.set_mesh_dirty();
});
}
void Undo::update_pose(Scene_ID id, Pose old) {
Scene_Item& item = *scene.get(id);
action([id, this, np=item.pose()](){
Scene_Item& item = *scene.get(id);
item.pose() = np;
}, [id, this, op=old](){
Scene_Item& item = *scene.get(id);
item.pose() = op;
});
Scene_Item &item = *scene.get(id);
action(
[id, this, np = item.pose()]() {
Scene_Item &item = *scene.get(id);
item.pose() = np;
},
[id, this, op = old]() {
Scene_Item &item = *scene.get(id);
item.pose() = op;
});
}
void Undo::update_camera(Gui::Widget_Camera& widget, Camera old) {
void Undo::update_camera(Gui::Widget_Camera &widget, Camera old) {
Camera newc = widget.get();
action([&widget, nc=newc](){
widget.load(nc.center(), nc.pos(), nc.get_ar(), nc.get_fov());
}, [&widget, oc=old](){
widget.load(oc.center(), oc.pos(), oc.get_ar(), oc.get_fov());
});
action(
[&widget, nc = newc]() { widget.load(nc.center(), nc.pos(), nc.get_ar(), nc.get_fov()); },
[&widget, oc = old]() { widget.load(oc.center(), oc.pos(), oc.get_ar(), oc.get_fov()); });
}
void Undo::anim_pose_bones(Scene_ID id, float t) {
Scene_Object& obj = scene.get_obj(id);
Scene_Object &obj = scene.get_obj(id);
bool had = obj.anim.splines.has(t) || obj.armature.has_keyframes();
Pose old_pose = obj.anim.at(t);
Pose new_pose = obj.pose;
auto old_joints = obj.armature.at(t);
......@@ -278,45 +293,51 @@ void Undo::anim_pose_bones(Scene_ID id, float t) {
obj.anim.set(t, new_pose);
obj.armature.set(t);
action([=](){
Scene_Object& obj = scene.get_obj(id);
obj.anim.set(t, new_pose);
obj.armature.set(t, new_joints);
gui.get_animate().refresh(scene);
}, [=](){
Scene_Object& obj = scene.get_obj(id);
if(had) {
obj.anim.set(t, old_pose);
obj.armature.set(t, old_joints);
} else {
obj.anim.splines.erase(t);
obj.armature.erase(t);
}
});
action(
[=]() {
Scene_Object &obj = scene.get_obj(id);
obj.anim.set(t, new_pose);
obj.armature.set(t, new_joints);
gui.get_animate().refresh(scene);
},
[=]() {
Scene_Object &obj = scene.get_obj(id);
if (had) {
obj.anim.set(t, old_pose);
obj.armature.set(t, old_joints);
} else {
obj.anim.splines.erase(t);
obj.armature.erase(t);
}
});
}
void Undo::anim_pose(Scene_ID id, float t) {
Scene_Item& item = *scene.get(id);
Scene_Item &item = *scene.get(id);
bool had = item.animation().splines.has(t);
Pose old = item.animation().at(t);
Pose p = item.pose();
item.animation().set(t, p);
action([=](){
Scene_Item& item = *scene.get(id);
item.animation().set(t, p);
gui.get_animate().refresh(scene);
}, [=](){
Scene_Item& item = *scene.get(id);
if(had) item.animation().set(t, old);
else item.animation().splines.erase(t);
});
action(
[=]() {
Scene_Item &item = *scene.get(id);
item.animation().set(t, p);
gui.get_animate().refresh(scene);
},
[=]() {
Scene_Item &item = *scene.get(id);
if (had)
item.animation().set(t, old);
else
item.animation().splines.erase(t);
});
}
void Undo::anim_camera(Gui::Anim_Camera& anim, float t, const Camera& cam) {
void Undo::anim_camera(Gui::Anim_Camera &anim, float t, const Camera &cam) {
bool had = anim.splines.has(t);
Camera oldc = anim.at(t);
......@@ -324,22 +345,26 @@ void Undo::anim_camera(Gui::Anim_Camera& anim, float t, const Camera& cam) {
anim.set(t, newc);
action([=, &anim](){
anim.set(t, newc);
gui.get_animate().refresh(scene);
}, [=, &anim](){
if(had) anim.set(t, oldc);
else anim.splines.erase(t);
});
action(
[=, &anim]() {
anim.set(t, newc);
gui.get_animate().refresh(scene);
},
[=, &anim]() {
if (had)
anim.set(t, oldc);
else
anim.splines.erase(t);
});
}
void Undo::anim_light(Scene_ID id, float t) {
Scene_Light& item = scene.get_light(id);
Scene_Light &item = scene.get_light(id);
bool had_l = item.lanim.splines.has(t);
bool had_p = item.anim.splines.has(t);
Pose old_pose = item.anim.at(t);
Pose new_pose = item.pose;
......@@ -350,35 +375,43 @@ void Undo::anim_light(Scene_ID id, float t) {
item.anim.set(t, new_pose);
item.lanim.set(t, new_l);
action([=](){
Scene_Light& item = scene.get_light(id);
item.lanim.set(t, new_l);
item.anim.set(t, new_pose);
gui.get_animate().refresh(scene);
}, [=](){
Scene_Light& item = scene.get_light(id);
if(had_l) item.lanim.set(t, old_l);
else item.lanim.splines.erase(t);
if(had_p) item.anim.set(t, old_pose);
else item.anim.splines.erase(t);
item.dirty();
});
action(
[=]() {
Scene_Light &item = scene.get_light(id);
item.lanim.set(t, new_l);
item.anim.set(t, new_pose);
gui.get_animate().refresh(scene);
},
[=]() {
Scene_Light &item = scene.get_light(id);
if (had_l)
item.lanim.set(t, old_l);
else
item.lanim.splines.erase(t);
if (had_p)
item.anim.set(t, old_pose);
else
item.anim.splines.erase(t);
item.dirty();
});
}
void Undo::action(std::unique_ptr<Action_Base>&& action) {
void Undo::action(std::unique_ptr<Action_Base> &&action) {
redos = {};
undos.push(std::move(action));
}
void Undo::undo() {
if (undos.empty()) return;
if (undos.empty())
return;
undos.top()->undo();
redos.push(std::move(undos.top()));
undos.pop();
}
void Undo::redo() {
if(redos.empty()) return;
if (redos.empty())
return;
redos.top()->redo();
undos.push(std::move(redos.top()));
redos.pop();
......
......@@ -4,76 +4,80 @@
#include <memory>
#include <stack>
#include "scene.h"
#include "../gui/widgets.h"
#include "scene.h"
namespace Gui { class Manager; class Anim_Camera; class Rig; }
namespace Gui {
class Manager;
class Anim_Camera;
class Rig;
} // namespace Gui
class Action_Base {
virtual void undo() = 0;
virtual void redo() = 0;
friend class Undo;
public:
virtual ~Action_Base() = default;
};
template<typename T>
class MeshOp : public Action_Base {
template <typename T> class MeshOp : public Action_Base {
void undo() {
Scene_Object& obj = scene.get_obj(id);
Scene_Object &obj = scene.get_obj(id);
obj.set_mesh(mesh);
}
void redo() {
Scene_Object& obj = scene.get_obj(id);
Scene_Object &obj = scene.get_obj(id);
auto sel = obj.set_mesh(mesh, eid);
op(obj.get_mesh(), sel);
}
Scene& scene;
Scene &scene;
Scene_ID id;
unsigned int eid;
T op;
Halfedge_Mesh mesh;
public:
MeshOp(Scene& s, Scene_ID i, unsigned int e, Halfedge_Mesh&& m, T&& t) :
scene(s), id(i), eid(e), op(t), mesh(std::move(m)) {}
MeshOp(Scene &s, Scene_ID i, unsigned int e, Halfedge_Mesh &&m, T &&t)
: scene(s), id(i), eid(e), op(t), mesh(std::move(m)) {}
~MeshOp() = default;
};
class Undo {
public:
Undo(Scene& scene, Gui::Manager& man);
Undo(Scene &scene, Gui::Manager &man);
// These could just take Scene_Object&& but this was easier
Scene_Object& add_obj(GL::Mesh&& mesh, std::string name);
Scene_Object& add_obj(Halfedge_Mesh&& mesh, std::string name);
void add_light(Scene_Light&& mesh);
Scene_Object &add_obj(GL::Mesh &&mesh, std::string name);
Scene_Object &add_obj(Halfedge_Mesh &&mesh, std::string name);
void add_light(Scene_Light &&mesh);
void del_obj(Scene_ID id);
void update_pose(Scene_ID id, Pose old);
void del_bone(Scene_ID id, Joint* j);
void add_bone(Scene_ID id, Joint* j);
void move_bone(Scene_ID id, Joint* j, Vec3 old);
void pose_bone(Scene_ID id, Joint* j, Vec3 old);
void del_bone(Scene_ID id, Joint *j);
void add_bone(Scene_ID id, Joint *j);
void move_bone(Scene_ID id, Joint *j, Vec3 old);
void pose_bone(Scene_ID id, Joint *j, Vec3 old);
void move_root(Scene_ID id, Vec3 old);
void update_light(Scene_ID id, Scene_Light::Options old);
void update_object(Scene_ID id, Scene_Object::Options old);
void update_material(Scene_ID id, Material::Options old);
void update_camera(Gui::Widget_Camera& widget, Camera old);
void update_camera(Gui::Widget_Camera &widget, Camera old);
template<typename T>
void update_mesh(Scene_ID id, Halfedge_Mesh&& old, unsigned int e_id, T&& op) {
template <typename T>
void update_mesh(Scene_ID id, Halfedge_Mesh &&old, unsigned int e_id, T &&op) {
std::stack<std::unique_ptr<Action_Base>> empty;
redos.swap(empty);
undos.push(std::make_unique<MeshOp<T>>(scene, id, e_id, std::move(old), std::move(op)));
}
void update_mesh_full(Scene_ID id, Halfedge_Mesh&& old_mesh);
void update_mesh_full(Scene_ID id, Halfedge_Mesh &&old_mesh);
void anim_pose(Scene_ID id, float t);
void anim_pose_bones(Scene_ID id, float t);
void anim_camera(Gui::Anim_Camera& anim, float t, const Camera& cam);
void anim_camera(Gui::Anim_Camera &anim, float t, const Camera &cam);
void anim_light(Scene_ID id, float t);
void undo();
......@@ -81,13 +85,12 @@ public:
void reset();
private:
Scene& scene;
Gui::Manager& gui;
Scene &scene;
Gui::Manager &gui;
template <typename R, typename U> void action(R &&redo, U &&undo);
void action(std::unique_ptr<Action_Base> &&action);
template<typename R, typename U>
void action(R&& redo, U&& undo);
void action(std::unique_ptr<Action_Base>&& action);
std::stack<std::unique_ptr<Action_Base>> undos;
std::stack<std::unique_ptr<Action_Base>> redos;
};
#include "debug.h"
#include "../lib/mathlib.h"
#include "debug.h"
bool BBox::hit(const Ray &ray, Vec2 &times) const {
bool BBox::hit(const Ray& ray, Vec2& times) const {
// TODO (PathTracer):
// Implement ray - bounding box intersection test
// If the ray intersected the bounding box within the range given by
......
#include "debug.h"
#include "../rays/bsdf.h"
#include "../util/rand.h"
#include "debug.h"
namespace PT {
......@@ -12,14 +12,14 @@ Vec3 reflect(Vec3 dir) {
return Vec3();
}
Vec3 refract(Vec3 out_dir, float index_of_refraction, bool& was_internal) {
Vec3 refract(Vec3 out_dir, float index_of_refraction, bool &was_internal) {
// TODO (PathTracer): Task 6
// Use Snell's Law to refract out_dir through the surface
// Return the refracted direction. Set was_internal to false if
// Return the refracted direction. Set was_internal to false if
// refraction does not occur due to total internal reflection,
// and true otherwise.
// and true otherwise.
// When dot(out_dir,normal=(0,1,0)) is positive, then out_dir corresponds to a
// ray exiting the surface into vaccum (ior = 1). However, note that
// you should actually treat this case as _entering_ the surface, because
......@@ -30,14 +30,14 @@ Vec3 refract(Vec3 out_dir, float index_of_refraction, bool& was_internal) {
}
BSDF_Sample BSDF_Lambertian::sample(Vec3 out_dir) const {
// TODO (PathTracer): Task 5
// Implement lambertian BSDF. Use of BSDF_Lambertian::sampler may be useful
BSDF_Sample ret;
ret.attenuation = Spectrum(); // What is the ratio of reflected/incoming light?
ret.direction = Vec3(); // What direction should we sample incoming light from?
ret.pdf = 0.0f; // Was was the PDF of the sampled direction?
ret.direction = Vec3(); // What direction should we sample incoming light from?
ret.pdf = 0.0f; // Was was the PDF of the sampled direction?
return ret;
}
......@@ -46,13 +46,13 @@ Spectrum BSDF_Lambertian::evaluate(Vec3 out_dir, Vec3 in_dir) const {
}
BSDF_Sample BSDF_Mirror::sample(Vec3 out_dir) const {
// TODO (PathTracer): Task 6
// Implement mirror BSDF
BSDF_Sample ret;
ret.attenuation = Spectrum(); // What is the ratio of reflected/incoming light?
ret.direction = Vec3(); // What direction should we sample incoming light from?
ret.direction = Vec3(); // What direction should we sample incoming light from?
ret.pdf = 0.0f; // Was was the PDF of the sampled direction? (In this case, the PMF)
return ret;
}
......@@ -60,16 +60,16 @@ BSDF_Sample BSDF_Mirror::sample(Vec3 out_dir) const {
Spectrum BSDF_Mirror::evaluate(Vec3 out_dir, Vec3 in_dir) const {
// Technically, we would return the proper reflectance
// if in_dir was the perfectly reflected out_dir, but given
// that we assume these are single exact directions in a
// continuous space, just assume that we never hit them
// _exactly_ and always return 0.
// that we assume these are single exact directions in a
// continuous space, just assume that we never hit them
// _exactly_ and always return 0.
return {};
}
BSDF_Sample BSDF_Glass::sample(Vec3 out_dir) const {
// TODO (PathTracer): Task 6
// Implement glass BSDF.
// (1) Compute Fresnel coefficient. Tip: use Schlick's approximation.
// (2) Reflect or refract probabilistically based on Fresnel coefficient. Tip: RNG::coin_flip
......@@ -79,14 +79,14 @@ BSDF_Sample BSDF_Glass::sample(Vec3 out_dir) const {
BSDF_Sample ret;
ret.attenuation = Spectrum(); // What is the ratio of reflected/incoming light?
ret.direction = Vec3(); // What direction should we sample incoming light from?
ret.direction = Vec3(); // What direction should we sample incoming light from?
ret.pdf = 0.0f; // Was was the PDF of the sampled direction? (In this case, the PMF)
return ret;
}
Spectrum BSDF_Glass::evaluate(Vec3 out_dir, Vec3 in_dir) const {
// As with BSDF_Mirror, just assume that we never hit the correct
// directions _exactly_ and always return 0.
// directions _exactly_ and always return 0.
return {};
}
......@@ -104,23 +104,23 @@ Spectrum BSDF_Diffuse::evaluate(Vec3 out_dir, Vec3 in_dir) const {
}
BSDF_Sample BSDF_Refract::sample(Vec3 out_dir) const {
// TODO (PathTracer): Task 6
// TODO (PathTracer): Task 6
// Implement pure refraction BSDF.
// Be wary of your eta1/eta2 ratio - are you entering or leaving the surface?
BSDF_Sample ret;
ret.attenuation = Spectrum(); // What is the ratio of reflected/incoming light?
ret.direction = Vec3(); // What direction should we sample incoming light from?
ret.direction = Vec3(); // What direction should we sample incoming light from?
ret.pdf = 0.0f; // Was was the PDF of the sampled direction? (In this case, the PMF)
return ret;
}
Spectrum BSDF_Refract::evaluate(Vec3 out_dir, Vec3 in_dir) const {
// As with BSDF_Mirror, just assume that we never hit the correct
// directions _exactly_ and always return 0.
// directions _exactly_ and always return 0.
return {};
}
}
} // namespace PT
#include "debug.h"
#include "../rays/bvh.h"
#include "debug.h"
#include <stack>
namespace PT {
template<typename Primitive>
void BVH<Primitive>::build(std::vector<Primitive>&& prims, size_t max_leaf_size) {
template <typename Primitive>
void BVH<Primitive>::build(std::vector<Primitive> &&prims, size_t max_leaf_size) {
// NOTE (PathTracer):
// This BVH is parameterized on the type of the primitive it contains. This allows
......@@ -37,12 +37,12 @@ void BVH<Primitive>::build(std::vector<Primitive>&& prims, size_t max_leaf_size)
// primitives.
BBox box;
for(const Primitive& prim : primitives) box.enclose(prim.bbox());
for (const Primitive &prim : primitives)
box.enclose(prim.bbox());
new_node(box, 0, primitives.size(), 0, 0);
}
template<typename Primitive>
Trace BVH<Primitive>::hit(const Ray& ray) const {
template <typename Primitive> Trace BVH<Primitive>::hit(const Ray &ray) const {
// TODO (PathTracer): Task 3
// Implement ray - BVH intersection test. A ray intersects
......@@ -53,24 +53,23 @@ Trace BVH<Primitive>::hit(const Ray& ray) const {
// Again, remember you can use hit() on any Primitive value.
Trace ret;
for(const Primitive& prim : primitives) {
for (const Primitive &prim : primitives) {
Trace hit = prim.hit(ray);
ret = Trace::min(ret, hit);
}
return ret;
}
template<typename Primitive>
BVH<Primitive>::BVH(std::vector<Primitive>&& prims, size_t max_leaf_size) {
template <typename Primitive>
BVH<Primitive>::BVH(std::vector<Primitive> &&prims, size_t max_leaf_size) {
build(std::move(prims), max_leaf_size);
}
template<typename Primitive>
bool BVH<Primitive>::Node::is_leaf() const {
template <typename Primitive> bool BVH<Primitive>::Node::is_leaf() const {
return l == 0 && r == 0;
}
template<typename Primitive>
template <typename Primitive>
size_t BVH<Primitive>::new_node(BBox box, size_t start, size_t size, size_t l, size_t r) {
Node n;
n.bbox = box;
......@@ -82,49 +81,44 @@ size_t BVH<Primitive>::new_node(BBox box, size_t start, size_t size, size_t l, s
return nodes.size() - 1;
}
template<typename Primitive>
BBox BVH<Primitive>::bbox() const {
return nodes[0].bbox;
}
template <typename Primitive> BBox BVH<Primitive>::bbox() const { return nodes[0].bbox; }
template<typename Primitive>
std::vector<Primitive> BVH<Primitive>::destructure() {
template <typename Primitive> std::vector<Primitive> BVH<Primitive>::destructure() {
nodes.clear();
return std::move(primitives);
}
template<typename Primitive>
void BVH<Primitive>::clear() {
template <typename Primitive> void BVH<Primitive>::clear() {
nodes.clear();
primitives.clear();
}
template<typename Primitive>
size_t BVH<Primitive>::visualize(GL::Lines& lines, GL::Lines& active, size_t level, const Mat4& trans) const {
template <typename Primitive>
size_t BVH<Primitive>::visualize(GL::Lines &lines, GL::Lines &active, size_t level,
const Mat4 &trans) const {
std::stack<std::pair<size_t,size_t>> tstack;
tstack.push({0,0});
std::stack<std::pair<size_t, size_t>> tstack;
tstack.push({0, 0});
size_t max_level = 0;
if(nodes.empty()) return max_level;
if (nodes.empty())
return max_level;
while (!tstack.empty()) {
auto [idx, lvl] = tstack.top();
max_level = std::max(max_level, lvl);
const Node& node = nodes[idx];
const Node &node = nodes[idx];
tstack.pop();
Vec3 color = lvl == level ? Vec3(1.0f, 0.0f, 0.0f) : Vec3(1.0f);
GL::Lines& add = lvl == level ? active : lines;
GL::Lines &add = lvl == level ? active : lines;
BBox box = node.bbox;
box.transform(trans);
Vec3 min = box.min, max = box.max;
auto edge = [&](Vec3 a, Vec3 b) {
add.add(a, b, color);
};
auto edge = [&](Vec3 a, Vec3 b) { add.add(a, b, color); };
edge(min, Vec3{max.x, min.y, min.z});
edge(min, Vec3{min.x, max.y, min.z});
......@@ -139,11 +133,13 @@ size_t BVH<Primitive>::visualize(GL::Lines& lines, GL::Lines& active, size_t lev
edge(Vec3{max.x, min.y, min.z}, Vec3{max.x, max.y, min.z});
edge(Vec3{max.x, min.y, min.z}, Vec3{max.x, min.y, max.z});
if(node.l) tstack.push({node.l, lvl + 1});
if(node.r) tstack.push({node.r, lvl + 1});
if (node.l)
tstack.push({node.l, lvl + 1});
if (node.r)
tstack.push({node.r, lvl + 1});
if(!node.l && !node.r) {
for(size_t i = node.start; i < node.start + node.size; i++) {
if (!node.l && !node.r) {
for (size_t i = node.start; i < node.start + node.size; i++) {
size_t c = primitives[i].visualize(lines, active, level - lvl, trans);
max_level = std::max(c, max_level);
}
......@@ -152,4 +148,4 @@ size_t BVH<Primitive>::visualize(GL::Lines& lines, GL::Lines& active, size_t lev
return max_level;
}
}
} // namespace PT
#include "debug.h"
#include "../util/camera.h"
#include "debug.h"
Ray Camera::generate_ray(Vec2 screen_coord) const {
......
......@@ -23,17 +23,17 @@ Debug_Data debug_data;
// This runs when the button is clicked
}
Similarly, you can directly connect UI elements to data values by
Similarly, you can directly connect UI elements to data values by
passing in the address of your storage variable:
Checkbox("My Checkbox", &bool_variable);
Then, bool_variable will always reflect the state of the checkbox.
Then, bool_variable will always reflect the state of the checkbox.
These constructs are composable to make pretty advanced UI elements!
The whole Scotty3D UI is implemented in this way.
Some useful functions are documented below, and you can refer to
Some useful functions are documented below, and you can refer to
deps/imgui/imgui.h for many more.
*/
void student_debug_ui() {
......@@ -43,13 +43,13 @@ void student_debug_ui() {
Checkbox("Pathtracer: use normal colors", &debug_data.normal_colors);
// ImGui examples
if(Button("Press Me")) {
if (Button("Press Me")) {
info("Debug button pressed!");
}
// We need to store values somewhere, or else they will get reset every time
// we run this function (which is every frame). For convenience, we make them
// static, which gives them the same storage class as global variables.
// static, which gives them the same storage class as global variables.
static int int_value = 0;
InputInt("Int Input", &int_value);
......
......@@ -4,17 +4,17 @@
/* Debugging Tips:
You may use this file, as well as debug.cpp, to add any debugging features and
UI options that you find useful. To do so, you can add fields to the global
Debug_Data type here and access them in any other student/ files via debug_data.field
You can use your fields to enable/disable features or otherwise change how your
implementation behaves in the other files.
UI options that you find useful. To do so, you can add fields to the global
Debug_Data type here and access them in any other student/ files via debug_data.field
You can use your fields to enable/disable features or otherwise change how your
implementation behaves in the other files.
You can also connect your debug fields to specific UI options by adding
ImGui calls in debug.cpp. This creates a special UI panel that can be enabled
by the Edit -> Edit Debug Data menu item or by pressing Ctrl+D.
by the Edit -> Edit Debug Data menu item or by pressing Ctrl+D.
This panel will contain your specific debug controls.
For example, we have already implemented an option to color pathtracer objects
For example, we have already implemented an option to color pathtracer objects
based on their surface normal. This involved adding the following field to
Debug_Data:
......@@ -25,7 +25,7 @@
This ImGui option to student_debug_ui in debug.cpp:
void student_debug_ui() {
// ...
Checkbox("Use Normal Colors", &debugger.normal_colors);
// ...
......@@ -34,9 +34,10 @@
And we finally used the option in pathtracer.cpp:
Spectrum Pathtracer::trace_ray(const Ray& ray) {
// ...
Spectrum radiance_out = debug_data.normal_colors ? Spectrum(0.5f) : Spectrum::direction(hit.normal);
Spectrum radiance_out = debug_data.normal_colors ? Spectrum(0.5f) :
Spectrum::direction(hit.normal);
// ...
}
*/
......
#include "debug.h"
#include "../rays/env_light.h"
#include "debug.h"
#include <limits>
......@@ -16,7 +16,7 @@ Light_Sample Env_Map::sample() const {
Samplers::Sphere::Uniform uniform;
ret.direction = uniform.sample(ret.pdf);
// Once you've implemented Samplers::Sphere::Image, remove the above and
// Once you've implemented Samplers::Sphere::Image, remove the above and
// uncomment this line to use importance sampling instead.
// ret.direction = sampler.sample(ret.pdf);
......@@ -42,7 +42,8 @@ Light_Sample Env_Hemisphere::sample() const {
}
Spectrum Env_Hemisphere::sample_direction(Vec3 dir) const {
if(dir.y > 0.0f) return radiance;
if (dir.y > 0.0f)
return radiance;
return {};
}
......@@ -54,8 +55,6 @@ Light_Sample Env_Sphere::sample() const {
return ret;
}
Spectrum Env_Sphere::sample_direction(Vec3) const {
return radiance;
}
Spectrum Env_Sphere::sample_direction(Vec3) const { return radiance; }
}
} // namespace PT
#include <set>
#include <queue>
#include <set>
#include <unordered_map>
#include "debug.h"
#include "../geometry/halfedge.h"
#include "debug.h"
/* Note on local operation return types:
The local operations all return a std::optional<T> type. This is used so that your
implementation can signify that it does not want to perform the operation for
whatever reason (e.g. you don't want to allow the user to erase the last vertex).
The local operations all return a std::optional<T> type. This is used so that your
implementation can signify that it does not want to perform the operation for
whatever reason (e.g. you don't want to allow the user to erase the last vertex).
An optional can have two values: std::nullopt, or a value of the type it is
parameterized on. In this way, it's similar to a pointer, but has two advantages:
the value it holds need not be allocated elsewhere, and it provides an API that
forces the user to check if it is null before using the value.
An optional can have two values: std::nullopt, or a value of the type it is
parameterized on. In this way, it's similar to a pointer, but has two advantages:
the value it holds need not be allocated elsewhere, and it provides an API that
forces the user to check if it is null before using the value.
In your implementaiton, if you have successfully performed the operation, you can
simply return the required reference:
In your implementaiton, if you have successfully performed the operation, you can
simply return the required reference:
... collapse the edge ...
return collapsed_vertex_ref;
... collapse the edge ...
return collapsed_vertex_ref;
And if you wish to deny the operation, you can return the null optional:
And if you wish to deny the operation, you can return the null optional:
return std::nullopt;
return std::nullopt;
Note that the stubs below all reject their duties by returning the null optional.
Note that the stubs below all reject their duties by returning the null optional.
*/
/*
This method should replace the given vertex and all its neighboring
edges and faces with a single face, returning the new face.
This method should replace the given vertex and all its neighboring
edges and faces with a single face, returning the new face.
*/
std::optional<Halfedge_Mesh::FaceRef> Halfedge_Mesh::erase_vertex(Halfedge_Mesh::VertexRef v) {
(void)v;
return std::nullopt;
(void)v;
return std::nullopt;
}
/*
This method should erase the given edge and return an iterator to the
merged face.
This method should erase the given edge and return an iterator to the
merged face.
*/
std::optional<Halfedge_Mesh::FaceRef> Halfedge_Mesh::erase_edge(Halfedge_Mesh::EdgeRef e) {
(void)e;
return std::nullopt;
(void)e;
return std::nullopt;
}
/*
This method should collapse the given edge and return an iterator to
the new vertex created by the collapse.
This method should collapse the given edge and return an iterator to
the new vertex created by the collapse.
*/
std::optional<Halfedge_Mesh::VertexRef> Halfedge_Mesh::collapse_edge(Halfedge_Mesh::EdgeRef e) {
(void)e;
return std::nullopt;
(void)e;
return std::nullopt;
}
/*
This method should collapse the given face and return an iterator to
the new vertex created by the collapse.
This method should collapse the given face and return an iterator to
the new vertex created by the collapse.
*/
std::optional<Halfedge_Mesh::VertexRef> Halfedge_Mesh::collapse_face(Halfedge_Mesh::FaceRef f) {
(void)f;
return std::nullopt;
(void)f;
return std::nullopt;
}
/*
This method should flip the given edge and return an iterator to the
flipped edge.
This method should flip the given edge and return an iterator to the
flipped edge.
*/
std::optional<Halfedge_Mesh::EdgeRef> Halfedge_Mesh::flip_edge(Halfedge_Mesh::EdgeRef e) {
(void)e;
return std::nullopt;
(void)e;
return std::nullopt;
}
/*
This method should split the given edge and return an iterator to the
newly inserted vertex. The halfedge of this vertex should point along
the edge that was split, rather than the new edges.
This method should split the given edge and return an iterator to the
newly inserted vertex. The halfedge of this vertex should point along
the edge that was split, rather than the new edges.
*/
std::optional<Halfedge_Mesh::VertexRef> Halfedge_Mesh::split_edge(Halfedge_Mesh::EdgeRef e) {
(void)e;
return std::nullopt;
(void)e;
return std::nullopt;
}
/* Note on the beveling process:
Each of the bevel_vertex, bevel_edge, and bevel_face functions do not represent
a full bevel operation. Instead, they should only update the _connectivity_ of
the mesh, _not_ the positions of newly created vertices. In fact, you should set
the positions of new vertices to be exactly the same as wherever they "started from."
When you click on a mesh element while in bevel mode, one of those three functions
is called. But, because you may then adjust the distance/offset of the newly
beveled face, we need another method of updating the positions of the new vertices.
This is where bevel_vertex_positions, bevel_edge_positions, and
bevel_face_positions come in: these functions are called repeatedly as you
move your mouse, the position of which determins the normal and tangent offset
parameters. These functions are also passed an array of the original vertex
positions: for bevel_vertex, it has one element, the original vertex position,
for bevel_edge, two for the two vertices, and for bevel_face, it has the original
position of each vertex in halfedge order. You should use these positions, as well
as the normal and tangent offset fields to assign positions to the new vertices.
Finally, note that the normal and tangent offsets are not relative values - you
should compute a particular new position from them, not a delta to apply.
Each of the bevel_vertex, bevel_edge, and bevel_face functions do not represent
a full bevel operation. Instead, they should only update the _connectivity_ of
the mesh, _not_ the positions of newly created vertices. In fact, you should set
the positions of new vertices to be exactly the same as wherever they "started from."
When you click on a mesh element while in bevel mode, one of those three functions
is called. But, because you may then adjust the distance/offset of the newly
beveled face, we need another method of updating the positions of the new vertices.
This is where bevel_vertex_positions, bevel_edge_positions, and
bevel_face_positions come in: these functions are called repeatedly as you
move your mouse, the position of which determins the normal and tangent offset
parameters. These functions are also passed an array of the original vertex
positions: for bevel_vertex, it has one element, the original vertex position,
for bevel_edge, two for the two vertices, and for bevel_face, it has the original
position of each vertex in halfedge order. You should use these positions, as well
as the normal and tangent offset fields to assign positions to the new vertices.
Finally, note that the normal and tangent offsets are not relative values - you
should compute a particular new position from them, not a delta to apply.
*/
/*
This method should replace the vertex v with a face, corresponding to
a bevel operation. It should return the new face. NOTE: This method is
responsible for updating the *connectivity* of the mesh only---it does not
need to update the vertex positions. These positions will be updated in
Halfedge_Mesh::bevel_vertex_positions (which you also have to
implement!)
This method should replace the vertex v with a face, corresponding to
a bevel operation. It should return the new face. NOTE: This method is
responsible for updating the *connectivity* of the mesh only---it does not
need to update the vertex positions. These positions will be updated in
Halfedge_Mesh::bevel_vertex_positions (which you also have to
implement!)
*/
std::optional<Halfedge_Mesh::FaceRef> Halfedge_Mesh::bevel_vertex(Halfedge_Mesh::VertexRef v) {
(void)v;
return std::nullopt;
(void)v;
return std::nullopt;
}
/*
This method should replace the edge e with a face, corresponding to a
bevel operation. It should return the new face. NOTE: This method is
responsible for updating the *connectivity* of the mesh only---it does not
need to update the vertex positions. These positions will be updated in
Halfedge_Mesh::bevel_edge_positions (which you also have to
implement!)
This method should replace the edge e with a face, corresponding to a
bevel operation. It should return the new face. NOTE: This method is
responsible for updating the *connectivity* of the mesh only---it does not
need to update the vertex positions. These positions will be updated in
Halfedge_Mesh::bevel_edge_positions (which you also have to
implement!)
*/
std::optional<Halfedge_Mesh::FaceRef> Halfedge_Mesh::bevel_edge(Halfedge_Mesh::EdgeRef e) {
(void)e;
return std::nullopt;
(void)e;
return std::nullopt;
}
/*
This method should replace the face f with an additional, inset face
(and ring of faces around it), corresponding to a bevel operation. It
should return the new face. NOTE: This method is responsible for updating
the *connectivity* of the mesh only---it does not need to update the vertex
positions. These positions will be updated in
Halfedge_Mesh::bevel_face_positions (which you also have to
implement!)
This method should replace the face f with an additional, inset face
(and ring of faces around it), corresponding to a bevel operation. It
should return the new face. NOTE: This method is responsible for updating
the *connectivity* of the mesh only---it does not need to update the vertex
positions. These positions will be updated in
Halfedge_Mesh::bevel_face_positions (which you also have to
implement!)
*/
std::optional<Halfedge_Mesh::FaceRef> Halfedge_Mesh::bevel_face(Halfedge_Mesh::FaceRef f) {
(void)f;
return std::nullopt;
(void)f;
return std::nullopt;
}
/*
Compute new vertex positions for the vertices of the beveled vertex.
These vertices can be accessed via new_halfedges[i]->vertex()->pos for
i = 1, ..., new_halfedges.size()-1.
The basic strategy here is to loop over the list of outgoing halfedges,
and use the original vertex position and its associated outgoing edge
to compute a new vertex position along the outgoing edge.
Compute new vertex positions for the vertices of the beveled vertex.
These vertices can be accessed via new_halfedges[i]->vertex()->pos for
i = 1, ..., new_halfedges.size()-1.
The basic strategy here is to loop over the list of outgoing halfedges,
and use the original vertex position and its associated outgoing edge
to compute a new vertex position along the outgoing edge.
*/
void Halfedge_Mesh::bevel_vertex_positions(const std::vector<Vec3>& start_positions, Halfedge_Mesh::FaceRef face,
float tangent_offset) {
void Halfedge_Mesh::bevel_vertex_positions(const std::vector<Vec3> &start_positions,
Halfedge_Mesh::FaceRef face, float tangent_offset) {
std::vector<HalfedgeRef> new_halfedges;
std::vector<HalfedgeRef> new_halfedges;
auto h = face->halfedge();
do {
new_halfedges.push_back(h);
h = h->next();
} while(h != face->halfedge());
} while (h != face->halfedge());
(void)new_halfedges;
(void)start_positions;
(void)face;
(void)tangent_offset;
(void)new_halfedges;
(void)start_positions;
(void)face;
(void)tangent_offset;
}
/*
Compute new vertex positions for the vertices of the beveled edge.
These vertices can be accessed via new_halfedges[i]->vertex()->pos for
i = 1, ..., new_halfedges.size()-1.
The basic strategy here is to loop over the list of outgoing halfedges,
and use the preceding and next vertex position from the original mesh
(in the orig array) to compute an offset vertex position.
Note that there is a 1-to-1 correspondence between halfedges in
newHalfedges and vertex positions
in orig. So, you can write loops of the form
for(size_t i = 0; i < new_halfedges.size(); i++)
{
Vector3D pi = start_positions[i]; // get the original vertex
position corresponding to vertex i
}
Compute new vertex positions for the vertices of the beveled edge.
These vertices can be accessed via new_halfedges[i]->vertex()->pos for
i = 1, ..., new_halfedges.size()-1.
The basic strategy here is to loop over the list of outgoing halfedges,
and use the preceding and next vertex position from the original mesh
(in the orig array) to compute an offset vertex position.
Note that there is a 1-to-1 correspondence between halfedges in
newHalfedges and vertex positions
in orig. So, you can write loops of the form
for(size_t i = 0; i < new_halfedges.size(); i++)
{
Vector3D pi = start_positions[i]; // get the original vertex
position corresponding to vertex i
}
*/
void Halfedge_Mesh::bevel_edge_positions(const std::vector<Vec3>& start_positions, Halfedge_Mesh::FaceRef face,
float tangent_offset) {
void Halfedge_Mesh::bevel_edge_positions(const std::vector<Vec3> &start_positions,
Halfedge_Mesh::FaceRef face, float tangent_offset) {
std::vector<HalfedgeRef> new_halfedges;
std::vector<HalfedgeRef> new_halfedges;
auto h = face->halfedge();
do {
new_halfedges.push_back(h);
h = h->next();
} while(h != face->halfedge());
} while (h != face->halfedge());
(void)new_halfedges;
(void)start_positions;
(void)face;
(void)tangent_offset;
(void)new_halfedges;
(void)start_positions;
(void)face;
(void)tangent_offset;
}
/*
Compute new vertex positions for the vertices of the beveled face.
These vertices can be accessed via new_halfedges[i]->vertex()->pos for
i = 1, ..., new_halfedges.size()-1.
The basic strategy here is to loop over the list of outgoing halfedges,
and use the preceding and next vertex position from the original mesh
(in the start_positions array) to compute an offset vertex
position.
Note that there is a 1-to-1 correspondence between halfedges in
new_halfedges and vertex positions
in orig. So, you can write loops of the form
for(size_t i = 0; i < new_halfedges.size(); hs++)
{
Vec3 pi = start_positions[i]; // get the original vertex
position corresponding to vertex i
}
Compute new vertex positions for the vertices of the beveled face.
These vertices can be accessed via new_halfedges[i]->vertex()->pos for
i = 1, ..., new_halfedges.size()-1.
The basic strategy here is to loop over the list of outgoing halfedges,
and use the preceding and next vertex position from the original mesh
(in the start_positions array) to compute an offset vertex
position.
Note that there is a 1-to-1 correspondence between halfedges in
new_halfedges and vertex positions
in orig. So, you can write loops of the form
for(size_t i = 0; i < new_halfedges.size(); hs++)
{
Vec3 pi = start_positions[i]; // get the original vertex
position corresponding to vertex i
}
*/
void Halfedge_Mesh::bevel_face_positions(const std::vector<Vec3>& start_positions, Halfedge_Mesh::FaceRef face,
float tangent_offset, float normal_offset) {
void Halfedge_Mesh::bevel_face_positions(const std::vector<Vec3> &start_positions,
Halfedge_Mesh::FaceRef face, float tangent_offset,
float normal_offset) {
if(flip_orientation) normal_offset = -normal_offset;
std::vector<HalfedgeRef> new_halfedges;
if (flip_orientation)
normal_offset = -normal_offset;
std::vector<HalfedgeRef> new_halfedges;
auto h = face->halfedge();
do {
new_halfedges.push_back(h);
h = h->next();
} while(h != face->halfedge());
} while (h != face->halfedge());
(void)new_halfedges;
(void)start_positions;
(void)face;
(void)tangent_offset;
(void)normal_offset;
(void)new_halfedges;
(void)start_positions;
(void)face;
(void)tangent_offset;
(void)normal_offset;
}
/*
Splits all non-triangular faces into triangles.
Splits all non-triangular faces into triangles.
*/
void Halfedge_Mesh::triangulate() {
// For each face...
// For each face...
}
/* Note on the quad subdivision process:
Unlike the local mesh operations (like bevel or edge flip), we will perform
subdivision by splitting *all* faces into quads "simultaneously." Rather
than operating directly on the halfedge data structure (which as you've
seen is quite difficult to maintain!) we are going to do something a bit nicer:
1. Create a raw list of vertex positions and faces (rather than a full-
blown halfedge mesh).
2. Build a new halfedge mesh from these lists, replacing the old one.
Sometimes rebuilding a data structure from scratch is simpler (and even
more efficient) than incrementally modifying the existing one. These steps are
detailed below.
Step I: Compute the vertex positions for the subdivided mesh.
Here we're going to do something a little bit strange: since we will
have one vertex in the subdivided mesh for each vertex, edge, and face in
the original mesh, we can nicely store the new vertex *positions* as
attributes on vertices, edges, and faces of the original mesh. These positions
can then be conveniently copied into the new, subdivided mesh.
This is what you will implement in linear_subdivide_positions() and
catmullclark_subdivide_positions().
Steps II-IV are provided (see Halfedge_Mesh::subdivide()), but are still detailed
Unlike the local mesh operations (like bevel or edge flip), we will perform
subdivision by splitting *all* faces into quads "simultaneously." Rather
than operating directly on the halfedge data structure (which as you've
seen is quite difficult to maintain!) we are going to do something a bit nicer:
1. Create a raw list of vertex positions and faces (rather than a full-
blown halfedge mesh).
2. Build a new halfedge mesh from these lists, replacing the old one.
Sometimes rebuilding a data structure from scratch is simpler (and even
more efficient) than incrementally modifying the existing one. These steps are
detailed below.
Step I: Compute the vertex positions for the subdivided mesh.
Here we're going to do something a little bit strange: since we will
have one vertex in the subdivided mesh for each vertex, edge, and face in
the original mesh, we can nicely store the new vertex *positions* as
attributes on vertices, edges, and faces of the original mesh. These positions
can then be conveniently copied into the new, subdivided mesh.
This is what you will implement in linear_subdivide_positions() and
catmullclark_subdivide_positions().
Steps II-IV are provided (see Halfedge_Mesh::subdivide()), but are still detailed
here:
Step II: Assign a unique index (starting at 0) to each vertex, edge, and
face in the original mesh. These indices will be the indices of the
vertices in the new (subdivided mesh). They do not have to be assigned
in any particular order, so long as no index is shared by more than one
mesh element, and the total number of indices is equal to V+E+F, i.e.,
the total number of vertices plus edges plus faces in the original mesh.
Basically we just need a one-to-one mapping between original mesh elements
and subdivided mesh vertices.
face in the original mesh. These indices will be the indices of the
vertices in the new (subdivided mesh). They do not have to be assigned
in any particular order, so long as no index is shared by more than one
mesh element, and the total number of indices is equal to V+E+F, i.e.,
the total number of vertices plus edges plus faces in the original mesh.
Basically we just need a one-to-one mapping between original mesh elements
and subdivided mesh vertices.
Step III: Build a list of quads in the new (subdivided) mesh, as tuples of
the element indices defined above. In other words, each new quad should be
of the form (i,j,k,l), where i,j,k and l are four of the indices stored on
our original mesh elements. Note that it is essential to get the orientation
right here: (i,j,k,l) is not the same as (l,k,j,i). Indices of new faces
should circulate in the same direction as old faces (think about the right-hand
rule).
the element indices defined above. In other words, each new quad should be
of the form (i,j,k,l), where i,j,k and l are four of the indices stored on
our original mesh elements. Note that it is essential to get the orientation
right here: (i,j,k,l) is not the same as (l,k,j,i). Indices of new faces
should circulate in the same direction as old faces (think about the right-hand
rule).
Step IV: Pass the list of vertices and quads to a routine that clears
the internal data for this halfedge mesh, and builds new halfedge data from
scratch, using the two lists.
the internal data for this halfedge mesh, and builds new halfedge data from
scratch, using the two lists.
*/
/*
Compute new vertex positions for a mesh that splits each polygon
into quads (by inserting a vertex at the face midpoint and each
of the edge midpoints). The new vertex positions will be stored
in the members Vertex::new_pos, Edge::new_pos, and
Face::new_pos. The values of the positions are based on
simple linear interpolation, e.g., the edge midpoints and face
centroids.
Compute new vertex positions for a mesh that splits each polygon
into quads (by inserting a vertex at the face midpoint and each
of the edge midpoints). The new vertex positions will be stored
in the members Vertex::new_pos, Edge::new_pos, and
Face::new_pos. The values of the positions are based on
simple linear interpolation, e.g., the edge midpoints and face
centroids.
*/
void Halfedge_Mesh::linear_subdivide_positions() {
// For each vertex, assign Vertex::new_pos to
// its original position, Vertex::pos.
// For each vertex, assign Vertex::new_pos to
// its original position, Vertex::pos.
// For each edge, assign the midpoint of the two original
// positions to Edge::new_pos.
// For each edge, assign the midpoint of the two original
// positions to Edge::new_pos.
// For each face, assign the centroid (i.e., arithmetic mean)
// of the original vertex positions to Face::new_pos. Note
// that in general, NOT all faces will be triangles!
// For each face, assign the centroid (i.e., arithmetic mean)
// of the original vertex positions to Face::new_pos. Note
// that in general, NOT all faces will be triangles!
}
/*
Compute new vertex positions for a mesh that splits each polygon
into quads (by inserting a vertex at the face midpoint and each
of the edge midpoints). The new vertex positions will be stored
in the members Vertex::new_pos, Edge::new_pos, and
Face::new_pos. The values of the positions are based on
the Catmull-Clark rules for subdivision.
Note: this will only be called on meshes without boundary
Compute new vertex positions for a mesh that splits each polygon
into quads (by inserting a vertex at the face midpoint and each
of the edge midpoints). The new vertex positions will be stored
in the members Vertex::new_pos, Edge::new_pos, and
Face::new_pos. The values of the positions are based on
the Catmull-Clark rules for subdivision.
Note: this will only be called on meshes without boundary
*/
void Halfedge_Mesh::catmullclark_subdivide_positions() {
// The implementation for this routine should be
// a lot like Halfedge_Mesh:linear_subdivide_positions:(),
// except that the calculation of the positions themsevles is
// slightly more involved, using the Catmull-Clark subdivision
// rules. (These rules are outlined in the Developer Manual.)
// The implementation for this routine should be
// a lot like Halfedge_Mesh:linear_subdivide_positions:(),
// except that the calculation of the positions themsevles is
// slightly more involved, using the Catmull-Clark subdivision
// rules. (These rules are outlined in the Developer Manual.)
// Faces
// Faces
// Edges
// Edges
// Vertices
// Vertices
}
/*
This routine should increase the number of triangles in the mesh
using Loop subdivision. Note: this is will only be called on triangle meshes.
This routine should increase the number of triangles in the mesh
using Loop subdivision. Note: this is will only be called on triangle meshes.
*/
void Halfedge_Mesh::loop_subdivide() {
// Compute new positions for all the vertices in the input mesh, using
// the Loop subdivision rule, and store them in Vertex::new_pos.
// -> At this point, we also want to mark each vertex as being a vertex of the
// original mesh. Use Vertex::is_new for this.
// -> Next, compute the updated vertex positions associated with edges, and
// store it in Edge::new_pos.
// -> Next, we're going to split every edge in the mesh, in any order. For
// future reference, we're also going to store some information about which
// subdivided edges come from splitting an edge in the original mesh, and
// which edges are new, by setting the flat Edge::is_new. Note that in this
// loop, we only want to iterate over edges of the original mesh.
// Otherwise, we'll end up splitting edges that we just split (and the
// loop will never end!)
// -> Now flip any new edge that connects an old and new vertex.
// -> Finally, copy the new vertex positions into final Vertex::pos.
// Each vertex and edge of the original surface can be associated with a
// vertex in the new (subdivided) surface.
// Therefore, our strategy for computing the subdivided vertex locations is to
// *first* compute the new positions
// using the connectivity of the original (coarse) mesh; navigating this mesh
// will be much easier than navigating
// the new subdivided (fine) mesh, which has more elements to traverse. We
// will then assign vertex positions in
// the new mesh based on the values we computed for the original mesh.
// Compute updated positions for all the vertices in the original mesh, using
// the Loop subdivision rule.
// Next, compute the updated vertex positions associated with edges.
// Next, we're going to split every edge in the mesh, in any order. For
// future reference, we're also going to store some information about which
// subdivided edges come from splitting an edge in the original mesh, and
// which edges are new.
// In this loop, we only want to iterate over edges of the original
// mesh---otherwise, we'll end up splitting edges that we just split (and
// the loop will never end!)
// Finally, flip any new edge that connects an old and new vertex.
// Copy the updated vertex positions to the subdivided mesh.
// Compute new positions for all the vertices in the input mesh, using
// the Loop subdivision rule, and store them in Vertex::new_pos.
// -> At this point, we also want to mark each vertex as being a vertex of the
// original mesh. Use Vertex::is_new for this.
// -> Next, compute the updated vertex positions associated with edges, and
// store it in Edge::new_pos.
// -> Next, we're going to split every edge in the mesh, in any order. For
// future reference, we're also going to store some information about which
// subdivided edges come from splitting an edge in the original mesh, and
// which edges are new, by setting the flat Edge::is_new. Note that in this
// loop, we only want to iterate over edges of the original mesh.
// Otherwise, we'll end up splitting edges that we just split (and the
// loop will never end!)
// -> Now flip any new edge that connects an old and new vertex.
// -> Finally, copy the new vertex positions into final Vertex::pos.
// Each vertex and edge of the original surface can be associated with a
// vertex in the new (subdivided) surface.
// Therefore, our strategy for computing the subdivided vertex locations is to
// *first* compute the new positions
// using the connectivity of the original (coarse) mesh; navigating this mesh
// will be much easier than navigating
// the new subdivided (fine) mesh, which has more elements to traverse. We
// will then assign vertex positions in
// the new mesh based on the values we computed for the original mesh.
// Compute updated positions for all the vertices in the original mesh, using
// the Loop subdivision rule.
// Next, compute the updated vertex positions associated with edges.
// Next, we're going to split every edge in the mesh, in any order. For
// future reference, we're also going to store some information about which
// subdivided edges come from splitting an edge in the original mesh, and
// which edges are new.
// In this loop, we only want to iterate over edges of the original
// mesh---otherwise, we'll end up splitting edges that we just split (and
// the loop will never end!)
// Finally, flip any new edge that connects an old and new vertex.
// Copy the updated vertex positions to the subdivided mesh.
}
/*
Isotropic remeshing. Note that this function returns success in a similar
manner to the local operations, except with only a boolean value.
(e.g. you may want to return false if this is not a triangle mesh)
Isotropic remeshing. Note that this function returns success in a similar
manner to the local operations, except with only a boolean value.
(e.g. you may want to return false if this is not a triangle mesh)
*/
bool Halfedge_Mesh::isotropic_remesh() {
// Compute the mean edge length.
// Repeat the four main steps for 5 or 6 iterations
// -> Split edges much longer than the target length (being careful about
// how the loop is written!)
// -> Collapse edges much shorter than the target length. Here we need to
// be EXTRA careful about advancing the loop, because many edges may have
// been destroyed by a collapse (which ones?)
// -> Now flip each edge if it improves vertex degree
// -> Finally, apply some tangential smoothing to the vertex positions
return false;
// Compute the mean edge length.
// Repeat the four main steps for 5 or 6 iterations
// -> Split edges much longer than the target length (being careful about
// how the loop is written!)
// -> Collapse edges much shorter than the target length. Here we need to
// be EXTRA careful about advancing the loop, because many edges may have
// been destroyed by a collapse (which ones?)
// -> Now flip each edge if it improves vertex degree
// -> Finally, apply some tangential smoothing to the vertex positions
return false;
}
/* Helper type for quadric simplification */
struct Edge_Record {
Edge_Record() {}
Edge_Record(std::unordered_map<Halfedge_Mesh::VertexRef, Mat4>& vertex_quadrics,
Halfedge_Mesh::EdgeRef e) : edge(e) {
// Compute the combined quadric from the edge endpoints.
// -> Build the 3x3 linear system whose solution minimizes the quadric error
// associated with these two endpoints.
// -> Use this system to solve for the optimal position, and store it in
// Edge_Record::optimal.
// -> Also store the cost associated with collapsing this edge in
// Edge_Record::cost.
Edge_Record(std::unordered_map<Halfedge_Mesh::VertexRef, Mat4> &vertex_quadrics,
Halfedge_Mesh::EdgeRef e)
: edge(e) {
// Compute the combined quadric from the edge endpoints.
// -> Build the 3x3 linear system whose solution minimizes the quadric error
// associated with these two endpoints.
// -> Use this system to solve for the optimal position, and store it in
// Edge_Record::optimal.
// -> Also store the cost associated with collapsing this edge in
// Edge_Record::cost.
}
Halfedge_Mesh::EdgeRef edge;
Vec3 optimal;
......@@ -451,7 +454,7 @@ struct Edge_Record {
};
/** Helper type for quadric simplification
*
*
* A PQueue is a minimum-priority queue that
* allows elements to be both inserted and removed from the
* queue. Together, one can easily change the priority of
......@@ -504,48 +507,47 @@ struct Edge_Record {
* queue.remove( item2 );
*
*/
template <class T>
struct PQueue {
void insert(const T& item) { queue.insert(item); }
void remove(const T& item) {
template <class T> struct PQueue {
void insert(const T &item) { queue.insert(item); }
void remove(const T &item) {
if (queue.find(item) != queue.end()) {
queue.erase(item);
}
}
const T& top(void) const { return *(queue.begin()); }
const T &top(void) const { return *(queue.begin()); }
void pop(void) { queue.erase(queue.begin()); }
size_t size() {return queue.size();}
size_t size() { return queue.size(); }
std::set<T> queue;
};
/*
Mesh simplification. Note that this function returns success in a similar
manner to the local operations, except with only a boolean value.
(e.g. you may want to return false if you can't simplify the mesh any
further without destroying it.)
/*
Mesh simplification. Note that this function returns success in a similar
manner to the local operations, except with only a boolean value.
(e.g. you may want to return false if you can't simplify the mesh any
further without destroying it.)
*/
bool Halfedge_Mesh::simplify() {
std::unordered_map<VertexRef, Mat4> vertex_quadrics;
std::unordered_map<VertexRef, Mat4> vertex_quadrics;
std::unordered_map<FaceRef, Mat4> face_quadrics;
std::unordered_map<EdgeRef, Edge_Record> edge_records;
PQueue<Edge_Record> edge_queue;
// Compute initial quadrics for each face by simply writing the plane equation
// for the face in homogeneous coordinates. These quadrics should be stored
// in face_quadrics
// -> Compute an initial quadric for each vertex as the sum of the quadrics
// associated with the incident faces, storing it in vertex_quadrics
// -> Build a priority queue of edges according to their quadric error cost,
// i.e., by building an Edge_Record for each edge and sticking it in the
// queue. You may want to use the above PQueue<Edge_Record> for this.
// -> Until we reach the target edge budget, collapse the best edge. Remember
// to remove from the queue any edge that touches the collapsing edge
// BEFORE it gets collapsed, and add back into the queue any edge touching
// the collapsed vertex AFTER it's been collapsed. Also remember to assign
// a quadric to the collapsed vertex, and to pop the collapsed edge off the
// top of the queue.
return false;
std::unordered_map<EdgeRef, Edge_Record> edge_records;
PQueue<Edge_Record> edge_queue;
// Compute initial quadrics for each face by simply writing the plane equation
// for the face in homogeneous coordinates. These quadrics should be stored
// in face_quadrics
// -> Compute an initial quadric for each vertex as the sum of the quadrics
// associated with the incident faces, storing it in vertex_quadrics
// -> Build a priority queue of edges according to their quadric error cost,
// i.e., by building an Edge_Record for each edge and sticking it in the
// queue. You may want to use the above PQueue<Edge_Record> for this.
// -> Until we reach the target edge budget, collapse the best edge. Remember
// to remove from the queue any edge that touches the collapsing edge
// BEFORE it gets collapsed, and add back into the queue any edge touching
// the collapsed vertex AFTER it's been collapsed. Also remember to assign
// a quadric to the collapsed vertex, and to pop the collapsed edge off the
// top of the queue.
return false;
}
#include "debug.h"
#include "../rays/pathtracer.h"
#include "../rays/samplers.h"
#include "../util/rand.h"
#include "debug.h"
namespace PT {
......@@ -12,13 +12,13 @@ Spectrum Pathtracer::trace_pixel(size_t x, size_t y) {
Vec2 wh((float)out_w, (float)out_h);
// TODO (PathTracer): Task 1
// Generate a sample within the pixel with coordinates xy and return the
// incoming light using trace_ray.
// Tip: Samplers::Rect::Uniform
// Tip: you may want to use log_ray for debugging
// This currently generates a ray at the bottom left of the pixel every time.
Ray out = camera.generate_ray(xy / wh);
......@@ -26,12 +26,12 @@ Spectrum Pathtracer::trace_pixel(size_t x, size_t y) {
return trace_ray(out);
}
Spectrum Pathtracer::trace_ray(const Ray& ray) {
Spectrum Pathtracer::trace_ray(const Ray &ray) {
// Trace ray into scene. If nothing is hit, sample the environment
Trace hit = scene.hit(ray);
if(!hit.hit) {
if(env_light.has_value()) {
if (!hit.hit) {
if (env_light.has_value()) {
return env_light.value().sample_direction(ray.dir);
}
return {};
......@@ -43,7 +43,7 @@ Spectrum Pathtracer::trace_ray(const Ray& ray) {
Mat4 object_to_world = Mat4::rotate_to(hit.normal);
Mat4 world_to_object = object_to_world.T();
Vec3 out_dir = world_to_object.rotate(ray.point - hit.position).unit();
const BSDF& bsdf = materials[hit.material];
const BSDF &bsdf = materials[hit.material];
// Now we can compute the rendering equation at this point.
// We split it into two stages: sampling lighting (i.e. directly connecting
......@@ -55,68 +55,71 @@ Spectrum Pathtracer::trace_ray(const Ray& ray) {
// indirect lighting components calculated in the code below. The starter
// code sets radiance_out to (0.5,0.5,0.5) so that you can test your geometry
// queries before you implement path tracing.
Spectrum radiance_out = debug_data.normal_colors ? Spectrum(0.5f) : Spectrum::direction(hit.normal);
Spectrum radiance_out =
debug_data.normal_colors ? Spectrum(0.5f) : Spectrum::direction(hit.normal);
{
auto sample_light = [&](const auto& light) {
auto sample_light = [&](const auto &light) {
// If the light is discrete (e.g. a point light), then we only need
// one sample, as all samples will be equivalent
int samples = light.is_discrete() ? 1 : (int)n_area_samples;
for(int i = 0; i < samples; i++) {
for (int i = 0; i < samples; i++) {
Light_Sample sample = light.sample(hit.position);
Vec3 in_dir = world_to_object.rotate(sample.direction);
// If the light is below the horizon, ignore it
float cos_theta = in_dir.y;
if(cos_theta <= 0.0f) continue;
if (cos_theta <= 0.0f)
continue;
// If the BSDF has 0 throughput in this direction, ignore it
// This is another oppritunity to do Russian roulette on low-throughput rays,
// which would allow us to skip the shadow ray cast, increasing efficiency.
Spectrum absorbsion = bsdf.evaluate(out_dir, in_dir);
if(absorbsion.luma() == 0.0f) continue;
if (absorbsion.luma() == 0.0f)
continue;
// TODO (PathTracer): Task 4
// Construct a shadow ray and compute whether the intersected surface is
// in shadow. Only accumulate light if not in shadow.
// Tip: when making your ray, you will want to slightly offset it from the
// surface it starts on, lest it intersect at time=0. Similarly, you may want
// Tip: when making your ray, you will want to slightly offset it from the
// surface it starts on, lest it intersect at time=0. Similarly, you may want
// to limit the ray slightly before it would hit the light itself.
// Note: that along with the typical cos_theta, pdf factors, we divide by samples.
// This is because we're doing another monte-carlo estimate of the lighting from area lights.
// Note: that along with the typical cos_theta, pdf factors, we divide by samples.
// This is because we're doing another monte-carlo estimate of the lighting from
// area lights.
radiance_out += (cos_theta / (samples * sample.pdf)) * sample.radiance * absorbsion;
}
};
// If the BSDF is discrete (i.e. uses dirac deltas/if statements), then we are never
// going to hit the exact right direction by sampling lights, so ignore them.
if(!bsdf.is_discrete()) {
for(const auto& light : lights)
if (!bsdf.is_discrete()) {
for (const auto &light : lights)
sample_light(light);
if(env_light.has_value())
if (env_light.has_value())
sample_light(env_light.value());
}
}
// TODO (PathTracer): Task 5
// Compute an indirect lighting estimate using pathtracing with Monte Carlo.
// (1) Ray objects have a depth field; you should use this to avoid
// traveling down one path forever.
// (2) randomly select a new ray direction (it may be reflection or transmittence
// (2) randomly select a new ray direction (it may be reflection or transmittence
// ray depending on surface type) using bsdf.sample()
// (3) potentially terminate path (using Russian roulette). You can make this
// (3) potentially terminate path (using Russian roulette). You can make this
// a function of the bsdf attenuation or track overall ray throughput
// (4) create new scene-space ray and cast it to get incoming light
// (5) add contribution due to incoming light with proper weighting
return radiance_out;
return radiance_out;
}
}
} // namespace PT
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment