*/}}
Browse Source

Mapping node sort of working

Yiming Wu 1 year ago
parent
commit
48702f7549
6 changed files with 237 additions and 2 deletions
  1. 24 0
      la_interface.h
  2. 1 0
      la_resource.c
  3. 3 1
      la_util.c
  4. 56 1
      resources/la_nodes_basic.c
  5. 11 0
      resources/la_properties.c
  6. 142 0
      resources/la_widgets.c

+ 24 - 0
la_interface.h

@@ -637,6 +637,7 @@ STRUCTURE(laSocketRecord){
 
 extern laPropContainer* LA_PC_SOCKET_IN;
 extern laPropContainer* LA_PC_SOCKET_OUT;
+extern laPropContainer* LA_PC_MAPPER;
 extern laPropContainer* LA_PROP_SOCKET_SOURCE;
 extern laPropContainer* LA_PROP_SOCKET_OUT;
 extern laProp* LA_PROP_CONTROLLER;
@@ -987,6 +988,7 @@ extern laWidget* LA_WIDGET_SYMBOL;
 extern laWidget *LA_WIDGET_NODE_SOCKET;
 extern laWidget *LA_WIDGET_HEIGHT_ADJUSTER;
 extern laWidget *LA_WIDGET_RAW;
+extern laWidget *LA_WIDGET_MAPPER;
 
 #define LA_CONDITION_TRUE 1
 #define LA_CONDITION_FALSE 2
@@ -1410,6 +1412,23 @@ STRUCTURE(laMathNode){
     real ValueL, ValueR;
     int Operation;
 };
+STRUCTURE(laValueMapperPoint){
+    laListItem Item;
+    real x,y;
+};
+STRUCTURE(laValueMapper){
+    real pad;
+    laListHandle Points;
+    real InRange[2],OutRange[2];
+};
+STRUCTURE(laMapperNode){
+    laBaseNode Base;
+    laNodeInSocket *In; /* *InMin,*InMax,*OutMin,*OutMax */
+    laNodeOutSocket* Out;
+    real rIn,rOut;
+    laValueMapper* Mapper;
+    int Clamp;
+};
 
 STRUCTURE(laNodeCategory){
     laListItem Item;
@@ -1422,6 +1441,10 @@ STRUCTURE(laNodeCategory){
 #define LA_INPUT_CONTROLLER_NODE_MODE_BTN 0
 #define LA_INPUT_CONTROLLER_NODE_MODE_AXIS 1
 
+laValueMapper* laValueMapperInit();
+laValueMapper* laValueMapperDestroy(laValueMapper* vm);
+real laValueMapperEvaluate(laValueMapper* vm, real x);
+
 void logPrintT(int Type, char* format, ...);
 void logPrint(char* format, ...);
 void logPrintNew(char* format, ...);
@@ -1897,6 +1920,7 @@ extern laUiType *_LA_UI_COLUMN_VIEWER;
 extern laUiType *_LA_UI_NODE_SOCKET;
 extern laUiType *_LA_UI_HEIGHT_ADJUSTER;
 extern laUiType *_LA_UI_RAW;
+extern laUiType *_LA_UI_MAPPER;
  
 extern laUiDescriptor _LA_UI_DESCRIPTOR_REF_UI;
 extern laUiDescriptor _LA_UI_DESCRIPTOR_REF_UIE;

+ 1 - 0
la_resource.c

@@ -36,6 +36,7 @@ laUiType *_LA_UI_COLUMN_VIEWER;
 laUiType *_LA_UI_NODE_SOCKET;
 laUiType *_LA_UI_HEIGHT_ADJUSTER;
 laUiType *_LA_UI_RAW;
+laUiType *_LA_UI_MAPPER;
 
 laUiDefineFunc _LA_SUBPROP_DONT_CARE;
 

+ 3 - 1
la_util.c

@@ -200,7 +200,7 @@ void lstPushItem(laListHandle* Handle, void* Item){
 
 };
 void* lstPopItem(laListHandle* Handle){
-	void* popitem;
+	laListItem* popitem;
 	laListItem* next;
 	if (!Handle->pFirst) return 0;
 
@@ -215,6 +215,8 @@ void* lstPopItem(laListHandle* Handle){
 		if (next) next->pPrev = 0;
 	};
 
+    popitem->pNext=popitem->pPrev=0;
+
 	return popitem;
 };
 int lstHaveItemInList(laListHandle *Handle){

+ 56 - 1
resources/la_nodes_basic.c

@@ -13,6 +13,7 @@ laBaseNodeType LA_IDN_COMBINE;
 laBaseNodeType LA_IDN_VALUES;
 laBaseNodeType LA_IDN_MATRIX;
 laBaseNodeType LA_IDN_MATH;
+laBaseNodeType LA_IDN_MAPPER;
 
 laPropContainer* LA_PC_IDN_GENERIC;
 laPropContainer* LA_PC_IDN_KEYBOARD;
@@ -25,6 +26,7 @@ laPropContainer* LA_PC_IDN_COMBINE;
 laPropContainer* LA_PC_IDN_VALUES;
 laPropContainer* LA_PC_IDN_MATRIX;
 laPropContainer* LA_PC_IDN_MATH;
+laPropContainer* LA_PC_IDN_MAPPER;
 
 laNodeCategory* LA_NODE_CATEGORY_INPUT;
 laNodeCategory* LA_NODE_CATEGORY_MATH;
@@ -467,6 +469,47 @@ void laui_MathNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn
     laEndRow(uil,b);
 }
 
+void IDN_MapperInit(laMapperNode* n){
+    strSafeSet(&n->Base.Name,"Mapper");
+    n->In=laCreateInSocket("IN",LA_PROP_FLOAT); n->Out=laCreateOutSocket(n,"OUT",LA_PROP_FLOAT);n->Out->Data=&n->rOut;
+    n->Mapper=laValueMapperInit();
+}
+void IDN_MapperDestroy(laMapperNode* n){
+    strSafeDestroy(&n->Base.Name);
+    laDestroyInSocket(n->In); laDestroyOutSocket(n->Out);
+    laValueMapperDestroy(n->Mapper); n->Mapper=0;
+}
+int IDN_MapperVisit(laMapperNode* n, laListHandle* l){
+    LA_GUARD_THIS_NODE(n);
+    if(LA_SRC_AND_PARENT(n->In)){ laBaseNode* bn=n->In->Source->Parent; LA_VISIT_NODE(bn); }
+    n->Base.Eval=LA_DAG_FLAG_PERM;
+    lstAppendPointer(l, n);
+    return LA_DAG_FLAG_PERM;
+}
+int IDN_MapperEval(laMapperNode* n){
+    real in=0;
+    if(LA_SRC_AND_PARENT(n->In) && (n->In->Source->DataType&LA_PROP_FLOAT)){ in=*((real*)n->In->Source->Data); }
+    real result=laValueMapperEvaluate(n->Mapper,in);
+    n->rOut=result;
+    return 1;
+}
+void laui_MapperNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); laMapperNode*n=This->EndInstance;
+    laColumn* cl,*cr; laSplitColumn(uil,c,0.4); cl=laLeftColumn(c,3); cr=laRightColumn(c,0);
+    laUiItem*b,*b2;
+    LA_BASE_NODE_HEADER(uil,c,This);
+
+    b=laBeginRow(uil,c,0,0);
+    laShowNodeSocket(uil,c,This,"in",0)->Flags|=LA_UI_SOCKET_LABEL_E; laShowItem(uil,c,This,"mapper.in_range")->Expand=1;
+    laEndRow(uil,b);
+
+    laShowItem(uil,c,This,"mapper");
+
+    b=laBeginRow(uil,c,0,0);
+    laShowItem(uil,c,This,"mapper.out_range")->Expand=1;laShowNodeSocket(uil,c,This,"out",0)->Flags|=LA_UI_SOCKET_LABEL_W;
+    laEndRow(uil,b);
+}
+
 
 int OPINV_AddInputMapperPage(laOperator* a, laEvent *e){
     laRackPage* dp=memAcquireHyper(sizeof(laRackPage));
@@ -561,6 +604,7 @@ int OPINV_DeleteNode(laOperator* a, laEvent *e){
     laNodeRack* parent=n->InRack;
 
     lstRemoveItem(&n->InRack->Nodes, n); n->Type->Destroy(n); memLeave(n);
+    laMappingRequestRebuild(); laDriverRequestRebuild();
     laNotifyInstanceUsers(parent); laRecordInstanceDifferences(parent,"la_node_rack"); laPushDifferences("Delete Node", 0);
 
     return LA_FINISHED;
@@ -854,6 +898,16 @@ void la_RegisterInputMapperOperators(){
     laAddEnumItemAs(p,"ATAN", "Arctangent", "atan(L)", LA_MATH_NODE_OP_ATAN, 0);
     laAddEnumItemAs(p,"ATAN2", "Atan2", "atan2(L,R) where L or R can be zero", LA_MATH_NODE_OP_ATAN2, 0);
 
+    pc=laAddPropertyContainer("la_mapper_node", "Mapper", "Mapper node",0,laui_MapperNode,sizeof(laMapperNode),0,0,1);
+    LA_PC_IDN_MAPPER=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,"in", "In","Input value","la_in_socket",0,0,0,offsetof(laMapperNode, In),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"out", "Out","Output value","la_out_socket",0,0,0,offsetof(laMapperNode, Out),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"mapper", "Mapper","Value mapper","la_value_mapper",0,LA_WIDGET_MAPPER,0,offsetof(laMapperNode, Mapper),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    p=laAddEnumProperty(pc,"clamp", "Clamp", "Clamp output", LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(laMapperNode,Clamp),0,0,0,0,0,0,0,0,0,0);
+    laAddEnumItemAs(p,"NONE", "None", "Don't clamp output", 0, 0);
+    laAddEnumItemAs(p,"CLAMP", "Clamp", "Clamp output to specified range", 1, 0);
+
     LA_IDN_REGISTER("Controller",L'🕹',LA_IDN_CONTROLLER,LA_PC_IDN_CONTROLLER, IDN_ControllerInit, IDN_ControllerDestroy, IDN_ControllerVisit, IDN_ControllerEval, laInputControllerNode);
     LA_IDN_REGISTER("Visualizer",L'🔍',LA_IDN_VISUALIZER,LA_PC_IDN_VISUALIZER, IDN_InputVisualizeInit, IDN_InputVisualizeDestroy, IDN_InputVisualizeVisit, IDN_InputVisualizerEval, laInputVisualizerNode);
     LA_IDN_REGISTER("Split",L'⚟',LA_IDN_SPLIT,LA_PC_IDN_SPLIT, IDN_SplitInit, IDN_SplitDestroy, IDN_SplitVisit, IDN_SplitEval, laSplitNode);
@@ -862,13 +916,14 @@ void la_RegisterInputMapperOperators(){
     LA_IDN_REGISTER("Values",0,LA_IDN_VALUES,LA_PC_IDN_VALUES, IDN_ValuesInit, IDN_ValuesDestroy, IDN_ValuesVisit, IDN_ValuesEval, laValuesNode);
     LA_IDN_REGISTER("Matrix",0,LA_IDN_MATRIX,LA_PC_IDN_MATRIX, IDN_MatrixInit, IDN_MatrixDestroy, IDN_MatrixVisit, IDN_MatrixEval, laMatrixNode);
     LA_IDN_REGISTER("Math",0,LA_IDN_MATH,LA_PC_IDN_MATH, IDN_MathInit, IDN_MathDestroy, IDN_MathVisit, IDN_MathEval, laMathNode);
+    LA_IDN_REGISTER("Mapper",0,LA_IDN_MAPPER,LA_PC_IDN_MAPPER, IDN_MapperInit, IDN_MapperDestroy, IDN_MapperVisit, IDN_MapperEval, laMapperNode);
 
     LA_NODE_CATEGORY_INPUT=laAddNodeCategory("Input",0,LA_RACK_TYPE_INPUT);
     LA_NODE_CATEGORY_MATH=laAddNodeCategory("Math",0,LA_RACK_TYPE_ALL);
     LA_NODE_CATEGORY_ROUTE=laAddNodeCategory("Route",0,LA_RACK_TYPE_ALL);
 
     laNodeCategoryAddNodeTypes(LA_NODE_CATEGORY_INPUT, &LA_IDN_CONTROLLER,0);
-    laNodeCategoryAddNodeTypes(LA_NODE_CATEGORY_MATH, &LA_IDN_MATRIX,&LA_IDN_MATH,0);
+    laNodeCategoryAddNodeTypes(LA_NODE_CATEGORY_MATH, &LA_IDN_MATRIX,&LA_IDN_MATH,&LA_IDN_MAPPER,0);
     laNodeCategoryAddNodeTypes(LA_NODE_CATEGORY_ROUTE, &LA_IDN_SPLIT, &LA_IDN_SWITCH, &LA_IDN_COMBINE, &LA_IDN_VALUES, &LA_IDN_VISUALIZER,0);
 }
 

+ 11 - 0
resources/la_properties.c

@@ -759,6 +759,7 @@ laPropContainer* LA_PC_SOCKET_IN;
 laPropContainer* LA_PC_SOCKET_OUT;
 laPropContainer* LA_PROP_SOCKET_SOURCE;
 laPropContainer* LA_PROP_SOCKET_OUT;
+laPropContainer* LA_PC_MAPPER;
 laPropContainer* TNS_PC_OBJECT_GENERIC;
 laPropContainer* TNS_PC_OBJECT_CAMERA;
 laPropContainer* TNS_PC_OBJECT_LIGHT;
@@ -1230,6 +1231,16 @@ void la_RegisterInternalProps(){
         } LA_PC_SOCKET_IN = p;
         laPropContainerExtraFunctions(p,0,0,latouched_NodeInSocket,0,0);
 
+        p = laAddPropertyContainer("la_value_mapper", "Value Mapper", "Value mapper", 0, 0, sizeof(laValueMapper), 0, 0, 1);{
+            laAddSubGroup(p, "points", "Points", "Points inside the mapper", "la_value_mapper_point",0, 0, -1, 0, 0, 0, 0, 0, 0, 0, offsetof(laValueMapper, Points), LA_UDF_REFER);
+            laAddFloatProperty(p, "in_range", "Input Range", "Input range from 0 to 1", 0, "Min,Max", 0, 0, 0, 0, 0, 0, offsetof(laValueMapper, InRange), 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0);
+            laAddFloatProperty(p, "out_range", "Output Range", "Output range from 0 to 1", 0, "Min,Max", 0, 0, 0, 0, 0, 0, offsetof(laValueMapper, OutRange), 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0);
+        } LA_PC_MAPPER=p;
+        p = laAddPropertyContainer("la_value_mapper_point", "Value Mapper", "Value mapper", 0, 0, sizeof(laValueMapperPoint), 0, 0, 1);{
+            laAddFloatProperty(p, "position", "Position", "XY Position ranging from 0 to 1", 0, "X,Y", 0, 0, 0, 0, 0, 0, offsetof(laValueMapperPoint, x), 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0);
+        }
+        
+
         // PROPERTIES ==========================================================================================
 
         p = laAddPropertyContainer("property_item", "Property Item", "Property Item For Data Types Like Int/Float/Enum/String/SubType", L'🔌', 0, sizeof(laProp), 0, 0, 1);{

+ 142 - 0
resources/la_widgets.c

@@ -42,6 +42,7 @@ laWidget _LA_WIDGET_SYMBOL={0};
 laWidget _LA_WIDGET_NODE_SOCKET={0};
 laWidget _LA_WIDGET_HEIGHT_ADJUSTER={0};
 laWidget _LA_WIDGET_RAW={0};
+laWidget _LA_WIDGET_MAPPER={0};
 
 laWidget *LA_WIDGET_FIXED_GROUP=&_LA_WIDGET_FIXED_GROUP;
 laWidget *LA_WIDGET_TAB=&_LA_WIDGET_TAB;
@@ -82,6 +83,7 @@ laWidget *LA_WIDGET_SYMBOL=&_LA_WIDGET_SYMBOL;
 laWidget *LA_WIDGET_NODE_SOCKET=&_LA_WIDGET_NODE_SOCKET;
 laWidget *LA_WIDGET_HEIGHT_ADJUSTER=&_LA_WIDGET_HEIGHT_ADJUSTER;
 laWidget *LA_WIDGET_RAW=&_LA_WIDGET_RAW;
+laWidget *LA_WIDGET_MAPPER=&_LA_WIDGET_MAPPER;
 
 //============================================== [Draw]
 
@@ -1271,6 +1273,41 @@ void la_RawPropDraw(laUiItem *ui, int h){
     //char* buf[128]; sprintf(buf,"%d bytes of raw data at 0x%0x", s, data);
     tnsDrawStringAuto("RAW DATA", laThemeColor(bt,LA_BT_TEXT), ui->L, ui->R, ui->U, ui->Flags|LA_TEXT_MONO);
 }
+void la_MapperDraw(laUiItem *ui, int h){
+    laBoxedTheme *bt = (*ui->Type->Theme);
+    int NoDecal=ui->Flags&LA_UI_FLAGS_NO_DECAL;
+
+    if(!NoDecal){
+        tnsUseNoTexture();
+        tnsColor4dv(laThemeColor(bt, LA_BT_NORMAL));
+        tnsVertex2d(ui->L, ui->U); tnsVertex2d(ui->R, ui->U);
+        tnsVertex2d(ui->R, ui->B); tnsVertex2d(ui->L, ui->B);
+        tnsPackAs(GL_TRIANGLE_FAN);
+
+        tnsColor4dv(laThemeColor(bt, LA_BT_BORDER));
+        tnsVertex2d(ui->L, ui->U); tnsVertex2d(ui->R, ui->U);
+        tnsVertex2d(ui->R, ui->B); tnsVertex2d(ui->L, ui->B);
+        tnsPackAs(GL_LINE_LOOP);
+    }
+
+    laPropContainer* pc=la_EnsureSubTarget(ui->PP.LastPs->p,ui->PP.EndInstance);
+    if(!pc || pc!=LA_PC_MAPPER || !ui->PP.EndInstance){
+        tnsDrawStringAuto("Property is not a la_value_mapper.",0,ui->L+bt->LM,ui->R-bt->RM,bt->TM,LA_TEXT_LINE_WRAP); return;
+    }
+
+    laValueMapper* vm=ui->PP.EndInstance; int any=0;
+
+    tnsColor4dv(laAccentColor(LA_BT_TEXT));
+    for(laValueMapperPoint* vmp=vm->Points.pFirst;vmp;vmp=vmp->Item.pNext){
+        tnsVertex2d(tnsInterpolate(ui->L,ui->R,vmp->x),tnsInterpolate(ui->B,ui->U,vmp->y)); any++;
+    } tnsPackAs(GL_LINE_STRIP);
+
+    for(laValueMapperPoint* vmp=vm->Points.pFirst;vmp;vmp=vmp->Item.pNext){
+        tnsVertex2d(tnsInterpolate(ui->L,ui->R,vmp->x),tnsInterpolate(ui->B,ui->U,vmp->y));
+    } tnsPackAs(GL_POINTS);
+
+    if(any){ glLineWidth(2); glPointSize(5); tnsFlush(); glPointSize(1); glLineWidth(1); }
+}
 
 void la_ValueMeterDraw(laUiItem *ui, int h){
     laBoxedTheme *bt = (*ui->Type->Theme);
@@ -1559,6 +1596,9 @@ void la_RegisterUiTypesBasic(){
     _LA_UI_RAW = la_RegisterUiType("LA_raw_prop_default", 0, 0, &_LA_THEME_COLLECTION_GROUP, la_RawPropDraw, 0, 0, 0);
     _LA_UI_RAW->ForType = LA_PROP_RAW;
 
+    LA_WIDGET_MAPPER->Type=
+    _LA_UI_MAPPER = la_RegisterUiType("LA_mapper_default", 0, "LA_value_mapper", &_LA_THEME_COLLECTION_GROUP, la_MapperDraw, la_ColorPickerGetHeight, la_GeneralUiInit, la_GeneralUiDestroy);
+
     _LA_UI_ROW_BEGIN.Theme=&_LA_THEME_BUTTON;
     _LA_UI_ROW_END.Theme=&_LA_THEME_BUTTON;
 }
@@ -2738,6 +2778,106 @@ int OPMOD_HeightAdjuster(laOperator *a, laEvent *e){
     return LA_RUNNING_PASS;
 }
 
+void la_ValueMapperSortPoint(laValueMapper* vm){
+    laValueMapperPoint* vmp,*ivmp; laListHandle temp={0}; int Inserted=0;
+    while(vmp=lstPopItem(&vm->Points)){
+        Inserted=0;
+        for(ivmp=temp.pLast;ivmp;ivmp=ivmp->Item.pPrev){
+            if(ivmp->x<vmp->x){ lstInsertItemAfter(&temp,vmp,ivmp); Inserted=1; break; }
+        }
+        if(!Inserted){ lstPushItem(&temp,vmp); }
+    }
+    vm->Points.pFirst=temp.pFirst; vm->Points.pLast=temp.pLast;
+}
+laValueMapperPoint* la_ValueMapperCreatePoint(laValueMapper* vm, real x, real y){
+    laValueMapperPoint* vmp=memAcquire(sizeof(laValueMapperPoint));
+    vmp->x=x;vmp->y=y; lstAppendItem(&vm->Points,vmp);
+    la_ValueMapperSortPoint(vm);
+    return vmp;
+}
+laValueMapperPoint* la_ValueMapperGetPoint(laValueMapper* vm, real x, real y){
+    laValueMapperPoint* vmp=0,*ClosestVMP=0; real MinD=FLT_MAX;
+    if(x<0||x>1||y>1||y<0) return 0;
+    const real thres=0.1;
+    for(vmp=vm->Points.pFirst;vmp;vmp=vmp->Item.pNext){
+        if(vmp->x>x+thres || vmp->x<x-thres || vmp->y>y+thres || vmp->y<y-thres) continue;
+        real d=tnsDistIdv2(x,y,vmp->x,vmp->y);
+        if(d<MinD){ ClosestVMP=vmp; MinD=d; }
+    }
+    vmp=ClosestVMP;
+    if(!vmp){ vmp=la_ValueMapperCreatePoint(vm,x,y); }
+    return vmp;
+}
+void la_ValueMapperEnsureValidPoints(laValueMapper* vm){
+    if(!vm->Points.pFirst){ la_ValueMapperCreatePoint(vm,0,0); }
+    if(vm->Points.pFirst==vm->Points.pLast){ la_ValueMapperCreatePoint(vm,1,1); }
+}
+laValueMapper* laValueMapperInit(){
+    laValueMapper* vm=memAcquire(sizeof(laValueMapper));
+    vm->InRange[0]=0; vm->InRange[1]=1; vm->OutRange[0]=0; vm->OutRange[1]=1;
+    la_ValueMapperEnsureValidPoints(vm);
+    return vm;
+}
+laValueMapper* laValueMapperDestroy(laValueMapper* vm){
+    laValueMapperPoint* vmp;
+    while(vmp=lstPopItem(&vm->Points)){ memLeave(vmp); }
+    memLeave(vm);
+}
+real laValueMapperEvaluate(laValueMapper* vm, real x){
+    if(fabs(vm->InRange[1]-vm->InRange[0])<1e-7) return 0;
+    laValueMapperPoint* vmp,*NextVMP;
+    real ey=0;
+    for(vmp=vm->Points.pFirst;vmp;vmp=vmp->Item.pNext){
+        NextVMP=vmp->Item.pNext;
+        if(NextVMP && (!(vmp->x<=x && NextVMP->x>x))) continue;
+        if(!NextVMP){ NextVMP=vmp; vmp=vmp->Item.pPrev; if(!vmp) return 0; }
+        if(fabs(vmp->x-NextVMP->x)<1e-7){ ey=vmp->y; break; }
+        real ratio=tnsGetRatiod(vmp->x,NextVMP->x,x);
+        ey=tnsInterpolate(vmp->y,NextVMP->y,ratio); break;
+    }
+    return tnsInterpolate(vm->OutRange[0],vm->OutRange[1],ey);
+}
+int OPMOD_ValueMapper(laOperator *a, laEvent *e){
+    laUiItem *ui = a->Instance;
+    laBoxedTheme *bt = (*ui->Type->Theme);
+    laGeneralUiExtraData *uit = a->CustomData;
+    int w=ui->R-ui->L, h=ui->B-ui->U;
+    real x=(real)(e->x-ui->L)/w,y=(real)(ui->B-e->y)/h;
+    int ClickedVal=0;
+
+    if ((!uit->Dragging) && (!laIsInUiItem(ui, e->x, e->y))){ return LA_FINISHED_PASS; }
+
+    laValueMapper* vm=ui->PP.EndInstance;
+
+    if (e->Type == LA_L_MOUSE_DOWN){
+        uit->Dragging=1; uit->LastX=e->x; uit->LastY=e->y;
+        return LA_RUNNING;
+    }
+    if (uit->Dragging && e->Type == LA_MOUSEMOVE){
+        laValueMapperPoint* vmp=uit->Ptr1;
+        if(uit->Dragging==1 && tnsDistIdv2(e->x,e->y,uit->LastX,uit->LastY)>LA_RH/2){ 
+            uit->Ptr1=la_ValueMapperGetPoint(vm,x,y); if(!uit->Ptr1){ uit->Dragging=0; return LA_RUNNING; }
+            uit->Dragging=2; 
+        }
+        elif(uit->Dragging==2){
+            if(x<0&&x>-0.05){ x=0; } if(x>1&&x<1.05){ x=1; } TNS_CLAMP(y,0,1);
+            vmp->x=x; vmp->y=y; la_ValueMapperSortPoint(vm);
+            laNotifyUsersPP(&ui->PP); laRedrawCurrentPanel(); laMarkPropChanged(&ui->PP);
+        }
+        return LA_RUNNING;
+    }
+    if (uit->Dragging && (e->Type == LA_L_MOUSE_UP || e->Type == LA_R_MOUSE_UP || e->key == LA_KEY_ESCAPE)){
+        uit->Dragging=0; laValueMapperPoint* vmp=uit->Ptr1;
+        if(vmp->x>1||vmp->x<0) {
+            lstRemoveItem(&vm->Points,vmp); la_ValueMapperEnsureValidPoints(vm);
+            laNotifyUsersPP(&ui->PP); laRedrawCurrentPanel(); laRecordAndPushProp(&ui->PP,0); laMarkPropChanged(&ui->PP);
+        }
+        return LA_RUNNING_PASS;
+    }
+
+    return LA_RUNNING_PASS;
+}
+
 void la_RegisterUiOperatorsBasic(){
     laPropContainer *pc, *p;
     laOperatorType *at;
@@ -2778,5 +2918,7 @@ void la_RegisterUiOperatorsBasic(){
                         0, 0, OPEXT_UiItem, OPINV_UiItem, OPMOD_NodeSocket, L'🖦', LA_EXTRA_TO_PANEL | LA_ACTUATOR_SYSTEM | LA_ACTUATOR_HIDDEN);
     laCreateOperatorType("LA_height_adjuster", "Height Adjuster Operator", "Drag to adjust heights of an instance",
                         0, 0, OPEXT_UiItem, OPINV_UiItem, OPMOD_HeightAdjuster, L'🖦', LA_EXTRA_TO_PANEL | LA_ACTUATOR_SYSTEM | LA_ACTUATOR_HIDDEN);
+    laCreateOperatorType("LA_value_mapper", "Value mapper Operator", "Drag to set value mapping from input to output",
+                        0, 0, OPEXT_UiItem, OPINV_UiItem, OPMOD_ValueMapper, L'🖦', LA_EXTRA_TO_PANEL | LA_ACTUATOR_SYSTEM | LA_ACTUATOR_HIDDEN);
 }