diff --git a/CMakeLists.txt b/CMakeLists.txt index 989e7e6..5f9f17f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,3 +26,4 @@ add_subdirectory(DataAnalysis) add_subdirectory(Objects) add_subdirectory(Widgets) add_subdirectory(LibraryViewer) +add_subdirectory(Sketch3D) \ No newline at end of file diff --git a/Sketch3D/CMakeLists.txt b/Sketch3D/CMakeLists.txt new file mode 100644 index 0000000..567767b --- /dev/null +++ b/Sketch3D/CMakeLists.txt @@ -0,0 +1,22 @@ +project(Sketch3Dlib) + +### ---------------------- Externals --------------------- ### +set(BINDINGS_INCLUDE ../Externals/glfw/include ${GLEW_INCLUDE_DIR}) +set(BINDINGS_LIBS glfw Imgui ${GLEW_LIB}) + +### ---------------------- Static Library --------------------- ### +file(GLOB SOURCES "./private/*.cpp" "./private/*/*.cpp") +file(GLOB HEADERS "./public/*.hpp" "./public/*/*.hpp") + +add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS}) + +target_include_directories(${PROJECT_NAME} PUBLIC ./public/ ${BINDINGS_INCLUDE} ./ext/) +target_link_libraries(${PROJECT_NAME} PUBLIC Graphics Connection Widgets Math Strings) +target_link_libraries(${PROJECT_NAME} PUBLIC ${BINDINGS_LIBS}) + +file(COPY "rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") + +### -------------------------- Applications -------------------------- ### +add_executable(Sketch3D ./applications/Entry.cpp) +target_link_libraries(Sketch3D ${PROJECT_NAME}) + diff --git a/Sketch3D/applications/Entry.cpp b/Sketch3D/applications/Entry.cpp new file mode 100644 index 0000000..33b0d6c --- /dev/null +++ b/Sketch3D/applications/Entry.cpp @@ -0,0 +1,34 @@ + +#include "nodes.h" +#include "StrokesWidget.h" + +int main(int argc, char* argv[]) { + + auto nodes = (argc == 2) ? nd::NodesCore::createFast(argv[1]) : nd::NodesCore::createFast(); + + tp::init_utils(); + + obj::NDO->define(&obj::StrokesObject::TypeData); + obj::NDO->define(&nd::StrokesWidget::TypeData); + obj::NDO->type_groups.addType(&obj::StrokesObject::TypeData, { "Strokes", "StrokesProject" }); + obj::NDO->type_groups.addType(&nd::StrokesWidget::TypeData, { "Strokes", "StrokesWidget" }); + + nd::StrokesWidget::OpsInit(); + nd::StrokesWidget::addShortcuts(nodes); + + auto root_widget = NDO_CAST(nd::Widget, obj::NDO->create("RootWidget")); + + auto childs = root_widget->getMember("childs"); + + childs->pushBack(obj::NDO->create("StrokesWidget")); + + nodes->bindRootWindow(root_widget); + + nodes->run(); + + nd::StrokesWidget::OpsUnInit(); + tp::finalize_utils(); + + nodes->destroyFast(); + tp::terminate(); +} \ No newline at end of file diff --git a/Sketch3D/private/StrokesWidget.cpp b/Sketch3D/private/StrokesWidget.cpp new file mode 100644 index 0000000..3e72dd8 --- /dev/null +++ b/Sketch3D/private/StrokesWidget.cpp @@ -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("TargetProject"); + if (!link) { + return NULL; + } + auto obj = link->getLink(); + if (!obj) { + link->setLink(getMember("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("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("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(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(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("uis"); + NDO_CASTV(nd::GUI, uis_list->getItems()[0], gui); + NDO_CASTV(nd::TUI, uis_list->getItems()[1], tui); + + auto shortcuts = tui->getMemberAssert("shortcuts"); + auto inputs = tui->getMemberAssert("inputs"); + auto input_states = tui->getMemberAssert("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("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("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("state"); + trigger->addComparator("comp", back, input_state_hold); + } +} \ No newline at end of file diff --git a/Sketch3D/private/strokes.cpp b/Sketch3D/private/strokes.cpp new file mode 100644 index 0000000..b38ae87 --- /dev/null +++ b/Sketch3D/private/strokes.cpp @@ -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* 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::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 passed_poits; + + for (auto idx : tp::Range(mPoints.length())) { + passed_poits.pushBack(mPoints[idx]); + } + + tp::ListNode* min_node = NULL; + do { + min_node = NULL; + tp::halnf min_factor = factor; + + tp::ListNode* 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 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(&mCol); + + tp::alni length = mPoints.length(); + file.write(&length); + for (auto piter : mPoints) { + file.write(&piter.data()); + } +} + +void Stroke::load(tp::File& file) { + tp::rgba color; + file.read(&color); + + tp::alni p_len; + file.read(&p_len); + + mPoints.reserve(p_len); + for (auto piter : mPoints) { + file.read(&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(&mCamera); + file.write(&mBackgroundColor); + file.write(&mActiveLayer); + + tp::alni lay_len = mLayers.length(); + file.write(&lay_len); + for (auto layer : mLayers) { + layer.data()->name.save(&file); + + file.write(&layer.data()->enabled); + + tp::alni len = layer.data()->strokes.length(); + file.write(&len); + for (auto stiter : layer.data()->strokes) { + stiter->save(file); + } + } +} + +void Project::load(tp::File& file) { + file.read(&mCamera); + file.read(&mBackgroundColor); + file.read(&mActiveLayer); + + tp::alni layers_len; + file.read(&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(&layer->enabled); + + tp::alni len; + file.read(&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() { +} diff --git a/Sketch3D/private/strokesobject.cpp b/Sketch3D/private/strokesobject.cpp new file mode 100644 index 0000000..49ade1c --- /dev/null +++ b/Sketch3D/private/strokesobject.cpp @@ -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, +}; diff --git a/Sketch3D/private/temp.cpp b/Sketch3D/private/temp.cpp new file mode 100644 index 0000000..0a0e4d2 --- /dev/null +++ b/Sketch3D/private/temp.cpp @@ -0,0 +1,1049 @@ + /* + +#include "app.h" + +#include "glutils.h" + +#include "shader.h" +#include "strings.h" + +#include "imgui_internal.h" + +#include "ImGuiUtils.h" + +using namespace tp; + +void StrokeApp::draw_explorer() { + using namespace ImGui; + //objects_gui.oexplorer(200); + //if (SubMenuBegin("Type Info", 1)) { + //objects_gui.oproperties(objects_gui.active->type); + //SubMenuEnd(1); + //} +} + +void StrokeApp::draw_brush_properties(rectf rect) { + using namespace ImGui; + if (ImGui::Button(project->sampler.eraser ? "Eraser" : "Pencil")) { + project->sampler.eraser = !project->sampler.eraser; + } ImGui::SameLine(); + + SetNextItemWidth(rect.z / 2.f); + //ImGui::SetNextItemWidth(rect.w); + if (project->sampler.eraser) { + ImGui::SliderFloat(" ", &project->sampler.eraser_size, 0.001f, 0.2f); + } else { + ImGui::SliderFloat(" ", &project->sampler.screen_thikness, 0.001f, 0.2f); + + SetNextItemWidth(rect.z / 1.5f); + int flags = ImGuiColorEditFlags_NoLabel | + ImGuiColorEditFlags_NoSmallPreview | + ImGuiColorEditFlags_NoSidePreview | + ImGuiColorEditFlags_NoInputs; + ImGui::ColorPicker4("Color", &project->sampler.stroke_col.r, flags); + + PushItemWidth(rect.z / 4.f); + if (SubMenuBegin("Sampler Settings", 1)) { + + if (SubMenuBegin("Denoising Passes", 2)) { + SliderInt("Position", &project->denoise_passes, 0, 10); + SliderInt("Thickness", &project->denoise_passes_thikness, 0, 10); + SubMenuEnd(2); + } + + if (SubMenuBegin("Size Reduction", 2)) { + SliderFloat("Input Precision", &project->sampler.screen_precision, 0, 0.005); + Checkbox("Auto Apply", &project->auto_reduction); + SubMenuEnd(2); + } + + if (SubMenuBegin("Pen Pressure", 2)) { + //ImGui::Bezier("easeOutSine", tablet_input_formater); + SubMenuEnd(2); + } + + SubMenuEnd(1); + } + PopItemWidth(); + } + + +} + +void StrokeApp::draw_toolbar(rectf rec) { + const halnf nbuttons = project ? 6 : 1; + halnf buttsize = clamp(rec.z / nbuttons, 5.f, 50.f) + 30; + vec2f buttrec = {buttsize - 20, buttsize - 50}; + + vec2f midpoint = (rec.pos + rec.size_vecw()) / 2.f; + rec.size = {buttsize * nbuttons + 20, buttsize - 30}; + rec.pos = midpoint - (rec.size / 2.f); + + //ImGui::SetNextWindowPos(ImVec2(rec.x, window.size.y - rec.y)); + ImGui::SetNextWindowSize(ImVec2(rec.z, rec.w)); + ImGui::Begin("ToolBar", 0, ImGui::frame_window); + + vec2f popup_size = vec2f(200, 300); + + auto popup = ImGui::ButtonHoverPopupBegin("Explore", buttrec, popup_size); + if (popup) { draw_explorer(); } + ImGui::HoverPopupEnd(popup); + + if (!project) { + ImGui::End(); + return; + } + + ImGui::SameLine(); + popup = ImGui::ButtonHoverPopupBegin("Layers", buttrec, popup_size); + if (popup) { + if (ImGui::Button("Add New", ImVec2(150, 30))) { + project->layers.pushBack(new drawlayer()); + } + + ImGui::Separator(); + + for (auto lay : project->layers) { + ImGui::PushID((int) lay.idx()); + + bool higlight = lay.data() == project->active_layer; + if (higlight) { + ImGui::PushStyleColor(ImGuiCol_Button, GImGui->Style.Colors[ImGuiCol_ButtonActive]); + } + + if (ImGui::Button(lay->name.cstr(), ImVec2(100, 30))) { + project->active_layer = lay.data(); + } + + if (higlight) { + ImGui::PopStyleColor(); + } + + + if (ImGui::BeginPopupContextItem(lay->name.cstr(), ImGuiPopupFlags_MouseButtonRight)) { + static char name[150] = {"asdas"}; + if (ImGui::InputTextEx(" ", "new name", name, 150, {150 , 30}, ImGuiInputTextFlags_EnterReturnsTrue)) { + lay->name = name; + lay->name.capture(); + } + if (ImGui::Button("Delete", ImVec2(150, 30))) { + project->active_layer = lay.node()->prev ? lay.node()->prev->data : NULL; + delete lay.node()->data; + project->layers.delNode(lay.node()); + ImGui::EndPopup(); + ImGui::PopID(); + break; + } + if (lay.node()->prev && ImGui::Button("Move Up", ImVec2(150, 30))) { + swap(lay.node()->prev->data, lay.data()); + } + if (lay.node()->next && ImGui::Button("Move Down", ImVec2(150, 30))) { + swap(lay.node()->next->data, lay.data()); + } + ImGui::EndPopup(); + } + + ImGui::SameLine(); + bool visable = !lay->hiden; + ImGui::Checkbox("", &visable); + lay->hiden = !visable; + + ImGui::PopID(); + } + } + HoverPopupEnd(popup); + + if (!project->active_layer) { + ImGui::End(); + return; + } + + ImGui::SameLine(); + popup = ImGui::ButtonHoverPopupBegin("Hist", buttrec, vec2f(200, 300)); + if (popup) { + if (ImGui::Button("Undo", ImVec2(100, 30))) { + project->active_layer->undo(); + } + if (ImGui::Button("Redo", ImVec2(100, 30))) { + project->active_layer->redo(); + } + if (ImGui::Button("Clear History", ImVec2(100, 30))) { + project->active_layer->clear_history(); + } + } + HoverPopupEnd(popup); + + ImGui::SameLine(); + popup = ImGui::ButtonHoverPopupBegin("Tool", buttrec, popup_size); + if (popup) { draw_brush_properties(rectf(0, 0, 200, 600)); } + ImGui::HoverPopupEnd(popup); + + + ImGui::SameLine(); + popup = ImGui::ButtonHoverPopupBegin("Proj", buttrec, popup_size); + if (popup) { + + ImGui::SetNextItemWidth(popup_size.x / 2.f); + int flags = ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoInputs; + ImGui::ColorPicker4("Background Color", &project->canvas_color.r, flags); + + halni width = ImGui::GetWindowContentRegionWidth(); + if (ImGui::Button("Clear All Strokes", ImVec2(width, 30))) { + project->active_layer->clear_canvas(); + } + + if (ImGui::Button("Reduce Size", ImVec2(width, 30))) { + alni length = project->save_size() / (1024); + project->active_layer->reduce_size(project->pass_factor); + alni length_after = project->save_size() / (1024); + ImGui::Notify(sfmt("Size Redused from %i to %i kb", length, length_after).cstr()); + } + + ImGui::SetNextItemWidth(width / 2); + ImGui::SliderFloat("Pass Facor", &project->pass_factor, 0, 0.01); + } + HoverPopupEnd(popup); + + ImGui::SameLine(); + popup = ImGui::ButtonHoverPopupBegin("Cam", buttrec, vec2f(200, 300)); + if (popup) { + if (ImGui::Button("Reset", ImVec2(100, 30))) { + project->cam.lookat({0, 0, 0}, {100, 0, 0}, {0, 0, 1}); + } + halnf fov = project->cam.get_fov(); + ImGui::Text("Field Of View"); + ImGui::SetNextItemWidth(110); + ImGui::SliderFloat(" ", &fov, 0.1, PI - 0.1); + project->cam.set_fov(fov); + } + HoverPopupEnd(popup); + + ImGui::End(); +} + +void StrokeApp::gui_draw() { + /* + halnf toolbar_size = 290; + draw_toolbar(rectf(window.size.x / 2.f - toolbar_size / 2.f, window.size.y, toolbar_size, 60)); + + if (!project) { + DrawTextR(rectf(window.size / 2.2, 0), "Select Strokes Object", rgba(0.75, 0.75, 0.75, 1)); + + } else if (!gui_is_active || 1) { + + if (project->sampler.eraser) { + halnf size = project->sampler.eraser_size * window.size.x / 2.f; + DrawCircle(window.cursor(), size, rgba(0.9, 0.9, 0.9, 0.7), 2.f); + } else { + halnf size = project->sampler.screen_thikness / 2.f * window.size.x / 2.f; + DrawCircle(window.cursor(), size, rgba(0, 0, 0, 0.7), 4.f); + DrawCircle(window.cursor(), size, project->sampler.stroke_col, 2.f); + } + } +} +*/ + +/* + +#pragma once + +#include "gl.h" +#include "glutils.h" +#include "window.h" +#include "fbuffer.h" + +#include "filesystem.h" + +#include "strokes.h" +#include "ImGuiObjectEditor.h" + +#include "primitives/primitives.h" +#include "strokesobject.h" + +class StrokeApp { + + strokes_project* project = NULL; + + tp::halnf tablet_input_formater[5] = {0.5f, 0.f, 1.f, .5f}; + + public: + + StrokeApp(tp::vec2f size = tp::vec2f(1400, 800)); + ~StrokeApp(); + + private: + + void MainProcTick(); + void MainDrawTick(); + + void camera_controller(); + + private: + + void gui_draw(); + void draw_explorer(); + void draw_toolbar(tp::rectf rect); + void draw_brush_properties(tp::rectf rect); +}; + +using namespace tp; +using namespace obj; + +StrokeApp::StrokeApp(vec2f size) { + //window.col_clear = rgba(0.2, 0.2, 0.23, 1); + //main_window = false; + //window.minsize.assign(400, 400); + + //Object* new_scratch = NULL; + //alni dict_idx = objects_gui.root->items.presents("Scratch"); + //if (dict_idx == -1) { + //new_scratch = NDO->create("strokes"); + //objects_gui.root->items.put("Scratch", new_scratch); + //} else { + //new_scratch = objects_gui.root->items[dict_idx]; + //} + //objects_gui.cd(new_scratch, "Scratch"); +} + +void StrokeApp::MainProcTick() { + + if (!objects_gui.active || objects_gui.active->type->name != "strokes") { + project = NULL; + window.col_clear = rgba(0.22, 0.22, 0.25, 1); + return; + } + + project = &NDO_CAST(StrokesObject, objects_gui.active)->project; + + if (gui_is_active) { + return; + } + + if (!project->active_layer) { + return; + } + + camera_controller(); + + halnf pen_pressure = ImGui::BezierValue(window.pen_pressure(), tablet_input_formater); + project->sampler.sample(&project->active_layer->strokes, &project->active_layer->strokes_undo, window.cursor(1), pen_pressure, &project->cam); + + if (!project->sampler.active_state() && project->sampler.has_input()) { + stroke& str = project->sampler.get_stroke(); + + str.denoise_positions(project->denoise_passes); + str.denoise_thickness(project->denoise_passes_thikness); + + if (project->auto_reduction) { + str.reduce_nof_points(project->pass_factor); + } + + project->active_layer->add_stroke(project->sampler.get_stroke()); + project->sampler.clear(); + } + + whait_for_event = !project->sampler.active_state(); +} + +void StrokeApp::MainDrawTick() { + gui_draw(); + + if (project) { + mat4f cammat = (project->cam.projmat() * project->cam.viewmat()).transpose(); + project->sampler.draw(cammat); + + for (alni idx = project->layers.length() - 1; idx > -1; idx--) { + if (!project->layers[idx]->hiden) { + project->layers[idx]->draw(cammat); + } + } + + window.col_clear = project->canvas_color; + } +} + + +void StrokeApp::camera_controller() { + + vec2f prevcur = window.prevcursor(1); + vec2f cur = window.cursor(1); + + vec2f delta = prevcur - cur; + vec3f target = project->cam.get_target(); + + if (glfwGetKey(window.geth(), GLFW_KEY_LEFT_ALT) == GLFW_PRESS) { + project->cam.rotate(delta.x * 5, delta.y * 5); + } + + if (glfwGetKey(window.geth(), GLFW_KEY_Z) == GLFW_PRESS) { + project->cam.zoom((prevcur.y + 1.f) / (cur.y + 1.f)); + } + + if (glfwGetKey(window.geth(), GLFW_KEY_SPACE) == GLFW_PRESS) { + project->cam.move(cur, prevcur); + } + + if (glfwGetKey(window.geth(), GLFW_KEY_X) == GLFW_PRESS) { + project->pickTargetLength(cur); + } + + if (glfwGetKey(window.geth(), GLFW_KEY_B) == GLFW_PRESS) { + project->sampler.eraser = false; + } + + if (glfwGetKey(window.geth(), GLFW_KEY_E) == GLFW_PRESS) { + project->sampler.eraser = true; + } + + project->cam.set_ratio(window.aspect_ratio()); +} + +StrokeApp::~StrokeApp() {} +*/ + + +/* + +struct StrokesRootWidget : nd::Widget { + + static obj::ObjectType TypeData; + static struct Vtable : Widget::Vtable {} vtable; + + static void constructor(StrokesRootWidget* self) { + } + + static void destructor(StrokesRootWidget* self) {} + static void copy(StrokesRootWidget* self, const StrokesRootWidget* in) {} + + static void procInputs(StrokesRootWidget* self, nd::GUI* gui) { + + auto project = &self->strokes->project; + auto rec = self->getRect(); + + //camera_controller(); + auto cursor = gui->gui_window.mNativeWindow.mEvents.mCursor; + + cursor.y = rec.size.y - cursor.y; + cursor -= tp::vec2f(rec.size) / 2.f; + cursor /= tp::vec2f(rec.size) / 2.f; + + //bool = gui->gui_window.mNativeWindow.mEvents.mLMouseDown; + tp::alnf pressure = gui->gui_window.mNativeWindow.mEvents.mLMouseDown; + //tp::halnf pen_pressure = ImGui::BezierValue(window.pen_pressure(), tablet_input_formater); + project->sampler.sample(&project->active_layer->strokes, &project->active_layer->strokes_undo, cursor, pressure, &project->cam); + + if (!project->sampler.active_state() && project->sampler.has_input()) { + stroke& str = project->sampler.get_stroke(); + + str.denoise_positions(project->denoise_passes); + str.denoise_thickness(project->denoise_passes_thikness); + + if (project->auto_reduction) { + str.reduce_nof_points(project->pass_factor); + } + + project->active_layer->add_stroke(project->sampler.get_stroke()); + project->sampler.clear(); + } + } + + static void presentOutput(StrokesRootWidget* self, nd::GUI* gui) { + if (!self->retrieveProject()) { + return; + } + + tp::rect rec = self->getRect(); + rec.pos = 0; + + glViewport(0, 0, rec.z, rec.w); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + auto project = &self->strokes->project; + + tp::mat4f cammat = (project->cam.projmat() * project->cam.viewmat()).transpose(); + project->sampler.draw(cammat); + + for (auto lay : project->layers) { + if (!lay->hiden) { + lay->draw(cammat); + } + } + + //window.col_clear = project->canvas_color; + } + +}; + +*/ + + +/* + +// ----------------------------- strokes ------------------------ // + + +#pragma once + +#include "glcommon.h" +#include "shader.h" + +#include "array.h" +#include "list.h" +#include "map.h" +#include "topology.h" +#include "strings.h" + +struct StrokeMesh { + tp::Array vbo; + + tp::ogl::shader* shader; + tp::rgba color = tp::rgba(1); + + GLuint VertexArrayID; + GLuint vertexbuffer; + GLuint MatrixID; + GLuint ColorID; + + void init(); + + StrokeMesh(); + void operator=(const StrokeMesh& in); + + void bind_buffers(); + void draw_mesh(const tp::mat4f& cammat); + ~StrokeMesh(); +}; + +struct stroke_point { + tp::vec3f pos; + tp::vec3f normal; + tp::halnf thikness; + stroke_point(); +}; + +struct stroke { + + tp::Array points; + + StrokeMesh mesh; + + void gen_quad(tp::alni pidx, stroke_point* p1, stroke_point* p2, tp::vec3f dir1, tp::vec3f dir2); + tp::vec3f split_dir(tp::vec3f v1, tp::vec3f v2, const tp::vec3f& norm); + + void gen_mesh(); + + void drawcall(const tp::mat4f& cammat); + void add_point(const stroke_point& p); + + +}; + +class drawlayer { + public: + tp::string name = "new layer"; + tp::List strokes; + tp::List strokes_undo; + bool hiden = false; + + void undo(); + void redo(); + + void add_stroke(const stroke& str); + void draw(const tp::mat4f& cammat); + + void clear_history() { + strokes_undo.free(); + } + + void clear_canvas() { + tp::alni len = strokes.length(); + for (tp::alni idx = 0; idx < len; idx++) { + undo(); + } + } + + void reduce_size(tp::halnf pass_factor) { + for (auto str : strokes) { + str.data().reduce_nof_points(pass_factor); + str.data().gen_mesh(); + } + } + + void clear() { + strokes.free(); + strokes_undo.free(); + } +}; + +class inputsmpler { + public: + + bool is_active = false; + + stroke input; + tp::halnf pressure; + + tp::rgba stroke_col = tp::rgba(0.77, 0.77, 0.77, 1); + + tp::halnf screen_precision = 0.000f; + tp::halnf precision = 0.002; + tp::halnf screen_thikness = 0.01f; + tp::halnf thickness = 0.04; + + bool eraser = false; + tp::halnf eraser_size = 0.1f; + + void add_point(const tp::vec3f& pos, const tp::vec3f& norm, float thickness); + bool passed(const tp::vec3f& point); + void start(const tp::vec2f& cpos, tp::Camera* cam); + void sample_util(const tp::vec2f& cpos, tp::Camera* cam); + void erase_util(tp::List* pull, tp::List* undo, const tp::vec2f& cpos, tp::Camera* cam); + void finish(const tp::vec2f& cpos, tp::Camera* cam); + + void sample(tp::List* pool, tp::List* undo, tp::vec2f curs, float pressure, tp::Camera* cam); + void draw(const tp::mat4f& cammat); + + bool active_state(); + bool has_input(); + void clear(); + stroke& get_stroke(); +}; + +struct strokes_project { + + tp::Camera cam; + drawlayer* active_layer = NULL; + tp::List layers; + inputsmpler sampler; + tp::rgba canvas_color = tp::rgba(0.22f, 0.22f, 0.25f, 1.f); + + tp::halni denoise_passes = 1; + tp::halni denoise_passes_thikness = 3; + bool auto_reduction = true; + tp::halnf pass_factor = tp::halnf(0.001); + + strokes_project() { + cam.lookat({0, 0, 0}, {100, 0, 0}, {0, 0, 1}); + } + + drawlayer* get_base_layer() { + if (!layers.length()) { + layers.pushBack(new drawlayer()); + } + return layers[0]; + } + + void append_layers(drawlayer* base) { + for (auto lay : layers) { + base->strokes += lay->strokes; + } + } + + void pickTargetLength(const tp::vec2f& cur) { + using namespace tp; + stroke_point* target_point = NULL; + alnf min_length = FLT_MAX; + alnf min_tollerance = 0.05f; + + for (auto lay : layers) { + for (auto stroke : lay->strokes) { + for (auto pnt : stroke.data().points) { + + alnf tollerance = (cam.project(pnt.data().pos) - cur).length(); + alnf len = (pnt->pos - cam.get_pos()).dot(cam.get_fw()); + + alnf toll2 = abs(min_tollerance - tollerance); + bool selected = (toll2 < 0.01) ? (min_length > len) : (min_tollerance > tollerance); + + if (selected) { + target_point = &pnt.data(); + min_length = len; + min_tollerance = tollerance; + } + } + } + } + + if (target_point) { + cam.lookat(cam.get_pos() + (cam.get_fw() * min_length), cam.get_pos(), cam.get_up()); + } + } + + tp::alni save_size(); + void save(tp::File& file); + void load(tp::File& file); + + ~strokes_project() { + for (auto lay : layers) { + delete lay.data(); + } + } +}; + + + +#include "strokes.h" + +#include "glutils.h" + +#include "StrokesVersions.h" + +using namespace tp; + +void StrokeMesh::init() { + static ogl::shader shader("rsc/shaders/stroke", NULL, "rsc/shaders/stroke"); + this->shader = &shader; + + MatrixID = shader.getu("MVP"); + ColorID = shader.getu("StrokeColor"); + + glGenVertexArrays(1, &VertexArrayID); + glBindVertexArray(VertexArrayID); + + glGenBuffers(1, &vertexbuffer); +} + +StrokeMesh::StrokeMesh() { + init(); +} + +void StrokeMesh::operator=(const StrokeMesh& in) { + + glDeleteBuffers(1, &vertexbuffer); + glDeleteVertexArrays(1, &VertexArrayID); + + init(); + + vbo = in.vbo; + color = in.color; + + bind_buffers(); +} + +void StrokeMesh::bind_buffers() { + + glBindVertexArray(VertexArrayID); + + glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(vbo[0]) * vbo.length(), vbo.buff(), GL_STATIC_DRAW); +} + + +void StrokeMesh::draw_mesh(const mat4f& cammat) { + + glBindVertexArray(VertexArrayID); + + shader->bind(); + glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &cammat[0][0]); + glUniform4fv(ColorID, 1, &color.rgbs.r); + + // 1st attribute buffer : vertices + glEnableVertexAttribArray(0); + glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0); + + // Draw the triangles ! // mode. count. type. element. array buffer offset + glDrawArrays(GL_TRIANGLES, 0, vbo.length()); + + glDisableVertexAttribArray(0); + + shader->unbind(); +} + +StrokeMesh::~StrokeMesh() { + glDeleteBuffers(1, &vertexbuffer); + glDeleteVertexArrays(1, &VertexArrayID); +} + + +stroke_point::stroke_point() { + pos = vec3f(0, 0, 0); + normal = vec3f(0, 1, 0); + thikness = 0.06; +} + +void stroke::gen_quad(alni pidx, stroke_point* p1, stroke_point* p2, vec3f dir1, vec3f dir2) { + //vec3 perp = normalize(cross(p1->normal, p2->pos - p1->pos)) * p1->thikness; + //float cosa1 = dot(perp, dir1) / (dir1.length() * perp.length()); + //float cosa2 = dot(perp, dir2) / (dir2.length() * perp.length()); + //float thickness1 = -p1->thikness / cosa1; + //float thickness2 = -p2->thikness / cosa2; + +vec3f v1 = p1->pos + dir1 * p1->thikness; +vec3f v2 = p2->pos + dir2 * p2->thikness; +vec3f v3 = p2->pos - dir2 * p2->thikness; +vec3f v4 = p1->pos - dir1 * p1->thikness; + +alni vidx = pidx * 6; + +mesh.vbo[vidx] = v1; +mesh.vbo[vidx + 1] = v2; +mesh.vbo[vidx + 2] = v3; +mesh.vbo[vidx + 3] = v3; +mesh.vbo[vidx + 4] = v4; +mesh.vbo[vidx + 5] = v1; +} + +vec3f stroke::split_dir(vec3f v1, vec3f v2, const vec3f& norm) { + + v1.normalize(); + v2.normalize(); + + vec3f plane_normal; + + halnf val = v2.dot(v1); + + if (val > -0.999999) { + vec3f crossp = v1.cross(v2); + mat3f rot_matrix = mat3f::rotmat(crossp, acos(v1.dot(v2)) / 2.f); + vec3f middle = rot_matrix * v1; + plane_normal = middle.cross(crossp); + } + else { + plane_normal = v2; + } + + return plane_normal.cross(norm); +} + +void stroke::gen_mesh() { + + alni nvert = (points.length() - 1) * 6; + + mesh.vbo.reserve(nvert); + + for (alni pidx = 0; pidx < points.length() - 1; pidx++) { + + stroke_point pt0; + stroke_point pt1 = points[pidx]; + stroke_point pt2 = points[pidx + 1]; + stroke_point pt3; + + vec3f dir1; + vec3f dir2; + + if (pidx > 0) { + pt0 = points[pidx - 1]; + } + else { + pt0.pos = pt1.pos + (pt1.pos - pt2.pos); + pt0.thikness = 0.001; + } + + if (pidx < points.length() - 2) { + pt3 = points[pidx + 2]; + } + else { + pt3.pos = pt2.pos + (pt2.pos - pt1.pos); + pt3.thikness = 0.001; + } + + dir1 = split_dir(pt0.pos - pt1.pos, pt2.pos - pt1.pos, pt1.normal); + dir2 = split_dir(pt1.pos - pt2.pos, pt3.pos - pt2.pos, pt2.normal); + + if (dir1.dot(dir2) < 0) { + dir1 *= -1; + } + + gen_quad(pidx, &pt1, &pt2, dir1.unitv(), dir2.unitv()); + } + + mesh.bind_buffers(); +} + +void stroke::drawcall(const mat4f& cammat) { + mesh.draw_mesh(cammat); +} + +void stroke::add_point(const stroke_point& p) { + points.pushBack(p); +} + + +void drawlayer::undo() { + if (strokes.last()) { + strokes_undo.pushBack(strokes.last()->data); + strokes.delNode(strokes.last()); + } +} + +void drawlayer::redo() { + if (strokes_undo.last()) { + strokes.pushBack(strokes_undo.last()->data); + strokes_undo.delNode(strokes_undo.last()); + } +} + +void drawlayer::add_stroke(const stroke& str) { + strokes.pushBack(str); + strokes.last()->data.gen_mesh(); +} + +void drawlayer::draw(const mat4f& cammat) { + for (ListNode* str = strokes.last(); str; str = str->prev) { + str->data.drawcall(cammat); + } +} + +void inputsmpler::add_point(const vec3f& pos, const vec3f& norm, float thickness) { + stroke_point p; + p.pos = pos; + p.normal = norm; + p.thikness = thickness * pressure; + input.add_point(p); +} + +bool inputsmpler::passed(const vec3f& point) { + if (input.points.length()) { + return (point - input.points[input.points.length() - 1].pos).length() > precision; + } + return true; +} + +void inputsmpler::start(const vec2f& cpos, Camera* cam) { + input.points.free(); + vec3f point = cam->project(cpos); + add_point(point, cam->get_fw(), 0.001f); +} + +void inputsmpler::sample_util(const vec2f& cpos, Camera* cam) { + vec3f point = cam->project(cpos); + if (passed(point)) { + add_point(point, cam->get_fw(), thickness); + } +} + +void inputsmpler::erase_util(List* pull, List* undo, const vec2f& cpos, Camera* cam) { + + ListNode* str = pull->first(); + while (str) { + bool remove = false; + + for (auto pnt : str->data.points) { + if ((cam->project(pnt.data().pos) - cpos).length() < eraser_size) { + remove = true; + break; + } + } + + if (remove) { + ListNode* tmp = str->next; + undo->pushBack(str->data); + pull->delNode(str); + str = tmp; + } + else { + str = str->next; + } + } +} + +void inputsmpler::finish(const vec2f& cpos, Camera* cam) { + if (input.points.length() <= 1) { + input.points.free(); + } + else { + input.points[input.points.length() - 1].thikness = 0.001f; + cam->offset_target(0.0001); + } +} + +void inputsmpler::sample(List* pull, List* undo, vec2f curs, float pressure, Camera* cam) { + + this->input.mesh.color = stroke_col; + this->pressure = pressure; + + if (eraser) { + if (pressure > 0) { + input.points.free(); + erase_util(pull, undo, curs, cam); + } + return; + } + else { + vec3f zero_projection = cam->project(vec2f(0.f)); + thickness = (cam->project(vec2f(0.f, screen_thikness)) - zero_projection).length(); + precision = (cam->project(vec2f(0.f, screen_precision)) - zero_projection).length(); + } + + switch (is_active) { + case false: + { + if (pressure) { + start(curs, cam); + is_active = true; + } + return; + } + case true: + { + sample_util(curs, cam); + if (!pressure) { + finish(curs, cam); + is_active = false; + return; + } + return; + } + } +} + +void inputsmpler::draw(const mat4f& cammat) { + + static int prev_len = input.points.length(); + if (input.points.length() > 1) { + if (prev_len < input.points.length()) { + input.gen_mesh(); + } + input.drawcall(cammat); + } + prev_len = input.points.length(); +} + +bool inputsmpler::active_state() { + return is_active; +} +bool inputsmpler::has_input() { + return input.points.length() > 1; +} +void inputsmpler::clear() { + input.points.free(); + is_active = false; +} +stroke& inputsmpler::get_stroke() { + return input; +} + + +alni strokes_project::save_size() { + alni version_size = StrokesVersion1::save_size(this); + return version_size + sizeof(ProjectInfo); +} + +void strokes_project::save(File& file) { + ProjectInfo head; + head.init(1); + file.write(&head); + StrokesVersion1::save(file, this); +} + +void strokes_project::load(File& file) { + ProjectInfo head; + file.read(&head); + + if (string(head.name) != "strokes") { + return; + } + + alni version = alni(string(head.version)); + if (version > 1 || version < 0) { + return; + } + + version == 1 ? StrokesVersion1::load(file, this) : StrokesVersion0::load(file, this); + + active_layer = get_base_layer(); +} + +*/ \ No newline at end of file diff --git a/Sketch3D/public/StrokesWidget.h b/Sketch3D/public/StrokesWidget.h new file mode 100644 index 0000000..729c8f1 --- /dev/null +++ b/Sketch3D/public/StrokesWidget.h @@ -0,0 +1,29 @@ + +#include "gui/Widgets.h" + +#include "nodes.h" +#include "StrokesObject.h" + +namespace nd { + struct StrokesWidget : Widget { + static obj::ObjectType TypeData; + static struct Vtable : Widget::Vtable {} vtable; + + strokes::Renderer mRenderer; + nd::GUIdrawer::ImageHandle mImage; + nd::GUIdrawer* mDrawer = NULL; + + static void constructor(StrokesWidget* self); + static void destructor(StrokesWidget* self); + static void copy(Widget* self, const Widget* in); + static void procInputs(StrokesWidget* self, nd::GUIInputs* inputs); + static void presentOutput(StrokesWidget* self, nd::GUIdrawer* drawer); + + strokes::Project* getTargetProject(); + + static void OpsInit(); + static void OpsUnInit(); + + static void addShortcuts(nd::NodesCore* core); + }; +}; \ No newline at end of file diff --git a/Sketch3D/public/strokes.h b/Sketch3D/public/strokes.h new file mode 100644 index 0000000..f8bf250 --- /dev/null +++ b/Sketch3D/public/strokes.h @@ -0,0 +1,153 @@ + +#include "Buffer.hpp" +#include "List.hpp" +#include "Map.hpp" +#include "Topology.hpp" +#include "Color.hpp" +#include "Strings.hpp" + +namespace strokes { + + class Renderer; + class Project; + + class Stroke { + + friend Renderer; + + public: + struct Point { + tp::vec3f pos; + tp::halnf thickness = NULL; + }; + + tp::Array mPoints; + tp::rgba mCol; + + private: + struct GLHandles { + GLuint VertexArrayID = 0; + GLuint vertexbuffer = 0; + GLuint vbo_len = 0; + GLHandles(); + void sendDataToGPU(tp::Array* mPoints); + ~GLHandles(); + } mGl; + + public: + + Stroke(); + + void denoisePos(tp::halni passes); + void denoiseThickness(tp::halni passes); + void compress(tp::halnf factor); + void subdiv(tp::halnf precition, tp::Camera* cam, tp::halni passes = 1); + + void updateGpuBuffers(); + + tp::Array& buff(); + + tp::halni saveSize(); + void save(tp::File& file); + void load(tp::File& file); + }; + + class Brush { + public: + tp::string mType = "equal"; + Brush() {} + virtual void sample(Project* proj, tp::vec2f crs, tp::halnf pressure) {} + virtual void draw(Renderer* render, tp::Camera* camera) {} + virtual ~Brush() {} + }; + + class PencilBrush : public Brush { + + tp::halnf mPrecision = 0.001f; + + tp::halni mDenoisePassesPos = 1; + tp::halni mDenoisePassesThick = 3; + tp::halnf mCompressionFactor = 0.0001f; + tp::halni mSubdivPasses = 3; + bool mEnableCompression = true; + + tp::halni mMaxPoints = 100; + + Stroke* mStroke = NULL; + Stroke mShowStroke; + + void unsureReady(Stroke* stroke, tp::Camera* cam, bool debug = false); + + public: + + tp::rgba mCol = tp::rgba(1.0f); + tp::halnf mSize = 0.01f; + + PencilBrush(); + virtual void sample(Project* proj, tp::vec2f crs, tp::halnf pressure) override; + virtual void draw(Renderer* render, tp::Camera* camera) override; + virtual ~PencilBrush(); + }; + + struct EraserBrush : public Brush { + EraserBrush() { mType = "eraser"; } + virtual void sample(Project* proj, tp::vec2f crs, tp::halnf pressure) override {} + virtual void draw(Renderer* render, tp::Camera* camera) override {} + virtual ~EraserBrush() {} + }; + + class Project { + + public: + + struct Layer { + tp::string name = "new layer"; + tp::List strokes; + bool enabled = true; + ~Layer(); + }; + + tp::Array mLayers; + tp::halni mActiveLayer = -1; + + tp::Camera mCamera; + tp::rgba mBackgroundColor = { 0.22f, 0.22f, 0.25f, 1.f }; + + tp::HashMap mBrushes; + tp::string mActiveBrush; + + Project(); + ~Project(); + + tp::alni saveSize(); + void save(tp::File& file); + void load(tp::File& file); + }; + + class Renderer { + tp::ogl::fbuffer mBufferDowncast; + tp::ogl::fbuffer mBuffer; + tp::ogl::shader mShader; + + // shader uniforms + GLuint mMatrixUniform = 0; + GLuint mColorUniform = 0; + GLuint mRatioUniform = 0; + GLuint mTargetUniform = 0; + GLuint mBGColUniform = 0; + + public: + Renderer(tp::vec2f size); + + void renderBegin(); + void setViewport(tp::rectf viewport); + void drawStroke(Stroke* str, tp::Camera* camera); + void renderEnd(); + + void setClearCol(tp::rgba col); + tp::uhalni getTextudeId(); + tp::ogl::fbuffer* getBuff(); + + ~Renderer(); + }; +}; \ No newline at end of file diff --git a/Sketch3D/public/strokes_versions.h b/Sketch3D/public/strokes_versions.h new file mode 100644 index 0000000..869221e --- /dev/null +++ b/Sketch3D/public/strokes_versions.h @@ -0,0 +1,178 @@ +#pragma once + +#include "strokes.h" + +struct ProjectInfo { + char name[10] = {0}; + char version[10] = {0}; + + void init(tp::alni v) { + tp::string version_s(v); + tp::memcp(name, "strokes", tp::slen("strokes")); + tp::memcp(version, version_s.cstr(), version_s.size()); + } +}; + +namespace StrokesVersion0 { + + tp::alni save_size(strokes_project* proj) { + tp::alni out = 0; + out += sizeof(tp::Camera); + out += sizeof(tp::rgba); + + out += sizeof(tp::alni); + for (auto layer : proj->layers) { + for (auto stiter : layer->strokes) { + stroke* str = &stiter.data(); + + out += sizeof(tp::rgba); + + out += sizeof(tp::alni); + out += str->points.length() * sizeof(stroke_point); + } + } + return out; + } + + void save(tp::File& file, strokes_project* proj) { + + file.write(&proj->cam); + file.write(&proj->canvas_color); + + drawlayer base_layer; + proj->append_layers(&base_layer); + + tp::alni len = base_layer.strokes.length(); + file.write(&len); + for (auto stiter : base_layer.strokes) { + stroke* str = &stiter.data(); + + tp::alni length = str->points.length(); + file.write(&length); + file.write(&str->mesh.color); + + for (auto piter : str->points) { + file.write(&piter.data()); + } + } + } + + void load(tp::File& file, strokes_project* proj) { + file.read(&proj->cam); + + file.read(&proj->canvas_color); + + drawlayer* base = proj->get_base_layer(); + + tp::alni len; + file.read(&len); + + for (tp::alni str_idx = 0; str_idx < len; str_idx++) { + stroke str = stroke(); + + tp::alni p_len; file.read(&p_len); + tp::rgba color; file.read(&color); + + str.points.reserve(p_len); + for (auto piter : str.points) { + file.read(&piter.data()); + } + + str.mesh.color = color; + base->add_stroke(str); + } + } +}; + +namespace StrokesVersion1 { + + tp::alni save_size(strokes_project* proj) { + tp::alni out = 0; + out += sizeof(tp::Camera); + out += sizeof(tp::halnf); + out += sizeof(tp::halnf); + out += sizeof(tp::rgba); + + out += sizeof(tp::alni); + for (auto layer : proj->layers) { + out += layer->name.save_size(); + out += sizeof(bool); + out += sizeof(tp::alni); + for (auto stiter : layer->strokes) { + stroke* str = &stiter.data(); + out += sizeof(tp::rgba); + out += sizeof(tp::alni); + out += str->points.length() * sizeof(stroke_point); + } + } + return out; + } + + void save(tp::File& file, strokes_project* proj) { + + file.write(&proj->cam); + file.write(&proj->sampler.eraser_size); + file.write(&proj->sampler.screen_thikness); + file.write(&proj->canvas_color); + + tp::alni lay_len = proj->layers.length(); + file.write(&lay_len); + for (auto layer : proj->layers) { + layer->name.save(&file); + + file.write(&layer->hiden); + + tp::alni len = layer->strokes.length(); + file.write(&len); + for (auto stiter : layer->strokes) { + stroke* str = &stiter.data(); + + file.write(&str->mesh.color); + + tp::alni length = str->points.length(); + file.write(&length); + for (auto piter : str->points) { + file.write(&piter.data()); + } + } + } + } + + void load(tp::File& file, strokes_project* proj) { + file.read(&proj->cam); + + file.read(&proj->sampler.eraser_size); + file.read(&proj->sampler.screen_thikness); + file.read(&proj->canvas_color); + + tp::alni layers_len; + file.read(&layers_len); + for (tp::alni str_idx = 0; str_idx < layers_len; str_idx++) { + + tp::string key; key.load(&file); + drawlayer* layer = new drawlayer(); + layer->name = key; + proj->layers.pushBack(layer); + + file.read(&layer->hiden); + + tp::alni len; + file.read(&len); + + for (tp::alni str_idx = 0; str_idx < len; str_idx++) { + stroke str = stroke(); + + tp::rgba color; file.read(&color); + + tp::alni p_len; file.read(&p_len); + str.points.reserve(p_len); + for (auto piter : str.points) { + file.read(&piter.data()); + } + + str.mesh.color = color; + layer->add_stroke(str); + } + } + } +}; diff --git a/Sketch3D/rsc/Font.ttf b/Sketch3D/rsc/Font.ttf new file mode 100644 index 0000000..8a63054 Binary files /dev/null and b/Sketch3D/rsc/Font.ttf differ diff --git a/Sketch3D/rsc/shaders/Texture.frag b/Sketch3D/rsc/shaders/Texture.frag new file mode 100644 index 0000000..c72e4c9 --- /dev/null +++ b/Sketch3D/rsc/shaders/Texture.frag @@ -0,0 +1,15 @@ +#version 330 core + +in vec2 UV; + +out vec4 color; + +uniform sampler2D renderedTexture; + +void main(){ + vec4 texColor = texture(renderedTexture, UV); + if(texColor.a < 0.2) + discard; + + color = texColor; +} diff --git a/Sketch3D/rsc/shaders/Texture.vert b/Sketch3D/rsc/shaders/Texture.vert new file mode 100644 index 0000000..7700076 --- /dev/null +++ b/Sketch3D/rsc/shaders/Texture.vert @@ -0,0 +1,12 @@ +#version 330 core + +// Input vertex data, different for all executions of this shader. +layout(location = 0) in vec3 vertexPosition_modelspace; + +// Output data ; will be interpolated for each fragment. +out vec2 UV; + +void main(){ + gl_Position = vec4(vertexPosition_modelspace.x, vertexPosition_modelspace.y, -0.99, 1); + UV = (vertexPosition_modelspace.xy + vec2(1, 1)) / 2.0; +} \ No newline at end of file diff --git a/Sketch3D/rsc/shaders/stroke.frag b/Sketch3D/rsc/shaders/stroke.frag new file mode 100644 index 0000000..0257290 --- /dev/null +++ b/Sketch3D/rsc/shaders/stroke.frag @@ -0,0 +1,28 @@ + +#version 330 core + +uniform vec4 Color; + +layout(location = 0) out vec4 color; + +uniform float Target; +uniform vec4 BGCol; + +#define A 0.1f + +void main() { + float x = gl_FragCoord.z; + + float val = A; + + float f = Target * 2; + + if (x < f) { + val = ((A - 1) / f) * x + 1; + } + + val = clamp(val, A, 1); + + + color = vec4((Color.xyz - BGCol.xyz) * val + BGCol.xyz, 1.f); +} \ No newline at end of file diff --git a/Sketch3D/rsc/shaders/stroke.geom b/Sketch3D/rsc/shaders/stroke.geom new file mode 100644 index 0000000..04199b2 --- /dev/null +++ b/Sketch3D/rsc/shaders/stroke.geom @@ -0,0 +1,170 @@ +#version 330 core + +#define PI 3.14 + +layout (lines) in; +layout (triangle_strip, max_vertices = 16) out; + +in float thickness[]; + +uniform float Ratio; + +void main2() { + + float r = sqrt(Ratio); + vec4 l1 = gl_in[0].gl_Position; + vec4 l2 = gl_in[1].gl_Position; + + vec2 l1p = l1.xy / l1.w; + vec2 l2p = l2.xy / l2.w; + + float t1 = thickness[0] / l1.w; + float t2 = thickness[1] / l2.w; + + vec2 l = normalize(vec2(l2p.x / r, l2p.y * r) - vec2(l1p.x / r, l1p.y * r)); + vec2 tmp = vec2(-l.y, l.x); + + vec2 tmp_poj = vec2(tmp.x * r, tmp.y / r); + vec2 l_poj = vec2(l.x * r, l.y / r); + + vec2 p1 = l1p.xy + (tmp_poj * t1); + vec2 p2 = l1p.xy - (tmp_poj * t1); + vec2 p3 = l2p.xy + (tmp_poj * t2); + vec2 p4 = l2p.xy - (tmp_poj * t2); + + gl_Position = vec4(p1.x, p1.y , l1.z, 1); + EmitVertex(); + gl_Position = vec4(p2.x, p2.y, l1.z, 1); + EmitVertex(); + + gl_Position = vec4(p3.x, p3.y, l2.z, 1); + EmitVertex(); + gl_Position = vec4(p4.x, p4.y, l2.z, 1); + EmitVertex(); + EndPrimitive(); + + + p1 = l1p.xy + (tmp_poj * t1); + p2 = l1p.xy - (tmp_poj * t1); + p3 = l1p.xy + (tmp_poj * t1) - (l_poj * t1) / 2.f; + p4 = l1p.xy - (tmp_poj * t1) - (l_poj * t1) / 2.f; + vec2 p5 = l1p.xy + (tmp_poj * t1) / 2.f - (l_poj * t1); + vec2 p6 = l1p.xy - (tmp_poj * t1) / 2.f - (l_poj * t1); + + gl_Position = vec4(p1.x, p1.y, l1.z, 1); + EmitVertex(); + gl_Position = vec4(p2.x, p2.y, l1.z, 1); + EmitVertex(); + gl_Position = vec4(p3.x, p3.y, l1.z, 1); + EmitVertex(); + gl_Position = vec4(p4.x, p4.y, l1.z, 1); + EmitVertex(); + gl_Position = vec4(p5.x, p5.y, l1.z, 1); + EmitVertex(); + gl_Position = vec4(p6.x, p6.y, l1.z, 1); + EmitVertex(); + + EndPrimitive(); + + p1 = l2p.xy + (tmp_poj * t2); + p2 = l2p.xy - (tmp_poj * t2); + p3 = l2p.xy + (tmp_poj * t2) + (l_poj * t2) / 2.f; + p4 = l2p.xy - (tmp_poj * t2) + (l_poj * t2) / 2.f; + p5 = l2p.xy + (tmp_poj * t2) / 2.f + (l_poj * t2); + p6 = l2p.xy - (tmp_poj * t2) / 2.f + (l_poj * t2); + + gl_Position = vec4(p1.x, p1.y, l2.z, 1); + EmitVertex(); + gl_Position = vec4(p2.x, p2.y, l2.z, 1); + EmitVertex(); + gl_Position = vec4(p3.x, p3.y, l2.z, 1); + EmitVertex(); + gl_Position = vec4(p4.x, p4.y, l2.z, 1); + EmitVertex(); + gl_Position = vec4(p5.x, p5.y, l2.z, 1); + EmitVertex(); + gl_Position = vec4(p6.x, p6.y, l2.z, 1); + EmitVertex(); + + EndPrimitive(); +} + +void main() { + + float r = sqrt(Ratio); + vec4 l1 = gl_in[0].gl_Position; + vec4 l2 = gl_in[1].gl_Position; + + vec2 l1p = l1.xy / l1.w; + vec2 l2p = l2.xy / l2.w; + + float t1 = thickness[0] / l1.w; + float t2 = thickness[1] / l2.w; + + vec2 l = normalize(vec2(l2p.x / r, l2p.y * r) - vec2(l1p.x / r, l1p.y * r)); + vec2 tmp = vec2(-l.y, l.x); + + vec2 tmp_poj = vec2(tmp.x * r, tmp.y / r); + vec2 l_poj = vec2(l.x * r, l.y / r); + + vec2 p1 = l1p.xy + (tmp_poj * t1); + vec2 p2 = l1p.xy - (tmp_poj * t1); + vec2 p3 = l2p.xy + (tmp_poj * t2); + vec2 p4 = l2p.xy - (tmp_poj * t2); + + gl_Position = vec4(p1.x * l1.w, p1.y * l1.w, l1.z, l1.w); + EmitVertex(); + gl_Position = vec4(p2.x * l1.w, p2.y * l1.w, l1.z, l1.w); + EmitVertex(); + + gl_Position = vec4(p3.x * l2.w, p3.y * l2.w, l2.z, l2.w); + EmitVertex(); + gl_Position = vec4(p4.x * l2.w, p4.y * l2.w, l2.z, l2.w); + EmitVertex(); + EndPrimitive(); + + + p1 = l1p.xy + (tmp_poj * t1); + p2 = l1p.xy - (tmp_poj * t1); + p3 = l1p.xy + (tmp_poj * t1) - (l_poj * t1) / 2.f; + p4 = l1p.xy - (tmp_poj * t1) - (l_poj * t1) / 2.f; + vec2 p5 = l1p.xy + (tmp_poj * t1) / 2.f - (l_poj * t1); + vec2 p6 = l1p.xy - (tmp_poj * t1) / 2.f - (l_poj * t1); + + gl_Position = vec4(p1.x * l1.w, p1.y * l1.w, l1.z, 1 * l1.w); + EmitVertex(); + gl_Position = vec4(p2.x * l1.w, p2.y * l1.w, l1.z, 1 * l1.w); + EmitVertex(); + gl_Position = vec4(p3.x * l1.w, p3.y * l1.w, l1.z, 1 * l1.w); + EmitVertex(); + gl_Position = vec4(p4.x * l1.w, p4.y * l1.w, l1.z, 1 * l1.w); + EmitVertex(); + gl_Position = vec4(p5.x * l1.w, p5.y * l1.w, l1.z, 1 * l1.w); + EmitVertex(); + gl_Position = vec4(p6.x * l1.w, p6.y * l1.w, l1.z, 1 * l1.w); + EmitVertex(); + + EndPrimitive(); + + p1 = l2p.xy + (tmp_poj * t2); + p2 = l2p.xy - (tmp_poj * t2); + p3 = l2p.xy + (tmp_poj * t2) + (l_poj * t2) / 2.f; + p4 = l2p.xy - (tmp_poj * t2) + (l_poj * t2) / 2.f; + p5 = l2p.xy + (tmp_poj * t2) / 2.f + (l_poj * t2); + p6 = l2p.xy - (tmp_poj * t2) / 2.f + (l_poj * t2); + + gl_Position = vec4(p1.x * l2.w, p1.y * l2.w, l2.z, 1 * l2.w); + EmitVertex(); + gl_Position = vec4(p2.x * l2.w, p2.y * l2.w, l2.z, 1 * l2.w); + EmitVertex(); + gl_Position = vec4(p3.x * l2.w, p3.y * l2.w, l2.z, 1 * l2.w); + EmitVertex(); + gl_Position = vec4(p4.x * l2.w, p4.y * l2.w, l2.z, 1 * l2.w); + EmitVertex(); + gl_Position = vec4(p5.x * l2.w, p5.y * l2.w, l2.z, 1 * l2.w); + EmitVertex(); + gl_Position = vec4(p6.x * l2.w, p6.y * l2.w, l2.z, 1 * l2.w); + EmitVertex(); + + EndPrimitive(); +} diff --git a/Sketch3D/rsc/shaders/stroke.vert b/Sketch3D/rsc/shaders/stroke.vert new file mode 100644 index 0000000..220e25e --- /dev/null +++ b/Sketch3D/rsc/shaders/stroke.vert @@ -0,0 +1,16 @@ +#version 330 core + +layout(location = 0) in vec4 point; + +out float thickness; + +uniform mat4 MVP; + +void main() { + thickness = point.w; + // gl_Position = MVP * vec4(point.xyz, 1); + + vec4 vec = MVP * vec4(point.xyz, 1); + vec.z *= vec.w; + gl_Position = vec; +} \ No newline at end of file