chore: add modifier pipeline

This commit is contained in:
Beriff
2026-03-15 16:30:49 +07:00
parent e9d1e5c47e
commit 90eec8d301
11 changed files with 175 additions and 50 deletions

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">
<defs>
<style id="current-color-scheme" type="text/css">
.ColorScheme-Text { color:#444444; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
</style>
</defs>
<path style="fill:currentColor" class="ColorScheme-Text" d="M 14.574219 1.0058594 C 13.520146 0.87298937 10.770478 2.75775 8.0605469 5.46875 C 6.8520776 6.67795 5.8032796 7.8729 5 9 C 5.9414561 9.29995 6.7002076 10.0582 7 11 C 8.1266713 10.19649 9.3243336 9.1522594 10.533203 7.9433594 C 13.607725 4.8675594 15.546263 1.8205187 14.863281 1.1367188 C 14.793083 1.0660188 14.697306 1.0216494 14.574219 1.0058594 z M 4.5 10.330078 L 4.5 10.332031 C 1.0001889 11.270271 3.6248533 13.4865 1 15 C 4.4998111 15 6.25 13.248178 6.25 12.080078 C 6.25 11.497798 6.3093545 10.426978 4.5 10.330078 z" transform="translate(3 3)"/>
</svg>

After

Width:  |  Height:  |  Size: 1005 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" version="1.1">
<defs>
<style id="current-color-scheme" type="text/css">
.ColorScheme-Text { color:#444444; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
</style>
</defs>
<g transform="translate(3,3)">
<path style="fill:currentColor" class="ColorScheme-Text" d="M 12.778,1.2222 C 12.778,1.2222 12.278,0.72224 11.778,1.2222 L 10,3 13,6 14.778,4.2222 C 15.278,3.7222 14.778,3.2222 14.778,3.2222 Z M 9,4 1,12 V 15 H 4 L 12,7 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 646 B

View File

@@ -47,7 +47,7 @@ static void appstate_on_color_change(VektorColorWheel* wheel,
appstate->currentColor = c; appstate->currentColor = c;
if (appstate->selectedShape != NULL) { if (appstate->selectedShape != NULL) {
appstate->selectedShape->base.style.stroke_color = c; appstate->selectedShape->base->style.stroke_color = c;
} }
// set entry fields under the color selector // set entry fields under the color selector
@@ -125,7 +125,7 @@ begin_click_dispatch:
state->selectedShape = state->selectedShape =
&(state->shapeBuffer->nodes[state->shapeBuffer->count - 1]); &(state->shapeBuffer->nodes[state->shapeBuffer->count - 1]);
} else if (state->selectedShape->base.primitive.kind != } else if (state->selectedShape->base->primitive.kind !=
VEKTOR_POLYLINE) { VEKTOR_POLYLINE) {
// selecting a tool resets the selection, so this condition // selecting a tool resets the selection, so this condition
// should not happen // should not happen
@@ -134,13 +134,13 @@ begin_click_dispatch:
goto begin_click_dispatch; // retry goto begin_click_dispatch; // retry
} }
vektor_polyline_add_point(state->selectedShape->base.primitive.polyline, vektor_polyline_add_point(state->selectedShape->base->primitive.polyline,
pos); pos);
state->selectedShape->base.bbox = state->selectedShape->base->bbox =
vektor_primitive_get_bbox(state->selectedShape->base.primitive); vektor_primitive_get_bbox(state->selectedShape->base->primitive);
// polyline's handle count is not fixed, so we have to add them manually // polyline's handle count is not fixed, so we have to add them manually
vektor_shape_add_handle(&state->selectedShape->base, pos); vektor_shape_add_handle(state->selectedShape->base, pos);
} else if (state->selectedTool == VektorPolygonTool) { } else if (state->selectedTool == VektorPolygonTool) {
// create new polygon shape if none is selected // create new polygon shape if none is selected
@@ -158,20 +158,20 @@ begin_click_dispatch:
state->selectedShape = state->selectedShape =
&(state->shapeBuffer->nodes[state->shapeBuffer->count - 1]); &(state->shapeBuffer->nodes[state->shapeBuffer->count - 1]);
} else if (state->selectedShape->base.primitive.kind != } else if (state->selectedShape->base->primitive.kind !=
VEKTOR_POLYGON) { VEKTOR_POLYGON) {
g_warning("Invalid selected primitive; polygon expected"); g_warning("Invalid selected primitive; polygon expected");
vektor_appstate_deselect_shape(state); vektor_appstate_deselect_shape(state);
goto begin_click_dispatch; // retry goto begin_click_dispatch; // retry
} }
vektor_polygon_add_point(state->selectedShape->base.primitive.polygon, vektor_polygon_add_point(state->selectedShape->base->primitive.polygon,
pos); pos);
state->selectedShape->base.bbox = state->selectedShape->base->bbox =
vektor_primitive_get_bbox(state->selectedShape->base.primitive); vektor_primitive_get_bbox(state->selectedShape->base->primitive);
// polygon's handle count is not fixed, so we have to add them manually // polygon's handle count is not fixed, so we have to add them manually
vektor_shape_add_handle(&state->selectedShape->base, pos); vektor_shape_add_handle(state->selectedShape->base, pos);
} else if (state->selectedTool == VektorCircleTool) { } else if (state->selectedTool == VektorCircleTool) {
@@ -189,18 +189,18 @@ begin_click_dispatch:
vektor_circle_free(circle); vektor_circle_free(circle);
vektor_circle_set_center(&state->selectedShape->base.primitive.circle, vektor_circle_set_center(&state->selectedShape->base->primitive.circle,
pos); pos);
vektor_circle_set_radius(&state->selectedShape->base.primitive.circle, vektor_circle_set_radius(&state->selectedShape->base->primitive.circle,
0.1f); 0.1f);
state->selectedShape->base.bbox = state->selectedShape->base->bbox =
vektor_primitive_get_bbox(state->selectedShape->base.primitive); vektor_primitive_get_bbox(state->selectedShape->base->primitive);
vektor_circle_create_handles( vektor_circle_create_handles(
&state->selectedShape->base.primitive.circle, &state->selectedShape->base->primitive.circle,
&state->selectedShape->base.handles, &state->selectedShape->base->handles,
&state->selectedShape->base.handleCount); &state->selectedShape->base->handleCount);
} else if (state->selectedTool == VektorRectangleTool) { } else if (state->selectedTool == VektorRectangleTool) {
VektorRectangle* rect = vektor_rectangle_new(); VektorRectangle* rect = vektor_rectangle_new();
@@ -218,22 +218,22 @@ begin_click_dispatch:
vektor_rectangle_free(rect); vektor_rectangle_free(rect);
vektor_rectangle_set_start( vektor_rectangle_set_start(
&state->selectedShape->base.primitive.rectangle, pos); &state->selectedShape->base->primitive.rectangle, pos);
vektor_rectangle_set_end( vektor_rectangle_set_end(
&state->selectedShape->base.primitive.rectangle, &state->selectedShape->base->primitive.rectangle,
vec2_add(pos, (V2){0.1f, 0.1f})); vec2_add(pos, (V2){0.1f, 0.1f}));
vektor_rectangle_create_handles( vektor_rectangle_create_handles(
&state->selectedShape->base.primitive.rectangle, &state->selectedShape->base->primitive.rectangle,
&state->selectedShape->base.handles, &state->selectedShape->base->handles,
&state->selectedShape->base.handleCount); &state->selectedShape->base->handleCount);
state->selectedShape->base.bbox = state->selectedShape->base->bbox =
vektor_primitive_get_bbox(state->selectedShape->base.primitive); vektor_primitive_get_bbox(state->selectedShape->base->primitive);
} else if (state->selectedTool == VektorSelectionTool) { } else if (state->selectedTool == VektorSelectionTool) {
for (size_t i = 0; i < state->shapeBuffer->count; i++) { for (size_t i = 0; i < state->shapeBuffer->count; i++) {
VektorBBox bbox = vektor_primitive_get_bbox( VektorBBox bbox = vektor_primitive_get_bbox(
state->shapeBuffer->nodes[i].base.primitive); state->shapeBuffer->nodes[i].base->primitive);
// expand the bbox a little so its not painful to // expand the bbox a little so its not painful to
// try to grab handles located on the border of said bbox // try to grab handles located on the border of said bbox
@@ -268,9 +268,9 @@ void vektor_appstate_canvas_drag_begin(GtkGestureDrag* gesture, gdouble x,
// get selected shape's handles and check // get selected shape's handles and check
// if we click any of them // if we click any of them
for (size_t i = 0; i < selectedShape->base.handleCount; i++) { for (size_t i = 0; i < selectedShape->base->handleCount; i++) {
VektorBBox bbox = VektorBBox bbox =
vektor_shape_get_handle_bbox(selectedShape->base.handles[i]); vektor_shape_get_handle_bbox(selectedShape->base->handles[i]);
if (vektor_bbox_isinside(bbox, position)) { if (vektor_bbox_isinside(bbox, position)) {
// clicked inside handle // clicked inside handle
state->heldHandleIndex = i; state->heldHandleIndex = i;
@@ -303,8 +303,8 @@ void vektor_appstate_canvas_drag_update(GtkGestureDrag* gesture, gdouble x,
// drag handle if selected // drag handle if selected
if (state->selectedShape != NULL && state->heldHandleIndex != -1) { if (state->selectedShape != NULL && state->heldHandleIndex != -1) {
state->selectedShape->base.handles[state->heldHandleIndex] = position; state->selectedShape->base->handles[state->heldHandleIndex] = position;
vektor_shape_handles_updated(&state->selectedShape->base, vektor_shape_handles_updated(state->selectedShape->base,
&state->heldHandleIndex); &state->heldHandleIndex);
vektor_canvas_geometry_changed(state->renderInfo); vektor_canvas_geometry_changed(state->renderInfo);
} }

View File

@@ -1,14 +1,69 @@
#include "modifier.h" #include "modifier.h"
#include <glib.h>
VektorShapeNode vektor_shapenode_new(VektorShape shape) { VektorShapeNode vektor_shapenode_new(VektorShape* shape) {
VektorShapeNode node = (VektorShapeNode){ VektorShapeNode node = (VektorShapeNode){
.base = shape, .modifier_count = 0, .evaluated = shape}; .base = shape,
.modifier_count = 0,
.evaluated = shape,
.base_dirty = true
};
return node; return node;
} }
VektorShape* vektor_shapenode_get_evaluated(VektorShapeNode* shapeNode) { void vektor_shapenode_free(VektorShapeNode* shapeNode) {
if(shapeNode->base == shapeNode->evaluated) {
free(shapeNode->base); // avoid double free()
} else {
free(shapeNode->base);
free(shapeNode->evaluated);
}
free(shapeNode->modifiers);
}
VektorShape* vektor_shapenode_get_evaluated(VektorShapeNode* shapeNode) {
return shapeNode->evaluated;
}
VektorShape vektor_modifier_apply(VektorModifier* mod, VektorShape* input) {
mod->cachedEvaluatedShape = mod->apply(mod, input);
return mod->cachedEvaluatedShape;
}
// lots of copies by value here, could be problematic
void vektor_shapenode_update(VektorShapeNode* shapeNode) {
// if the base is dirty, apply EVERY modifier
if(shapeNode->base_dirty) {
VektorShape* evaluated = shapeNode->base;
for(size_t i = 0; i < shapeNode->modifier_count; i++) {
*evaluated = vektor_modifier_apply(&shapeNode->modifiers[i], evaluated);
shapeNode->modifiers[i].dirty = false;
}
shapeNode->evaluated = evaluated;
shapeNode->base_dirty = false;
return;
}
// if the base is not dirty, start applying modifiers upstream
// starting from the first dirty
bool encountered_dirty = false;
for(size_t i = 0; i < shapeNode->modifier_count; i++) {
if(shapeNode->modifiers[i].dirty) { encountered_dirty = true; }
if(encountered_dirty) {
if(i == 0) {
vektor_modifier_apply(&shapeNode->modifiers[i], shapeNode->base);
} else {
vektor_modifier_apply(&shapeNode->modifiers[i], &shapeNode->modifiers[i - 1].cachedEvaluatedShape);
}
shapeNode->modifiers[i].dirty = false;
}
}
if (encountered_dirty) {
*shapeNode->evaluated = shapeNode->modifiers[shapeNode->modifier_count - 1].cachedEvaluatedShape;
}
return &shapeNode->evaluated;
} }
void vektor_shapenodebuf_add(VektorShapeNodeBuffer* buffer, void vektor_shapenodebuf_add(VektorShapeNodeBuffer* buffer,

View File

@@ -3,7 +3,10 @@
#include "src/core/primitives.h" #include "src/core/primitives.h"
typedef enum { VEKTOR_MODIFIER_BEVEL } VektorModifierType; typedef enum {
VEKTOR_MODIFIER_IDENTITY,
VEKTOR_MODIFIER_BEVEL
} VektorModifierType;
typedef struct VektorModifier { typedef struct VektorModifier {
VektorModifierType type; VektorModifierType type;
@@ -11,13 +14,14 @@ typedef struct VektorModifier {
bool dirty; bool dirty;
void* parameters; void* parameters;
VektorShape (*apply)(struct VektorModifier mod, VektorShape input); VektorShape (*apply)(struct VektorModifier* mod, VektorShape* input);
VektorShape cachedEvaluatedShape;
} VektorModifier; } VektorModifier;
typedef struct VektorShapeNode { typedef struct VektorShapeNode {
VektorShape base; VektorShape* base;
VektorShape evaluated; VektorShape* evaluated;
VektorModifier* modifiers; VektorModifier* modifiers;
size_t modifier_count; size_t modifier_count;
@@ -31,8 +35,12 @@ typedef struct VektorShapeNodeBuffer {
size_t capacity; size_t capacity;
} VektorShapeNodeBuffer; } VektorShapeNodeBuffer;
VektorShapeNode vektor_shapenode_new(VektorShape shape); VektorShape vektor_modifier_apply(VektorModifier* mod, VektorShape* input);
VektorShapeNode vektor_shapenode_new(VektorShape* shape);
void vektor_shapenode_free(VektorShapeNode* shapeNode);
VektorShape* vektor_shapenode_get_evaluated(VektorShapeNode* shapeNode); VektorShape* vektor_shapenode_get_evaluated(VektorShapeNode* shapeNode);
void vektor_shapenode_update(VektorShapeNode* shapeNode);
void vektor_shapenode_modifier_add(VektorShapeNode* shapeNode, void vektor_shapenode_modifier_add(VektorShapeNode* shapeNode,
VektorModifier* mod); VektorModifier* mod);
void vektor_shapenode_modifier_remove(VektorShapeNode* shapeNode, void vektor_shapenode_modifier_remove(VektorShapeNode* shapeNode,

View File

@@ -0,0 +1,16 @@
#include "../modifier.h"
static VektorShape vektor_m_identity_apply(VektorModifier* m, VektorShape* input) {
return *input;
}
VektorModifier vektor_m_identity_new() {
return (VektorModifier) {
.type = VEKTOR_MODIFIER_IDENTITY,
.enabled = true,
.dirty = true,
.parameters = NULL,
.apply = vektor_m_identity_apply
};
}

View File

@@ -450,9 +450,10 @@ VektorBBox vektor_bbox_expand(VektorBBox bbox, float val) {
// ------ SHAPE METHODS ------ // ------ SHAPE METHODS ------
VektorShape vektor_shape_new(VektorPrimitive prim, VektorStyle style, VektorShape* vektor_shape_new(VektorPrimitive prim, VektorStyle style,
int z_index) { int z_index) {
VektorShape shape = (VektorShape){.primitive = prim, VektorShape* shape = malloc(sizeof(VektorShape));
*shape = (VektorShape){.primitive = prim,
.style = style, .style = style,
.transform = m33_identity(), .transform = m33_identity(),
.z_index = z_index, .z_index = z_index,
@@ -464,7 +465,7 @@ VektorShape vektor_shape_new(VektorPrimitive prim, VektorStyle style,
the passed value's pointer to an array of handles remains valid in the the passed value's pointer to an array of handles remains valid in the
passed copy. passed copy.
*/ */
vektor_shape_create_handles(&shape); vektor_shape_create_handles(shape);
return shape; return shape;
} }

View File

@@ -85,7 +85,7 @@ void vektor_rectangle_set_end(VektorRectangle* rct, V2 point);
void vektor_rectangle_set_start(VektorRectangle* rct, V2 point); void vektor_rectangle_set_start(VektorRectangle* rct, V2 point);
void vektor_rectangle_free(VektorRectangle* rct); void vektor_rectangle_free(VektorRectangle* rct);
VektorShape vektor_shape_new(VektorPrimitive prim, VektorStyle style, VektorShape* vektor_shape_new(VektorPrimitive prim, VektorStyle style,
int z_index); int z_index);
VektorBBox vektor_polyline_get_bbox(VektorPrimitive prim); VektorBBox vektor_polyline_get_bbox(VektorPrimitive prim);

View File

@@ -74,9 +74,13 @@ void vektor_vb_rasterize(VertexBuffer* vb, VektorShapeNodeBuffer* nodebuf,
double scale) { double scale) {
for (size_t i = 0; i < nodebuf->count; i++) { for (size_t i = 0; i < nodebuf->count; i++) {
EdgeBuffer edges = {0}; EdgeBuffer edges = {0};
VektorPrimitive* p = &nodebuf->nodes[i].base.primitive;
VektorStyle style = nodebuf->nodes[i].base.style; vektor_shapenode_update(&nodebuf->nodes[i]);
M33 transform = nodebuf->nodes[i].base.transform; VektorShape* currentShape = vektor_shapenode_get_evaluated(&nodebuf->nodes[i]);
VektorPrimitive* p = &currentShape->primitive;
VektorStyle style = currentShape->style;
M33 transform = currentShape->transform;
switch (p->kind) { switch (p->kind) {
case VEKTOR_POLYLINE: case VEKTOR_POLYLINE:

View File

@@ -151,8 +151,8 @@ void vektor_canvas_geometry_changed(VektorCanvasRenderInfo* renderInfo) {
VektorShapeNode* selectedShape = *renderInfo->selectedShape; VektorShapeNode* selectedShape = *renderInfo->selectedShape;
// create handle quads if a shape is selected // create handle quads if a shape is selected
for (size_t i = 0; i < selectedShape->base.handleCount; i++) { for (size_t i = 0; i < selectedShape->base->handleCount; i++) {
V2 handle = selectedShape->base.handles[i]; V2 handle = selectedShape->base->handles[i];
VektorBBox handleBbox = vektor_shape_get_handle_bbox(handle); VektorBBox handleBbox = vektor_shape_get_handle_bbox(handle);
vektor_vb_add_quad(&vb, handleBbox.min, handleBbox.max, vektor_vb_add_quad(&vb, handleBbox.min, handleBbox.max,
vektor_color_new(255, 255, 255, 255)); vektor_color_new(255, 255, 255, 255));
@@ -162,7 +162,7 @@ void vektor_canvas_geometry_changed(VektorCanvasRenderInfo* renderInfo) {
// create selection quad if a shape is selected // create selection quad if a shape is selected
VektorBBox bbox = VektorBBox bbox =
vektor_primitive_get_bbox(selectedShape->base.primitive); vektor_primitive_get_bbox(selectedShape->base->primitive);
// expand it a little so it is not inset // expand it a little so it is not inset
bbox = vektor_bbox_expand(bbox, 0.03f); bbox = vektor_bbox_expand(bbox, 0.03f);
@@ -202,7 +202,7 @@ static gboolean render(GtkGLArea* a, GdkGLContext* ctx,
// re-fetch bbox (we know a shape is selected) // re-fetch bbox (we know a shape is selected)
VektorBBox bbox = vektor_primitive_get_bbox( VektorBBox bbox = vektor_primitive_get_bbox(
(*(renderInfo->selectedShape))->base.primitive); (*(renderInfo->selectedShape))->base->primitive);
bbox = vektor_bbox_expand(bbox, 0.03f); bbox = vektor_bbox_expand(bbox, 0.03f);
glUseProgram(selection_shader_program); glUseProgram(selection_shader_program);

View File

@@ -31,6 +31,29 @@
</object> </object>
</child> </child>
<!--Another topbar for style control-->
<child>
<object class="GtkBox">
<property name="orientation">horizontal</property>
<property name="hexpand">true</property>
<child>
<object class="GtkImage">
<property name="icon-name">tool-pencil-symbolic</property>
<property name="margin-start">10</property>
</object>
</child>
<child>
<object class="GtkEntry" id="thickness_entry">
<property name="text">1</property>
<property name="margin-start">10</property>
</object>
</child>
</object>
</child>
<!--Main area below--> <!--Main area below-->
<child> <child>
<object class="GtkPaned" id="workspace_paned"> <object class="GtkPaned" id="workspace_paned">