*/}}
Browse Source

Node based brushes

Yiming Wu 1 year ago
parent
commit
0d9f4eb3d4
5 changed files with 322 additions and 21 deletions
  1. 1 1
      CMakeLists.txt
  2. 210 0
      ournodes.c
  3. 62 19
      ouroperations.c
  4. 4 0
      ourpaint.c
  5. 45 1
      ourpaint.h

+ 1 - 1
CMakeLists.txt

@@ -14,7 +14,7 @@ include_directories(
 )
 
 file(GLOB_RECURSE OurPaintFiles 
-    ourpaint.c ouroperations.c
+    ourpaint.c ouroperations.c ournodes.c
 )
 
 add_executable(OurPaint ${OurPaintFiles})

+ 210 - 0
ournodes.c

@@ -0,0 +1,210 @@
+#include "ourpaint.h"
+
+extern LA MAIN;
+extern tnsMain* T;
+extern OurPaint *Our;
+
+laBaseNodeType OUR_IDN_BRUSH_SETTINGS;
+laBaseNodeType OUR_IDN_BRUSH_OUTPUTS;
+laBaseNodeType OUR_IDN_BRUSH_DEVICE;
+
+laPropContainer* OUR_PC_IDN_BRUSH_SETTINGS;
+laPropContainer* OUR_PC_IDN_BRUSH_OUTPUTS;
+laPropContainer* OUR_PC_IDN_BRUSH_DEVICE;
+
+void IDN_BrushSettingsInit(OurBrushSettingsNode* n){
+    n->Size=laCreateOutSocket(n,"Size",LA_PROP_FLOAT);                  n->Size->Data=&n->rSize;
+    n->Transparency=laCreateOutSocket(n,"Transparency",LA_PROP_FLOAT);  n->Transparency->Data=&n->rTransparency;
+    n->Hardness=laCreateOutSocket(n,"Hardness",LA_PROP_FLOAT);          n->Hardness->Data=&n->rHardness;
+    n->Smudge=laCreateOutSocket(n,"Smudge",LA_PROP_FLOAT);              n->Smudge->Data=&n->rSmudge;
+    n->SmudgeLength=laCreateOutSocket(n,"Smudge Length",LA_PROP_FLOAT); n->SmudgeLength->Data=&n->rSmudgeLength;
+    n->DabsPerSize=laCreateOutSocket(n,"Dabs Per Size",LA_PROP_FLOAT);  n->DabsPerSize->Data=&n->rDabsPerSize;
+    strSafeSet(&n->Base.Name, "Brush Settings");
+}
+void IDN_BrushSettingsDestroy(OurBrushSettingsNode* n){
+    laDestroyOutSocket(n->Size); laDestroyOutSocket(n->Transparency); laDestroyOutSocket(n->Hardness);
+    laDestroyOutSocket(n->Smudge); laDestroyOutSocket(n->SmudgeLength); laDestroyOutSocket(n->DabsPerSize);
+    strSafeDestroy(&n->Base.Name);
+}
+int IDN_BrushSettingsVisit(OurBrushSettingsNode* n, laListHandle* l){
+    LA_GUARD_THIS_NODE(n); n->Base.Eval=LA_DAG_FLAG_PERM; lstAppendPointer(l, n);
+    return LA_DAG_FLAG_PERM;
+}
+int IDN_BrushSettingsEval(OurBrushSettingsNode* n){
+    if(!Our->CurrentBrush){ return 0; } // unlikely;
+    n->rSize = Our->CurrentBrush->Size;
+    n->rTransparency = Our->CurrentBrush->Transparency;
+    n->rHardness = Our->CurrentBrush->Hardness;
+    n->rSmudge = Our->CurrentBrush->Smudge;
+    n->rSmudgeLength = Our->CurrentBrush->SmudgeResampleLength;
+    n->rDabsPerSize = Our->CurrentBrush->DabsPerSize;
+    return 1;
+}
+void ui_BrushSettingsNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); OurBrushSettingsNode*n=This->EndInstance;
+    laUiItem* b,*u;
+    LA_BASE_NODE_HEADER(uil,c,This);
+
+    b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Size",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1;          laShowNodeSocket(uil,c,This,"size",0);          laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Transparency",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1;  laShowNodeSocket(uil,c,This,"transparency",0);  laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Hardness",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1;      laShowNodeSocket(uil,c,This,"hardness",0);      laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Smudge",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1;        laShowNodeSocket(uil,c,This,"smudge",0);        laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Smudge Length",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1; laShowNodeSocket(uil,c,This,"smudge_length",0); laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Dabs Per Size",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1; laShowNodeSocket(uil,c,This,"dabs_per_size",0); laEndRow(uil,b);
+
+}
+
+void IDN_BrushOutputsInit(OurBrushOutputsNode* n){
+    n->Size=laCreateInSocket("Size",LA_PROP_FLOAT);
+    n->Transparency=laCreateInSocket("Transparency",LA_PROP_FLOAT);
+    n->Hardness=laCreateInSocket("Hardness",LA_PROP_FLOAT);
+    n->Smudge=laCreateInSocket("Smudge",LA_PROP_FLOAT);
+    n->SmudgeLength=laCreateInSocket("Smudge Length",LA_PROP_FLOAT);
+    n->DabsPerSize=laCreateInSocket("Dabs Per Size",LA_PROP_FLOAT);
+    strSafeSet(&n->Base.Name, "Brush Outputs");
+}
+void IDN_BrushOutputsDestroy(OurBrushOutputsNode* n){
+    laDestroyOutSocket(n->Size); laDestroyOutSocket(n->Transparency); laDestroyOutSocket(n->Hardness);
+    laDestroyOutSocket(n->Smudge); laDestroyOutSocket(n->SmudgeLength); laDestroyOutSocket(n->DabsPerSize);
+    strSafeDestroy(&n->Base.Name);
+}
+int IDN_BrushOutputsVisit(OurBrushOutputsNode* n, laListHandle* l){
+    LA_GUARD_THIS_NODE(n);
+#define BRUSH_OUT_VISIT(a)\
+    if(LA_SRC_AND_PARENT(n->a)){ laBaseNode*bn=n->a->Source->Parent; LA_VISIT_NODE(bn); }
+    BRUSH_OUT_VISIT(Size)
+    BRUSH_OUT_VISIT(Transparency)
+    BRUSH_OUT_VISIT(Hardness)
+    BRUSH_OUT_VISIT(Smudge)
+    BRUSH_OUT_VISIT(SmudgeLength)
+    BRUSH_OUT_VISIT(DabsPerSize)
+#undef BRUSH_OUT_VISIT
+    n->Base.Eval=LA_DAG_FLAG_PERM; lstAppendPointer(l, n);
+    return LA_DAG_FLAG_PERM;
+}
+int IDN_BrushOutputsEval(OurBrushOutputsNode* n){
+    if(!Our->CurrentBrush) return 0;
+#define BRUSH_OUT_EVAL(a)\
+    if(LA_SRC_AND_PARENT(n->a) && (n->a->Source->DataType&LA_PROP_FLOAT)){ Our->CurrentBrush->Eval##a=*((real*)n->a->Source->Data); }
+    BRUSH_OUT_EVAL(Size)
+    BRUSH_OUT_EVAL(Transparency)
+    BRUSH_OUT_EVAL(Hardness)
+    BRUSH_OUT_EVAL(Smudge)
+    BRUSH_OUT_EVAL(SmudgeLength)
+    BRUSH_OUT_EVAL(DabsPerSize)
+#undef BRUSH_OUT_EVAL
+    return 1;
+}
+void ui_BrushOutputsNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); OurBrushOutputsNode*n=This->EndInstance;
+    laUiItem* b,*u;
+    LA_BASE_NODE_HEADER(uil,c,This);
+
+    b=laBeginRow(uil,c,0,0); laShowNodeSocket(uil,c,This,"size",0);          laShowLabel(uil,c,"Size",0,0);          laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); laShowNodeSocket(uil,c,This,"transparency",0);  laShowLabel(uil,c,"Transparency",0,0);  laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); laShowNodeSocket(uil,c,This,"hardness",0);      laShowLabel(uil,c,"Hardness",0,0);      laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); laShowNodeSocket(uil,c,This,"smudge",0);        laShowLabel(uil,c,"Smudge",0,0);        laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); laShowNodeSocket(uil,c,This,"smudge_length",0); laShowLabel(uil,c,"Smudge Length",0,0); laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); laShowNodeSocket(uil,c,This,"dabs_per_size",0); laShowLabel(uil,c,"Dabs Per Size",0,0); laEndRow(uil,b);
+}
+
+void IDN_BrushDeviceInit(OurBrushDeviceNode* n){
+    n->Pressure=laCreateOutSocket(n,"Pressure",LA_PROP_FLOAT);               n->Pressure->Data=&n->rPressure;
+    n->Position=laCreateOutSocket(n,"Position",LA_PROP_FLOAT|LA_PROP_ARRAY); n->Position->Data=n->rPosition; n->Position->ArrLen=2;
+    n->Tilt=laCreateOutSocket(n,"Tilt",LA_PROP_FLOAT|LA_PROP_ARRAY);         n->Tilt->Data=n->rTilt; n->Tilt->ArrLen=2;
+    n->IsEraser=laCreateOutSocket(n,"Is Eraser",LA_PROP_INT);                n->IsEraser->Data=&n->rIsEraser;
+    strSafeSet(&n->Base.Name, "Brush Device");
+}
+void IDN_BrushDeviceDestroy(OurBrushDeviceNode* n){
+    laDestroyOutSocket(n->Pressure); laDestroyOutSocket(n->Tilt); laDestroyOutSocket(n->Position); laDestroyOutSocket(n->IsEraser);
+    strSafeDestroy(&n->Base.Name);
+}
+int IDN_BrushDeviceVisit(OurBrushDeviceNode* n, laListHandle* l){
+    LA_GUARD_THIS_NODE(n); n->Base.Eval=LA_DAG_FLAG_PERM; lstAppendPointer(l, n);
+    return LA_DAG_FLAG_PERM;
+}
+int IDN_BrushDeviceEval(OurBrushDeviceNode* n){
+    if(!Our->CurrentBrush){ return 0; } // unlikely;
+    tnsVectorSet2v(n->rPosition, Our->CurrentBrush->EvalPosition);
+    tnsVectorSet2v(n->rTilt, Our->CurrentBrush->EvalTilt);
+    n->rIsEraser = Our->CurrentBrush->EvalIsEraser;
+    n->rPressure = Our->CurrentBrush->EvalPressure;
+    return 1;
+}
+void ui_BrushDeviceNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); OurBrushDeviceNode*n=This->EndInstance;
+    laUiItem* b,*u;
+    LA_BASE_NODE_HEADER(uil,c,This);
+
+    b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Position",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1; laShowNodeSocket(uil,c,This,"position",0);  laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Pressure",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1; laShowNodeSocket(uil,c,This,"pressure",0);  laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Tilt",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1;     laShowNodeSocket(uil,c,This,"tilt",0);      laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Is Eraser",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1;laShowNodeSocket(uil,c,This,"is_eraser",0); laEndRow(uil,b);
+
+}
+
+int ourEvalBrush(){
+    for(laListItemPointer*lip=Our->BrushEval.pFirst;lip;lip=lip->pNext){ laBaseNode* n=lip->p; n->Type->Eval(n); }
+    return 1;
+}
+int ourRebuildBrushEval(){
+    while(lstPopPointer(&Our->BrushEval));
+    if(!Our->CurrentBrush || !Our->CurrentBrush->Rack) return LA_DAG_FLAG_PERM;
+    laListHandle pending={0}; laRackPage* rp=Our->CurrentBrush->Rack; if(!rp)return LA_DAG_FLAG_PERM;
+    for(laNodeRack* ir=rp->Racks.pFirst;ir;ir=ir->Item.pNext){
+        for(laBaseNode*bn=ir->Nodes.pFirst;bn;bn=bn->Item.pNext){ lstAppendPointer(&pending,bn); bn->Eval=0; }
+    }
+    laBaseNode*n;int result=LA_DAG_FLAG_PERM; laListItemPointer*NextLip;
+    for(laListItemPointer*lip=pending.pFirst;lip;lip=NextLip){ n=lip->p; NextLip=lip->pNext;
+        if(n->Eval&LA_DAG_FLAG_PERM) continue;
+        result=n->Type->Visit(n,&Our->BrushEval); if(result==LA_DAG_FLAG_ERR){ while(lstPopPointer(&pending)); break; }
+    }
+    if(result==LA_DAG_FLAG_ERR){ while(lstPopPointer(&MAIN.InputMapping->Eval)); return LA_DAG_FLAG_ERR; }
+    return LA_DAG_FLAG_PERM;
+}
+
+
+void ourRegisterNodes(){
+    laPropContainer *pc; laProp *p;
+    laOperatorType *at;
+    laEnumProp *ep;
+
+    pc=laAddPropertyContainer("our_node_brush_settings", "Brush Settings", "Brush settings node to read from",0,ui_BrushSettingsNode,sizeof(OurBrushSettingsNode),0,0,1);
+    OUR_PC_IDN_BRUSH_SETTINGS=pc; laPropContainerExtraFunctions(pc,0,0,0,0,laui_DefaultNodeOperationsPropUiDefine);
+    laAddSubGroup(pc,"base","Base","Base node","la_base_node",0,0,0,0,0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"size", "Size","Size","la_out_socket",0,0,0,offsetof(OurBrushSettingsNode,Size),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"transparency", "Transparency","Transparency","la_out_socket",0,0,0,offsetof(OurBrushSettingsNode,Transparency),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"hardness", "Hardness","Hardness","la_out_socket",0,0,0,offsetof(OurBrushSettingsNode,Hardness),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"smudge", "Smudge","Smudge","la_out_socket",0,0,0,offsetof(OurBrushSettingsNode,Smudge),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"smudge_length", "Smudge Length","Smudge length","la_out_socket",0,0,0,offsetof(OurBrushSettingsNode,SmudgeLength),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"dabs_per_size", "Dabs Per Size","Dabs per size","la_out_socket",0,0,0,offsetof(OurBrushSettingsNode,DabsPerSize),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+
+    pc=laAddPropertyContainer("our_node_brush_outputs", "Brush Outputs", "Brush outputs to draw actual dabs",0,ui_BrushOutputsNode,sizeof(OurBrushOutputsNode),0,0,1);
+    OUR_PC_IDN_BRUSH_OUTPUTS=pc; laPropContainerExtraFunctions(pc,0,0,0,0,laui_DefaultNodeOperationsPropUiDefine);
+    laAddSubGroup(pc,"base","Base","Base node","la_base_node",0,0,0,0,0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"size", "Size","Size","la_in_socket",0,0,0,offsetof(OurBrushOutputsNode,Size),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"transparency", "Transparency","Transparency","la_in_socket",0,0,0,offsetof(OurBrushOutputsNode,Transparency),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"hardness", "Hardness","Hardness","la_in_socket",0,0,0,offsetof(OurBrushOutputsNode,Hardness),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"smudge", "Smudge","Smudge","la_in_socket",0,0,0,offsetof(OurBrushOutputsNode,Smudge),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"smudge_length", "Smudge Length","Smudge length","la_in_socket",0,0,0,offsetof(OurBrushOutputsNode,SmudgeLength),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"dabs_per_size", "Dabs Per Size","Dabs per size","la_in_socket",0,0,0,offsetof(OurBrushOutputsNode,DabsPerSize),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+
+    pc=laAddPropertyContainer("our_node_brush_device", "Brush Device", "Brush device input",0,ui_BrushDeviceNode,sizeof(OurBrushDeviceNode),0,0,1);
+    OUR_PC_IDN_BRUSH_DEVICE =pc; laPropContainerExtraFunctions(pc,0,0,0,0,laui_DefaultNodeOperationsPropUiDefine);
+    laAddSubGroup(pc,"base","Base","Base node","la_base_node",0,0,0,0,0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"pressure","Pressure","Pressure of the input","la_out_socket",0,0,0,offsetof(OurBrushDeviceNode,Pressure),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"tilt", "Tilt","Pen tilt vector","la_out_socket",0,0,0,offsetof(OurBrushDeviceNode,Tilt),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"position", "Dab position","Interpolated dab position","la_out_socket",0,0,0,offsetof(OurBrushDeviceNode,Position),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"is_eraser", "Is Eraser","Input event is from an eraser","la_out_socket",0,0,0,offsetof(OurBrushDeviceNode,IsEraser),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+
+    LA_IDN_REGISTER("Brush Settings",L'🖌',OUR_IDN_BRUSH_SETTINGS,OUR_PC_IDN_BRUSH_SETTINGS, IDN_BrushSettingsInit, IDN_BrushSettingsDestroy, IDN_BrushSettingsVisit, IDN_BrushSettingsEval, OurBrushSettingsNode);
+    LA_IDN_REGISTER("Brush Outputs",L'🖌',OUR_IDN_BRUSH_OUTPUTS,OUR_PC_IDN_BRUSH_OUTPUTS, IDN_BrushOutputsInit, IDN_BrushOutputsDestroy, IDN_BrushOutputsVisit, IDN_BrushOutputsEval, OurBrushOutputsNode);
+    LA_IDN_REGISTER("Brush Device",L'🖳',OUR_IDN_BRUSH_DEVICE,OUR_PC_IDN_BRUSH_DEVICE, IDN_BrushDeviceInit, IDN_BrushDeviceDestroy, IDN_BrushDeviceVisit, IDN_BrushDeviceEval, OurBrushDeviceNode);
+    
+    laNodeCategory* nc=laAddNodeCategory("Our Paint",0,LA_RACK_TYPE_DRIVER);
+
+    laNodeCategoryAddNodeTypes(LA_NODE_CATEGORY_DRIVER, &OUR_IDN_BRUSH_OUTPUTS,0);
+    laNodeCategoryAddNodeTypes(nc, &OUR_IDN_BRUSH_DEVICE,0);
+    laNodeCategoryAddNodeTypes(nc, &OUR_IDN_BRUSH_SETTINGS,0);
+}
+

+ 62 - 19
ouroperations.c

@@ -150,22 +150,30 @@ void ourui_Brush(laUiList *uil, laPropPack *This, laPropPack *DetachedProps, laC
 }
 void ourui_ToolsPanel(laUiList *uil, laPropPack *This, laPropPack *DetachedProps, laColumn *UNUSED, int context){
     laColumn* c=laFirstColumn(uil); laColumn* cl,*cr; laSplitColumn(uil,c,0.5); cl=laLeftColumn(c,0);cr=laRightColumn(c,0);
-    laUiItem* b1;
+    laUiItem* b1, *b2;
 #define OUR_BR b1=laBeginRow(uil,c,0,0);
 #define OUR_ER laEndRow(uil,b1);
+#define OUR_PRESSURE(a)\
+    b2=laOnConditionThat(uil,c,laNot(laPropExpression(0,"our.tools.current_brush.use_nodes")));\
+    laShowItemFull(uil,c,0,"our.tools.current_brush." a,0,"text=P",0,0);\
+    laEndCondition(uil,b2);
 
     laShowItem(uil,c,0,"our.tool")->Flags|=LA_UI_FLAGS_EXPAND;
+    laShowLabel(uil,c,"Brush settings:",0,0);
     laUiItem* bt=laOnConditionThat(uil,c,laEqual(laPropExpression(0,"our.tool"),laIntExpression(OUR_TOOL_PAINT)));{
         laUiItem* b=laOnConditionThat(uil,c,laPropExpression(0,"our.tools.current_brush"));{
             laShowItem(uil,c,0,"our.tools.current_brush.name");
-            OUR_BR laShowItem(uil,c,0,"our.tools.current_brush.size")->Expand=1; laShowItemFull(uil,c,0,"our.tools.current_brush.pressure_size",0,"text=P",0,0); OUR_ER
-            OUR_BR laShowItem(uil,c,0,"our.tools.current_brush.transparency")->Expand=1;  laShowItemFull(uil,c,0,"our.tools.current_brush.pressure_transparency",0,"text=P",0,0); OUR_ER
-            OUR_BR laShowItem(uil,c,0,"our.tools.current_brush.hardness")->Expand=1;  laShowItemFull(uil,c,0,"our.tools.current_brush.pressure_hardness",0,"text=P",0,0); OUR_ER
-            OUR_BR laShowItem(uil,c,0,"our.tools.current_brush.smudge")->Expand=1; laShowItemFull(uil,c,0,"our.tools.current_brush.pressure_smudge",0,"text=P",0,0); OUR_ER
+            laShowItem(uil,c,0,"our.tools.current_brush.use_nodes");
+            OUR_BR laShowItem(uil,c,0,"our.tools.current_brush.size")->Expand=1; OUR_PRESSURE("pressure_size") OUR_ER
+            OUR_BR laShowItem(uil,c,0,"our.tools.current_brush.transparency")->Expand=1; OUR_PRESSURE("pressure_transparency")  OUR_ER
+            OUR_BR laShowItem(uil,c,0,"our.tools.current_brush.hardness")->Expand=1;  OUR_PRESSURE("pressure_hardness") OUR_ER
+            OUR_BR laShowItem(uil,c,0,"our.tools.current_brush.smudge")->Expand=1;  OUR_PRESSURE("pressure_smudge")  OUR_ER
             laShowItem(uil,c,0,"our.tools.current_brush.dabs_per_size");
             laShowItem(uil,c,0,"our.tools.current_brush.smudge_resample_length");
         }laEndCondition(uil,b);
 
+        laShowSeparator(uil,c);
+
         laShowLabel(uil,c,"Select a brush:",0,0);
 
         laShowItemFull(uil,c,0,"our.tools.brushes",0,0,0,0);
@@ -188,6 +196,14 @@ void ourui_ColorPanel(laUiList *uil, laPropPack *This, laPropPack *DetachedProps
     
     laShowItemFull(uil,c,0,"our.current_color",LA_WIDGET_FLOAT_COLOR_HCY,0,0,0);
 }
+void ourui_BrushPage(laUiList *uil, laPropPack *This, laPropPack *DetachedProps, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil);
+    
+    laShowItemFull(uil,c,0,"our.tools.current_brush",LA_WIDGET_COLLECTION_SELECTOR,0,laui_IdentifierOnly,0);
+    laUiItem* b=laOnConditionThat(uil,c,laPropExpression(0,"our.tools.current_brush"));{
+        laShowItemFull(uil,c,0,"our.tools.current_brush.rack_page",LA_WIDGET_COLLECTION_SINGLE,0,0,0)->Extra->HeightCoeff=-1;
+    }laEndCondition(uil,b);
+}
 
 void our_CanvasDrawTextures(){
     tnsUseImmShader; tnsEnableShaderv(T->immShader);
@@ -341,11 +357,13 @@ OurBrush* our_NewBrush(char* name, real Size, real Hardness, real DabsPerSize, r
     b->PressureHardness=PressureHardness; b->PressureSize=PressureSize; b->PressureTransparency=PressureTransparency; b->PressureSmudge=PressureSmudge;
     b->SmudgeResampleLength = SmudgeResampleLength;
     memAssignRef(Our, &Our->CurrentBrush, b);
+    b->Rack=memAcquireHyper(sizeof(laRackPage)); b->Rack->RackType=LA_RACK_TYPE_DRIVER;
     return b;
 }
 void our_RemoveBrush(OurBrush* b){
     strSafeDestroy(&b->Name); lstRemoveItem(&Our->Brushes, b);
     if(Our->CurrentLayer==b){ OurLayer* nb=b->Item.pPrev?b->Item.pPrev:b->Item.pNext; memAssignRef(Our, &Our->CurrentBrush, nb); }
+    memLeave(b->Rack); b->Rack=0;
     memLeave(b);
 }
 
@@ -758,30 +776,37 @@ void our_UiToCanvas(laCanvasExtra* ex, laEvent*e, real* x, real *y){
 void our_PaintResetBrushState(OurBrush* b){
     b->BrushRemainingDist = 0; b->SmudgeAccum=0; b->SmudgeRestart=1;
 }
-real our_PaintGetDabStepDistance(OurBrush* b, real pressure){
-    if(!b->PressureSize) return b->Size/b->DabsPerSize;
-    real d=b->Size/b->DabsPerSize*pressure; if(d<1e-2) d=1e-2; return d;
+real our_PaintGetDabStepDistance(real Size,real DabsPerSize){
+    real d=Size/DabsPerSize; if(d<1e-2) d=1e-2; return d;
 }
 int our_PaintGetDabs(OurBrush* b, OurLayer* l, real x, real y, real xto, real yto, real last_pressure, real pressure, int *tl, int *tr, int* tu, int* tb){
     Our->NextDab=0;
-    real size=b->Size; real dd=our_PaintGetDabStepDistance(b,last_pressure); real len=tnsDistIdv2(x,y,xto,yto); real rem=b->BrushRemainingDist;
+    if(!b->EvalDabsPerSize) b->EvalDabsPerSize=b->DabsPerSize;
+    real size=b->Size; real dd=our_PaintGetDabStepDistance(b->EvalSize, b->EvalDabsPerSize); real len=tnsDistIdv2(x,y,xto,yto); real rem=b->BrushRemainingDist;
     real alllen=len+rem; real uselen=dd,step=0; if(!len)return 0; if(dd>alllen){ b->BrushRemainingDist+=len; return 0; }
     real xmin=FLT_MAX,xmax=-FLT_MAX,ymin=FLT_MAX,ymax=-FLT_MAX;
+    b->EvalSize=b->Size; b->EvalHardness=b->Hardness; b->EvalSmudge=b->Smudge; b->EvalSmudgeLength=b->SmudgeResampleLength;
+    b->EvalTransparency=b->Transparency; b->EvalDabsPerSize=b->DabsPerSize;
     while(1){
         arrEnsureLength(&Our->Dabs,Our->NextDab,&Our->MaxDab,sizeof(OurDab)); OurDab* od=&Our->Dabs[Our->NextDab];
         real r=tnsGetRatiod(0,len,uselen-rem); od->X=tnsInterpolate(x,xto,r); od->Y=tnsInterpolate(y,yto,r); TNS_CLAMP(r,0,1);
-#define pfac(psw) (psw?tnsInterpolate(last_pressure,pressure,r):1)
-        real sizepfac=pfac(b->PressureSize);
-        od->Size = b->Size*sizepfac;       od->Hardness = b->Hardness*pfac(b->PressureHardness);
-        od->Smudge = b->Smudge*pfac(b->PressureSmudge); od->Color[3]=pow(b->Transparency*pfac(b->PressureTransparency),2.718);
+        if(b->UseNodes){
+            b->EvalPressure=tnsInterpolate(last_pressure,pressure,r); b->EvalPosition[0]=od->X; b->EvalPosition[1]=od->Y;
+            ourEvalBrush();
+            TNS_CLAMP(b->EvalSmudge,0,1); TNS_CLAMP(b->EvalSmudgeLength,0,100000); TNS_CLAMP(b->EvalTransparency,0,1); TNS_CLAMP(b->EvalHardness,0,1);  TNS_CLAMP(b->DabsPerSize,0,100000);
+        }
+        if(!b->EvalDabsPerSize) b->EvalDabsPerSize=1;
+#define pfac(psw) (((!b->UseNodes)&&psw)?tnsInterpolate(last_pressure,pressure,r):1)
+        od->Size = b->EvalSize*pfac(b->PressureSize);       od->Hardness = b->EvalHardness*pfac(b->PressureHardness);
+        od->Smudge = b->EvalSmudge*pfac(b->PressureSmudge); od->Color[3]=pow(b->EvalTransparency*pfac(b->PressureTransparency),2.718);
         tnsVectorSet3v(od->Color,Our->CurrentColor);
 #undef pfac;
         xmin=TNS_MIN2(xmin, od->X-od->Size); xmax=TNS_MAX2(xmax, od->X+od->Size); 
         ymin=TNS_MIN2(ymin, od->Y-od->Size); ymax=TNS_MAX2(ymax, od->Y+od->Size);
         if(od->Size>1e-1) Our->NextDab++;
-        step=our_PaintGetDabStepDistance(b,sizepfac);
+        step=our_PaintGetDabStepDistance(od->Size, b->EvalDabsPerSize);
         od->ResampleSmudge=0;
-        if(b->Smudge>1e-3){ b->SmudgeAccum+=step; if(b->SmudgeAccum>(b->SmudgeResampleLength*od->Size)){ b->SmudgeAccum-=(b->SmudgeResampleLength*od->Size); od->ResampleSmudge=1; } }
+        if(b->Smudge>1e-3){ b->SmudgeAccum+=step; if(b->SmudgeAccum>(b->EvalSmudgeLength*od->Size)){ b->SmudgeAccum-=(b->EvalSmudgeLength*od->Size); od->ResampleSmudge=1; } }
         if(step+uselen<alllen)uselen+=step; else break;
     }
     b->BrushRemainingDist=alllen-uselen;
@@ -981,19 +1006,22 @@ int ourmod_ExportImage(laOperator* a, laEvent* e){
 }
 
 int ourinv_NewBrush(laOperator* a, laEvent* e){
-    our_NewBrush("Our Brush",15,0.95,9,0.5,0.5,5,0,0,0,0); laNotifyUsers("our.tools.brushes");
+    our_NewBrush("Our Brush",15,0.95,9,0.5,0.5,5,0,0,0,0);
+    laNotifyUsers("our.tools.current_brush"); laNotifyUsers("our.tools.brushes"); laRecordInstanceDifferences(Our,"our_tools"); laPushDifferences("Add brush",0);
     return LA_FINISHED;
 }
 int ourinv_RemoveBrush(laOperator* a, laEvent* e){
     OurBrush* b=a->This?a->This->EndInstance:0; if(!b) return LA_CANCELED;
-    our_RemoveLayer(b); laNotifyUsers("our.tools.brushes");
+    our_RemoveBrush(b);
+    laNotifyUsers("our.tools.current_brush"); laNotifyUsers("our.tools.brushes"); laRecordInstanceDifferences(Our,"our_tools"); laPushDifferences("Remove brush",0);
     return LA_FINISHED;
 }
 int ourinv_MoveBrush(laOperator* a, laEvent* e){
     OurBrush* b=a->This?a->This->EndInstance:0; if(!b) return LA_CANCELED;
     char* direction=strGetArgumentString(a->ExtraInstructionsP,"direction");
-    if(strSame(direction,"up")&&b->Item.pPrev){ lstMoveUp(&Our->Brushes, b); laNotifyUsers("our.tools.brushes"); }
-    elif(b->Item.pNext){ lstMoveDown(&Our->Brushes, b); laNotifyUsers("our.tools.brushes"); }
+    if(strSame(direction,"up")&&b->Item.pPrev){ lstMoveUp(&Our->Brushes, b); }
+    elif(b->Item.pNext){ lstMoveDown(&Our->Brushes, b); }
+    laNotifyUsers("our.tools.brushes"); laRecordInstanceDifferences(Our,"our_tools"); laPushDifferences("Move brush",0);
     return LA_FINISHED;
 }
 
@@ -1156,6 +1184,11 @@ void ourui_MenuButtons(laUiList *uil, laPropPack *pp, laPropPack *actinst, laCol
     }
 }
 
+
+void ourPreFrame(){
+    if(MAIN.Drivers->NeedRebuild){ ourRebuildBrushEval(); }
+}
+
 void ourRegisterEverything(){
     laPropContainer* pc; laKeyMapper* km; laProp* p;
 
@@ -1175,6 +1208,7 @@ void ourRegisterEverything(){
     laRegisterUiTemplate("panel_layers", "Layers", ourui_LayersPanel, 0, 0,0);
     laRegisterUiTemplate("panel_tools", "Tools", ourui_ToolsPanel, 0, 0,0);
     laRegisterUiTemplate("panel_color", "Color", ourui_ColorPanel, 0, 0,0);
+    laRegisterUiTemplate("panel_brush_nodes", "Brush Nodes", ourui_BrushPage, 0, 0,0);
     
     pc=laDefineRoot();
     laAddSubGroup(pc,"our","Our","OurPaint main","our_paint",0,0,0,-1,ourget_our,0,0,0,0,0,0,LA_UDF_SINGLE);
@@ -1215,6 +1249,11 @@ void ourRegisterEverything(){
     OUR_ADD_PRESSURE_SWITCH(p);
     p=laAddEnumProperty(pc,"pressure_smudge","Pressure Smudge","Use pen pressure to control smudging",LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(OurBrush,PressureSmudge),0,0,0,0,0,0,0,0,0,0);
     OUR_ADD_PRESSURE_SWITCH(p);
+    p=laAddEnumProperty(pc,"use_nodes","Use Nodes","Use nodes to control brush dynamics",LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(OurBrush,UseNodes),0,0,0,0,0,0,0,0,0,0);
+    laAddEnumItemAs(p,"NONE","None","Not using nodes",0,0);
+    laAddEnumItemAs(p,"ENABLED","Enabled","Using nodes",1,0);
+    laAddSubGroup(pc,"rack_page","Rack Page","Nodes rack page of this brush","la_rack_page",0,0,laui_RackPage,offsetof(OurBrush,Rack),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    
     laAddOperatorProperty(pc,"move","Move","Move brush","OUR_move_brush",0,0);
     laAddOperatorProperty(pc,"remove","Remove","Remove brush","OUR_remove_brush",L'🗴',0);
 
@@ -1249,10 +1288,14 @@ void ourRegisterEverything(){
 
     laSetMenuBarTemplates(ourui_MenuButtons, laui_DefaultMenuExtras, "OurPaint v0.1");
 
+    ourRegisterNodes();
+
     laSaveProp("our.canvas");
     laSaveProp("our.tools");
 
     laGetSaverDummy(Our,Our->CanvasSaverDummyProp);
+
+    laSetFrameCallbacks(ourPreFrame,0,0);
 }
 
 

+ 4 - 0
ourpaint.c

@@ -12,6 +12,10 @@ int main(int argc, char *argv[]){
     laRefreshUDFRegistries();
     laEnsureUserPreferences();
 
+    //laAddRootDBInst("la.input_mapping");
+    //laAddRootDBInst("la.drivers");
+    laAddRootDBInst("our.tools");
+
     laWindow* w = laDesignWindow(-1,-1,600,600);
 
     laLayout* l = laDesignLayout(w, "Our Paint");

+ 45 - 1
ourpaint.h

@@ -53,6 +53,32 @@ STRUCTURE(OurLayerRead){
     size_t NextData;
 };
 
+STRUCTURE(OurBrushSettingsNode){
+    laBaseNode Base;
+    laNodeOutSocket* Size;         real rSize;
+    laNodeOutSocket* Transparency; real rTransparency;
+    laNodeOutSocket* Hardness;     real rHardness;
+    laNodeOutSocket* Smudge;       real rSmudge;
+    laNodeOutSocket* DabsPerSize;real rDabsPerSize;
+    laNodeOutSocket* SmudgeLength; real rSmudgeLength;
+};
+STRUCTURE(OurBrushOutputsNode){
+    laBaseNode Base;
+    laNodeInSocket* Size;
+    laNodeInSocket* Transparency;
+    laNodeInSocket* Hardness;
+    laNodeInSocket* Smudge;
+    laNodeInSocket* DabsPerSize;
+    laNodeInSocket* SmudgeLength;
+};
+STRUCTURE(OurBrushDeviceNode){
+    laBaseNode Base;
+    laNodeOutSocket* Pressure; real rPressure;
+    laNodeOutSocket* Position; real rPosition[2];
+    laNodeOutSocket* Tilt;     real rTilt[2];
+    laNodeOutSocket* IsEraser; int  rIsEraser;
+};
+
 STRUCTURE(OurBrush){
     laListItem Item;
     laSafeString Name;
@@ -63,8 +89,22 @@ STRUCTURE(OurBrush){
     real Smudge;
     real SmudgeResampleLength; real SmudgeAccum; int SmudgeRestart;
     real BrushRemainingDist;
-    int UseNodes; // the flexible way
     int PressureSize,PressureHardness,PressureTransparency,PressureSmudge; // the simple way
+
+    int UseNodes; // the flexible way
+    laRackPage* Rack;
+    
+    real EvalSize;
+    real EvalDabsPerSize;
+    real EvalHardness;
+    real EvalTransparency;
+    real EvalSmudge;
+    real EvalSmudgeLength;
+
+    real EvalPressure;
+    real EvalPosition[2];
+    real EvalTilt[2];
+    int  EvalIsEraser;
 };
 STRUCTURE(OurDab){
     float X,Y;
@@ -99,6 +139,7 @@ STRUCTURE(OurPaint){
     laListHandle Brushes;
     OurBrush*    CurrentBrush;
     OurDab* Dabs; int NextDab,MaxDab;
+    laListHandle BrushEval;
 
     int Tool,ActiveTool;
     int X,Y,W,H; //border
@@ -132,4 +173,7 @@ STRUCTURE(OurPaint){
 };
 
 void ourInit();
+void ourRegisterNodes();
+int ourRebuildBrushEval();
+int ourEvalBrush();