*/}}
Browse Source

Nodes api change and rgb and comment node etc

Yiming Wu 1 year ago
parent
commit
44c5db3e25
3 changed files with 120 additions and 48 deletions
  1. 87 36
      ournodes.c
  2. 17 8
      ouroperations.c
  3. 16 4
      ourpaint.h

+ 87 - 36
ournodes.c

@@ -12,17 +12,30 @@ laPropContainer* OUR_PC_IDN_BRUSH_SETTINGS;
 laPropContainer* OUR_PC_IDN_BRUSH_OUTPUTS;
 laPropContainer* OUR_PC_IDN_BRUSH_DEVICE;
 
-void IDN_BrushSettingsInit(OurBrushSettingsNode* n){
-    n->CanvasScale=laCreateOutSocket(n,"Canvas Scale",LA_PROP_FLOAT);   n->CanvasScale->Data=&n->rCanvasScale;
-    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;
-    n->Slender=laCreateOutSocket(n,"Slender",LA_PROP_FLOAT);            n->Slender->Data=&n->rSlender;
-    n->Angle=laCreateOutSocket(n,"Angle",LA_PROP_FLOAT);                n->Angle->Data=&n->rAngle;
-    strSafeSet(&n->Base.Name, "Brush Settings");
+void IDN_BrushSettingsInit(OurBrushSettingsNode* n, int NoCreate){
+    if(!NoCreate){
+        n->CanvasScale=laCreateOutSocket(n,"Canvas Scale",LA_PROP_FLOAT);
+        n->Size=laCreateOutSocket(n,"Size",LA_PROP_FLOAT);
+        n->Transparency=laCreateOutSocket(n,"Transparency",LA_PROP_FLOAT);
+        n->Hardness=laCreateOutSocket(n,"Hardness",LA_PROP_FLOAT);
+        n->Smudge=laCreateOutSocket(n,"Smudge",LA_PROP_FLOAT);
+        n->SmudgeLength=laCreateOutSocket(n,"Smudge Length",LA_PROP_FLOAT);
+        n->DabsPerSize=laCreateOutSocket(n,"Dabs Per Size",LA_PROP_FLOAT);
+        n->Slender=laCreateOutSocket(n,"Slender",LA_PROP_FLOAT);
+        n->Angle=laCreateOutSocket(n,"Angle",LA_PROP_FLOAT);
+        n->Color=laCreateOutSocket(n,"Color",LA_PROP_FLOAT|LA_PROP_ARRAY);
+        strSafeSet(&n->Base.Name, "Brush Settings");
+    }
+    n->CanvasScale->Data=&n->rCanvasScale;
+    n->Size->Data=&n->rSize;
+    n->Transparency->Data=&n->rTransparency;
+    n->Hardness->Data=&n->rHardness;
+    n->Smudge->Data=&n->rSmudge;
+    n->SmudgeLength->Data=&n->rSmudgeLength;
+    n->DabsPerSize->Data=&n->rDabsPerSize;
+    n->Slender->Data=&n->rSlender;
+    n->Angle->Data=&n->rAngle;
+    n->Color->Data=Our->CurrentColor; n->Color->ArrLen=3;
 }
 void IDN_BrushSettingsDestroy(OurBrushSettingsNode* n){
     laDestroyOutSocket(n->Size); laDestroyOutSocket(n->Transparency); laDestroyOutSocket(n->Hardness); laDestroyOutSocket(n->Smudge);
@@ -60,10 +73,12 @@ void ui_BrushSettingsNode(laUiList *uil, laPropPack *This, laPropPack *Extra, la
     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);
     b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Slender",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1;       laShowNodeSocket(uil,c,This,"slender",0);       laEndRow(uil,b);
     b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Angle",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1;         laShowNodeSocket(uil,c,This,"angle",0);         laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Color",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1;         laShowNodeSocket(uil,c,This,"color",0);         laEndRow(uil,b);
 }
 
-void IDN_BrushOutputsInit(OurBrushOutputsNode* n){
-    n->Position=laCreateInSocket("Position",LA_PROP_FLOAT);
+void IDN_BrushOutputsInit(OurBrushOutputsNode* n, int NoCreate){
+    if(NoCreate){ return; }
+    n->Offset=laCreateInSocket("Offset",LA_PROP_FLOAT);
     n->Size=laCreateInSocket("Size",LA_PROP_FLOAT);
     n->Transparency=laCreateInSocket("Transparency",LA_PROP_FLOAT);
     n->Hardness=laCreateInSocket("Hardness",LA_PROP_FLOAT);
@@ -72,19 +87,21 @@ void IDN_BrushOutputsInit(OurBrushOutputsNode* n){
     n->DabsPerSize=laCreateInSocket("Dabs Per Size",LA_PROP_FLOAT);
     n->Slender=laCreateInSocket("Slender",LA_PROP_FLOAT);
     n->Angle=laCreateInSocket("Angle",LA_PROP_FLOAT);
+    n->Color=laCreateInSocket("Color",LA_PROP_FLOAT);
     strSafeSet(&n->Base.Name, "Brush Outputs");
 }
 void IDN_BrushOutputsDestroy(OurBrushOutputsNode* n){
-    laDestroyInSocket(n->Position);
+    laDestroyInSocket(n->Offset);
     laDestroyInSocket(n->Size); laDestroyInSocket(n->Transparency); laDestroyInSocket(n->Hardness); laDestroyInSocket(n->Smudge);
     laDestroyInSocket(n->SmudgeLength); laDestroyInSocket(n->DabsPerSize); laDestroyInSocket(n->Slender); laDestroyInSocket(n->Angle);
+    laDestroyInSocket(n->Color);
     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(Position)
+    BRUSH_OUT_VISIT(Offset)
     BRUSH_OUT_VISIT(Size)
     BRUSH_OUT_VISIT(Transparency)
     BRUSH_OUT_VISIT(Hardness)
@@ -93,6 +110,7 @@ int IDN_BrushOutputsVisit(OurBrushOutputsNode* n, laListHandle* l){
     BRUSH_OUT_VISIT(DabsPerSize)
     BRUSH_OUT_VISIT(Slender)
     BRUSH_OUT_VISIT(Angle)
+    BRUSH_OUT_VISIT(Color)
 #undef BRUSH_OUT_VISIT
     n->Base.Eval=LA_DAG_FLAG_PERM; lstAppendPointer(l, n);
     return LA_DAG_FLAG_PERM;
@@ -101,9 +119,14 @@ 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); }
-    if(LA_SRC_AND_PARENT(n->Position) && (n->Position->Source->DataType&LA_PROP_FLOAT|LA_PROP_ARRAY) && n->Position->Source->ArrLen>=2){
-        Our->CurrentBrush->EvalPositionOut[0]=((real*)n->Position->Source->Data)[0];
-        Our->CurrentBrush->EvalPositionOut[1]=((real*)n->Position->Source->Data)[1];
+    if(LA_SRC_AND_PARENT(n->Offset) && (n->Offset->Source->DataType&LA_PROP_FLOAT|LA_PROP_ARRAY) && n->Offset->Source->ArrLen>=2){
+        Our->CurrentBrush->EvalOffset[0]=((real*)n->Offset->Source->Data)[0];
+        Our->CurrentBrush->EvalOffset[1]=((real*)n->Offset->Source->Data)[1];
+    }
+    if(LA_SRC_AND_PARENT(n->Color) && (n->Color->Source->DataType&LA_PROP_FLOAT|LA_PROP_ARRAY) && n->Color->Source->ArrLen>=3){
+        Our->CurrentBrush->EvalColor[0]=((real*)n->Color->Source->Data)[0];
+        Our->CurrentBrush->EvalColor[1]=((real*)n->Color->Source->Data)[1];
+        Our->CurrentBrush->EvalColor[2]=((real*)n->Color->Source->Data)[2];
     }
     BRUSH_OUT_EVAL(Size)
     BRUSH_OUT_EVAL(Transparency)
@@ -121,7 +144,7 @@ void ui_BrushOutputsNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laC
     laUiItem* b,*u;
     LA_BASE_NODE_HEADER(uil,c,This);
 
-    b=laBeginRow(uil,c,0,0); laShowNodeSocket(uil,c,This,"position",0);      laShowLabel(uil,c,"Position",0,0);      laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); laShowNodeSocket(uil,c,This,"offset",0);        laShowLabel(uil,c,"Offset",0,0);        laEndRow(uil,b);
     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);
@@ -130,18 +153,33 @@ void ui_BrushOutputsNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laC
     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);
     b=laBeginRow(uil,c,0,0); laShowNodeSocket(uil,c,This,"slender",0);       laShowLabel(uil,c,"Slender",0,0);       laEndRow(uil,b);
     b=laBeginRow(uil,c,0,0); laShowNodeSocket(uil,c,This,"angle",0);         laShowLabel(uil,c,"Angle",0,0);         laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); laShowNodeSocket(uil,c,This,"color",0);         laShowLabel(uil,c,"Color",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;
-    n->LastPosition=laCreateOutSocket(n,"Last Position",LA_PROP_FLOAT|LA_PROP_ARRAY); n->LastPosition->Data=n->rLastPosition; n->LastPosition->ArrLen=2;
-    strSafeSet(&n->Base.Name, "Brush Device");
+void IDN_BrushDeviceInit(OurBrushDeviceNode* n, int NoCreate){
+    if(!NoCreate){
+        n->Pressure=laCreateOutSocket(n,"Pressure",LA_PROP_FLOAT);
+        n->Tilt=laCreateOutSocket(n,"Tilt",LA_PROP_FLOAT|LA_PROP_ARRAY);
+        n->IsEraser=laCreateOutSocket(n,"Is Eraser",LA_PROP_INT);
+        n->Position=laCreateOutSocket(n,"Position",LA_PROP_FLOAT|LA_PROP_ARRAY);
+        n->Speed=laCreateOutSocket(n,"Speed",LA_PROP_FLOAT);
+        n->Angle=laCreateOutSocket(n,"Angle",LA_PROP_FLOAT);
+        n->Length=laCreateOutSocket(n,"Length",LA_PROP_FLOAT);
+        n->LengthAccum=laCreateOutSocket(n,"ACUM",LA_PROP_FLOAT);
+        strSafeSet(&n->Base.Name, "Brush Device");
+    }
+    n->Pressure->Data=&n->rPressure;
+    n->Tilt->Data=n->rTilt; n->Tilt->ArrLen=2;
+    n->IsEraser->Data=&n->rIsEraser;
+    n->Position->Data=n->rPosition; n->Position->ArrLen=2;
+    n->Speed->Data=&n->rSpeed;
+    n->Angle->Data=&n->rAngle;
+    n->Length->Data=&n->rLength;
+    n->LengthAccum->Data=&n->rLengthAccum;
 }
 void IDN_BrushDeviceDestroy(OurBrushDeviceNode* n){
-    laDestroyOutSocket(n->Pressure); laDestroyOutSocket(n->Tilt); laDestroyOutSocket(n->Position); laDestroyOutSocket(n->IsEraser); laDestroyOutSocket(n->LastPosition);
+    laDestroyOutSocket(n->Pressure); laDestroyOutSocket(n->Tilt); laDestroyOutSocket(n->Position); laDestroyOutSocket(n->IsEraser); laDestroyOutSocket(n->Speed);
+    laDestroyOutSocket(n->Angle); laDestroyOutSocket(n->Length); laDestroyOutSocket(n->LengthAccum); 
     strSafeDestroy(&n->Base.Name);
 }
 int IDN_BrushDeviceVisit(OurBrushDeviceNode* n, laListHandle* l){
@@ -151,10 +189,13 @@ int IDN_BrushDeviceVisit(OurBrushDeviceNode* n, laListHandle* l){
 int IDN_BrushDeviceEval(OurBrushDeviceNode* n){
     if(!Our->CurrentBrush){ return 0; } // unlikely;
     tnsVectorSet2v(n->rPosition, Our->CurrentBrush->EvalPosition);
-    tnsVectorSet2(n->rLastPosition, Our->CurrentBrush->LastX, Our->CurrentBrush->LastY); printf("%lf %lf\n",Our->CurrentBrush->LastX, Our->CurrentBrush->LastY);
     tnsVectorSet2v(n->rTilt, Our->CurrentBrush->EvalTilt);
+    n->rAngle=Our->CurrentBrush->EvalStrokeAngle;
     n->rIsEraser = Our->CurrentBrush->EvalIsEraser;
     n->rPressure = Our->CurrentBrush->EvalPressure;
+    n->rSpeed = Our->CurrentBrush->EvalSpeed;
+    n->rLength = Our->CurrentBrush->EvalStrokeLength;
+    n->rLengthAccum = Our->CurrentBrush->EvalStrokeLengthAccum;
     return 1;
 }
 void ui_BrushDeviceNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
@@ -162,16 +203,21 @@ void ui_BrushDeviceNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laCo
     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,"Last Position",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1; laShowNodeSocket(uil,c,This,"last_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);
-
+    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,"Speed",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1;    laShowNodeSocket(uil,c,This,"speed",0);     laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Moving Angle",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1;laShowNodeSocket(uil,c,This,"angle",0); laEndRow(uil,b);
+    b=laBeginRow(uil,c,0,0); u=laShowLabel(uil,c,"Stroke Length",0,0);u->Flags|=LA_TEXT_ALIGN_RIGHT; u->Expand=1;
+        laShowNodeSocket(uil,c,This,"length_accum",0)->Flags|=LA_UI_SOCKET_LABEL_W; laShowNodeSocket(uil,c,This,"length",0); laEndRow(uil,b);
+    
 }
 
 int ourEvalBrush(){
-    for(laListItemPointer*lip=Our->BrushEval.pFirst;lip;lip=lip->pNext){ laBaseNode* n=lip->p; n->Type->Eval(n); }
+    for(laListItemPointer*lip=Our->BrushEval.pFirst;lip;lip=lip->pNext){
+        laBaseNode* n=lip->p; if(!n->InitDone){ n->Type->Init(n,1); n->InitDone=1; } n->Type->Eval(n); 
+    }
     return 1;
 }
 int ourRebuildBrushEval(){
@@ -208,11 +254,12 @@ void ourRegisterNodes(){
     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);
     laAddSubGroup(pc,"slender", "Slender","Slender","la_out_socket",0,0,0,offsetof(OurBrushSettingsNode,Slender),0,0,0,0,0,0,0,LA_UDF_SINGLE);
     laAddSubGroup(pc,"angle", "Angle","Angle","la_out_socket",0,0,0,offsetof(OurBrushSettingsNode,Angle),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"color", "Color","Color","la_out_socket",0,0,0,offsetof(OurBrushSettingsNode,Color),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,"position", "Position","Position","la_in_socket",0,0,0,offsetof(OurBrushOutputsNode,Position),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"offset", "Offset","Offset","la_in_socket",0,0,0,offsetof(OurBrushOutputsNode,Offset),0,0,0,0,0,0,0,LA_UDF_SINGLE);
     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);
@@ -221,16 +268,20 @@ void ourRegisterNodes(){
     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);
     laAddSubGroup(pc,"slender", "Slender","Slender","la_in_socket",0,0,0,offsetof(OurBrushOutputsNode,Slender),0,0,0,0,0,0,0,LA_UDF_SINGLE);
     laAddSubGroup(pc,"angle", "Angle","Angle","la_in_socket",0,0,0,offsetof(OurBrushOutputsNode,Angle),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"color", "Color","Color","la_in_socket",0,0,0,offsetof(OurBrushOutputsNode,Color),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,"last_position", "Last position","Position of the previous dab","la_out_socket",0,0,0,offsetof(OurBrushDeviceNode,LastPosition),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);
-
+    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,"speed","Speed","Speed on the canvas","la_out_socket",0,0,0,offsetof(OurBrushDeviceNode,Speed),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"angle","Angle","Direction of the brush","la_out_socket",0,0,0,offsetof(OurBrushDeviceNode,Angle),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"length","Length","Length of this brush stroke","la_out_socket",0,0,0,offsetof(OurBrushDeviceNode,Length),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"length_accum","Accumulated Length","Accumulated stroke length","la_out_socket",0,0,0,offsetof(OurBrushDeviceNode,LengthAccum),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);

+ 17 - 8
ouroperations.c

@@ -184,6 +184,7 @@ void ourui_ToolsPanel(laUiList *uil, laPropPack *This, laPropPack *DetachedProps
             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");
+            laShowItem(uil,c,0,"our.tools.current_brush.smoothness");
         }laEndCondition(uil,b);
 
         laShowSeparator(uil,c);
@@ -794,43 +795,50 @@ 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 last_angle_x, real last_angle_y, real pressure, real angle_x, real angle_y, int *tl, int *tr, int* tu, int* tb){
+    real last_pressure, real last_angle_x, real last_angle_y, real pressure, real angle_x, real angle_y,
+    int *tl, int *tr, int* tu, int* tb, real* r_xto, real* r_yto){
     Our->NextDab=0;
     if(!b->EvalDabsPerSize) b->EvalDabsPerSize=b->DabsPerSize;
+    real smfac=(1-b->Smoothness/1.1); xto=tnsLinearItp(x,xto,smfac); yto=tnsLinearItp(y,yto,smfac);  *r_xto=xto; *r_yto=yto;
     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; b->EvalSlender=b->Slender; b->EvalAngle=b->Angle;
+    b->EvalSpeed=tnsDistIdv2(x,y,xto,yto)/b->Size;
+    if(Our->ResetBrush){ b->LastX=x; b->LastY=y; b->LastAngle=atan2(yto-y,xto-x); b->EvalStrokeLength=0; Our->ResetBrush=0; }
+    real this_angle=atan2(yto-y,xto-x);
 
-    if(Our->ResetBrush){ b->LastX=x; b->LastY=y; /*b->EvalLastAngle=atan2(yto-y,xto-x);*/ Our->ResetBrush=0; }
     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);
+        b->LastX=od->X; b->LastY=od->Y;
         if(b->UseNodes){
             b->EvalPressure=tnsInterpolate(last_pressure,pressure,r); b->EvalPosition[0]=od->X; b->EvalPosition[1]=od->Y;
-            b->EvalPositionOut[0]=od->X; b->EvalPositionOut[1]=od->Y;
+            b->EvalOffset[0]=0; b->EvalOffset[1]=0; b->EvalStrokeAngle=tnsInterpolate(b->LastAngle,this_angle,r);
             b->EvalTilt[0]=tnsInterpolate(last_angle_x,angle_x,r); b->EvalTilt[1]=tnsInterpolate(last_angle_y,angle_y,r);
+            tnsVectorSet3v(b->EvalColor, Our->CurrentColor);
             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);
-            od->X=b->EvalPositionOut[0]; od->Y=b->EvalPositionOut[1];
+            od->X+=b->EvalOffset[0]; od->Y+=b->EvalOffset[1];
         }
         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);
+        tnsVectorSet3v(od->Color,b->EvalColor);
 #undef pfac;
         od->Slender = b->EvalSlender; od->Angle=b->EvalAngle;
-        b->LastX=od->X; b->LastY=od->Y;
         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(od->Size, b->EvalDabsPerSize);
+        b->EvalStrokeLength+=step/b->Size; b->EvalStrokeLengthAccum+=step/b->Size; if(b->EvalStrokeLengthAccum>1e6){b->EvalStrokeLengthAccum-=1e6;}
         od->ResampleSmudge=0;
         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->LastAngle=this_angle;
     b->BrushRemainingDist=alllen-uselen;
     if(Our->NextDab) {
         our_LayerEnsureTiles(l,xmin,xmax,ymin,ymax,0,tl,tr,tu,tb);
@@ -1070,11 +1078,11 @@ int ourmod_Paint(laOperator* a, laEvent* e){
         real x,y; our_UiToCanvas(&ex->Base,e,&x,&y);
         int tl,tr,tu,tb;
         if(our_PaintGetDabs(ob,l,ex->CanvasLastX,ex->CanvasLastY,x,y,
-            ex->LastPressure,ex->LastTilt[0],ex->LastTilt[1],e->Pressure,e->AngleX,e->AngleY,&tl,&tr,&tu,&tb)){
+            ex->LastPressure,ex->LastTilt[0],ex->LastTilt[1],e->Pressure,e->AngleX,e->AngleY,&tl,&tr,&tu,&tb,&ex->CanvasLastX,&ex->CanvasLastY)){
             our_PaintDoDabsWithSmudgeSegments(l,tl,tr,tu,tb);
             laNotifyUsers("our.canvas"); laMarkMemChanged(Our->CanvasSaverDummyList.pFirst);
         }
-        ex->CanvasLastX=x;ex->CanvasLastY=y;ex->LastPressure=e->Pressure;ex->LastTilt[0]=e->AngleX;ex->LastTilt[1]=e->AngleY;
+        ex->LastPressure=e->Pressure;ex->LastTilt[0]=e->AngleX;ex->LastTilt[1]=e->AngleY;
     }
 
     return LA_RUNNING;
@@ -1268,6 +1276,7 @@ void ourRegisterEverything(){
     laAddFloatProperty(pc,"smudge_resample_length","Smudge Resample Length","How long of a distance (based on size) should elapse before resampling smudge",0,0,0,0,0,0,0,0,offsetof(OurBrush,SmudgeResampleLength),0,0,0,0,0,0,0,0,0,0,0);
     laAddFloatProperty(pc,"slender","Slender","Slenderness of the brush",0,0, 0,10,0,0.1,0,0,offsetof(OurBrush,Slender),0,0,0,0,0,0,0,0,0,0,0);
     laAddFloatProperty(pc,"angle","Angle","Angle of the brush",0,0, 0,TNS_PI,-TNS_PI,0.1,0,0,offsetof(OurBrush,Angle),0,0,0,0,0,0,0,0,0,0,LA_RAD_ANGLE);
+    laAddFloatProperty(pc,"smoothness","Smoothness","Smoothness of the brush",0,0, 0,1,0,0.05,0,0,offsetof(OurBrush,Smoothness),0,0,0,0,0,0,0,0,0,0,0);
     p=laAddEnumProperty(pc,"pressure_size","Pressure Size","Use pen pressure to control size",LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(OurBrush,PressureSize),0,0,0,0,0,0,0,0,0,0);
     OUR_ADD_PRESSURE_SWITCH(p);
     p=laAddEnumProperty(pc,"pressure_transparency","Pressure Transparency","Use pen pressure to control transparency",LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(OurBrush,PressureTransparency),0,0,0,0,0,0,0,0,0,0);

+ 16 - 4
ourpaint.h

@@ -65,10 +65,11 @@ STRUCTURE(OurBrushSettingsNode){
     laNodeOutSocket* SmudgeLength; real rSmudgeLength;
     laNodeOutSocket* Slender;      real rSlender;
     laNodeOutSocket* Angle;        real rAngle;
+    laNodeOutSocket* Color;
 };
 STRUCTURE(OurBrushOutputsNode){
     laBaseNode Base;
-    laNodeInSocket* Position;
+    laNodeInSocket* Offset;
     laNodeInSocket* Size;
     laNodeInSocket* Transparency;
     laNodeInSocket* Hardness;
@@ -77,6 +78,7 @@ STRUCTURE(OurBrushOutputsNode){
     laNodeInSocket* SmudgeLength;
     laNodeInSocket* Slender;
     laNodeInSocket* Angle;
+    laNodeInSocket* Color;
 };
 STRUCTURE(OurBrushDeviceNode){
     laBaseNode Base;
@@ -84,7 +86,10 @@ STRUCTURE(OurBrushDeviceNode){
     laNodeOutSocket* Position; real rPosition[2];
     laNodeOutSocket* Tilt;     real rTilt[2];
     laNodeOutSocket* IsEraser; int  rIsEraser;
-    laNodeOutSocket* LastPosition; real rLastPosition[2];
+    laNodeOutSocket* Speed;    real rSpeed;
+    laNodeOutSocket* Angle;    real rAngle;
+    laNodeOutSocket* Length;   real rLength;
+    laNodeOutSocket* LengthAccum; real rLengthAccum;
 };
 
 STRUCTURE(OurBrush){
@@ -98,14 +103,17 @@ STRUCTURE(OurBrush){
     real SmudgeResampleLength; real SmudgeAccum; int SmudgeRestart; real BrushRemainingDist;
     real Slender;
     real Angle;
+    real Smoothness;
+    real MaxStrokeLength;
     int PressureSize,PressureHardness,PressureTransparency,PressureSmudge; // the simple way
 
     int UseNodes; // the flexible way
     laRackPage* Rack;
 
-    real LastX,LastY;
+    real LastX,LastY,LastAngle;
     
-    real EvalPositionOut[2];
+    real EvalColor[3];
+    real EvalOffset[2];
     real EvalSize;
     real EvalDabsPerSize;
     real EvalHardness;
@@ -115,9 +123,13 @@ STRUCTURE(OurBrush){
     real EvalSlender;
     real EvalAngle;
 
+    real EvalSpeed;
+    real EvalStrokeLength;
+    real EvalStrokeLengthAccum;
     real EvalPressure;
     real EvalPosition[2];
     real EvalTilt[2];
+    real EvalStrokeAngle;
     int  EvalIsEraser;
 };
 STRUCTURE(OurDab){