Sketch ini

This commit is contained in:
Ilusha 2024-03-17 10:04:52 +03:00 committed by IlyaShurupov
parent be0177b1b0
commit b8bab2264f
16 changed files with 2510 additions and 0 deletions

View file

@ -0,0 +1,260 @@
#include "StrokesWidget.h"
#include "operator.h"
using namespace nd;
void StrokesWidget::constructor(StrokesWidget* self) {
new (&self->mRenderer) strokes::Renderer({ 1980, 1020 });
self->createMember("link", "TargetProject");
self->createMember("StrokesProject", "default");
self->createMember("rgba", "draw_color");
self->addMember(obj::ColorObject::create({ 0.13f, 0.13f, 0.13f, 1.f }), "bg_color");
}
void StrokesWidget::destructor(StrokesWidget* self) {
self->mRenderer.~Renderer();
self->mImage.free(self->mDrawer);
self->mImage.~ImageHandle();
}
void StrokesWidget::copy(Widget* self, const Widget* in) {}
strokes::Project* StrokesWidget::getTargetProject() {
auto link = getMember<obj::LinkObject>("TargetProject");
if (!link) {
return NULL;
}
auto obj = link->getLink();
if (!obj) {
link->setLink(getMember<obj::StrokesObject>("default"));
return getTargetProject();
}
auto strokes = NDO_CAST(obj::StrokesObject, obj);
if (!strokes) {
return NULL;
}
return &strokes->mProject;
}
void StrokesWidget::procInputs(StrokesWidget* self, nd::GUIInputs* inputs) {
auto proj = self->getTargetProject();
if (!proj) {
return;
}
auto rec = self->getRect();
proj->mCamera.set_ratio(rec.w / rec.z);
auto col_obj_bg = self->getMember<obj::ColorObject>("bg_color");
if (col_obj_bg) {
proj->mBackgroundColor = col_obj_bg->mCol;
}
if (!rec.inside(inputs->mCrsPrev)) {
return;
}
auto pressure = inputs->mPressure;
if (!inputs->Anticipating()) {
pressure = 0.f;
}
auto idx = proj->mBrushes.presents(proj->mActiveBrush);
if (idx) {
auto brush = proj->mBrushes.getSlotVal(idx);
auto crs = (inputs->mCrs - rec.pos);
crs.x /= rec.z;
crs.y /= rec.w;
crs = (crs - 0.5) * 2;
if (brush->mType == "pencil") {
auto col_obj = self->getMember<obj::ColorObject>("draw_color");
if (col_obj) {
((strokes::PencilBrush*)brush)->mCol = col_obj->mCol;
}
}
brush->sample(proj, crs, pressure);
}
}
void StrokesWidget::presentOutput(StrokesWidget* self, nd::GUIdrawer* drawer) {
auto proj = self->getTargetProject();
if (!proj) {
return;
}
if (!self->mImage.mId) {
self->mDrawer = drawer;
self->mImage.createFromBuff(self->mRenderer.getBuff(), drawer);
}
auto rec = self->getRect();
self->mRenderer.setViewport({ 0, 0, rec.z, rec.w });
self->mRenderer.setClearCol(proj->mBackgroundColor);
self->mRenderer.renderBegin();
for (auto lay : proj->mLayers) {
if (lay.data()->enabled) {
for (auto str : lay.data()->strokes) {
self->mRenderer.drawStroke(str.data(), &proj->mCamera);
}
}
}
auto idx = proj->mBrushes.presents(proj->mActiveBrush);
if (idx) {
auto brush = proj->mBrushes.getSlotVal(idx);
brush->draw(&self->mRenderer, &proj->mCamera);
}
self->mRenderer.renderEnd();
drawer->drawImage(rec, &self->mImage, 0, 1, 12);
}
struct obj::ObjectType StrokesWidget::TypeData = {
.base = &Widget::TypeData,
.constructor = (obj::object_constructor)StrokesWidget::constructor,
.destructor = (obj::object_destructor)StrokesWidget::destructor,
.copy = (obj::object_copy)StrokesWidget::copy,
.size = sizeof(StrokesWidget),
.name = "StrokesWidget",
.vtable = &StrokesWidget::vtable
};
struct StrokesWidget::Vtable StrokesWidget::vtable = {
(void (*)(Widget*, nd::GUIInputs*)) & StrokesWidget::procInputs,
(void (*)(Widget*, nd::GUIdrawer*)) & StrokesWidget::presentOutput,
};
class StrokesWidgetOps : public nd::TypeOperators {
struct CameraOrbit : public nd::Operator {
tp::vec2f prev_crs;
struct Invoke : public nd::OpCallBack {
Invoke() {
auto transf = obj::EnumObject::create({ "move", "rotate" , "scale" });
arg_interface.put("Gui", obj::NDO->create("link"));
arg_interface.put("transf", transf);
func = exec;
description = "invoke";
}
static void exec(Operator* op, obj::DictObject* args) {
auto active = OpCallBack::getActiveWidget<StrokesWidget>(args);
if (!active) {
return;
}
auto proj = active->getTargetProject();
if (!proj) {
return;
}
auto rec = active->getRect();
auto gui = OpCallBack::getGUI(args);
auto prev_crs = ((CameraOrbit*)op)->prev_crs;
prev_crs = tp::vec2f(prev_crs.x / rec.size.x, prev_crs.y / rec.size.y);
prev_crs = (prev_crs - 0.5f) * 2.f;
auto crs = tp::vec2f(gui->mInputs.mCrs.x / rec.size.x, gui->mInputs.mCrs.y / rec.size.y);
crs = (crs - 0.5f) * 2.f;
auto delta = (crs - prev_crs);
if (tp::abs(delta.x) < 0.1 && tp::abs(delta.y) < 0.1) {
auto type = OpCallBack::getArg<obj::EnumObject>(args, "transf");
switch (type->active) {
case 0: {
proj->mCamera.move(crs, prev_crs);
break;
}
case 1: {
proj->mCamera.rotate(-delta.x * 5, -delta.y * 5);
break;
}
case 2: {
proj->mCamera.zoom((crs.y + 1.f) / (prev_crs.y + 1.f));
break;
}
}
}
((CameraOrbit*)op)->prev_crs = gui->mInputs.mCrs;
}
} invoke_callback;
CameraOrbit() {
description = "Orbits camera";
callbacks.put("invoke", &invoke_callback);
}
} camera_orbit_op;
public:
StrokesWidgetOps() {
operators.put("camera_orbit", &camera_orbit_op);
}
};
void StrokesWidget::OpsInit() {
assert(!StrokesWidget::TypeData.nodes_custom_data);
StrokesWidget::TypeData.nodes_custom_data = new StrokesWidgetOps();
}
void StrokesWidget::OpsUnInit() {
assert(StrokesWidget::TypeData.nodes_custom_data);
auto del = (StrokesWidgetOps*)StrokesWidget::TypeData.nodes_custom_data;
StrokesWidget::TypeData.nodes_custom_data = NULL;
delete del;
}
void StrokesWidget::addShortcuts(nd::NodesCore* nodes) {
auto uis_list = nodes->getMemberAssert<obj::ListObject>("uis");
NDO_CASTV(nd::GUI, uis_list->getItems()[0], gui);
NDO_CASTV(nd::TUI, uis_list->getItems()[1], tui);
auto shortcuts = tui->getMemberAssert<obj::ListObject>("shortcuts");
auto inputs = tui->getMemberAssert<obj::DictObject>("inputs");
auto input_states = tui->getMemberAssert<obj::DictObject>("input_states");
auto input_state_hold = input_states->get("hold");
{ // orbit
auto shortcut = nd::Shortcut::createShortcut("StrokesWidget", "camera_orbit");
auto args = NDO_CAST(obj::DictObject, shortcut->callbacks_arguments->get("invoke"));
NDO_CAST(obj::LinkObject, args->get("Gui"))->setLink(gui);
NDO_CAST(obj::EnumObject, args->get("transf"))->active = 1;
shortcuts->pushBack(shortcut);
auto trigger = shortcut->addTrigger("invoke");
auto back = tui->getInput(tp::Keycode::LEFT_ALT)->getMember<obj::EnumObject>("state");
trigger->addComparator("comp", back, input_state_hold);
}
{ // zoom
auto shortcut = nd::Shortcut::createShortcut("StrokesWidget", "camera_orbit");
auto args = NDO_CAST(obj::DictObject, shortcut->callbacks_arguments->get("invoke"));
NDO_CAST(obj::LinkObject, args->get("Gui"))->setLink(gui);
NDO_CAST(obj::EnumObject, args->get("transf"))->active = 2;
shortcuts->pushBack(shortcut);
auto trigger = shortcut->addTrigger("invoke");
auto back = tui->getInput(tp::Keycode::LEFT_CONTROL)->getMember<obj::EnumObject>("state");
trigger->addComparator("comp", back, input_state_hold);
}
{ // pan
auto shortcut = nd::Shortcut::createShortcut("StrokesWidget", "camera_orbit");
auto args = NDO_CAST(obj::DictObject, shortcut->callbacks_arguments->get("invoke"));
NDO_CAST(obj::LinkObject, args->get("Gui"))->setLink(gui);
shortcuts->pushBack(shortcut);
auto trigger = shortcut->addTrigger("invoke");
auto back = tui->getInput(tp::Keycode::SPACE)->getMember<obj::EnumObject>("state");
trigger->addComparator("comp", back, input_state_hold);
}
}

View file

@ -0,0 +1,501 @@
#include "Strokes.h"
using namespace strokes;
Stroke::GLHandles::GLHandles() {
glGenVertexArrays(1, &VertexArrayID);
glBindVertexArray(VertexArrayID);
glGenBuffers(1, &vertexbuffer);
}
void Stroke::GLHandles::sendDataToGPU(tp::Array<Point>* mPoints) {
glBindVertexArray(VertexArrayID);
vbo_len = mPoints->length();
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(mPoints[0]) * vbo_len, mPoints->buff(), GL_STATIC_DRAW);
}
Stroke::GLHandles::~GLHandles() {
glDeleteBuffers(1, &vertexbuffer);
glDeleteVertexArrays(1, &VertexArrayID);
}
Stroke::Stroke() {}
tp::Array<Stroke::Point>& Stroke::buff() {
return mPoints;
}
void Stroke::updateGpuBuffers() {
mGl.sendDataToGPU(&mPoints);
}
void Stroke::denoisePos(tp::halni passes) {
for (auto pass : tp::Range(passes)) {
for (auto pi : tp::Range(mPoints.length() - 2)) {
mPoints[pi + 1].pos = (mPoints[pi + 1].pos + mPoints[pi].pos + mPoints[pi + 2].pos) / 3.f;
}
}
}
void Stroke::denoiseThickness(tp::halni passes) {
for (auto pass : tp::Range(passes)) {
for (auto pi : tp::Range(mPoints.length() - 2)) {
mPoints[pi + 1].thickness = (mPoints[pi].thickness + mPoints[pi + 2].thickness) / 2.f;
}
}
}
void Stroke::compress(tp::halnf factor) {
if (mPoints.length() < 3) {
return;
}
tp::List<Point> passed_poits;
for (auto idx : tp::Range(mPoints.length())) {
passed_poits.pushBack(mPoints[idx]);
}
tp::ListNode<Point>* min_node = NULL;
do {
min_node = NULL;
tp::halnf min_factor = factor;
tp::ListNode<Point>* iter = passed_poits.first()->next;
for (; iter->next; iter = iter->next) {
tp::vec3f dir1 = (iter->data.pos - iter->prev->data.pos).normalize();
tp::vec3f dir2 = (iter->next->data.pos - iter->data.pos).normalize();
tp::halnf factor = 1 - dir1.dot(dir2);
if (factor < min_factor) {
min_node = iter;
min_factor = factor;
}
}
if (min_node) {
passed_poits.delNode(min_node);
}
} while (min_node);
mPoints.reserve(passed_poits.length());
for (auto point : passed_poits) {
mPoints[point.idx()] = point.data();
}
}
void Stroke::subdiv(tp::halnf precision, tp::Camera* cam, tp::halni passes) {
// TODO
if (mPoints.length() < 4) {
return;
}
tp::List<Point> new_points;
for (auto idx : tp::Range(mPoints.length())) {
new_points.pushBack(mPoints[idx]);
}
auto viewmat = cam->viewmat();
auto projmat = cam->projmat();
for (auto i : tp::Range(passes)) {
auto n_points = new_points.length();
auto p0 = new_points.first();
auto p1 = p0->next;
auto p2 = p1->next;
auto p3 = p2->next;
while (p3) {
auto p1_2d = cam->project(p1->data.pos, viewmat, projmat);
auto p2_2d = cam->project(p2->data.pos, viewmat, projmat);
auto len = (p1_2d - p2_2d).length();
if (len > precision * 1.5f) {
auto const a = (p1->data.pos - p0->data.pos).unitv();
auto const b = (p2->data.pos - p3->data.pos).unitv();
auto const l = p1->data.pos - p2->data.pos;
auto const l_len = l.length();
auto const ab = a.dot(b);
auto const la = l.dot(a);
auto const lb = l.dot(b);
tp::vec3f mid;
if (1 - tp::abs(ab) < 0.001f) {
goto SKIP;
}
auto k = (lb - ab * la) / (1 - ab * ab);
auto t = ab * k - la;
if (k < 0 || t < 0) {
goto SKIP;
}
if (k > l_len) {
k = l_len;
}
if (t > l_len) {
t = l_len;
}
auto const p1_mid = p1->data.pos + (a * t);
auto const p2_mid = p2->data.pos + (b * k);
mid = (p1_mid + p2_mid) / 2.f;
{
auto const p0 = ((p2->data.pos - p1->data.pos) / 2.f) + p1->data.pos;
mid = ((mid - p0) / 3.f) + p0;
}
auto node = new_points.newNode();
node->data.pos = mid;
node->data.thickness = (p1->data.thickness + p2->data.thickness) / 2.f;
auto insert_after = p1;
p0 = p1;
p1 = p2;
p2 = p3;
p3 = p3->next;
new_points.attach(node, insert_after);
p0 = p0->next;
continue;
}
SKIP:
p0 = p1;
p1 = p2;
p2 = p3;
p3 = p3->next;
}
if (n_points == new_points.length()) {
break;
}
}
if (new_points.length() != mPoints.length()) {
mPoints.reserve(new_points.length());
for (auto point : new_points) {
mPoints[point.idx()] = point.data();
}
}
}
tp::halni Stroke::saveSize() {
tp::halni out = 0;
out += sizeof(tp::rgba);
out += sizeof(tp::alni);
out += mPoints.length() * sizeof(Point);
return out;
}
void Stroke::save(tp::File& file) {
file.write<tp::rgba>(&mCol);
tp::alni length = mPoints.length();
file.write<tp::alni>(&length);
for (auto piter : mPoints) {
file.write<Point>(&piter.data());
}
}
void Stroke::load(tp::File& file) {
tp::rgba color;
file.read<tp::rgba>(&color);
tp::alni p_len;
file.read<tp::alni>(&p_len);
mPoints.reserve(p_len);
for (auto piter : mPoints) {
file.read<Point>(&piter.data());
}
updateGpuBuffers();
}
PencilBrush::PencilBrush() {
mType = "pencil";
}
void PencilBrush::unsureReady(Stroke* stroke, tp::Camera* cam, bool debug) {
if (stroke->buff().length() == 1) {
auto new_point = stroke->buff()[0];
new_point.pos += 0.00001f;
stroke->buff().pushBack(new_point);
}
if (mEnableCompression && !debug) {
stroke->subdiv(mPrecision, cam, mSubdivPasses);
stroke->denoisePos(mDenoisePassesPos);
stroke->compress(mCompressionFactor);
}
stroke->updateGpuBuffers();
}
void PencilBrush::sample(Project* proj, tp::vec2f crs, tp::halnf pressure) {
if (proj->mActiveLayer == -1) {
return;
}
bool max_level = mStroke && mStroke->mPoints.length() > mMaxPoints;
if (!pressure || max_level) {
if (mStroke) {
unsureReady(mStroke, &proj->mCamera);
proj->mLayers[proj->mActiveLayer]->strokes.pushBack(mStroke);
mStroke = NULL;
}
if (max_level) {
mStroke = new Stroke();
mStroke->mCol = mCol;
mStroke->mPoints.pushBack(proj->mLayers[proj->mActiveLayer]->strokes.last()->data->mPoints.last());
}
if (!pressure) {
return;
}
}
auto thickness = pressure * (proj->mCamera.project({ 0.f, 0.f }) - proj->mCamera.project({ mSize, 0.f })).length();
bool point_passed = true;
if (mStroke && mStroke->buff().length()) {
auto last_point_2d = proj->mCamera.project(mStroke->buff().last().pos);
point_passed = (crs - last_point_2d).length() > mPrecision;
}
if (!point_passed) {
return;
}
if (!mStroke) {
mStroke = new Stroke();
mStroke->mCol = mCol;
}
auto point_coords = proj->mCamera.project(crs);
mStroke->buff().pushBack({ point_coords, (tp::halnf) thickness });
mStroke->updateGpuBuffers();
}
void PencilBrush::draw(Renderer* render, tp::Camera* camera) {
if (mStroke) {
if (mEnableCompression) {
mShowStroke.buff() = mStroke->buff();
mShowStroke.mCol = mStroke->mCol;
unsureReady(&mShowStroke, camera, 0);
render->drawStroke(&mShowStroke, camera);
} else {
render->drawStroke(mStroke, camera);
}
}
}
PencilBrush::~PencilBrush() {
if (mStroke) delete mStroke;
}
Project::Layer::~Layer() {
for (auto str : strokes) {
delete str.data();
}
}
Project::Project() {
mCamera.lookat({ 0.0f, 0.0f, 0.0f }, { 2.0f, 0.0f, 0.f }, { 0, 0, 1 });
auto lay = new Layer();
lay->enabled = true;
mLayers.pushBack(lay);
mActiveLayer = 0;
mBrushes.put("eraser", new EraserBrush());
mBrushes.put("pencil", new PencilBrush());
mActiveBrush = "pencil";
auto vec = mCamera.project({0, 0, 0});
vec = mCamera.project({-1, 0, 0});
vec = mCamera.project({1, 0, 0});
}
Project::~Project() {
for (auto brush : mBrushes) {
delete brush->val;
}
}
tp::alni Project::saveSize() {
tp::alni out = 0;
out += sizeof(tp::Camera);
out += sizeof(tp::rgba);
out += sizeof(tp::halni);
out += sizeof(tp::alni);
for (auto layer : mLayers) {
out += layer.data()->name.save_size();
out += sizeof(bool);
out += sizeof(tp::alni);
for (auto stiter : layer.data()->strokes) {
out += stiter->saveSize();
}
}
return out;
}
void Project::save(tp::File& file) {
file.write<tp::Camera>(&mCamera);
file.write<tp::rgba>(&mBackgroundColor);
file.write<tp::halni>(&mActiveLayer);
tp::alni lay_len = mLayers.length();
file.write<tp::alni>(&lay_len);
for (auto layer : mLayers) {
layer.data()->name.save(&file);
file.write<bool>(&layer.data()->enabled);
tp::alni len = layer.data()->strokes.length();
file.write<tp::alni>(&len);
for (auto stiter : layer.data()->strokes) {
stiter->save(file);
}
}
}
void Project::load(tp::File& file) {
file.read<tp::Camera>(&mCamera);
file.read<tp::rgba>(&mBackgroundColor);
file.read<tp::halni>(&mActiveLayer);
tp::alni layers_len;
file.read<tp::alni>(&layers_len);
mLayers.reserve(layers_len);
for (tp::alni idx = 0; idx < layers_len; idx++) {
tp::string key; key.load(&file);
auto layer = new Layer();
layer->name = key;
mLayers[idx] = layer;
file.read<bool>(&layer->enabled);
tp::alni len;
file.read<tp::alni>(&len);
for (tp::alni str_idx = 0; str_idx < len; str_idx++) {
auto str = new Stroke();
layer->strokes.pushBack(str);
layer->strokes.last()->data->load(file);
}
}
}
Renderer::Renderer(tp::vec2f size) :
mBuffer(size, { 0.f, 0.f, 1.f, 1.f }, 4),
mBufferDowncast(size, { 0.f, 0.f, 1.f, 1.f }),
mShader(".\\rsc\\shaders\\stroke", ".\\rsc\\shaders\\stroke", ".\\rsc\\shaders\\stroke")
{
mMatrixUniform = mShader.getu("MVP");
mColorUniform = mShader.getu("Color");
mRatioUniform = mShader.getu("Ratio");
mTargetUniform = mShader.getu("Target");
mBGColUniform = mShader.getu("BGCol");
}
void Renderer::renderBegin() {
mBuffer.begin_draw();
mBuffer.clear();
}
void Renderer::setViewport(tp::rectf viewport) {
mBuffer.setViewport(viewport);
}
void Renderer::drawStroke(Stroke* str, tp::Camera* camera) {
//return;
glEnable(GL_DEPTH_TEST);
glEnable(GL_MULTISAMPLE);
GLint val;
glGetIntegerv(GL_SAMPLES, &val);
glBindVertexArray(str->mGl.VertexArrayID);
mShader.bind();
auto cam_mat = camera->transform_mat().transpose();
glUniformMatrix4fv(mMatrixUniform, 1, GL_FALSE, &cam_mat[0][0]);
glUniform4fv(mColorUniform, 1, &str->mCol.r);
glUniform4fv(mBGColUniform, 1, &mBuffer.col_clear.r);
auto ratio = camera->get_ratio();
glUniform1fv(mRatioUniform, 1, &ratio);
auto target = tp::halnf(((camera->get_target() - camera->get_pos()).length() - camera->get_near())
/ (camera->get_far() - camera->get_near()));
glUniform1fv(mTargetUniform, 1, &target);
// 1st attribute buffer : vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, str->mGl.vertexbuffer);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, (void*)0);
glDrawArrays(GL_LINE_STRIP, 0, str->mGl.vbo_len);
glDisableVertexAttribArray(0);
mShader.unbind();
}
void Renderer::renderEnd() {
mBuffer.end_draw();
mBufferDowncast.begin_draw();
auto size = mBufferDowncast.getSize();
glBindFramebuffer(GL_READ_FRAMEBUFFER, mBuffer.buffId());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mBufferDowncast.buffId());
mBufferDowncast.clear();
glBlitFramebuffer(0, 0, size.x, size.y, 0, 0, size.x, size.y, GL_COLOR_BUFFER_BIT, GL_NEAREST);
mBufferDowncast.end_draw();
}
tp::uhalni Renderer::getTextudeId() {
return mBufferDowncast.texId();
}
tp::ogl::fbuffer* Renderer::getBuff() {
return &mBufferDowncast;
}
void Renderer::setClearCol(tp::rgba col) {
mBuffer.col_clear = col;
mBufferDowncast.col_clear = col;
}
Renderer::~Renderer() {
}

View file

@ -0,0 +1,42 @@
#include "strokesobject.h"
using namespace tp;
using namespace obj;
void StrokesObject::constructor(StrokesObject* self) {
new (&self->mProject) strokes::Project();
}
void StrokesObject::copy(StrokesObject* self, const StrokesObject* in) {
self->mProject = in->mProject;
}
void StrokesObject::destructor(StrokesObject* self) {
self->mProject.~Project();
}
static alni save_size(StrokesObject* self) {
return self->mProject.saveSize();
}
static void save(StrokesObject* self, File& file_self) {
self->mProject.save(file_self);
}
static void load(File& file_self, StrokesObject* self) {
new (&self->mProject) strokes::Project();
self->mProject.load(file_self);
}
struct ObjectType obj::StrokesObject::TypeData = {
.base = NULL,
.constructor = (object_constructor) StrokesObject::constructor,
.destructor = (object_destructor) StrokesObject::destructor,
.copy = (object_copy) StrokesObject::copy,
.size = sizeof(StrokesObject),
.name = "StrokesProject",
.save_size = (object_save_size) save_size,
.save = (object_save) save,
.load = (object_load) load,
};

1049
Sketch3D/private/temp.cpp Normal file

File diff suppressed because it is too large Load diff