add Value/Curve

This commit is contained in:
falsycat 2022-08-21 00:46:26 +09:00
parent 92c1568455
commit c8efb73853
7 changed files with 536 additions and 7 deletions

View File

@ -125,6 +125,7 @@ target_sources(nf7
file/system_imgui.cc
file/system_logger.cc
file/system_native_file.cc
file/value_curve.cc
)
target_link_libraries(nf7
PRIVATE

View File

@ -5,6 +5,8 @@
#include <imgui.h>
#include <imgui_internal.h>
#include <ImNodes.h>
namespace nf7::gui {

View File

@ -7,7 +7,7 @@
namespace nf7::gui {
bool Resizer(ImVec2* size, const ImVec2& min, const ImVec2& max, float scale,
inline bool Resizer(ImVec2* size, const ImVec2& min, const ImVec2& max, float scale,
const char* idstr = "##resizer") noexcept {
const auto id = ImGui::GetID(idstr);

View File

@ -247,7 +247,9 @@ bool Timeline::BeginItem(Item item, uint64_t begin, uint64_t end) noexcept {
const auto h = layer_h_;
ImGui::SetCursorPos({left, std::round(layer_y_+pad)});
if (ImGui::BeginChild(ImGui::GetID(item), {w, h})) {
constexpr auto kFlags = ImGuiWindowFlags_NoScrollbar;
if (ImGui::BeginChild(ImGui::GetID(item), {w, h}, false, kFlags)) {
const auto resizer_w = std::min(1*em, w/2);
ImGui::SetCursorPos({0, 0});

View File

@ -20,13 +20,11 @@
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui_node.hh"
#include "common/gui_resizer.hh"
#include "common/gui_value.hh"
#include "common/life.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/value.hh"
#include "common/yas_imgui.hh"
namespace nf7 {

View File

@ -225,9 +225,17 @@ class Adaptor::Editor final : public nf7::Sequencer::Editor {
void Adaptor::UpdateItem(Sequencer::Editor&) noexcept {
const auto em = ImGui::GetFontSize();
ImGui::SetCursorPos({.25f*em, .25f*em});
target_editor_.SmallButton();
try {
auto& seq = target_.GetFileOrThrow().interfaceOrThrow<nf7::Sequencer>();
if (seq.flags() & nf7::Sequencer::kCustomItem) {
Adaptor::Editor ed;
seq.UpdateItem(ed);
}
} catch (nf7::Exception&) {
const auto em = ImGui::GetFontSize();
ImGui::SetCursorPos({.25f*em, .25f*em});
target_editor_.SmallButton();
}
}
void Adaptor::UpdateParamPanel(Sequencer::Editor&) noexcept {
bool commit = false;

518
file/value_curve.cc Normal file
View File

@ -0,0 +1,518 @@
#include <cmath>
#include <memory>
#include <string_view>
#include <utility>
#include <imgui.h>
#include <imgui_internal.h>
#include <yas/serialize.hpp>
#include <yas/types/utility/usertype.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui_node.hh"
#include "common/life.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/sequencer.hh"
#include "common/value.hh"
#include "common/yas_imgui.hh"
// TODO: currently bezier curve is not implemented
namespace nf7 {
namespace {
class Curve final : public nf7::File,
public nf7::DirItem,
public nf7::Node,
public nf7::Sequencer {
public:
static inline const nf7::GenericTypeInfo<Curve> kType =
{"Value/Curve", {"nf7::DirItem", "nf7::Node", "nf7::Sequencer"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("bezier curve");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Sequencer");
ImGui::Bullet(); ImGui::TextUnformatted(
"changes will be applied to active lambdas immediately");
}
class NodeLambda;
class SeqLambda;
struct Term {
ImVec2 p1, p2, p3; // p4 is next point's p1
uint64_t id; // id is not saved
bool break_prev = false;
void serialize(auto& ar) {
ar(p1, p2, p3, break_prev);
}
};
struct Data {
std::vector<Term> terms = {
{.p1 = {0, 0}, .p2 = {0, 0}, .p3 = {1, 1}, .id = 0,},
{.p1 = {1, 1}, .p2 = {1, 1}, .p3 = {1, 1}, .id = 0,}};
Data() {}
void serialize(auto& ar) {
ar(terms);
}
};
Curve(nf7::Env& env, Data&& data = {}) noexcept :
nf7::File(kType, env),
nf7::DirItem(nf7::DirItem::kNone),
nf7::Sequencer(nf7::Sequencer::kCustomItem),
life_(*this), mem_(std::move(data)) {
AssignId();
Sanitize();
input_ = {"x"};
output_ = {"y"};
mem_.onRestore = [this]() { Touch(); };
mem_.onCommit = [this]() { Touch(); };
}
Curve(nf7::Env& env, Deserializer& ar) : Curve(env) {
ar(mem_.data());
AssignId();
Sanitize();
}
void Serialize(Serializer& ar) const noexcept override {
ar(mem_.data());
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Curve>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
const std::shared_ptr<nf7::Context>&) noexcept override;
void UpdateItem(nf7::Sequencer::Editor&) noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node, nf7::Sequencer>(t).Select(this, &mem_);
}
private:
nf7::Life<Curve> life_;
uint64_t next_id_ = 1;
nf7::GenericMemento<Data> mem_;
// GUI parameters
std::unordered_set<uint64_t> selected_;
bool last_action_moved_;
void AddPoint(const ImVec2& pos) noexcept {
auto& terms = mem_.data().terms;
const auto x = std::clamp(pos.x, 0.f, 1.f);
auto itr = std::find_if(terms.begin(), terms.end(),
[x](auto& a) { return x <= a.p1.x; });
assert(itr != terms.end());
if (itr == terms.begin()) {
++itr;
}
auto& pt = *(itr-1);
auto nt = itr+1 < terms.end()? &*(itr+1): nullptr;
auto p3 = pt.p3;
pt.p3 = pos;
pt.p2.x = std::clamp(pt.p2.x, pt.p1.x, pos.x);
p3.x = std::clamp(p3.x, pos.x, nt? nt->p1.x: 1.f);
terms.insert(itr, {.p1 = pos, .p2 = pos, .p3 = p3, .id = next_id_++});
}
void RemoveSelectedPoints() noexcept {
auto& terms = mem_.data().terms;
terms.erase(
std::remove_if(terms.begin(), terms.end(),
[&](auto& x) { return selected_.contains(x.id); }),
terms.end());
selected_.clear();
}
void ResetControlsOfSelectedPoints() noexcept {
auto& terms = mem_.data().terms;
for (auto id : selected_) {
auto itr = std::find_if(terms.begin(), terms.end(),
[id](auto& x) { return x.id == id; });
if (itr == terms.end()) {
continue;
}
auto& t = *itr;
auto pt = itr > terms.begin()? &*(itr-1): nullptr;
if (pt) {
pt->p3 = t.p1;
}
t.p2 = t.p1;
}
}
void MovePoint(ImVec2 diff) noexcept {
auto& terms = mem_.data().terms;
for (auto id : selected_) {
auto itr = std::find_if(terms.begin(), terms.end(),
[id](auto& x) { return x.id == id; });
if (itr == terms.end()) {
continue;
}
auto& t = *itr;
auto pt = itr > terms.begin()? &*(itr-1): nullptr;
auto nt = itr+1 < terms.end()? &*(itr+1): nullptr;
const auto pp1 = t.p1;
t.p1 += diff;
t.p1.x = std::clamp(t.p1.x, 0.f, 1.f);
t.p1.y = std::clamp(t.p1.y, 0.f, 1.f);
if (!pt) {
t.p1.x = 0;
} else if (!nt) {
t.p1.x = 1;
}
const auto adiff = t.p1 - pp1;
t.p2 += adiff;
t.p2.x = std::clamp(t.p2.x, t.p1.x, nt? nt->p1.x: 0);
t.p3.x = std::clamp(t.p3.x, t.p1.x, nt? nt->p1.x: 0);
if (pt) {
pt->p3 += adiff;
pt->p2.x = std::clamp(pt->p2.x, pt->p1.x, t.p1.x);
pt->p3.x = std::clamp(pt->p3.x, pt->p1.x, t.p1.x);
}
}
}
void SelectPoint(uint64_t id, bool single = !ImGui::GetIO().KeyCtrl) noexcept {
if (single) {
selected_.clear();
}
selected_.insert(id);
}
void AssignId() noexcept {
for (auto& term : mem_.data().terms) {
term.id = next_id_++;
}
}
void Sanitize() noexcept {
auto& terms = mem_.data().terms;
std::sort(terms.begin(), terms.end(),
[](auto& a, auto& b) { return a.p1.x < b.p1.x; });
for (auto itr = terms.begin(); itr+1 < terms.end(); ++itr) {
auto& a = *itr;
auto& b = *(itr+1);
a.p2.x = std::clamp(a.p2.x, a.p1.x, b.p1.x);
a.p3.x = std::clamp(a.p3.x, a.p1.x, b.p1.x);
}
}
double Calc(double x) const noexcept {
const auto& terms = mem_.data().terms;
assert(terms.size() >= 2);
x = std::clamp(x, 0., 1.);
auto r_itr = std::find_if(terms.begin(), terms.end(),
[x](auto& a) { return x <= a.p1.x; });
assert(r_itr != terms.end());
if (r_itr == terms.begin()) {
return static_cast<double>(r_itr->p1.y);
}
auto l_itr = r_itr-1;
const auto lx = static_cast<double>(l_itr->p1.x);
const auto rx = static_cast<double>(r_itr->p1.x);
const auto xlen = rx-lx;
if (xlen == 0) {
return l_itr->p1.y;
}
const auto ly = static_cast<double>(l_itr->p1.y);
const auto ry = static_cast<double>(r_itr->p1.y);
const auto ylen = ry-ly;
const auto xf = (x-lx)/xlen;
const auto x1 = (static_cast<double>(l_itr->p2.x)-lx)/xlen;
const auto y1 = (static_cast<double>(l_itr->p2.y)-ly)/ylen;
const auto x2 = (static_cast<double>(l_itr->p3.x)-lx)/xlen;
const auto y2 = (static_cast<double>(l_itr->p3.y)-ly)/ylen;
const auto b = Bezier(xf, x1, y1, x2, y2);
return b*ylen + ly;
}
static double Bezier(double x, double x1, double y1, double x2, double y2) noexcept {
double a = 0.5;
double t = 0.5;
for (;;) {
const auto rt = 1-t;
const auto xt = 3*t*rt*rt*x1 + 3*t*t*rt*x2 + t*t*t;
const auto diff = std::abs(xt - x);
if (diff < 1e-2) {
break;
}
a /= 2;
if (xt > x) {
t -= a;
} else if (xt < x) {
t += a;
}
}
const auto rt = 1-t;
return 3*t*rt*rt*y1 + 3*t*t*rt*y2 + t*t*t;
}
};
class Curve::NodeLambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Curve::NodeLambda> {
public:
NodeLambda(Curve& f, const std::shared_ptr<Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_) {
}
void Handle(std::string_view, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
try {
f_.EnforceAlive();
caller->Handle("y", f_->Calc(v.scalar()), shared_from_this());
} catch (nf7::Exception&) {
}
private:
nf7::Life<Curve>::Ref f_;
};
std::shared_ptr<nf7::Node::Lambda> Curve::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Curve::NodeLambda>(*this, parent);
}
class Curve::SeqLambda final : public nf7::Sequencer::Lambda {
public:
SeqLambda(Curve& f, const std::shared_ptr<nf7::Context>& parent) noexcept :
nf7::Sequencer::Lambda(f, parent), f_(f.life_) {
}
void Run(const std::shared_ptr<nf7::Sequencer::Session>& ss) noexcept {
try {
ss->Send("y", nf7::Value {f_->Calc(ss->ReceiveOrThrow("x").scalar())});
} catch (nf7::Exception&) {
}
ss->Finish();
}
private:
nf7::Life<Curve>::Ref f_;
};
std::shared_ptr<nf7::Sequencer::Lambda> Curve::CreateLambda(
const std::shared_ptr<nf7::Context>& parent) noexcept {
return std::make_shared<Curve::SeqLambda>(*this, parent);
}
void Curve::UpdateItem(nf7::Sequencer::Editor&) noexcept {
const auto& io = ImGui::GetIO();
auto d = ImGui::GetWindowDrawList();
const auto em = ImGui::GetFontSize();
const auto sz = ImGui::GetContentRegionAvail();
const auto col = ImGui::GetColorU32(ImGuiCol_Text);
const auto colg = ImGui::GetColorU32(ImGuiCol_Text, .5f);
const auto cols = ImGui::GetColorU32(ImGuiCol_TextSelectedBg);
const auto pos = ImGui::GetCursorScreenPos();
const auto grip = em/2.4f;
const auto mpos = ImGui::GetMousePos() - pos;
const auto mposn = ImVec2 {
std::clamp(mpos.x/sz.x, 0.f, 1.f),
std::clamp(1-mpos.y/sz.y, 0.f, 1.f),
};
// title
ImGui::TextUnformatted("Value/Curve");
// draw lines
auto& terms = mem_.data().terms;
for (size_t i = 0; i+1 < terms.size(); ++i) {
const auto& a = terms[i];
const auto& b = terms[i+1];
const auto p1 = ImVec2 {sz.x*a.p1.x, sz.y*(1-a.p1.y)};
const auto p2 = ImVec2 {sz.x*a.p2.x, sz.y*(1-a.p2.y)};
const auto p3 = ImVec2 {sz.x*a.p3.x, sz.y*(1-a.p3.y)};
const auto p4 = ImVec2 {sz.x*b.p1.x, sz.y*(1-b.p1.y)};
d->AddBezierCubic(pos+p1, pos+p2, pos+p3, pos+p4, col, 1);
}
// draw points
bool request_sort = false;
bool skip_adding = false;
bool remove_selected = false;
for (size_t i = 0; i < terms.size(); ++i) {
auto& t = terms[i];
auto pt = i >= 1? &terms[i-1]: nullptr;
auto nt = i+1 < terms.size()? &terms[i+1]: nullptr;
const bool sel = selected_.contains(t.id);
ImGui::PushID(static_cast<int>(t.id));
const auto x = std::clamp(sz.x*t.p1.x, 1.f, sz.x-1);
const auto y = std::clamp(sz.y*(1-t.p1.y), 1.f, sz.y-1);
const auto p1 = ImVec2 {x, y};
d->AddCircleFilled(pos+p1, grip, col);
if (sel) {
d->AddCircleFilled(pos+p1, grip, cols);
}
ImGui::SetCursorPos(p1 - ImVec2 {grip, grip});
if (!io.KeyShift) {
ImGui::InvisibleButton("grip", {grip*2, grip*2});
if (ImGui::IsItemActive()) {
if (ImGui::IsItemActivated()) {
SelectPoint(t.id);
last_action_moved_ = false;
}
request_sort = true;
skip_adding = true;
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (io.MouseDelta.x != 0 || io.MouseDelta.y != 0) {
MovePoint(mposn-t.p1);
last_action_moved_ = true;
}
} else {
if (ImGui::IsItemDeactivated() && last_action_moved_) {
mem_.Commit();
}
if (ImGui::IsItemHovered()) {
skip_adding = true;
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
}
}
if (ImGui::BeginPopupContextItem()) {
if (ImGui::IsWindowAppearing()) {
SelectPoint(t.id);
}
if (ImGui::MenuItem("remove points")) {
remove_selected = true;
}
if (ImGui::MenuItem("reset control points")) {
mem_.Commit();
}
ImGui::EndPopup();
}
}
// define control point handler
const auto HandleControlPoint = [&](ImVec2& p, float xmin, float xmax) {
bool ret = false;
if (ImGui::IsItemActive()) {
if (ImGui::IsItemActivated()) {
last_action_moved_ = false;
}
skip_adding = true;
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (io.MouseDelta.x != 0 || io.MouseDelta.y != 0) {
p = mposn;
p.x = std::clamp(p.x, xmin, xmax);
last_action_moved_ = true;
}
ret = true;
} else {
if (ImGui::IsItemHovered()) {
skip_adding = true;
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
}
}
return ret;
};
// p2 control point
if (io.KeyShift && nt) {
const auto p2 = ImVec2 {sz.x*t.p2.x, sz.y*(1-t.p2.y)};
ImGui::SetCursorPos(p2 - ImVec2 {grip, grip});
ImGui::InvisibleButton("grip-p2", {grip*2, grip*2});
if (HandleControlPoint(t.p2, t.p1.x, nt->p1.x)) {
if (!t.break_prev) {
// TODO calc reversal vector
}
}
if (ImGui::IsItemDeactivated() && last_action_moved_) {
mem_.Commit();
}
d->AddLine(p1+pos, p2+pos, colg);
d->AddCircleFilled(pos+p2, grip, colg);
}
// prev term's p3 control point
if (io.KeyShift && pt) {
const auto p3 = ImVec2 {sz.x*pt->p3.x, sz.y*(1-pt->p3.y)};
ImGui::SetCursorPos(p3 - ImVec2 {grip, grip});
ImGui::InvisibleButton("grip-p3", {grip*2, grip*2});
if (HandleControlPoint(pt->p3, pt->p1.x, t.p1.x)) {
if (!t.break_prev && nt) {
// TODO calc reversal vector
}
}
if (ImGui::IsItemDeactivated() && last_action_moved_) {
mem_.Commit();
}
d->AddLine(p1+pos, p3+pos, colg);
d->AddCircleFilled(pos+p3, grip, colg);
}
ImGui::PopID();
}
if (request_sort) {
Sanitize();
}
if (remove_selected) {
RemoveSelectedPoints();
}
// add new point
if (!skip_adding) {
ImGui::PushID(static_cast<int>(next_id_));
const auto y = static_cast<float>(Calc(mposn.x));
const auto diff = y - mposn.y;
if (std::abs(diff * sz.y) < grip) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);
ImGui::SetCursorPos(mpos-ImVec2 {grip/2, grip/2});
ImGui::InvisibleButton("grip", {grip, grip});
if (ImGui::IsItemActivated()) {
SelectPoint(next_id_);
AddPoint({mposn.x, y});
}
d->AddCircle(ImVec2 {mpos.x, sz.y*(1-y)} + pos, grip, col);
}
ImGui::PopID();
}
}
}
} // namespace nf7