451 lines
10 KiB
C++
451 lines
10 KiB
C++
|
|
#include "Sketch3D.hpp"
|
|
|
|
using namespace tp;
|
|
|
|
StrokeGPUHandles::StrokeGPUHandles() {
|
|
glGenVertexArrays(1, &VertexArrayID);
|
|
glBindVertexArray(VertexArrayID);
|
|
|
|
glGenBuffers(1, &vertexbuffer);
|
|
}
|
|
|
|
void StrokeGPUHandles::sendDataToGPU(Buffer<StrokePoint>* mPoints) {
|
|
glBindVertexArray(VertexArrayID);
|
|
vbo_len = mPoints->size();
|
|
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(StrokePoint) * vbo_len, mPoints->getBuff(), GL_STATIC_COPY);
|
|
}
|
|
|
|
StrokeGPUHandles::~StrokeGPUHandles() {
|
|
glDeleteBuffers(1, &vertexbuffer);
|
|
glDeleteVertexArrays(1, &VertexArrayID);
|
|
}
|
|
|
|
Stroke::Stroke() = default;
|
|
|
|
Buffer<StrokePoint>& Stroke::getPoints() { return mPoints; }
|
|
|
|
const Buffer<StrokePoint>& Stroke::getPoints() const { return mPoints; }
|
|
|
|
void Stroke::setColor(const RGBA& col) { mColor = col; }
|
|
|
|
const RGBA& Stroke::getColor() const { return mColor; }
|
|
|
|
void Stroke::updateGpuBuffers() { mGPUHandles.sendDataToGPU(&mPoints); }
|
|
|
|
void Stroke::denoisePos(halni passes) {
|
|
for (auto pass : Range(passes)) {
|
|
for (auto pi : Range(mPoints.size() - 2)) {
|
|
mPoints[pi + 1].pos = (mPoints[pi + 1].pos + mPoints[pi].pos + mPoints[pi + 2].pos) / 3.f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Stroke::denoiseThickness(halni passes) {
|
|
for (auto pass : Range(passes)) {
|
|
for (auto pi : Range(mPoints.size() - 2)) {
|
|
mPoints[pi + 1].thickness = (mPoints[pi].thickness + mPoints[pi + 2].thickness) / 2.f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Stroke::compress(halnf factor) {
|
|
if (mPoints.size() < 3) {
|
|
return;
|
|
}
|
|
|
|
List<StrokePoint> passed_poits;
|
|
|
|
for (auto idx : Range(mPoints.size())) {
|
|
passed_poits.pushBack(mPoints[idx]);
|
|
}
|
|
|
|
List<StrokePoint>::Node* min_node = NULL;
|
|
do {
|
|
min_node = NULL;
|
|
halnf min_factor = factor;
|
|
|
|
List<StrokePoint>::Node* iter = passed_poits.first()->next;
|
|
for (; iter->next; iter = iter->next) {
|
|
Vec3F dir1 = (iter->data.pos - iter->prev->data.pos).normalize();
|
|
Vec3F dir2 = (iter->next->data.pos - iter->data.pos).normalize();
|
|
halnf factor = 1 - dir1.dot(dir2);
|
|
|
|
if (factor < min_factor) {
|
|
min_node = iter;
|
|
min_factor = factor;
|
|
}
|
|
}
|
|
|
|
if (min_node) {
|
|
passed_poits.removeNode(min_node);
|
|
}
|
|
} while (min_node);
|
|
|
|
|
|
mPoints.reserve(passed_poits.length());
|
|
|
|
ualni idx = 0;
|
|
for (auto point : passed_poits) {
|
|
mPoints[idx] = point.data();
|
|
idx++;
|
|
}
|
|
}
|
|
|
|
void Stroke::subdiv(halnf precision, const Camera* cam, halni passes) {
|
|
// TODO
|
|
|
|
if (mPoints.size() < 4) {
|
|
return;
|
|
}
|
|
|
|
List<StrokePoint> new_points;
|
|
for (auto idx : Range(mPoints.size())) {
|
|
new_points.pushBack(mPoints[idx]);
|
|
}
|
|
|
|
auto viewmat = cam->calculateViewMatrix();
|
|
auto projmat = cam->calculateProjectionMatrix();
|
|
|
|
for (auto i : 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);
|
|
Vec3F mid;
|
|
|
|
if (1 - 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.size()) {
|
|
mPoints.reserve(new_points.length());
|
|
ualni idx = 0;
|
|
for (auto point : new_points) {
|
|
mPoints[idx] = point.data();
|
|
idx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
PencilBrush::PencilBrush() {
|
|
mType = "pencil";
|
|
}
|
|
|
|
void PencilBrush::ensureReady(Stroke* stroke, const Camera* cam, bool debug) const {
|
|
if (stroke->getPoints().size() == 1) {
|
|
auto new_point = stroke->getPoints()[0];
|
|
new_point.pos += 0.00001f;
|
|
stroke->getPoints().append(new_point);
|
|
}
|
|
|
|
if (mEnableCompression && !debug) {
|
|
stroke->subdiv(mPrecision, cam, mSubdivPasses);
|
|
stroke->denoisePos(mDenoisePassesPos);
|
|
stroke->compress(mCompressionFactor);
|
|
}
|
|
|
|
stroke->updateGpuBuffers();
|
|
}
|
|
|
|
void PencilBrush::sample(Project* proj, Vec2F crs, halnf pressure) {
|
|
if (proj->mActiveLayer == -1) {
|
|
return;
|
|
}
|
|
|
|
bool max_level = mStroke && mStroke->getPoints().size() > mMaxPoints;
|
|
if (!pressure || max_level) {
|
|
if (mStroke) {
|
|
ensureReady(mStroke, &proj->mCamera);
|
|
proj->mLayers[proj->mActiveLayer]->strokes.pushBack(mStroke);
|
|
mStroke = NULL;
|
|
}
|
|
|
|
if (max_level) {
|
|
mStroke = new Stroke();
|
|
mStroke->setColor(mCol);
|
|
|
|
mStroke->getPoints().append(proj->mLayers[proj->mActiveLayer]->strokes.last()->data->getPoints().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->getPoints().size()) {
|
|
auto last_point_2d = proj->mCamera.project(mStroke->getPoints().last().pos);
|
|
point_passed = (crs - last_point_2d).length() > mPrecision;
|
|
}
|
|
|
|
if (!point_passed) {
|
|
return;
|
|
}
|
|
|
|
if (!mStroke) {
|
|
mStroke = new Stroke();
|
|
mStroke->setColor(mCol);
|
|
}
|
|
|
|
auto point_coords = proj->mCamera.project(crs);
|
|
mStroke->getPoints().append({ point_coords, (halnf) thickness });
|
|
|
|
// mStroke->updateGpuBuffers();
|
|
}
|
|
|
|
void PencilBrush::draw(Renderer* render, const Camera* camera) {
|
|
if (mStroke) {
|
|
if (mEnableCompression) {
|
|
mTempDisplayStroke.getPoints() = mStroke->getPoints();
|
|
mTempDisplayStroke.setColor(mStroke->getColor());
|
|
ensureReady(&mTempDisplayStroke, camera, 0);
|
|
|
|
render->drawStroke(&mTempDisplayStroke, camera);
|
|
} else {
|
|
render->drawStroke(mStroke, camera);
|
|
}
|
|
}
|
|
}
|
|
|
|
PencilBrush::~PencilBrush() {
|
|
if (mStroke) delete mStroke;
|
|
}
|
|
|
|
Layer::~Layer() {
|
|
for (auto str : strokes) {
|
|
delete str.data();
|
|
}
|
|
}
|
|
|
|
Project::Project() {
|
|
mCamera.lookAtPoint({ 0.0f, 0.0f, 0.0f }, { 2.0f, 0.0f, 0.f }, { 0, 0, 1 });
|
|
|
|
auto lay = new Layer();
|
|
lay->enabled = true;
|
|
mLayers.append(lay);
|
|
mActiveLayer = 0;
|
|
|
|
mBrushes.put("eraser", new EraserBrush());
|
|
mBrushes.put("pencil", new PencilBrush());
|
|
|
|
mActiveBrush = "pencil";
|
|
|
|
mBackgroundColor = 0.f;
|
|
}
|
|
|
|
void Project::sample(halnf pressure, halnf cameraRatio, Vec2F relativeCameraPos) {
|
|
mCamera.setRatio(cameraRatio);
|
|
|
|
auto idx = mBrushes.presents(mActiveBrush);
|
|
if (idx) {
|
|
auto brush = mBrushes.getSlotVal(idx);
|
|
|
|
if (brush->mType == "pencil") {
|
|
((tp::PencilBrush*) brush)->mCol = RGBA(1.f);
|
|
}
|
|
|
|
brush->sample(this, relativeCameraPos, pressure);
|
|
}
|
|
}
|
|
|
|
Project::~Project() {
|
|
for (auto brush : mBrushes) {
|
|
delete brush->val;
|
|
}
|
|
}
|
|
|
|
Renderer::Renderer(Vec2F size) :
|
|
mBuffer(size, 4),
|
|
mBufferDowncast(size),
|
|
mShader("rsc/shaders/stroke.vert", "rsc/shaders/stroke.geom", "rsc/shaders/stroke.frag")
|
|
{
|
|
mMaxSize = size;
|
|
|
|
mMatrixUniform = mShader.getu("MVP");
|
|
mColorUniform = mShader.getu("Color");
|
|
mRatioUniform = mShader.getu("Ratio");
|
|
mTargetUniform = mShader.getu("Target");
|
|
mBGColUniform = mShader.getu("BGCol");
|
|
|
|
mBuffer.mClearCol = 0.f;
|
|
mBufferDowncast.mClearCol = 0.f;
|
|
}
|
|
|
|
void Renderer::renderToTexture(const Project* project, Vec2F size) {
|
|
size.clamp({ 1, 1 }, mMaxSize);
|
|
|
|
setViewport({ 0, 0, size.x, size.y });
|
|
setClearCol(project->mBackgroundColor);
|
|
renderBegin();
|
|
|
|
for (auto lay : project->mLayers) {
|
|
if (lay.data()->enabled) {
|
|
for (auto str : lay.data()->strokes) {
|
|
drawStroke(str.data(), &project->mCamera);
|
|
}
|
|
}
|
|
}
|
|
|
|
auto idx = project->mBrushes.presents(project->mActiveBrush);
|
|
if (idx) {
|
|
auto brush = project->mBrushes.getSlotVal(idx);
|
|
brush->draw(this, &project->mCamera);
|
|
}
|
|
|
|
renderEnd();
|
|
}
|
|
|
|
void Renderer::renderBegin() {
|
|
mBuffer.beginDraw();
|
|
mBuffer.clear();
|
|
}
|
|
|
|
void Renderer::setViewport(RectF viewport) {
|
|
mBuffer.setViewport(viewport);
|
|
}
|
|
|
|
void Renderer::drawStroke(const Stroke* str, const Camera* camera) {
|
|
//return;
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
glEnable(GL_MULTISAMPLE);
|
|
|
|
GLint val;
|
|
glGetIntegerv(GL_SAMPLES, &val);
|
|
|
|
glBindVertexArray(str->mGPUHandles.VertexArrayID);
|
|
mShader.bind();
|
|
|
|
auto cam_mat = camera->calculateTransformationMatrix().transpose();
|
|
glUniformMatrix4fv(mMatrixUniform, 1, GL_FALSE, &cam_mat[0][0]);
|
|
|
|
glUniform4fv(mColorUniform, 1, &str->mColor.r);
|
|
|
|
glUniform4fv(mBGColUniform, 1, &mBuffer.mClearCol.r);
|
|
|
|
auto ratio = camera->getRatio();
|
|
glUniform1fv(mRatioUniform, 1, &ratio);
|
|
|
|
auto target = halnf(((camera->getTarget() - camera->getPos()).length() - camera->getNear())
|
|
/ (camera->getFar() - camera->getNear()));
|
|
|
|
glUniform1fv(mTargetUniform, 1, &target);
|
|
|
|
// 1st attribute buffer : vertices
|
|
glEnableVertexAttribArray(0);
|
|
glBindBuffer(GL_ARRAY_BUFFER, str->mGPUHandles.vertexbuffer);
|
|
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, (void*)0);
|
|
|
|
glDrawArrays(GL_LINE_STRIP, 0, str->mGPUHandles.vbo_len);
|
|
|
|
glDisableVertexAttribArray(0);
|
|
|
|
mShader.unbind();
|
|
|
|
}
|
|
|
|
void Renderer::renderEnd() {
|
|
mBuffer.endDraw();
|
|
|
|
mBufferDowncast.beginDraw();
|
|
|
|
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.endDraw();
|
|
}
|
|
|
|
uhalni Renderer::getTextudeId() {
|
|
return mBufferDowncast.texId();
|
|
}
|
|
|
|
RenderBuffer* Renderer::getBuff() {
|
|
return &mBufferDowncast;
|
|
}
|
|
|
|
void Renderer::setClearCol(RGBA col) {
|
|
mBuffer.mClearCol = col;
|
|
mBufferDowncast.mClearCol = col;
|
|
}
|
|
|
|
Renderer::~Renderer() {
|
|
}
|