*/}}
Browse Source

More nodes and drivers basics

Yiming Wu 2 years ago
parent
commit
177f8062b9

+ 117 - 25
source/lagui/la_interface.h

@@ -136,6 +136,10 @@ typedef void (*laUiInitFunc)(laUiItem *);
 typedef void (*laUiRefreshFunc)(laUiItem *);
 typedef void (*laLayoutBkgDrawFunc)(void *, void *l);
 
+typedef void (*laPreFrameF)();
+typedef void (*laPreDrawF)();
+typedef void (*laPostFrameF)();
+
 STRUCTURE(laEvent){
     laListItem Item;
 
@@ -214,6 +218,11 @@ STRUCTURE(laNodeInSocket){
     int RuntimeX, RuntimeY, RuntimePX, RuntimePY;
 };
 
+NEED_STRUCTURE(laBaseNodeType);
+NEED_STRUCTURE(laDriverPage);
+
+typedef laBaseNodeType* (*laGetBaseNodeTypeF)(char* str);
+
 STRUCTURE(LA){
     laListItem Hyper;
 
@@ -222,6 +231,10 @@ STRUCTURE(LA){
 
     laListHandle Windows;
     laListHandle WastedPanels;
+    
+    laPreFrameF  PreFrame;
+    laPreDrawF   PreDraw;
+    laPostFrameF PostFrame;
 
     real IdleStart, DelayStart;
     int DelayTriggered;
@@ -388,6 +401,17 @@ STRUCTURE(LA){
     laListHandle InputMappingEval;
     int MappingNeedEval,MappingNeedRebuild;
 
+    laDriverPage* CurrentDriverPage;
+    laListHandle DriverPages;
+    laListHandle DriverEval;
+    int DriverNeedEval,DriverNeedRebuild;
+
+    laBaseNodeType** NodeTypes; int NodeTypeNext, NodeTypeMax;
+    laUiDefineFunc ExtraAddInputNodes;
+    laUiDefineFunc ExtraAddDriverNodes;
+    laGetBaseNodeTypeF ExtraGetInputNodeType;
+    laGetBaseNodeTypeF ExtraGetDriverNodeType;
+
     //laHash256 RootNodes;
     //LLVMContextRef llvmContext;
     //LLVMModuleRef llvmModule;
@@ -1172,12 +1196,6 @@ laController* la_FindControllerWithID(int id);
 
 void la_RegisterInputMapperOperators();
 
-NEED_STRUCTURE(laBaseNode);
-typedef void (*laBaseNodeInitF)(laBaseNode*);
-typedef void (*laBaseNodeDestroyF)(laBaseNode*);
-typedef int (*laBaseNodeVisitF)(laBaseNode*, laListHandle*);
-typedef int (*laBaseNodeEvalF)(laBaseNode*);
-
 #define LA_DAG_FLAG_ERR  0
 #define LA_DAG_FLAG_TEMP 1
 #define LA_DAG_FLAG_PERM 2
@@ -1187,25 +1205,77 @@ void laMappingRequestEval();
 int la_RebuildInputMapping();
 int la_RunInputMapping();
 
-STRUCTURE(laInputRack){
+void laDriverRequestRebuild();
+void laDriverRequestEval();
+int la_RebuildDriverEval();
+int la_RunDrivers();
+
+laBaseNode* la_CreateDriverNode(laNodeRack* ir, laBaseNodeType* NodeType);
+laBaseNode* la_CreateInputMapperNode(laNodeRack* ir, laBaseNodeType* NodeType);
+
+#define LA_NODE_TYPE_INPUT 0
+#define LA_NODE_TYPE_DRIVER 1
+
+laPropContainer* laget_BaseNodeType(laBaseNode* bn);
+void laRegisterNode(laBaseNodeType* type, laPropContainer* pc, laBaseNodeInitF init, laBaseNodeDestroyF destroy, laBaseNodeVisitF visit, laBaseNodeEvalF eval, int nodesize);
+void laSetExtraNodeFunctions(laUiDefineFunc AddInputNodes, laUiDefineFunc AddDriverNodes, laGetBaseNodeTypeF GetInputNodeType, laGetBaseNodeTypeF GetDriverNodeType);
+
+#define LA_BASE_NODE_HEADER(uil,c,This)\
+    {laUiItem* _b=laBeginRow(uil,c,0,0);\
+    laShowHeightAdjuster(uil,c,This,"base.__gap",0);\
+    laShowItem(uil,c,This,"base.name")->Expand=1;\
+    laEndRow(uil,_b);}
+
+#define LA_IDN_REGISTER(a,pc,init,destroy,visit,eval,type)\
+    laRegisterNode(&a,pc,init,destroy,visit,eval,sizeof(type))
+
+#define LA_SRC_AND_PARENT(socket)\
+    ((socket)->Source && (socket)->Source->Parent)
+
+#define LA_VISIT_NODE(node)\
+    {int result=node->Type->Visit(node,l); if(result!=LA_DAG_FLAG_PERM) return LA_DAG_FLAG_ERR;}
+
+#define LA_GUARD_THIS_NODE(n)\
+    {if(n->Base.Eval==LA_DAG_FLAG_PERM) return LA_DAG_FLAG_PERM; if(n->Base.Eval==LA_DAG_FLAG_TEMP) return LA_DAG_FLAG_ERR; n->Base.Eval=LA_DAG_FLAG_TEMP; }
+
+extern laBaseNodeType LA_IDN_KEYBOARD;
+extern laBaseNodeType LA_IDN_MOUSE;
+extern laBaseNodeType LA_IDN_CONTROLLER;
+
+extern laBaseNodeType LA_IDN_VISUALIZER;
+extern laBaseNodeType LA_IDN_SPLIT;
+extern laBaseNodeType LA_IDN_SWITCH;
+extern laBaseNodeType LA_IDN_COMBINE;
+extern laBaseNodeType LA_IDN_VALUES;
+extern laBaseNodeType LA_IDN_MATRIX;
+
+extern laBaseNodeType TNS_IDN_TRANSFORM;
+extern laBaseNodeType TNS_IDN_MAKE_TRANSFORM;
+
+#define LA_VALUES_NODE_FLOAT 0
+#define LA_VALUES_NODE_INT 1
+#define LA_VALUES_NODE_ENUM 2
+
+#define LA_MATRIX_NODE_OP_MUL 0
+#define LA_MATRIX_NODE_OP_INV 1
+
+STRUCTURE(laDriverPage){
     laListItem Item;
     laSafeString* Name;
-    laListHandle Nodes;
+    laListHandle Racks;
 };
-STRUCTURE(laBaseNodeType){
-    laBaseNodeInitF    Init;
-    laBaseNodeDestroyF Destroy;
-    laBaseNodeVisitF   Visit;
-    laBaseNodeEvalF    Eval;
-    int NodeSize;
+STRUCTURE(laDriverRack){
+    laListItem Item;
+    laSafeString* Name;
+    laListHandle Nodes;
 };
-STRUCTURE(laBaseNode){
+#define LA_RACK_TYPE_INPUT 0
+#define LA_RACK_TYPE_DRIVER 1
+STRUCTURE(laNodeRack){
     laListItem Item;
     laSafeString* Name;
-    laBaseNodeType* Type;
-    laInputRack* InRack;
-    int Gap;
-    int Eval;
+    laListHandle Nodes;
+    int RackType;
 };
 NEED_STRUCTURE(laInputControllerNode);
 STRUCTURE(laInputControllerNodeSocket){
@@ -1226,27 +1296,48 @@ STRUCTURE(laInputVisualizerNode){
     laNodeInSocket* In;
     int IntVal[8]; real RealVal[8];
 };
-STRUCTURE(laInputSplitNodeOutSocket){
+STRUCTURE(laSplitNodeOutSocket){
     laNodeOutSocket* Out;
 };
-STRUCTURE(laInputSplitNode){
+STRUCTURE(laSplitNode){
     laBaseNode Base;
     laNodeInSocket* In;
-    laInputSplitNodeOutSocket Out[8];
+    laSplitNodeOutSocket Out[8];
     int IntVal[8]; real RealVal[8];
     int ArrLen;
 };
-STRUCTURE(laInputSwitchNodeInSocket){
+STRUCTURE(laSwitchNodeInSocket){
     laNodeInSocket* In;
 };
-STRUCTURE(laInputSwitchNode){
+STRUCTURE(laSwitchNode){
     laBaseNode Base;
-    laInputSwitchNodeInSocket In[8];
+    laSwitchNodeInSocket In[8];
     laNodeOutSocket* Out;
     laNodeInSocket* SwitchIn;
     real TempVal;
     int Switch;
 };
+STRUCTURE(laCombineNode){
+    laBaseNode Base;
+    laSwitchNodeInSocket In[8];
+    laNodeOutSocket* Out;
+    laNodeOutSocket* OutInt;
+    laNodeOutSocket* OutEnum;
+    real Values[8]; int ValuesI[8];
+};
+STRUCTURE(laValuesNode){
+    laBaseNode Base;
+    laSplitNodeOutSocket Out[8];
+    real Values[8]; int ValuesI[8], ValuesE[8];
+    int Modes[8];
+};
+STRUCTURE(laMatrixNode){
+    laBaseNode Base;
+    laNodeInSocket *InL,*InR;
+    laNodeOutSocket* Out;
+    tnsMatrix44d Mat;
+    int Operation;
+};
 
 #define LA_INPUT_CONTROLLER_NODE_MODE_BTN 0
 #define LA_INPUT_CONTROLLER_NODE_MODE_AXIS 1
@@ -1401,6 +1492,7 @@ void laSetWindowCursor(int id);
 void laRenameWindow(laWindow* wnd, char* name);
 
 int laGetReady();
+void laSetFrameCallbacks(laPreFrameF PreFrame, laPreDrawF PreDraw, laPostFrameF PostFrame);
 laWindow *laDesignWindow(int X, int Y, int W, int H);
 laLayout *laDesignLayout(laWindow *w, char *Title);
 void laFoldBlockTitle(laBlock* b);

+ 17 - 0
source/lagui/la_kernel.c

@@ -335,6 +335,8 @@ int laGetReady(){
     arrEnsureLength(&MAIN.InputBuf,0,&MAIN.InputBufMax,sizeof(char));
     arrEnsureLength(&MAIN.InputBufU,0,&MAIN.InputBufUMax,sizeof(uint32_t));
 
+    arrEnsureLength(&MAIN.NodeTypes,0,&MAIN.NodeTypeMax,sizeof(laBaseNode*));
+
     //XDestroyWindow(MAIN.dpy, win);
 
     MAIN.ColorAccessCorrectGamma = 1;
@@ -386,6 +388,7 @@ int laGetReady(){
     la_InitControllers();
     la_RegisterControllerProps();
     la_RegisterInputMapperOperators();
+    tns_RegisterNodes();
 
     la_InitThreadEnviornment();
 
@@ -650,6 +653,10 @@ void laRetriggerOperators(){
     MAIN.ReTriggerOperators = 1;
 }
 
+void laSetFrameCallbacks(laPreFrameF PreFrame, laPreDrawF PreDraw, laPostFrameF PostFrame){
+    MAIN.PreFrame=PreFrame; MAIN.PreDraw=PreDraw; MAIN.PostFrame=PostFrame;
+}
+
 //====================================================================================================
 
 void la_DestroyTheme(laTheme* t){
@@ -6392,17 +6399,24 @@ void laMainLoop(){
     while (1){
         laRecordTime(&FrameStartTime);
 
+        if(MAIN.PreFrame){ MAIN.PreFrame(); }
+
         if(!la_ProcessSysMessage()){ return; }
 
         la_UpdateControllerStatus();
 
         if(MAIN.MappingNeedRebuild){ la_RebuildInputMapping(); }
         if(MAIN.MappingNeedEval){ la_RunInputMapping(); }
+        if(MAIN.DriverNeedRebuild){ la_RebuildDriverEval(); }
+        if(MAIN.DriverNeedEval){ la_RunDrivers(); }
 
         for (w=MAIN.Windows.pFirst;w;w = NextW){
             NextW = w->Item.pNext;
             if(!la_HandleEvents(w)){ laShutoff(); return; }
         }
+
+        if(MAIN.PreDraw){ MAIN.PreDraw(); }
+
         for(w=MAIN.Windows.pFirst;w;w=w->Item.pNext){
             tnsSwitchToCurrentWindowContext(w);
             la_DrawWindow(w);
@@ -6410,6 +6424,9 @@ void laMainLoop(){
         for(w=MAIN.Windows.pFirst;w;w=w->Item.pNext){
             glXSwapBuffers(MAIN.dpy, w->win);
         }
+
+        if(MAIN.PostFrame){ MAIN.PostFrame(); }
+
         //t2 = clock();
         laRecordTime(&FrameEndTime);
         TimeInterval = laTimeElapsedSecondsf(&FrameEndTime, &FrameStartTime);

+ 22 - 0
source/lagui/la_tns.h

@@ -534,12 +534,30 @@ struct _tnsObject
 
     tnsMatrix44d GlobalTransform;
     tnsMatrix44d SelfTransform;
+    tnsMatrix44d DeltaTransform;
     //tnsMatrix44d CameraTransform;
 
     tnsObject *Active;  // may be used to store last select info in child objects.
     tnsObject *InRoot;
     tnsObject *ParentObject;   // reused as root active sun.
     laListHandle ChildObjects;
+
+    laListHandle Drivers; // rack
+};
+
+NEED_STRUCTURE(laNodeInSocket);
+NEED_STRUCTURE(laNodeOutSocket);
+
+STRUCTURE(tnsTransformNode){
+    laBaseNode Base;
+    laNodeInSocket* Mat;
+    tnsObject* Target;
+};
+STRUCTURE(tnsMakeTransformNode){
+    laBaseNode Base;
+    laNodeInSocket* Loc; laNodeInSocket* Rot; laNodeInSocket* Angle; laNodeInSocket* Sca;
+    laNodeOutSocket* Out;
+    tnsMatrix44d Mat; tnsVector3d UseLoc; tnsVector3d UseRot; real UseSca; real UseAngle;
 };
 
 #define TNS_PRESPECTIVE_CAMERA 0
@@ -1038,6 +1056,8 @@ int tnsLoadExchange(char *FileName);
 
 tnsObject *tnsFindObject(char *Name, tnsObject *From);
 
+void tns_RegisterNodes();
+
 void tnsCopyObjectTransformationsLocal(tnsObject* to, tnsObject* from);
 void tnsCopyObjectTransformationsGlobal(tnsObject* to, tnsObject* from);
 void tnsRotateObjectGlobal(tnsObject *o, real x, real y, real z, real angle, real cx,real cy,real cz);
@@ -1046,8 +1066,10 @@ void tnsRotateObjectLocalValues(tnsObject *o, real x, real y, real z);
 void tnsTranslateObjectGlobal(tnsObject *o, real x, real y, real z);
 void tnsTranslateObjectLocal(tnsObject *o, real x, real y, real z);
 void tnsMoveObjectLocal(tnsObject *o, real x, real y, real z);
+void tnsMoveObjectDelta(tnsObject *o, real x, real y, real z);
 void tnsMoveObjectGlobal(tnsObject *o, real x, real y, real z);
 void tnsScaleObject(tnsObject *o, real fac, real cx,real cy,real cz);
+void tnsScaleObjectDelta(tnsObject *o, real fac, real cx,real cy,real cz);
 int tnsCheckParentable(tnsObject* child, tnsObject* parent);
 void tnsParentObject(tnsObject *child, tnsObject *parent, int KeepTransform);
 void tnsUnparentObject(tnsObject *o, int KeepTransform);

+ 43 - 4
source/lagui/la_tns_kernel.c

@@ -3188,8 +3188,15 @@ void tnsCopyGlobalTransform(tnsObject *to, tnsObject *from){
 }
 
 void tnsSelfMatrixChanged(tnsObject* o, int ApplyToChild){
-    if (!o->ParentObject){ memcpy(o->GlobalTransform, o->SelfTransform, sizeof(tnsMatrix44d)); }
-    else{ tnsMultiply44d(o->GlobalTransform, o->ParentObject->GlobalTransform, o->SelfTransform); }
+    tnsMatrix44d mix;
+    if (!o->ParentObject){
+        tnsMultiply44d(mix, o->SelfTransform, o->DeltaTransform);
+        memcpy(o->GlobalTransform, mix, sizeof(tnsMatrix44d)); 
+    }
+    else{
+        tnsMultiply44d(mix, o->ParentObject->GlobalTransform, o->SelfTransform);
+        tnsMultiply44d(o->GlobalTransform, mix, o->DeltaTransform);
+    }
     tnsExtractSelfTransformValue(o);
     tnsExtractGlobalTransformValue(o);
     if(ApplyToChild) for (laListItemPointer* li=o->ChildObjects.pFirst;li;li=li->pNext){ tnsSelfMatrixChanged(li->p,1); }
@@ -3268,6 +3275,7 @@ void tnsInitObjectBase(tnsObject *o, tnsObject *under, char *Name, int Type,
     o->Scale = Scale;
     o->Show = 1;
     o->DrawMode = GL_LINE_LOOP;
+    tnsLoadIdentity44d(o->DeltaTransform);
     tnsSelfTransformValueChanged(o);
     lstAppendItem(&T->World.AllObjects, o);
     if (under){ lstAppendPointer(&under->ChildObjects, o);
@@ -3321,6 +3329,13 @@ void tnsMoveObjectLocal(tnsObject *o, real x, real y, real z){
     memcpy(o->SelfTransform, res1, sizeof(tnsMatrix44d));
     tnsSelfMatrixChanged(o,1);
 }
+void tnsMoveObjectDelta(tnsObject *o, real x, real y, real z){
+    tnsMatrix44d mat, res1, res2, res3;
+    tnsMakeTranslationMatrix44d(res2, x, y, z);
+    tnsMultiply44d(res1, o->DeltaTransform, res2);
+    memcpy(o->DeltaTransform, res1, sizeof(tnsMatrix44d));
+    tnsSelfMatrixChanged(o,1);
+}
 void tnsMoveObjectGlobal(tnsObject *o, real x, real y, real z){
     tnsMatrix44d mat, res1, res2, res3;
     tnsMakeTranslationMatrix44d(res2, x, y, z);
@@ -3360,6 +3375,19 @@ void tnsRotateObjectLocal(tnsObject *o, real x, real y, real z, real angle, real
     tnsMultiply44d(o->SelfTransform,res2,tfwd);
     tnsSelfMatrixChanged(o,1);
 }
+void tnsRotateObjectDelta(tnsObject *o, real x, real y, real z, real angle, real gcx,real gcy,real gcz){
+    tnsMatrix44d tback,tfwd,rot,res1,res2;
+    real xs, ys, zs;
+    tnsVector3d gp,gpt; gp[0]=gcx; gp[1]=gcy; gp[2]=gcz;
+    tnsApplyTransform43d(gpt,o->DeltaTransform,gp);
+    tnsMakeTranslationMatrix44d(tfwd, gpt[0]-gcx, gpt[0]-gcy, gpt[0]-gcz);
+    tnsInverse44d(tback,tfwd);
+    tnsMakeRotationMatrix44d(rot,angle,x,y,z);
+    tnsMultiply44d(res1,o->SelfTransform,tback);
+    tnsMultiply44d(res2,res1,rot);
+    tnsMultiply44d(o->SelfTransform,res2,tfwd);
+    tnsSelfMatrixChanged(o,1);
+}
 void tnsScaleObject(tnsObject *o, real fac, real cx,real cy,real cz){
     tnsMatrix44d sca,res1,res2;
     tnsMakeScaleMatrix44d(sca,fac,fac,fac);
@@ -3371,6 +3399,17 @@ void tnsScaleObject(tnsObject *o, real fac, real cx,real cy,real cz){
     memcpy(o->GlobalTransform, res1, sizeof(tnsMatrix44d));
     tnsGlobalMatrixChanged(o,1);
 }
+void tnsScaleObjectDelta(tnsObject *o, real fac, real cx,real cy,real cz){
+    tnsMatrix44d sca,res1,res2;
+    tnsMakeScaleMatrix44d(sca,fac,fac,fac);
+    tnsMultiply44d(res1,o->DeltaTransform,sca);
+    tnsVector3d delta; delta[0]=res1[12]-cx; delta[1]=res1[13]-cy; delta[2]=res1[14]-cz;
+    tnsVector3d c; c[0]=cx; c[1]=cy; c[2]=cz;
+    tnsVectorMultiSelf3d(delta, fac);
+    tnsVectorPlus3d(&res1[12],c,delta);
+    memcpy(o->DeltaTransform, res1, sizeof(tnsMatrix44d));
+    tnsGlobalMatrixChanged(o,1);
+}
 void tnsZoomViewingCamera(tnsCamera *c, real Ratio){
     if (c->FocusDistance < 0.1) return;
     tnsMoveObjectLocal(c, 0, 0, -c->FocusDistance * Ratio);
@@ -3573,8 +3612,8 @@ void tnsApplyObjectMatrix(tnsObject *Object){
 
     mat = tnsGetModelMatrix();
 
-    tnsMultiply44d(result, mat, Object->SelfTransform);
-    memcpy(mat, result, sizeof(tnsMatrix44d));
+    //tnsMultiply44d(result, mat, Object->SelfTransform);
+    memcpy(mat, Object->GlobalTransform, sizeof(tnsMatrix44d));
 
     //Actually This Works Pretty Fast,but we are currently testing its functionality;
     //memcpy(mat, Object->GlobalTransform, sizeof(tnsMatrix44d));

+ 26 - 0
source/lagui/la_util.h

@@ -342,6 +342,32 @@ STRUCTURE(laTranslationMatch) {
 };
 
 
+NEED_STRUCTURE(laBaseNode);
+typedef void (*laBaseNodeInitF)(laBaseNode*);
+typedef void (*laBaseNodeDestroyF)(laBaseNode*);
+typedef int (*laBaseNodeVisitF)(laBaseNode*, laListHandle*);
+typedef int (*laBaseNodeEvalF)(laBaseNode*);
+
+STRUCTURE(laBaseNodeType){
+    laBaseNodeInitF    Init;
+    laBaseNodeDestroyF Destroy;
+    laBaseNodeVisitF   Visit;
+    laBaseNodeEvalF    Eval;
+    laPropContainer*   pc;
+    int NodeSize;
+};
+NEED_STRUCTURE(laNodeRack);
+STRUCTURE(laBaseNode){
+    laListItem Item;
+    laSafeString* Name;
+    laBaseNodeType* Type;
+    laNodeRack* InRack;
+    int Gap;
+    int Eval;
+};
+
+
+
 char * txtReadFileAsString(char * FileName);
 
 

+ 0 - 480
source/lagui/resources/la_input_mapping.c

@@ -1,480 +0,0 @@
-#include "../la_5.h"
-
-extern LA MAIN;
-extern struct _tnsMain *T;
-
-
-laBaseNodeType LA_IDN_KEYBOARD;
-laBaseNodeType LA_IDN_MOUSE;
-laBaseNodeType LA_IDN_CONTROLLER;
-laBaseNodeType LA_IDN_VISUALIZER;
-laBaseNodeType LA_IDN_SPLIT;
-laBaseNodeType LA_IDN_SWITCH;
-
-laPropContainer* LA_PC_IDN_GENERIC;
-laPropContainer* LA_PC_IDN_KEYBOARD;
-laPropContainer* LA_PC_IDN_MOUSE;
-laPropContainer* LA_PC_IDN_CONTROLLER;
-laPropContainer* LA_PC_IDN_VISUALIZER;
-laPropContainer* LA_PC_IDN_SPLIT;
-laPropContainer* LA_PC_IDN_SWITCH;
-
-#define LA_IDN_CONTROLLER_RESET_SOCKET(ns)\
-    {ns->IntVal[0]=0; ns->Out->DataType=LA_PROP_INT; ns->Offset=0; ns->Out->Data=&ns->IntVal;}
-
-void IDN_ControllerInit(laInputControllerNode* n){
-    for(int i=0;i<8;i++){ n->Sockets[i].Out=laCreateOutSocket(n, "out", 0); n->Sockets[i].Parent=n; }
-    strSafeSet(&n->Base.Name,"Controller Output");
-}
-void IDN_ControllerDestroy(laInputControllerNode* n){
-    for(int i=0;i<8;i++){ laDestroyOutSocket(n->Sockets[i].Out); }
-    strSafeDestroy(&n->Base.Name);
-}
-int IDN_ControllerVisit(laInputControllerNode* n, laListHandle* l){
-    laController* c=la_FindControllerWithID(n->UserID);
-    if(!c){ for(int i=0;i<8;i++){ laInputControllerNodeSocket* ns=&n->Sockets[i]; LA_IDN_CONTROLLER_RESET_SOCKET(ns); } return LA_DAG_FLAG_PERM; }
-    else{
-        for(int i=0;i<8;i++){ laInputControllerNodeSocket* ns=&n->Sockets[i];
-            if(!ns->Which || !ns->Which->Ptr){ LA_IDN_CONTROLLER_RESET_SOCKET(ns); continue; }
-            laPropContainer*pc=la_EnsureSubTarget(LA_PROP_CONTROLLER, c);
-            laProp* p=la_PropLookup(&pc->Props, n->Sockets[i].Which->Ptr);
-            if((!p)||(!p->Offset)||
-                ((p->PropertyType!=LA_PROP_INT)&&(p->PropertyType!=LA_PROP_ENUM)&&
-                 (p->PropertyType!=(LA_PROP_INT|LA_PROP_ARRAY))&&(p->PropertyType!=(LA_PROP_ENUM|LA_PROP_ARRAY)))){ LA_IDN_CONTROLLER_RESET_SOCKET(ns); continue; }
-            if(p->PropertyType==LA_PROP_INT){ ns->Out->DataType=LA_PROP_FLOAT; ns->Out->Data=&ns->RealVal; ns->Out->ArrLen=1; }
-            elif(p->PropertyType==(LA_PROP_INT|LA_PROP_ARRAY)){ ns->Out->DataType=(LA_PROP_FLOAT|LA_PROP_ARRAY); ns->Out->Data=&ns->RealVal; ns->Out->ArrLen=p->Len; }
-            elif(p->PropertyType==LA_PROP_ENUM){ ns->Out->DataType=LA_PROP_ENUM; ns->Out->Data=&ns->IntVal; ns->Out->ArrLen=1; }
-            elif(p->PropertyType==(LA_PROP_ENUM|LA_PROP_ARRAY)){ ns->Out->DataType=(LA_PROP_ENUM|LA_PROP_ARRAY); ns->Out->Data=&ns->IntVal; ns->Out->ArrLen=p->Len; }
-            ns->Offset=p->Offset;
-        }
-    }
-    n->Base.Eval=LA_DAG_FLAG_PERM;
-    lstAppendPointer(l, n);
-    return LA_DAG_FLAG_PERM;
-}
-int IDN_ControllerEval(laInputControllerNode* n){
-    laNotifyInstanceUsers(n);
-    laController* c=la_FindControllerWithID(n->UserID); if(!c){ 
-        for(int i=0;i<8;i++){ laInputControllerNodeSocket* ns=&n->Sockets[i]; LA_IDN_CONTROLLER_RESET_SOCKET(ns); } return 1;
-    }
-    for(int i=0;i<8;i++){ laInputControllerNodeSocket* ns=&n->Sockets[i]; 
-        int *addr=((char*)c)+ns->Offset; char* addc=addr;
-        if(ns->Out->DataType==LA_PROP_FLOAT){ ns->RealVal[0]=(real)(*addr)/32767.0; }
-        if(ns->Out->DataType==(LA_PROP_FLOAT|LA_PROP_ARRAY)){ for(int a=0;a<ns->Out->ArrLen;a++) ns->RealVal[a]=((real)addr[a])/32767.0; }
-        elif(ns->Out->DataType==LA_PROP_ENUM){ ns->IntVal[0]=(*addc); }
-        elif(ns->Out->DataType==(LA_PROP_ENUM|LA_PROP_ARRAY)){ for(int a=0;a<ns->Out->ArrLen;a++) ns->IntVal[a]=addc[a]; }
-    }
-    return 1;
-}
-void laui_ControllerNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
-    laColumn* c=laFirstColumn(uil); laInputControllerNode*n=This->EndInstance;
-    laColumn* cl,*cr; laSplitColumn(uil,c,0.3); cl=laLeftColumn(c,0); cr=laRightColumn(c,0);
-
-    laUiItem* b=laBeginRow(uil,c,0,0);
-    laShowHeightAdjuster(uil,c,This,"base.__gap",0);
-    laShowItem(uil,c,This,"base.name")->Expand=1;
-    laShowItem(uil,c,This,"user_id");
-    laEndRow(uil,b);
-
-    char* buf[128],buf2[128];
-    for(int i=0;i<8;i++){
-        sprintf(buf,"out%d.which",i); laShowItem(uil,cl,This,buf);
-        laUiItem* b=laBeginRow(uil,cr,0,0);
-        sprintf(buf2,"out%d.out.data_type",i);
-        laUiItem* b2=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,buf2),laIntExpression(LA_PROP_FLOAT)));{
-            sprintf(buf,"out%d.axis",i); laShowItem(uil,cr,This,buf)->Expand=1;
-        }laElse(uil,b2);{
-            laUiItem* b3=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,buf2),laIntExpression(LA_PROP_FLOAT|LA_PROP_ARRAY)));{
-                sprintf(buf,"out%d.axis2d",i); laUiItem* aui=laShowItem(uil,cr,This,buf);aui->Expand=1;aui->Flags|=LA_UI_FLAGS_TRANSPOSE;aui->Extra->HeightCoeff=1;
-            }laElse(uil,b3);{
-                sprintf(buf,"out%d.switch",i); laUiItem* sui=laShowItem(uil,cr,This,buf);sui->Expand=1;sui->Flags|=LA_UI_FLAGS_TRANSPOSE;
-            }laEndCondition(uil,b3);
-        }laEndCondition(uil,b2);
-        sprintf(buf,"out%d.out",i); laShowNodeSocket(uil,cr,This,buf,0);
-        laEndRow(uil,b);
-    }
-}
-
-void IDN_InputVisualizeInit(laInputVisualizerNode* n){
-    n->In=laCreateInSocket("in", 0);
-    strSafeSet(&n->Base.Name,"Input Visualizer");
-}
-void IDN_InputVisualizeDestroy(laInputVisualizerNode* n){
-    laDestroyInSocket(n->In);
-    strSafeDestroy(&n->Base.Name);
-}
-int IDN_InputVisualizeVisit(laInputVisualizerNode* n, laListHandle* l){
-    n->Base.Eval=LA_DAG_FLAG_TEMP;
-    if(n->In->Source){ int result;
-        laBaseNode* sn=n->In->Source->Parent; result=sn->Type->Visit(sn,l); if(result==LA_DAG_FLAG_TEMP) return LA_DAG_FLAG_ERR;
-    }
-    n->Base.Eval=LA_DAG_FLAG_PERM;
-    lstAppendPointer(l, n);
-    return LA_DAG_FLAG_PERM;
-}
-int IDN_InputVisualizerEval(laInputVisualizerNode* n){
-    if(!n->In->Source) return 0;
-    laNodeOutSocket* os=n->In->Source; int arrlen=1;
-    switch(os->DataType){
-    case LA_PROP_FLOAT|LA_PROP_ARRAY:
-    case LA_PROP_FLOAT: if(os->ArrLen)arrlen=os->ArrLen; memcpy(n->RealVal,os->Data,sizeof(real)*arrlen); n->In->ArrLen=arrlen; break;
-    case LA_PROP_ENUM|LA_PROP_ARRAY:
-    case LA_PROP_ENUM: if(os->ArrLen)arrlen=os->ArrLen; memcpy(n->IntVal,os->Data,sizeof(int)*arrlen); n->In->ArrLen=arrlen; break;
-    default: n->IntVal[0]=0; n->In->ArrLen=1; break;
-    }
-    n->In->DataType=os->DataType;
-    laNotifyInstanceUsers(n);
-    return 1;
-}
-void laui_InputVisualizeNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
-    laColumn* c=laFirstColumn(uil); laInputVisualizerNode*n=This->EndInstance;
-    laColumn* cl,*cr; laSplitColumn(uil,c,0.4); cl=laLeftColumn(c,1); cr=laRightColumn(c,0);
-
-    laUiItem* b=laBeginRow(uil,c,0,0);
-    laShowHeightAdjuster(uil,c,This,"base.__gap",0);
-    laShowItem(uil,c,This,"base.name");
-    laEndRow(uil,b);
-
-    laShowNodeSocket(uil,cl,This,"in",0);
-    laUiItem* b2=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,"in.data_type"),laIntExpression(LA_PROP_FLOAT)));{
-        laShowItem(uil,cr,This,"axis");
-    }laElse(uil,b2);{
-        laUiItem* b3=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,"in.data_type"),laIntExpression(LA_PROP_FLOAT|LA_PROP_ARRAY)));{
-            laUiItem* aui=laShowItem(uil,cr,This,"axis2d");
-        }laElse(uil,b3);{
-            laUiItem* sui=laShowItem(uil,cr,This,"switch");
-        }laEndCondition(uil,b3);
-    }laEndCondition(uil,b2);
-}
-
-void IDN_InputSplitInit(laInputSplitNode* n){
-    n->In=laCreateInSocket("in", 0); strSafeSet(&n->Base.Name,"Split");
-    for(int i=0;i<8;i++){ char str[4]; sprintf(str,"%d",i); n->Out[i].Out=laCreateOutSocket(n,str,0); }
-}
-void IDN_InputSplitDestroy(laInputSplitNode* n){
-    laDestroyInSocket(n->In); strSafeDestroy(&n->Base.Name);
-    for(int i=0;i<8;i++){ laDestroyOutSocket(n->Out[i].Out); }
-}
-int IDN_InputSplitVisit(laInputSplitNode* n, laListHandle* l){
-    if(n->Base.Eval==LA_DAG_FLAG_TEMP||n->Base.Eval==LA_DAG_FLAG_PERM) return LA_DAG_FLAG_ERR;
-    n->Base.Eval=LA_DAG_FLAG_TEMP;
-    if(n->In->Source){ int result;
-        laBaseNode* sn=n->In->Source->Parent; result=sn->Type->Visit(sn,l); if(result==LA_DAG_FLAG_TEMP) return LA_DAG_FLAG_ERR;
-    }
-    n->Base.Eval=LA_DAG_FLAG_PERM;
-    lstAppendPointer(l, n);
-    return LA_DAG_FLAG_PERM;
-}
-int IDN_InputSplitEval(laInputSplitNode* n){
-    if(!n->In->Source) return 0;
-    laNodeOutSocket* os=n->In->Source; int arrlen=1;
-    switch(os->DataType){
-    case LA_PROP_FLOAT|LA_PROP_ARRAY:
-    case LA_PROP_FLOAT: if(os->ArrLen) n->ArrLen=os->ArrLen;
-        for(int i=0;i<TNS_MIN2(n->ArrLen,8);i++){ n->Out[i].Out->DataType=LA_PROP_FLOAT; n->Out[i].Out->Data=&n->RealVal[i]; }
-        memcpy(n->RealVal,os->Data,sizeof(real)*n->ArrLen); n->In->ArrLen=arrlen; break;
-    case LA_PROP_ENUM|LA_PROP_ARRAY:
-    case LA_PROP_ENUM: if(os->ArrLen) n->ArrLen=os->ArrLen;
-        for(int i=0;i<TNS_MIN2(n->ArrLen,8);i++){ n->Out[i].Out->DataType=LA_PROP_ENUM; n->Out[i].Out->Data=&n->IntVal[i]; }
-        memcpy(n->IntVal,os->Data,sizeof(int)*n->ArrLen); n->In->ArrLen=arrlen; break;
-    default:
-        for(int i=0;i<TNS_MIN2(n->ArrLen,8);i++){ n->Out[i].Out->DataType=LA_PROP_ENUM; n->Out[i].Out->Data=&n->IntVal[i]; }
-        n->IntVal[0]=0; n->In->ArrLen=1; break;
-    }
-    n->In->DataType=os->DataType;
-    return 1;
-}
-void laui_InputSplitNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
-    laColumn* c=laFirstColumn(uil); laInputSplitNode*n=This->EndInstance;
-    laColumn* cl,*cr; laSplitColumn(uil,c,0.4); cl=laLeftColumn(c,1); cr=laRightColumn(c,0);
-
-    laUiItem* b=laBeginRow(uil,c,0,0);
-    laShowHeightAdjuster(uil,c,This,"base.__gap",0);
-    laShowItem(uil,c,This,"base.name");
-    laEndRow(uil,b);
-
-    b=laBeginRow(uil,c,0,0);
-    laShowNodeSocket(uil,c,This,"in",0);laShowSeparator(uil,c)->Expand=1;
-    laShowItemFull(uil,c,This,"array_length",LA_WIDGET_INT_PLAIN,0,0,0);
-    for(int i=0;i<8;i++){
-        char* buf[128]; sprintf(buf,"out%d.out",i); laShowNodeSocket(uil,cr,This,buf,0);
-    }
-    laEndRow(uil,b);
-}
-
-void IDN_InputSwitchInit(laInputSwitchNode* n){
-    n->SwitchIn=laCreateInSocket("sw in",0); n->Out=laCreateOutSocket(n,"out",0); strSafeSet(&n->Base.Name,"Switch");
-    for(int i=0;i<8;i++){ char str[4]; sprintf(str,"%d",i); n->In[i].In=laCreateInSocket(n,str); }
-}
-void IDN_InputSwitchDestroy(laInputSwitchNode* n){
-    laDestroyInSocket(n->SwitchIn); laDestroyOutSocket(n->Out); strSafeDestroy(&n->Base.Name);
-    for(int i=0;i<8;i++){ laDestroyInSocket(n->In[i].In); }
-}
-int IDN_InputSwitchVisit(laInputSwitchNode* n, laListHandle* l){
-    if(n->Base.Eval==LA_DAG_FLAG_TEMP||n->Base.Eval==LA_DAG_FLAG_PERM) return LA_DAG_FLAG_ERR;
-    n->Base.Eval=LA_DAG_FLAG_TEMP;
-    for(int i=0;i<8;i++){
-        if(n->In[i].In->Source){ int result;
-            laBaseNode* sn=n->In[i].In->Source->Parent; result=sn->Type->Visit(sn,l); if(result==LA_DAG_FLAG_TEMP) return LA_DAG_FLAG_ERR;
-        }
-    }
-    laBaseNode* sw=n->SwitchIn->Source?n->SwitchIn->Source->Parent:0; if(sw){
-        int result=sw->Type->Visit(sw,l); if(result==LA_DAG_FLAG_TEMP) return LA_DAG_FLAG_ERR;
-    }
-    n->Base.Eval=LA_DAG_FLAG_PERM;
-    lstAppendPointer(l, n);
-    return LA_DAG_FLAG_PERM;
-}
-int IDN_InputSwitchEval(laInputSwitchNode* n){
-    int sw=n->Switch;
-    if(n->SwitchIn->Source){ laNodeOutSocket* os=n->SwitchIn->Source; int* id; real* fd;
-        switch(os->DataType){
-        case LA_PROP_ARRAY|LA_PROP_ENUM:
-            id=os->Data; for(int i=0;i<os->ArrLen;i++){ if(id[i]){sw=i; break;} } break;
-        case LA_PROP_ENUM: case LA_PROP_INT: case LA_PROP_INT|LA_PROP_ARRAY:
-            id=os->Data; sw=*id; break;
-        case LA_PROP_FLOAT: case LA_PROP_FLOAT|LA_PROP_ARRAY:
-            fd=os->Data; sw=(int)(*fd); break;
-        default: sw=0; break;
-        }
-    }
-    TNS_CLAMP(sw,0,7);
-    laInputSwitchNodeInSocket *is=&n->In[sw];
-    if(is->In->Source){ n->Out->Data=is->In->Source->Data; n->Out->DataType=is->In->Source->DataType; n->Out->ArrLen=is->In->Source->ArrLen; }
-    else{ n->Out->Data=&n->TempVal; n->Out->DataType=LA_PROP_FLOAT; n->Out->ArrLen=1; }
-    return 1;
-}
-void laui_InputSwitchNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
-    laColumn* c=laFirstColumn(uil); laInputSwitchNode*n=This->EndInstance;
-    laColumn* cl,*cr; laSplitColumn(uil,c,0.4); cl=laLeftColumn(c,1); cr=laRightColumn(c,0);
-
-    laUiItem* b=laBeginRow(uil,c,0,0);
-    laShowHeightAdjuster(uil,c,This,"base.__gap",0);
-    laShowItem(uil,c,This,"base.name");
-    laEndRow(uil,b);
-
-    b=laBeginRow(uil,c,0,0);
-    for(int i=0;i<8;i++){
-        char* buf[128]; sprintf(buf,"in%d.in",i); laShowNodeSocket(uil,cr,This,buf,0);
-    } laShowSeparator(uil,c)->Expand=1;
-    laEndRow(uil,b);
-
-    b=laBeginRow(uil,c,0,0);
-    laShowItem(uil,c,This,"switch_in");
-    laUiItem* b2=laOnConditionThat(uil,c,laNot(laPropExpression(This,"switch_in.source")));{
-        laShowItem(uil,c,This,"switch");
-    };laEndCondition(uil,b2);
-    laShowSeparator(uil,c)->Expand=1; 
-    laShowNodeSocket(uil,c,This,"out",0);
-    laEndRow(uil,b);
-}
-
-
-int OPINV_AddInputMapperRack(laOperator* a, laEvent *e){
-    laInputRack* pivot=a->This?a->This->EndInstance:0;
-    laInputRack* ir=memAcquire(sizeof(laInputRack));
-
-    if(strSame(strGetArgumentString(a->ExtraInstructionsP,"before"),"true")){
-        if(pivot){ lstInsertItemBefore(&MAIN.InputMappingRacks,ir,pivot); }else{ lstPushItem(&MAIN.InputMappingRacks,ir); }
-    }else { if(pivot){ lstInsertItemAfter(&MAIN.InputMappingRacks,ir,pivot); }else{ lstAppendItem(&MAIN.InputMappingRacks,ir); } }
-    laNotifyUsers("la.input_racks");
-    
-    return LA_FINISHED;
-}
-
-laBaseNode* la_CreateInputMapperNode(laInputRack* ir, laBaseNodeType* NodeType){
-    laBaseNode* bn=memAcquire(NodeType->NodeSize);
-    bn->Type=NodeType; NodeType->Init(bn); lstAppendItem(&ir->Nodes, bn); bn->InRack=ir;
-    laNotifyUsers("la.input_racks");
-    return bn;
-}
-void la_DestroyInputMapperNode(laBaseNode* bn){
-    lstRemoveItem(bn->InRack, bn); bn->Type->Destroy(bn);
-    laNotifyUsers("la.input_racks");
-    memFree(bn);
-}
-
-int OPINV_AddInputMapperNode(laOperator* a, laEvent *e){
-    laInputRack* ir=a->This?a->This->EndInstance:0; if(!ir) return LA_CANCELED;
-
-    char* type=strGetArgumentString(a->ExtraInstructionsP,"type");
-    if(!type){ laEnableOperatorPanel(a,a->This,e->x,e->y,200,200,0,0,0,0,0,0,0,0,e); return LA_RUNNING; }
-    elif(strSame(type, "KEYBOARD")){}
-    elif(strSame(type, "MOUSE")){}
-    elif(strSame(type, "CONTROLLER")){ la_CreateInputMapperNode(ir, &LA_IDN_CONTROLLER); }
-    elif(strSame(type, "VISUALIZER")){ la_CreateInputMapperNode(ir, &LA_IDN_VISUALIZER); }
-    elif(strSame(type, "SPLIT")){ la_CreateInputMapperNode(ir, &LA_IDN_SPLIT); }
-    elif(strSame(type, "SWITCH")){ la_CreateInputMapperNode(ir, &LA_IDN_SWITCH); }
-
-    return LA_FINISHED;
-}
-void laui_AddInputMapperNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
-    laColumn* c=laFirstColumn(uil);
-    laShowItemFull(uil,c,This,"add_node",0,"type=KEYBOARD;text=Keyboard",0,0);
-    laShowItemFull(uil,c,This,"add_node",0,"type=MOUSE;text=Mouse",0,0);
-    laShowItemFull(uil,c,This,"add_node",0,"type=CONTROLLER;text=Controller",0,0);
-    laShowSeparator(uil,c);
-    laShowItemFull(uil,c,This,"add_node",0,"type=SPLIT;text=Split",0,0);
-    laShowItemFull(uil,c,This,"add_node",0,"type=SWITCH;text=Switch",0,0);
-    laShowItemFull(uil,c,This,"add_node",0,"type=VISUALIZER;text=Visualizer",0,0);
-}
-
-laPropContainer* laget_InputNodeType(laBaseNode* bn){
-    if(bn->Type==&LA_IDN_CONTROLLER) return LA_PC_IDN_CONTROLLER;
-    if(bn->Type==&LA_IDN_VISUALIZER) return LA_PC_IDN_VISUALIZER;
-    if(bn->Type==&LA_IDN_SPLIT) return LA_PC_IDN_SPLIT;
-    if(bn->Type==&LA_IDN_SWITCH) return LA_PC_IDN_SWITCH;
-    return LA_PC_IDN_GENERIC;
-}
-int laget_InputNodeGap(laInputRack* rack_unused, laBaseNode* n){
-    return n->Gap;
-}
-void laset_InputNodeGap(laBaseNode* n, int gap){
-    if(gap<0){
-        int done=0;
-        laBaseNode* nn=n; while(nn){ if(nn->Gap>0){ nn->Gap--; done=1; break; } nn=nn->Item.pPrev; }
-        if(done){ nn=n->Item.pNext; while(nn){ if(nn->Gap>0){ nn->Gap++; break; } nn=nn->Item.pNext; } }
-    }
-    if(gap>0){
-        n->Gap+=gap;
-        laBaseNode* nn=n->Item.pNext; while(nn){ if(nn->Gap>0){ nn->Gap--; break; } nn=nn->Item.pNext; }
-    }
-}
-void laset_InputNodeUserID(laInputControllerNode* n, int i){
-    laNotifyUsers("la.input_racks");
-}
-void laset_InputControllerNodeSocketWhich(laInputControllerNodeSocket* s, char* str){
-    strSafeSet(&s->Which, str);
-    laNotifyUsers("la.input_racks"); laMappingRequestRebuild();
-}
-int laget_SocketEnumArrayLength(laInputControllerNodeSocket* s){
-    return s->Out->ArrLen?s->Out->ArrLen:1;
-}
-int laget_VisualizerArrayLength(laInputVisualizerNode* s){
-    return s->In->ArrLen?s->In->ArrLen:1;
-}
-
-#define LA_IDN_REGISTER(a,init,destroy,visit,eval,type)\
-    {a.Init = init; a.Destroy = destroy; a.Visit=visit; a.Eval=eval; a.NodeSize=sizeof(type);}
-
-void la_RegisterInputMapperOperators(){
-    laPropContainer *pc; laProp *p;
-    laOperatorType *at;
-    laEnumProp *ep;
-
-    LA_IDN_REGISTER(LA_IDN_CONTROLLER, IDN_ControllerInit, IDN_ControllerDestroy, IDN_ControllerVisit, IDN_ControllerEval, laInputControllerNode);
-    LA_IDN_REGISTER(LA_IDN_VISUALIZER, IDN_InputVisualizeInit, IDN_InputVisualizeDestroy, IDN_InputVisualizeVisit, IDN_InputVisualizerEval, laInputVisualizerNode);
-    LA_IDN_REGISTER(LA_IDN_SPLIT, IDN_InputSplitInit, IDN_InputSplitDestroy, IDN_InputSplitVisit, IDN_InputSplitEval, laInputSplitNode);
-    LA_IDN_REGISTER(LA_IDN_SWITCH, IDN_InputSwitchInit, IDN_InputSwitchDestroy, IDN_InputSwitchVisit, IDN_InputSwitchEval, laInputSwitchNode);
-
-    laCreateOperatorType("LA_add_input_mapper_rack", "Add Rack", "Add a rack for input mapper nodes", 0,0,0,OPINV_AddInputMapperRack,0,'+',0);
-    at=laCreateOperatorType("LA_add_input_mapper_node", "Add Node", "Add a input mapper node",0,0,0,OPINV_AddInputMapperNode,OPMOD_FinishOnData,'+',0);
-    at->UiDefine=laui_AddInputMapperNode;
-
-    pc=laAddPropertyContainer("la_input_rack", "Input Rack", "Input rack for putting input mapping nodes",0,0,sizeof(laInputRack),0,0,1);
-    laAddStringProperty(pc,"name","Name","Name of this rack",0,0,0,0,1,offsetof(laInputRack,Name),0,0,0,0,LA_AS_IDENTIFIER);
-    p=laAddSubGroup(pc,"nodes","Nodes","Nodes under this rack","la_input_node",laget_InputNodeType,0,0,-1,0,0,0,0,0,0,offsetof(laInputRack,Nodes),0);
-    laSubGroupExtraFunctions(p,0,0,laget_InputNodeGap);
-    laAddOperatorProperty(pc,"add_node","Add Node","Add a node into this rack","LA_add_input_mapper_node",'+',0);
-
-    pc=laAddPropertyContainer("la_input_node", "Input Node", "Input logic node",0,0,sizeof(laBaseNode),0,0,1);
-    LA_PC_IDN_GENERIC=pc;
-    laAddStringProperty(pc,"name","Name","Name of this input node",0,0,0,0,1,offsetof(laBaseNode,Name),0,0,0,0,LA_AS_IDENTIFIER);
-    laAddIntProperty(pc,"__gap", "Gap", "Gap of the node", 0,0,0,0,0,0,0,0,offsetof(laBaseNode,Gap),0,laset_InputNodeGap,0,0,0,0,0,0,0,0,0);
-
-    pc=laAddPropertyContainer("la_input_controller_node", "Controller output", "Output controller values",0,laui_ControllerNode,sizeof(laInputControllerNode),0,0,1);
-    LA_PC_IDN_CONTROLLER=pc;
-    laAddSubGroup(pc,"base","Base","Base node","la_input_node",0,0,0,0,0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddIntProperty(pc,"user_id", "User ID", "Which controller should the data come from", 0,0,0,0,0,0,0,0,offsetof(laInputControllerNode,UserID),0,0,0,0,0,0,0,0,0,0,0);
-    laAddSubGroup(pc,"out0","Out 0","Output 0","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[0]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out1","Out 1","Output 1","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[1]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out2","Out 2","Output 2","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[2]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out3","Out 3","Output 3","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[3]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out4","Out 4","Output 4","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[4]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out5","Out 5","Output 5","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[5]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out6","Out 6","Output 6","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[6]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out7","Out 7","Output 7","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[7]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-
-    pc=laAddPropertyContainer("la_input_controller_node_socket", "Controller Socket", "One value from a controller output",0,0,sizeof(laInputControllerNodeSocket),0,0,1|LA_PROP_OTHER_ALLOC);
-    laAddStringProperty(pc,"which","Which","Select which output from the controller",0,0,0,0,1,offsetof(laInputControllerNodeSocket,Which),0,0,laset_InputControllerNodeSocketWhich,0,LA_AS_IDENTIFIER);
-    laAddFloatProperty(pc,"axis", "🡘", "Axis value", LA_WIDGET_VALUE_METER,0,0,1,-1,0,0,0,offsetof(laInputControllerNodeSocket,RealVal),0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
-    laAddFloatProperty(pc,"axis2d", "2D Axis", "2D Axis value", LA_WIDGET_VALUE_METER,0,0,1,-1,0,0,0,offsetof(laInputControllerNodeSocket,RealVal),0,0,2,0,0,0,0,0,0,0,LA_READ_ONLY);
-    p=laAddEnumProperty(pc,"switch", "SW", "Switch value", LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(laInputControllerNodeSocket,IntVal),0,0,0,laget_SocketEnumArrayLength,0,0,0,0,0,LA_READ_ONLY);
-    laAddEnumItemAs(p,"IDLE", "Idle", "Button is not pressed", 0, 0);
-    laAddEnumItemAs(p,"ACTIVE", "Active", "Button is pressed", 1, 0);
-    laAddSubGroup(pc, "out", "Out","Output value","la_out_socket",0,0,0,offsetof(laInputControllerNodeSocket,Out),0,0,0,0,0,0,0,LA_UDF_SINGLE);
-
-    pc=laAddPropertyContainer("la_input_visualizer_node", "Visualizer", "Visualizer node",0,laui_InputVisualizeNode,sizeof(laInputVisualizerNode),0,0,1);
-    LA_PC_IDN_VISUALIZER=pc;
-    laAddSubGroup(pc,"base","Base","Base node","la_input_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(laInputVisualizerNode,In),0,0,0,0,0,0,0,LA_UDF_SINGLE);
-    laAddFloatProperty(pc,"axis", "🡘", "Axis value", LA_WIDGET_VALUE_METER,0,0,1,-1,0,0,0,offsetof(laInputVisualizerNode,RealVal),0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
-    laAddFloatProperty(pc,"axis2d", "2D Axis", "2D Axis value", LA_WIDGET_VALUE_METER_2D,0,0,1,-1,0,0,0,offsetof(laInputVisualizerNode,RealVal),0,0,2,0,0,0,0,0,0,0,LA_READ_ONLY);
-    p=laAddEnumProperty(pc,"switch", "SW", "Switch value", LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(laInputVisualizerNode,IntVal),0,0,0,laget_VisualizerArrayLength,0,0,0,0,0,LA_READ_ONLY);
-    laAddEnumItemAs(p,"IDLE", "Idle", "Button is not pressed", 0, 0);
-    laAddEnumItemAs(p,"ACTIVE", "Active", "Button is pressed", 1, 0);
-
-    pc=laAddPropertyContainer("la_input_split_node", "Split", "Split node",0,laui_InputSplitNode,sizeof(laInputSplitNode),0,0,1);
-    LA_PC_IDN_SPLIT=pc;
-    laAddSubGroup(pc,"base","Base","Base node","la_input_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(laInputSplitNode,In),0,0,0,0,0,0,0,LA_UDF_SINGLE);
-    laAddSubGroup(pc,"out0","Out 0","Output 0","la_input_split_node_out_socket",0,0,0,offsetof(laInputSplitNode, Out[0]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out1","Out 1","Output 1","la_input_split_node_out_socket",0,0,0,offsetof(laInputSplitNode, Out[1]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out2","Out 2","Output 2","la_input_split_node_out_socket",0,0,0,offsetof(laInputSplitNode, Out[2]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out3","Out 3","Output 3","la_input_split_node_out_socket",0,0,0,offsetof(laInputSplitNode, Out[3]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out4","Out 4","Output 4","la_input_split_node_out_socket",0,0,0,offsetof(laInputSplitNode, Out[4]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out5","Out 5","Output 5","la_input_split_node_out_socket",0,0,0,offsetof(laInputSplitNode, Out[5]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out6","Out 6","Output 6","la_input_split_node_out_socket",0,0,0,offsetof(laInputSplitNode, Out[6]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out7","Out 7","Output 7","la_input_split_node_out_socket",0,0,0,offsetof(laInputSplitNode, Out[7]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddIntProperty(pc, "array_length", "Array Length", "Array length of data", 0, 0, 0, 0, 0, 0, 0, 0, offsetof(laInputSplitNode, ArrLen), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,LA_READ_ONLY);
-
-    pc=laAddPropertyContainer("la_input_split_node_out_socket", "Split Out", "One value from an array input",0,0,sizeof(laInputSplitNodeOutSocket),0,0,1|LA_PROP_OTHER_ALLOC);
-    laAddSubGroup(pc, "out", "Out","Output value","la_out_socket",0,0,0,offsetof(laInputSplitNodeOutSocket,Out),0,0,0,0,0,0,0,LA_UDF_SINGLE);
-
-    pc=laAddPropertyContainer("la_input_switch_node", "Switch", "Switch node",0,laui_InputSwitchNode,sizeof(laInputSwitchNode),0,0,1);
-    LA_PC_IDN_SWITCH=pc;
-    laAddSubGroup(pc,"base","Base","Base node","la_input_node",0,0,0,0,0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"out", "Out","Output value","la_out_socket",0,0,0,offsetof(laInputSwitchNode, Out),0,0,0,0,0,0,0,LA_UDF_SINGLE);
-    laAddSubGroup(pc,"in0","In 0","Input 0","la_input_switch_node_in_socket",0,0,0,offsetof(laInputSwitchNode, In[0]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"in1","In 1","Input 1","la_input_switch_node_in_socket",0,0,0,offsetof(laInputSwitchNode, In[1]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"in2","In 2","Input 2","la_input_switch_node_in_socket",0,0,0,offsetof(laInputSwitchNode, In[2]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"in3","In 3","Input 3","la_input_switch_node_in_socket",0,0,0,offsetof(laInputSwitchNode, In[3]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"in4","In 4","Input 4","la_input_switch_node_in_socket",0,0,0,offsetof(laInputSwitchNode, In[4]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"in5","In 5","Input 5","la_input_switch_node_in_socket",0,0,0,offsetof(laInputSwitchNode, In[5]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"in6","In 6","Input 6","la_input_switch_node_in_socket",0,0,0,offsetof(laInputSwitchNode, In[6]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddSubGroup(pc,"in7","In 7","Input 7","la_input_switch_node_in_socket",0,0,0,offsetof(laInputSwitchNode, In[7]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
-    laAddIntProperty(pc, "switch", "Switch", "Switch which input to use", 0, 0, 0, 0, 0, 0, 0, 0, offsetof(laInputSwitchNode, Switch), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
-    laAddSubGroup(pc,"switch_in", "Switch In","Switch control","la_in_socket",0,0,0,offsetof(laInputSwitchNode,SwitchIn),0,0,0,0,0,0,0,LA_UDF_SINGLE);
-
-    pc=laAddPropertyContainer("la_input_switch_node_in_socket", "Switch In", "Input of many values",0,0,sizeof(laInputSwitchNodeInSocket),0,0,1|LA_PROP_OTHER_ALLOC);
-    laAddSubGroup(pc, "in", "In","Input value","la_in_socket",0,0,0,offsetof(laInputSwitchNodeInSocket,In),0,0,0,0,0,0,0,LA_UDF_SINGLE);
-
-}
-
-
-
-void laMappingRequestRebuild(){ MAIN.MappingNeedRebuild=1; }
-void laMappingRequestEval(){ MAIN.MappingNeedEval=1; }
-
-int la_RunInputMapping(){
-    MAIN.MappingNeedEval = 0;
-    for(laListItemPointer*lip=MAIN.InputMappingEval.pFirst;lip;lip=lip->pNext){
-        laBaseNode* n=lip->p; n->Type->Eval(n);
-    }
-    return 1;
-}
-int la_RebuildInputMapping(){
-    MAIN.MappingNeedRebuild = 0;
-    while(lstPopPointer(&MAIN.InputMappingEval));
-    laListHandle pending={0};
-    for(laInputRack* ir=MAIN.InputMappingRacks.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,&MAIN.InputMappingEval); if(result==LA_DAG_FLAG_ERR){ while(lstPopPointer(&pending)); break; }
-    }
-    if(result==LA_DAG_FLAG_ERR){ while(lstPopPointer(&MAIN.InputMappingEval)); return LA_DAG_FLAG_ERR; }
-    return LA_DAG_FLAG_PERM;
-}

+ 715 - 0
source/lagui/resources/la_nodes_basic.c

@@ -0,0 +1,715 @@
+#include "../la_5.h"
+
+extern LA MAIN;
+extern struct _tnsMain *T;
+
+laBaseNodeType LA_IDN_KEYBOARD;
+laBaseNodeType LA_IDN_MOUSE;
+laBaseNodeType LA_IDN_CONTROLLER;
+laBaseNodeType LA_IDN_VISUALIZER;
+laBaseNodeType LA_IDN_SPLIT;
+laBaseNodeType LA_IDN_SWITCH;
+laBaseNodeType LA_IDN_COMBINE;
+laBaseNodeType LA_IDN_VALUES;
+laBaseNodeType LA_IDN_MATRIX;
+
+laPropContainer* LA_PC_IDN_GENERIC;
+laPropContainer* LA_PC_IDN_KEYBOARD;
+laPropContainer* LA_PC_IDN_MOUSE;
+laPropContainer* LA_PC_IDN_CONTROLLER;
+laPropContainer* LA_PC_IDN_VISUALIZER;
+laPropContainer* LA_PC_IDN_SPLIT;
+laPropContainer* LA_PC_IDN_SWITCH;
+laPropContainer* LA_PC_IDN_COMBINE;
+laPropContainer* LA_PC_IDN_VALUES;
+laPropContainer* LA_PC_IDN_MATRIX;
+
+#define LA_IDN_CONTROLLER_RESET_SOCKET(ns)\
+    {ns->IntVal[0]=0; ns->Out->DataType=LA_PROP_INT; ns->Offset=0; ns->Out->Data=&ns->IntVal;}
+
+void IDN_ControllerInit(laInputControllerNode* n){
+    for(int i=0;i<8;i++){ n->Sockets[i].Out=laCreateOutSocket(n, "out", 0); n->Sockets[i].Parent=n; }
+    strSafeSet(&n->Base.Name,"Controller Output");
+}
+void IDN_ControllerDestroy(laInputControllerNode* n){
+    for(int i=0;i<8;i++){ laDestroyOutSocket(n->Sockets[i].Out); }
+    strSafeDestroy(&n->Base.Name);
+}
+int IDN_ControllerVisit(laInputControllerNode* n, laListHandle* l){
+    laController* c=la_FindControllerWithID(n->UserID);
+    if(!c){ for(int i=0;i<8;i++){ laInputControllerNodeSocket* ns=&n->Sockets[i]; LA_IDN_CONTROLLER_RESET_SOCKET(ns); } return LA_DAG_FLAG_PERM; }
+    else{
+        for(int i=0;i<8;i++){ laInputControllerNodeSocket* ns=&n->Sockets[i];
+            if(!ns->Which || !ns->Which->Ptr){ LA_IDN_CONTROLLER_RESET_SOCKET(ns); continue; }
+            laPropContainer*pc=la_EnsureSubTarget(LA_PROP_CONTROLLER, c);
+            laProp* p=la_PropLookup(&pc->Props, n->Sockets[i].Which->Ptr);
+            if((!p)||(!p->Offset)||
+                ((p->PropertyType!=LA_PROP_INT)&&(p->PropertyType!=LA_PROP_ENUM)&&
+                 (p->PropertyType!=(LA_PROP_INT|LA_PROP_ARRAY))&&(p->PropertyType!=(LA_PROP_ENUM|LA_PROP_ARRAY)))){ LA_IDN_CONTROLLER_RESET_SOCKET(ns); continue; }
+            if(p->PropertyType==LA_PROP_INT){ ns->Out->DataType=LA_PROP_FLOAT; ns->Out->Data=&ns->RealVal; ns->Out->ArrLen=1; }
+            elif(p->PropertyType==(LA_PROP_INT|LA_PROP_ARRAY)){ ns->Out->DataType=(LA_PROP_FLOAT|LA_PROP_ARRAY); ns->Out->Data=&ns->RealVal; ns->Out->ArrLen=p->Len; }
+            elif(p->PropertyType==LA_PROP_ENUM){ ns->Out->DataType=LA_PROP_ENUM; ns->Out->Data=&ns->IntVal; ns->Out->ArrLen=1; }
+            elif(p->PropertyType==(LA_PROP_ENUM|LA_PROP_ARRAY)){ ns->Out->DataType=(LA_PROP_ENUM|LA_PROP_ARRAY); ns->Out->Data=&ns->IntVal; ns->Out->ArrLen=p->Len; }
+            ns->Offset=p->Offset;
+        }
+    }
+    n->Base.Eval=LA_DAG_FLAG_PERM;
+    lstAppendPointer(l, n);
+    return LA_DAG_FLAG_PERM;
+}
+int IDN_ControllerEval(laInputControllerNode* n){
+    laNotifyInstanceUsers(n);
+    laController* c=la_FindControllerWithID(n->UserID); if(!c){ 
+        for(int i=0;i<8;i++){ laInputControllerNodeSocket* ns=&n->Sockets[i]; LA_IDN_CONTROLLER_RESET_SOCKET(ns); } return 1;
+    }
+    for(int i=0;i<8;i++){ laInputControllerNodeSocket* ns=&n->Sockets[i]; 
+        int *addr=((char*)c)+ns->Offset; char* addc=addr;
+        if(ns->Out->DataType==LA_PROP_FLOAT){ ns->RealVal[0]=(real)(*addr)/32767.0; }
+        if(ns->Out->DataType==(LA_PROP_FLOAT|LA_PROP_ARRAY)){ for(int a=0;a<ns->Out->ArrLen;a++) ns->RealVal[a]=((real)addr[a])/32767.0; }
+        elif(ns->Out->DataType==LA_PROP_ENUM){ ns->IntVal[0]=(*addc); }
+        elif(ns->Out->DataType==(LA_PROP_ENUM|LA_PROP_ARRAY)){ for(int a=0;a<ns->Out->ArrLen;a++) ns->IntVal[a]=addc[a]; }
+    }
+    return 1;
+}
+void laui_ControllerNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); laInputControllerNode*n=This->EndInstance;
+    laColumn* cl,*cr; laSplitColumn(uil,c,0.3); cl=laLeftColumn(c,0); cr=laRightColumn(c,0);
+
+    laUiItem* b=laBeginRow(uil,c,0,0);
+    laShowHeightAdjuster(uil,c,This,"base.__gap",0);
+    laShowItem(uil,c,This,"base.name")->Expand=1;
+    laShowItem(uil,c,This,"user_id");
+    laEndRow(uil,b);
+
+    char* buf[128],buf2[128];
+    for(int i=0;i<8;i++){
+        sprintf(buf,"out%d.which",i); laShowItem(uil,cl,This,buf);
+        laUiItem* b=laBeginRow(uil,cr,0,0);
+        sprintf(buf2,"out%d.out.data_type",i);
+        laUiItem* b2=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,buf2),laIntExpression(LA_PROP_FLOAT)));{
+            sprintf(buf,"out%d.axis",i); laShowItem(uil,cr,This,buf)->Expand=1;
+        }laElse(uil,b2);{
+            laUiItem* b3=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,buf2),laIntExpression(LA_PROP_FLOAT|LA_PROP_ARRAY)));{
+                sprintf(buf,"out%d.axis2d",i); laUiItem* aui=laShowItem(uil,cr,This,buf);aui->Expand=1;aui->Flags|=LA_UI_FLAGS_TRANSPOSE;aui->Extra->HeightCoeff=1;
+            }laElse(uil,b3);{
+                sprintf(buf,"out%d.switch",i); laUiItem* sui=laShowItem(uil,cr,This,buf);sui->Expand=1;sui->Flags|=LA_UI_FLAGS_TRANSPOSE;
+            }laEndCondition(uil,b3);
+        }laEndCondition(uil,b2);
+        sprintf(buf,"out%d.out",i); laShowNodeSocket(uil,cr,This,buf,0);
+        laEndRow(uil,b);
+    }
+}
+
+void IDN_InputVisualizeInit(laInputVisualizerNode* n){
+    n->In=laCreateInSocket("in", 0);
+    strSafeSet(&n->Base.Name,"Input Visualizer");
+}
+void IDN_InputVisualizeDestroy(laInputVisualizerNode* n){
+    laDestroyInSocket(n->In);
+    strSafeDestroy(&n->Base.Name);
+}
+int IDN_InputVisualizeVisit(laInputVisualizerNode* n, laListHandle* l){
+    n->Base.Eval=LA_DAG_FLAG_TEMP;
+    if(n->In->Source){ laBaseNode* sn=n->In->Source->Parent;LA_VISIT_NODE(sn); }
+    n->Base.Eval=LA_DAG_FLAG_PERM;
+    lstAppendPointer(l, n);
+    return LA_DAG_FLAG_PERM;
+}
+int IDN_InputVisualizerEval(laInputVisualizerNode* n){
+    if(!n->In->Source) return 0;
+    laNodeOutSocket* os=n->In->Source; int arrlen=1;
+    switch(os->DataType){
+    case LA_PROP_FLOAT|LA_PROP_ARRAY:
+    case LA_PROP_FLOAT: if(os->ArrLen)arrlen=os->ArrLen; memcpy(n->RealVal,os->Data,sizeof(real)*arrlen); n->In->ArrLen=arrlen; break;
+    case LA_PROP_ENUM|LA_PROP_ARRAY:
+    case LA_PROP_ENUM: if(os->ArrLen)arrlen=os->ArrLen; memcpy(n->IntVal,os->Data,sizeof(int)*arrlen); n->In->ArrLen=arrlen; break;
+    default: n->IntVal[0]=0; n->In->ArrLen=1; break;
+    }
+    n->In->DataType=os->DataType;
+    laNotifyInstanceUsers(n);
+    return 1;
+}
+void laui_InputVisualizeNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); laInputVisualizerNode*n=This->EndInstance;
+    laColumn* cl,*cr; laSplitColumn(uil,c,0.4); cl=laLeftColumn(c,1); cr=laRightColumn(c,0);
+
+    LA_BASE_NODE_HEADER(uil,c,This);
+
+    laShowNodeSocket(uil,cl,This,"in",0);
+    laUiItem* b2=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,"in.data_type"),laIntExpression(LA_PROP_FLOAT)));{
+        laShowItem(uil,cr,This,"axis");
+    }laElse(uil,b2);{
+        laUiItem* b3=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,"in.data_type"),laIntExpression(LA_PROP_FLOAT|LA_PROP_ARRAY)));{
+            laUiItem* aui=laShowItem(uil,cr,This,"axis2d");
+        }laElse(uil,b3);{
+            laUiItem* sui=laShowItem(uil,cr,This,"switch");
+        }laEndCondition(uil,b3);
+    }laEndCondition(uil,b2);
+}
+
+void IDN_SplitInit(laSplitNode* n){
+    n->In=laCreateInSocket("in", 0); strSafeSet(&n->Base.Name,"Split");
+    for(int i=0;i<8;i++){ char str[4]; sprintf(str,"%d",i); n->Out[i].Out=laCreateOutSocket(n,str,0); }
+}
+void IDN_SplitDestroy(laSplitNode* n){
+    laDestroyInSocket(n->In); strSafeDestroy(&n->Base.Name);
+    for(int i=0;i<8;i++){ laDestroyOutSocket(n->Out[i].Out); }
+}
+int IDN_SplitVisit(laSplitNode* n, laListHandle* l){
+    LA_GUARD_THIS_NODE(n);
+    if(n->In->Source){ laBaseNode* sn=n->In->Source->Parent; LA_VISIT_NODE(sn); }
+    n->Base.Eval=LA_DAG_FLAG_PERM;
+    lstAppendPointer(l, n);
+    return LA_DAG_FLAG_PERM;
+}
+int IDN_SplitEval(laSplitNode* n){
+    if(!n->In->Source) return 0;
+    laNodeOutSocket* os=n->In->Source; int arrlen=1;
+    switch(os->DataType){
+    case LA_PROP_FLOAT|LA_PROP_ARRAY:
+    case LA_PROP_FLOAT: if(os->ArrLen) n->ArrLen=os->ArrLen;
+        for(int i=0;i<TNS_MIN2(n->ArrLen,8);i++){ n->Out[i].Out->DataType=LA_PROP_FLOAT; n->Out[i].Out->Data=&n->RealVal[i]; }
+        memcpy(n->RealVal,os->Data,sizeof(real)*n->ArrLen); n->In->ArrLen=arrlen; break;
+    case LA_PROP_ENUM|LA_PROP_ARRAY:
+    case LA_PROP_ENUM: if(os->ArrLen) n->ArrLen=os->ArrLen;
+        for(int i=0;i<TNS_MIN2(n->ArrLen,8);i++){ n->Out[i].Out->DataType=LA_PROP_ENUM; n->Out[i].Out->Data=&n->IntVal[i]; }
+        memcpy(n->IntVal,os->Data,sizeof(int)*n->ArrLen); n->In->ArrLen=arrlen; break;
+    default:
+        for(int i=0;i<TNS_MIN2(n->ArrLen,8);i++){ n->Out[i].Out->DataType=LA_PROP_ENUM; n->Out[i].Out->Data=&n->IntVal[i]; }
+        n->IntVal[0]=0; n->In->ArrLen=1; break;
+    }
+    n->In->DataType=os->DataType;
+    return 1;
+}
+void laui_SplitNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); laSplitNode*n=This->EndInstance;
+    laColumn* cl,*cr; laSplitColumn(uil,c,0.4); cl=laLeftColumn(c,1); cr=laRightColumn(c,0);
+
+    LA_BASE_NODE_HEADER(uil,c,This);
+
+    laUiItem* b=laBeginRow(uil,c,0,0);
+    laShowNodeSocket(uil,c,This,"in",0);laShowSeparator(uil,c)->Expand=1;
+    laShowItemFull(uil,c,This,"array_length",LA_WIDGET_INT_PLAIN,0,0,0);
+    for(int i=0;i<8;i++){
+        char* buf[128]; sprintf(buf,"out%d.out",i); laShowNodeSocket(uil,cr,This,buf,0);
+    }
+    laEndRow(uil,b);
+}
+
+void IDN_SwitchInit(laSwitchNode* n){
+    n->SwitchIn=laCreateInSocket("sw in",0); n->Out=laCreateOutSocket(n,"out",0); strSafeSet(&n->Base.Name,"Switch");
+    for(int i=0;i<8;i++){ char str[4]; sprintf(str,"%d",i); n->In[i].In=laCreateInSocket(n,str); }
+}
+void IDN_SwitchDestroy(laSwitchNode* n){
+    laDestroyInSocket(n->SwitchIn); laDestroyOutSocket(n->Out); strSafeDestroy(&n->Base.Name);
+    for(int i=0;i<8;i++){ laDestroyInSocket(n->In[i].In); }
+}
+int IDN_SwitchVisit(laSwitchNode* n, laListHandle* l){
+    LA_GUARD_THIS_NODE(n);
+    for(int i=0;i<8;i++){
+        if(n->In[i].In->Source){ laBaseNode* sn=n->In[i].In->Source->Parent; LA_VISIT_NODE(sn); }
+    }
+    laBaseNode* sw=n->SwitchIn->Source?n->SwitchIn->Source->Parent:0; if(sw){ LA_VISIT_NODE(sw); }
+    n->Base.Eval=LA_DAG_FLAG_PERM;
+    lstAppendPointer(l, n);
+    return LA_DAG_FLAG_PERM;
+}
+int IDN_SwitchEval(laSwitchNode* n){
+    int sw=n->Switch;
+    if(n->SwitchIn->Source){ laNodeOutSocket* os=n->SwitchIn->Source; int* id; real* fd;
+        switch(os->DataType){
+        case LA_PROP_ARRAY|LA_PROP_ENUM:
+            id=os->Data; for(int i=0;i<os->ArrLen;i++){ if(id[i]){sw=i; break;} } break;
+        case LA_PROP_ENUM: case LA_PROP_INT: case LA_PROP_INT|LA_PROP_ARRAY:
+            id=os->Data; sw=*id; break;
+        case LA_PROP_FLOAT: case LA_PROP_FLOAT|LA_PROP_ARRAY:
+            fd=os->Data; sw=(int)(*fd); break;
+        default: sw=0; break;
+        }
+    }
+    TNS_CLAMP(sw,0,7);
+    laSwitchNodeInSocket *is=&n->In[sw];
+    if(is->In->Source){ n->Out->Data=is->In->Source->Data; n->Out->DataType=is->In->Source->DataType; n->Out->ArrLen=is->In->Source->ArrLen; }
+    else{ n->Out->Data=&n->TempVal; n->Out->DataType=LA_PROP_FLOAT; n->Out->ArrLen=1; }
+    return 1;
+}
+void laui_SwitchNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); laSwitchNode*n=This->EndInstance;
+    laColumn* cl,*cr; laSplitColumn(uil,c,0.4); cl=laLeftColumn(c,1); cr=laRightColumn(c,0);
+
+    LA_BASE_NODE_HEADER(uil,c,This);
+
+    laUiItem* b=laBeginRow(uil,c,0,0);
+    for(int i=0;i<8;i++){
+        char* buf[128]; sprintf(buf,"in%d.in",i); laShowNodeSocket(uil,cr,This,buf,0);
+    } laShowSeparator(uil,c)->Expand=1;
+    laEndRow(uil,b);
+
+    b=laBeginRow(uil,c,0,0);
+    laShowItem(uil,c,This,"switch_in");
+    laUiItem* b2=laOnConditionThat(uil,c,laNot(laPropExpression(This,"switch_in.source")));{
+        laShowItem(uil,c,This,"switch");
+    };laEndCondition(uil,b2);
+    laShowSeparator(uil,c)->Expand=1; 
+    laShowNodeSocket(uil,c,This,"out",0);
+    laEndRow(uil,b);
+}
+
+void IDN_CombineInit(laCombineNode* n){
+    n->Out=laCreateOutSocket(n,"out",0);n->OutInt=laCreateOutSocket(n,"out_int",0);n->OutEnum=laCreateOutSocket(n,"out_enum",0); strSafeSet(&n->Base.Name,"Combine");
+    for(int i=0;i<8;i++){ char str[4]; sprintf(str,"%d",i); n->In[i].In=laCreateInSocket(n,str); }
+    n->Out->Data=n->Values; n->OutInt->Data=n->ValuesI; n->OutEnum->Data=n->ValuesI;
+    n->Out->DataType=LA_PROP_FLOAT|LA_PROP_ARRAY; n->OutInt->DataType=LA_PROP_INT|LA_PROP_ARRAY; n->OutEnum->DataType=LA_PROP_ENUM|LA_PROP_ARRAY;
+}
+void IDN_CombineDestroy(laCombineNode* n){
+    laDestroyOutSocket(n->Out);laDestroyOutSocket(n->OutInt);laDestroyOutSocket(n->OutEnum); strSafeDestroy(&n->Base.Name);
+    for(int i=0;i<8;i++){ laDestroyInSocket(n->In[i].In); }
+}
+int IDN_CombineVisit(laCombineNode* n, laListHandle* l){
+    LA_GUARD_THIS_NODE(n);
+    for(int i=0;i<8;i++){ if(LA_SRC_AND_PARENT(n->In[i].In)){ laBaseNode* sn=n->In[i].In->Source->Parent; LA_VISIT_NODE(sn); } }
+    n->Base.Eval=LA_DAG_FLAG_PERM;
+    lstAppendPointer(l, n);
+    return LA_DAG_FLAG_PERM;
+}
+int IDN_CombineEval(laCombineNode* n){
+    int maxlen=0;
+    for(int i=0;i<8;i++){ laNodeInSocket *is=n->In[i].In;
+        if(LA_SRC_AND_PARENT(is)){
+            if((is->Source->DataType&LA_PROP_FLOAT) && is->Source->Data){ n->Values[i]=*((real*)is->Source->Data); }
+            elif((is->Source->DataType&LA_PROP_INT) && is->Source->Data){ n->Values[i]=*((int*)is->Source->Data); }
+            elif((is->Source->DataType&LA_PROP_ENUM) && is->Source->Data){ n->Values[i]=*((int*)is->Source->Data); }
+            else n->Values[i]=0;
+            maxlen=i+1;
+        }
+        else n->Values[i]=0;
+        n->ValuesI[i]=n->Values[i];
+    }
+    n->Out->ArrLen=n->OutInt->ArrLen=n->OutEnum->ArrLen=maxlen;
+    return 1;
+}
+void laui_CombineNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); laCombineNode*n=This->EndInstance;
+    laColumn* cl,*cr; laSplitColumn(uil,c,0.4); cl=laLeftColumn(c,1); cr=laRightColumn(c,0);
+
+    LA_BASE_NODE_HEADER(uil,c,This);
+
+    laUiItem* b=laBeginRow(uil,c,0,0);
+    for(int i=0;i<8;i++){
+        char* buf[128]; sprintf(buf,"in%d.in",i); laShowNodeSocket(uil,cr,This,buf,0);
+    } laShowSeparator(uil,c)->Expand=1;
+    laEndRow(uil,b);
+
+    b=laBeginRow(uil,c,0,0);
+    laShowSeparator(uil,c)->Expand=1; 
+    laShowLabel(uil,c,"E",0,0)->Flags|=LA_TEXT_ALIGN_RIGHT;
+    laShowNodeSocket(uil,c,This,"out_enum",0);
+    laShowLabel(uil,c,"I",0,0)->Flags|=LA_TEXT_ALIGN_RIGHT;
+    laShowNodeSocket(uil,c,This,"out_int",0);
+    laShowLabel(uil,c,"F",0,0)->Flags|=LA_TEXT_ALIGN_RIGHT;
+    laShowNodeSocket(uil,c,This,"out",0);
+    laEndRow(uil,b);
+}
+
+void IDN_ValuesInit(laValuesNode* n){
+    strSafeSet(&n->Base.Name,"Values");
+    for(int i=0;i<8;i++){ char str[4]; sprintf(str,"%d",i); n->Out[i].Out=laCreateOutSocket(n,str,0); }
+}
+void IDN_ValuesDestroy(laValuesNode* n){
+    strSafeDestroy(&n->Base.Name);
+    for(int i=0;i<8;i++){ laDestroyOutSocket(n->Out[i].Out); }
+}
+int IDN_ValuesVisit(laValuesNode* 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_ValuesEval(laValuesNode* n){
+    for(int i=0;i<8;i++){ laNodeOutSocket *is=n->Out[i].Out;
+        if(n->Modes[i]==LA_VALUES_NODE_FLOAT){ is->Data=&n->Values[i];  is->DataType=LA_PROP_FLOAT; }
+        if(n->Modes[i]==LA_VALUES_NODE_INT){   is->Data=&n->ValuesI[i]; is->DataType=LA_PROP_INT; }
+        if(n->Modes[i]==LA_VALUES_NODE_ENUM){  is->Data=&n->ValuesE[i]; is->DataType=LA_PROP_ENUM; }
+    }
+    return 1;
+}
+void laui_ValuesNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); laValuesNode*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);
+
+    for(int i=0;i<8;i++){
+        char* bufm[32]; sprintf(bufm,"mode%d",i); laShowItem(uil,cl,This,bufm);
+        b=laBeginRow(uil,cr,0,0);
+        b2=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,bufm),laIntExpression(LA_VALUES_NODE_FLOAT)));{
+            char* buf[32]; sprintf(buf,"value%d",i); laShowItem(uil,cr,This,buf)->Expand=1;
+        }laEndCondition(uil,b2);
+        b2=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,bufm),laIntExpression(LA_VALUES_NODE_INT)));{
+            char* buf[32]; sprintf(buf,"valuei%d",i); laShowItem(uil,cr,This,buf)->Expand=1;
+        }laEndCondition(uil,b2);
+        b2=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,bufm),laIntExpression(LA_VALUES_NODE_ENUM)));{
+            char* buf[32]; sprintf(buf,"valuee%d",i); laUiItem* eui=laShowItem(uil,cr,This,buf); eui->Expand=1; eui->Flags|=LA_UI_FLAGS_HIGHLIGHT;
+        }laEndCondition(uil,b2);
+        sprintf(bufm,"out%d.out",i); laShowNodeSocket(uil,cr,This,bufm,0);
+        laEndRow(uil,b);
+    }
+}
+
+void IDN_MatrixInit(laMatrixNode* n){
+    strSafeSet(&n->Base.Name,"Matrix");
+    n->InL=laCreateInSocket("l",0); n->InR=laCreateInSocket("r",0); n->Out=laCreateOutSocket(n,"out",LA_PROP_FLOAT|LA_PROP_ARRAY);
+    n->Out->ArrLen=16; n->Out->Data=n->Mat;
+}
+void IDN_MatrixDestroy(laMatrixNode* n){
+    strSafeDestroy(&n->Base.Name);
+    laDestroyInSocket(n->InL); laDestroyInSocket(n->InR); laDestroyOutSocket(n->Out);
+}
+int IDN_MatrixVisit(laMatrixNode* n, laListHandle* l){
+    LA_GUARD_THIS_NODE(n);
+    if(LA_SRC_AND_PARENT(n->InL)){ laBaseNode* bn=n->InL->Source->Parent; LA_VISIT_NODE(bn); }
+    if(LA_SRC_AND_PARENT(n->InR)){ laBaseNode* bn=n->InR->Source->Parent; LA_VISIT_NODE(bn); }
+    n->Base.Eval=LA_DAG_FLAG_PERM;
+    lstAppendPointer(l, n);
+    return LA_DAG_FLAG_PERM;
+}
+int IDN_MatrixEval(laMatrixNode* n){
+    int hasl=LA_SRC_AND_PARENT(n->InL),hasr=LA_SRC_AND_PARENT(n->InR);
+    if((!hasl) && (!hasr)){ tnsLoadIdentity44d(n->Mat); return 0; }
+    if(hasl&&((n->InL->Source->DataType!=(LA_PROP_FLOAT|LA_PROP_ARRAY))||n->InL->Source->ArrLen!=16)){tnsLoadIdentity44d(n->Mat); return 0;}
+    if(hasr&&((n->InR->Source->DataType!=(LA_PROP_FLOAT|LA_PROP_ARRAY))||n->InR->Source->ArrLen!=16)){tnsLoadIdentity44d(n->Mat); return 0;}
+    if(n->Operation==LA_MATRIX_NODE_OP_INV){
+        real* mat; if(hasl)mat=n->InL->Source->Data;else mat=n->InR->Source->Data;
+        tnsInverse44d(n->Mat, mat);
+    }else{
+        if(!hasl) { memcpy(n->Mat,n->InR->Source->Data,sizeof(tnsMatrix44d)); }
+        elif(!hasr) { memcpy(n->Mat,n->InL->Source->Data,sizeof(tnsMatrix44d)); }
+        else{
+            tnsMultiply44d(n->Mat,n->InL->Source->Data,n->InR->Source->Data);
+        }
+    }
+    return 1;
+}
+void laui_MatrixNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); laMatrixNode*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_l",0); laShowNodeSocket(uil,c,This,"in_r",0); laShowItem(uil,c,This,"operation");
+    laShowSeparator(uil,c)->Expand=1; laShowNodeSocket(uil,c,This,"out",0);
+    laEndRow(uil,b);
+}
+
+
+
+int OPINV_AddInputMapperRack(laOperator* a, laEvent *e){
+    laNodeRack* pivot=a->This?a->This->EndInstance:0;
+    laNodeRack* ir=memAcquire(sizeof(laNodeRack));
+
+    if(strSame(strGetArgumentString(a->ExtraInstructionsP,"before"),"true")){
+        if(pivot){ lstInsertItemBefore(&MAIN.InputMappingRacks,ir,pivot); }else{ lstPushItem(&MAIN.InputMappingRacks,ir); }
+    }else { if(pivot){ lstInsertItemAfter(&MAIN.InputMappingRacks,ir,pivot); }else{ lstAppendItem(&MAIN.InputMappingRacks,ir); } }
+    laNotifyUsers("la.input_racks");
+    
+    return LA_FINISHED;
+}
+
+laBaseNode* la_CreateInputMapperNode(laNodeRack* ir, laBaseNodeType* NodeType){
+    laBaseNode* bn=memAcquire(NodeType->NodeSize);
+    bn->Type=NodeType; NodeType->Init(bn); lstAppendItem(&ir->Nodes, bn); bn->InRack=ir;
+    laNotifyUsers("la.input_racks");
+    return bn;
+}
+void la_DestroyInputMapperNode(laBaseNode* bn){
+    lstRemoveItem(bn->InRack, bn); bn->Type->Destroy(bn);
+    laNotifyUsers("la.input_racks");
+    memFree(bn);
+}
+
+int OPINV_AddInputMapperNode(laOperator* a, laEvent *e){
+    laNodeRack* ir=a->This?a->This->EndInstance:0; if(!ir) return LA_CANCELED;
+    laBaseNodeType* bnt=0;
+
+    char* type=strGetArgumentString(a->ExtraInstructionsP,"type");
+    if(!type){ laEnableOperatorPanel(a,a->This,e->x,e->y,200,200,0,0,0,0,0,0,0,0,e); return LA_RUNNING; }
+    elif(strSame(type, "KEYBOARD")){}
+    elif(strSame(type, "MOUSE")){}
+    elif(strSame(type, "CONTROLLER")){ la_CreateInputMapperNode(ir, &LA_IDN_CONTROLLER); }
+    elif(strSame(type, "VISUALIZER")){ la_CreateInputMapperNode(ir, &LA_IDN_VISUALIZER); }
+    elif(strSame(type, "SPLIT")){ la_CreateInputMapperNode(ir, &LA_IDN_SPLIT); }
+    elif(strSame(type, "SWITCH")){ la_CreateInputMapperNode(ir, &LA_IDN_SWITCH); }
+    elif(strSame(type, "COMBINE")){ la_CreateInputMapperNode(ir, &LA_IDN_COMBINE); }
+    elif(strSame(type, "VALUES")){ la_CreateInputMapperNode(ir, &LA_IDN_VALUES); }
+    elif(strSame(type, "MATRIX")){ la_CreateInputMapperNode(ir, &LA_IDN_MATRIX); }
+    elif(MAIN.ExtraGetInputNodeType && (bnt=MAIN.ExtraGetInputNodeType(type))){ la_CreateInputMapperNode(ir, bnt); }
+
+    return LA_FINISHED;
+}
+void laui_AddInputMapperNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil);
+    if(MAIN.ExtraAddInputNodes){
+        MAIN.ExtraAddInputNodes(uil,This,Extra,0,0);
+    }
+    laShowLabel(uil,c,"Sources:",0,0);
+    laShowItemFull(uil,c,This,"add_node_input",0,"type=KEYBOARD;text=Keyboard",0,0);
+    laShowItemFull(uil,c,This,"add_node_input",0,"type=MOUSE;text=Mouse",0,0);
+    laShowItemFull(uil,c,This,"add_node_input",0,"type=CONTROLLER;text=Controller",0,0);
+    laShowLabel(uil,c,"Operations:",0,0);
+    laShowItemFull(uil,c,This,"add_node_input",0,"type=SPLIT;text=Split",0,0);
+    laShowItemFull(uil,c,This,"add_node_input",0,"type=SWITCH;text=Switch",0,0);
+    laShowItemFull(uil,c,This,"add_node_input",0,"type=COMBINE;text=Combine",0,0);
+    laShowItemFull(uil,c,This,"add_node_input",0,"type=VALUES;text=Values",0,0);
+    laShowLabel(uil,c,"Math:",0,0);
+    laShowItemFull(uil,c,This,"add_node_input",0,"type=MATRIX;text=Matrix",0,0);
+    laShowLabel(uil,c,"Visualizations:",0,0);
+    laShowItemFull(uil,c,This,"add_node_input",0,"type=VISUALIZER;text=Visualizer",0,0);
+}
+
+laPropContainer* laget_BaseNodeType(laBaseNode* bn){
+    for(int i=0;i<MAIN.NodeTypeNext;i++){ if(bn->Type==MAIN.NodeTypes[i]) return MAIN.NodeTypes[i]->pc; }
+    return LA_PC_IDN_GENERIC;
+}
+int laget_InputNodeGap(laNodeRack* rack_unused, laBaseNode* n){
+    return n->Gap;
+}
+void laset_InputNodeGap(laBaseNode* n, int gap){
+    if(gap<0){
+        int done=0;
+        laBaseNode* nn=n; while(nn){ if(nn->Gap>0){ nn->Gap--; done=1; break; } nn=nn->Item.pPrev; }
+        if(done){ nn=n->Item.pNext; while(nn){ if(nn->Gap>0){ nn->Gap++; break; } nn=nn->Item.pNext; } }
+    }
+    if(gap>0){
+        n->Gap+=gap;
+        laBaseNode* nn=n->Item.pNext; while(nn){ if(nn->Gap>0){ nn->Gap--; break; } nn=nn->Item.pNext; }
+    }
+}
+void laset_InputNodeUserID(laInputControllerNode* n, int i){
+    laNotifyUsers("la.input_racks");
+}
+void laset_InputControllerNodeSocketWhich(laInputControllerNodeSocket* s, char* str){
+    strSafeSet(&s->Which, str);
+    laNotifyUsers("la.input_racks"); laMappingRequestRebuild();
+}
+int laget_SocketEnumArrayLength(laInputControllerNodeSocket* s){
+    return s->Out->ArrLen?s->Out->ArrLen:1;
+}
+int laget_VisualizerArrayLength(laInputVisualizerNode* s){
+    return s->In->ArrLen?s->In->ArrLen:1;
+}
+
+void laRegisterNode(laBaseNodeType* type, laPropContainer* pc, laBaseNodeInitF init, laBaseNodeDestroyF destroy, laBaseNodeVisitF visit, laBaseNodeEvalF eval, int nodesize){
+    arrEnsureLength(&MAIN.NodeTypes, MAIN.NodeTypeNext, &MAIN.NodeTypeMax, sizeof(laBaseNode*));
+    type->Init = init; type->Destroy = destroy; type->Visit=visit; type->Eval=eval; type->NodeSize=nodesize; type->pc=pc;
+    MAIN.NodeTypes[MAIN.NodeTypeNext]=type; MAIN.NodeTypeNext++;
+}
+
+void la_AddValuesNodeEnum(laProp* p){
+    laAddEnumItemAs(p,"FLOAT","Float","Float value",LA_VALUES_NODE_FLOAT,0);
+    laAddEnumItemAs(p,"INT","Int","Int value",LA_VALUES_NODE_INT,0);
+    laAddEnumItemAs(p,"ENUM","Switch","Swich value",LA_VALUES_NODE_ENUM,0);
+}
+void la_AddValuesNodeEnumValue(laProp* p){
+    laAddEnumItemAs(p,"IDLE","Idle","Idle",0,0);
+    laAddEnumItemAs(p,"ACTIVE","Active","Active",1,0);
+}
+void la_RegisterInputMapperOperators(){
+    laPropContainer *pc; laProp *p;
+    laOperatorType *at;
+    laEnumProp *ep;
+
+    laCreateOperatorType("LA_add_input_mapper_rack", "Add Rack", "Add a rack for input mapper nodes", 0,0,0,OPINV_AddInputMapperRack,0,'+',0);
+    at=laCreateOperatorType("LA_add_input_mapper_node", "Add Node", "Add a input mapper node",0,0,0,OPINV_AddInputMapperNode,OPMOD_FinishOnData,'+',0);
+    at->UiDefine=laui_AddInputMapperNode;
+
+    pc=laAddPropertyContainer("la_node_rack", "Input Rack", "Input rack for putting input mapping nodes",0,0,sizeof(laNodeRack),0,0,1);
+    laAddStringProperty(pc,"name","Name","Name of this rack",0,0,0,0,1,offsetof(laNodeRack,Name),0,0,0,0,LA_AS_IDENTIFIER);
+    p=laAddSubGroup(pc,"nodes","Nodes","Nodes under this rack","la_base_node",laget_BaseNodeType,0,0,-1,0,0,0,0,0,0,offsetof(laNodeRack,Nodes),0);
+    laSubGroupExtraFunctions(p,0,0,laget_InputNodeGap);
+    laAddOperatorProperty(pc,"add_node_input","Add Node","Add a node into this rack","LA_add_input_mapper_node",'+',0);
+    laAddOperatorProperty(pc,"add_node_driver","Add Node","Add a node into this rack","LA_add_driver_node",'+',0);
+
+    pc=laAddPropertyContainer("la_base_node", "Input Node", "Input logic node",0,0,sizeof(laBaseNode),0,0,1);
+    LA_PC_IDN_GENERIC=pc;
+    laAddStringProperty(pc,"name","Name","Name of this input node",0,0,0,0,1,offsetof(laBaseNode,Name),0,0,0,0,LA_AS_IDENTIFIER);
+    laAddIntProperty(pc,"__gap", "Gap", "Gap of the node", 0,0,0,0,0,0,0,0,offsetof(laBaseNode,Gap),0,laset_InputNodeGap,0,0,0,0,0,0,0,0,0);
+
+    pc=laAddPropertyContainer("la_input_controller_node", "Controller output", "Output controller values",0,laui_ControllerNode,sizeof(laInputControllerNode),0,0,1);
+    LA_PC_IDN_CONTROLLER=pc;
+    laAddSubGroup(pc,"base","Base","Base node","la_base_node",0,0,0,0,0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddIntProperty(pc,"user_id", "User ID", "Which controller should the data come from", 0,0,0,0,0,0,0,0,offsetof(laInputControllerNode,UserID),0,0,0,0,0,0,0,0,0,0,0);
+    laAddSubGroup(pc,"out0","Out 0","Output 0","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[0]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out1","Out 1","Output 1","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[1]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out2","Out 2","Output 2","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[2]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out3","Out 3","Output 3","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[3]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out4","Out 4","Output 4","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[4]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out5","Out 5","Output 5","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[5]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out6","Out 6","Output 6","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[6]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out7","Out 7","Output 7","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[7]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+
+    pc=laAddPropertyContainer("la_input_controller_node_socket", "Controller Socket", "One value from a controller output",0,0,sizeof(laInputControllerNodeSocket),0,0,1|LA_PROP_OTHER_ALLOC);
+    laAddStringProperty(pc,"which","Which","Select which output from the controller",0,0,0,0,1,offsetof(laInputControllerNodeSocket,Which),0,0,laset_InputControllerNodeSocketWhich,0,LA_AS_IDENTIFIER);
+    laAddFloatProperty(pc,"axis", "🡘", "Axis value", LA_WIDGET_VALUE_METER,0,0,1,-1,0,0,0,offsetof(laInputControllerNodeSocket,RealVal),0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
+    laAddFloatProperty(pc,"axis2d", "2D Axis", "2D Axis value", LA_WIDGET_VALUE_METER,0,0,1,-1,0,0,0,offsetof(laInputControllerNodeSocket,RealVal),0,0,2,0,0,0,0,0,0,0,LA_READ_ONLY);
+    p=laAddEnumProperty(pc,"switch", "SW", "Switch value", LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(laInputControllerNodeSocket,IntVal),0,0,0,laget_SocketEnumArrayLength,0,0,0,0,0,LA_READ_ONLY);
+    laAddEnumItemAs(p,"IDLE", "Idle", "Button is not pressed", 0, 0);
+    laAddEnumItemAs(p,"ACTIVE", "Active", "Button is pressed", 1, 0);
+    laAddSubGroup(pc, "out", "Out","Output value","la_out_socket",0,0,0,offsetof(laInputControllerNodeSocket,Out),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+
+    pc=laAddPropertyContainer("la_input_visualizer_node", "Visualizer", "Visualizer node",0,laui_InputVisualizeNode,sizeof(laInputVisualizerNode),0,0,1);
+    LA_PC_IDN_VISUALIZER=pc;
+    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(laInputVisualizerNode,In),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddFloatProperty(pc,"axis", "🡘", "Axis value", LA_WIDGET_VALUE_METER,0,0,1,-1,0,0,0,offsetof(laInputVisualizerNode,RealVal),0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
+    laAddFloatProperty(pc,"axis2d", "2D Axis", "2D Axis value", LA_WIDGET_VALUE_METER_2D,0,0,1,-1,0,0,0,offsetof(laInputVisualizerNode,RealVal),0,0,2,0,0,0,0,0,0,0,LA_READ_ONLY);
+    p=laAddEnumProperty(pc,"switch", "SW", "Switch value", LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(laInputVisualizerNode,IntVal),0,0,0,laget_VisualizerArrayLength,0,0,0,0,0,LA_READ_ONLY);
+    laAddEnumItemAs(p,"IDLE", "Idle", "Button is not pressed", 0, 0);
+    laAddEnumItemAs(p,"ACTIVE", "Active", "Button is pressed", 1, 0);
+
+    pc=laAddPropertyContainer("la_split_node", "Split", "Split node",0,laui_SplitNode,sizeof(laSplitNode),0,0,1);
+    LA_PC_IDN_SPLIT=pc;
+    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(laSplitNode,In),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"out0","Out 0","Output 0","la_split_node_out_socket",0,0,0,offsetof(laSplitNode, Out[0]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out1","Out 1","Output 1","la_split_node_out_socket",0,0,0,offsetof(laSplitNode, Out[1]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out2","Out 2","Output 2","la_split_node_out_socket",0,0,0,offsetof(laSplitNode, Out[2]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out3","Out 3","Output 3","la_split_node_out_socket",0,0,0,offsetof(laSplitNode, Out[3]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out4","Out 4","Output 4","la_split_node_out_socket",0,0,0,offsetof(laSplitNode, Out[4]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out5","Out 5","Output 5","la_split_node_out_socket",0,0,0,offsetof(laSplitNode, Out[5]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out6","Out 6","Output 6","la_split_node_out_socket",0,0,0,offsetof(laSplitNode, Out[6]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out7","Out 7","Output 7","la_split_node_out_socket",0,0,0,offsetof(laSplitNode, Out[7]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddIntProperty(pc, "array_length", "Array Length", "Array length of data", 0, 0, 0, 0, 0, 0, 0, 0, offsetof(laSplitNode, ArrLen), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,LA_READ_ONLY);
+
+    pc=laAddPropertyContainer("la_split_node_out_socket", "Split Out", "One value from an array input",0,0,sizeof(laSplitNodeOutSocket),0,0,1|LA_PROP_OTHER_ALLOC);
+    laAddSubGroup(pc, "out", "Out","Output value","la_out_socket",0,0,0,offsetof(laSplitNodeOutSocket,Out),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+
+    pc=laAddPropertyContainer("la_switch_node", "Switch", "Switch node",0,laui_SwitchNode,sizeof(laSwitchNode),0,0,1);
+    LA_PC_IDN_SWITCH=pc;
+    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,"out", "Out","Output value","la_out_socket",0,0,0,offsetof(laSwitchNode, Out),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"in0","In 0","Input 0","la_switch_node_in_socket",0,0,0,offsetof(laSwitchNode, In[0]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"in1","In 1","Input 1","la_switch_node_in_socket",0,0,0,offsetof(laSwitchNode, In[1]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"in2","In 2","Input 2","la_switch_node_in_socket",0,0,0,offsetof(laSwitchNode, In[2]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"in3","In 3","Input 3","la_switch_node_in_socket",0,0,0,offsetof(laSwitchNode, In[3]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"in4","In 4","Input 4","la_switch_node_in_socket",0,0,0,offsetof(laSwitchNode, In[4]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"in5","In 5","Input 5","la_switch_node_in_socket",0,0,0,offsetof(laSwitchNode, In[5]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"in6","In 6","Input 6","la_switch_node_in_socket",0,0,0,offsetof(laSwitchNode, In[6]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"in7","In 7","Input 7","la_switch_node_in_socket",0,0,0,offsetof(laSwitchNode, In[7]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddIntProperty(pc, "switch", "Switch", "Switch which input to use", 0, 0, 0, 0, 0, 0, 0, 0, offsetof(laSwitchNode, Switch), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+    laAddSubGroup(pc,"switch_in", "Switch In","Switch control","la_in_socket",0,0,0,offsetof(laSwitchNode,SwitchIn),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+
+    pc=laAddPropertyContainer("la_switch_node_in_socket", "Switch In", "Input of many values",0,0,sizeof(laSwitchNodeInSocket),0,0,1|LA_PROP_OTHER_ALLOC);
+    laAddSubGroup(pc, "in", "In","Input value","la_in_socket",0,0,0,offsetof(laSwitchNodeInSocket,In),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+
+    pc=laAddPropertyContainer("la_combine_node", "Combine", "Combine node",0,laui_CombineNode,sizeof(laCombineNode),0,0,1);
+    LA_PC_IDN_COMBINE=pc;
+    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,"out", "Out","Output value","la_out_socket",0,0,0,offsetof(laCombineNode, Out),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"out_int", "Out Int","Output value in int format","la_out_socket",0,0,0,offsetof(laCombineNode, OutInt),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"out_enum", "Out Enum","Output value in Enum format","la_out_socket",0,0,0,offsetof(laCombineNode, OutEnum),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"in0","In 0","Input 0","la_switch_node_in_socket",0,0,0,offsetof(laCombineNode, In[0]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"in1","In 1","Input 1","la_switch_node_in_socket",0,0,0,offsetof(laCombineNode, In[1]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"in2","In 2","Input 2","la_switch_node_in_socket",0,0,0,offsetof(laCombineNode, In[2]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"in3","In 3","Input 3","la_switch_node_in_socket",0,0,0,offsetof(laCombineNode, In[3]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"in4","In 4","Input 4","la_switch_node_in_socket",0,0,0,offsetof(laCombineNode, In[4]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"in5","In 5","Input 5","la_switch_node_in_socket",0,0,0,offsetof(laCombineNode, In[5]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"in6","In 6","Input 6","la_switch_node_in_socket",0,0,0,offsetof(laCombineNode, In[6]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"in7","In 7","Input 7","la_switch_node_in_socket",0,0,0,offsetof(laCombineNode, In[7]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+
+    pc=laAddPropertyContainer("la_values_node", "Values", "Values node",0,laui_ValuesNode,sizeof(laValuesNode),0,0,1);
+    LA_PC_IDN_VALUES=pc;
+    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,"out0","Out 0","Output 0","la_split_node_out_socket",0,0,0,offsetof(laValuesNode, Out[0]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out1","Out 1","Output 1","la_split_node_out_socket",0,0,0,offsetof(laValuesNode, Out[1]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out2","Out 2","Output 2","la_split_node_out_socket",0,0,0,offsetof(laValuesNode, Out[2]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out3","Out 3","Output 3","la_split_node_out_socket",0,0,0,offsetof(laValuesNode, Out[3]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out4","Out 4","Output 4","la_split_node_out_socket",0,0,0,offsetof(laValuesNode, Out[4]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out5","Out 5","Output 5","la_split_node_out_socket",0,0,0,offsetof(laValuesNode, Out[5]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out6","Out 6","Output 6","la_split_node_out_socket",0,0,0,offsetof(laValuesNode, Out[6]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out7","Out 7","Output 7","la_split_node_out_socket",0,0,0,offsetof(laValuesNode, Out[7]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    p=laAddEnumProperty(pc,"mode0","Mode 0","Mode 0",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, Modes[0]),0,0,0,0,0,0,0,0,0,0); la_AddValuesNodeEnum(p);
+    p=laAddEnumProperty(pc,"mode1","Mode 1","Mode 1",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, Modes[1]),0,0,0,0,0,0,0,0,0,0); la_AddValuesNodeEnum(p);
+    p=laAddEnumProperty(pc,"mode2","Mode 2","Mode 2",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, Modes[2]),0,0,0,0,0,0,0,0,0,0); la_AddValuesNodeEnum(p);
+    p=laAddEnumProperty(pc,"mode3","Mode 3","Mode 3",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, Modes[3]),0,0,0,0,0,0,0,0,0,0); la_AddValuesNodeEnum(p);
+    p=laAddEnumProperty(pc,"mode4","Mode 4","Mode 4",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, Modes[4]),0,0,0,0,0,0,0,0,0,0); la_AddValuesNodeEnum(p);
+    p=laAddEnumProperty(pc,"mode5","Mode 5","Mode 5",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, Modes[5]),0,0,0,0,0,0,0,0,0,0); la_AddValuesNodeEnum(p);
+    p=laAddEnumProperty(pc,"mode6","Mode 6","Mode 6",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, Modes[6]),0,0,0,0,0,0,0,0,0,0); la_AddValuesNodeEnum(p);
+    p=laAddEnumProperty(pc,"mode7","Mode 7","Mode 7",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, Modes[7]),0,0,0,0,0,0,0,0,0,0); la_AddValuesNodeEnum(p);
+    laAddIntProperty(pc, "valuei0", "Value", "Int value 0", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, ValuesI[0]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddIntProperty(pc, "valuei1", "Value", "Int value 1", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, ValuesI[1]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddIntProperty(pc, "valuei2", "Value", "Int value 2", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, ValuesI[2]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddIntProperty(pc, "valuei3", "Value", "Int value 3", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, ValuesI[3]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddIntProperty(pc, "valuei4", "Value", "Int value 4", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, ValuesI[4]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddIntProperty(pc, "valuei5", "Value", "Int value 5", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, ValuesI[5]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddIntProperty(pc, "valuei6", "Value", "Int value 6", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, ValuesI[6]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddIntProperty(pc, "valuei7", "Value", "Int value 7", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, ValuesI[7]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddFloatProperty(pc, "value0", "Value", "Float value 0", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, Values[0]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddFloatProperty(pc, "value1", "Value", "Float value 1", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, Values[1]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddFloatProperty(pc, "value2", "Value", "Float value 2", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, Values[2]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddFloatProperty(pc, "value3", "Value", "Float value 3", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, Values[3]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddFloatProperty(pc, "value4", "Value", "Float value 4", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, Values[4]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddFloatProperty(pc, "value5", "Value", "Float value 5", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, Values[5]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddFloatProperty(pc, "value6", "Value", "Float value 6", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, Values[6]),0,0,0,0,0,0,0,0,0,0,0);
+    laAddFloatProperty(pc, "value7", "Value", "Float value 7", 0,0,0,0,0,0,0,0,offsetof(laValuesNode, Values[7]),0,0,0,0,0,0,0,0,0,0,0);
+    p=laAddEnumProperty(pc,"valuee0","SW","Enum Value 0",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, ValuesE[0]),0,0,0,0,0,0,0,0,0,0);la_AddValuesNodeEnumValue(p);
+    p=laAddEnumProperty(pc,"valuee1","SW","Enum Value 1",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, ValuesE[1]),0,0,0,0,0,0,0,0,0,0);la_AddValuesNodeEnumValue(p);
+    p=laAddEnumProperty(pc,"valuee2","SW","Enum Value 2",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, ValuesE[2]),0,0,0,0,0,0,0,0,0,0);la_AddValuesNodeEnumValue(p);
+    p=laAddEnumProperty(pc,"valuee3","SW","Enum Value 3",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, ValuesE[3]),0,0,0,0,0,0,0,0,0,0);la_AddValuesNodeEnumValue(p);
+    p=laAddEnumProperty(pc,"valuee4","SW","Enum Value 4",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, ValuesE[4]),0,0,0,0,0,0,0,0,0,0);la_AddValuesNodeEnumValue(p);
+    p=laAddEnumProperty(pc,"valuee5","SW","Enum Value 5",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, ValuesE[5]),0,0,0,0,0,0,0,0,0,0);la_AddValuesNodeEnumValue(p);
+    p=laAddEnumProperty(pc,"valuee6","SW","Enum Value 6",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, ValuesE[6]),0,0,0,0,0,0,0,0,0,0);la_AddValuesNodeEnumValue(p);
+    p=laAddEnumProperty(pc,"valuee7","SW","Enum Value 7",LA_WIDGET_ENUM_CYCLE,0,0,0,0,offsetof(laValuesNode, ValuesE[7]),0,0,0,0,0,0,0,0,0,0);la_AddValuesNodeEnumValue(p);
+
+    pc=laAddPropertyContainer("la_matrix_node", "Matrix", "Matrix node",0,laui_MatrixNode,sizeof(laMatrixNode),0,0,1);
+    LA_PC_IDN_MATRIX=pc;
+    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_l", "L","Left input","la_in_socket",0,0,0,offsetof(laMatrixNode, InL),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"in_r", "R","Right input","la_in_socket",0,0,0,offsetof(laMatrixNode, InR),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"out", "Out","Output value","la_out_socket",0,0,0,offsetof(laMatrixNode, Out),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    p=laAddEnumProperty(pc,"operation", "Operation", "Operation", 0,0,0,0,0,offsetof(laMatrixNode,Operation),0,0,0,0,0,0,0,0,0,0);
+    laAddEnumItemAs(p,"MUL", "Multiply", "L x R", LA_MATRIX_NODE_OP_MUL, 0);
+    laAddEnumItemAs(p,"INV", "Invert", "Invert L or R", LA_MATRIX_NODE_OP_INV, 0);
+
+    LA_IDN_REGISTER(LA_IDN_CONTROLLER,LA_PC_IDN_CONTROLLER, IDN_ControllerInit, IDN_ControllerDestroy, IDN_ControllerVisit, IDN_ControllerEval, laInputControllerNode);
+    LA_IDN_REGISTER(LA_IDN_VISUALIZER,LA_PC_IDN_VISUALIZER, IDN_InputVisualizeInit, IDN_InputVisualizeDestroy, IDN_InputVisualizeVisit, IDN_InputVisualizerEval, laInputVisualizerNode);
+    LA_IDN_REGISTER(LA_IDN_SPLIT,LA_PC_IDN_SPLIT, IDN_SplitInit, IDN_SplitDestroy, IDN_SplitVisit, IDN_SplitEval, laSplitNode);
+    LA_IDN_REGISTER(LA_IDN_SWITCH,LA_PC_IDN_SWITCH, IDN_SwitchInit, IDN_SwitchDestroy, IDN_SwitchVisit, IDN_SwitchEval, laSwitchNode);
+    LA_IDN_REGISTER(LA_IDN_COMBINE,LA_PC_IDN_COMBINE, IDN_CombineInit, IDN_CombineDestroy, IDN_CombineVisit, IDN_CombineEval, laCombineNode);
+    LA_IDN_REGISTER(LA_IDN_VALUES,LA_PC_IDN_VALUES, IDN_ValuesInit, IDN_ValuesDestroy, IDN_ValuesVisit, IDN_ValuesEval, laValuesNode);
+    LA_IDN_REGISTER(LA_IDN_MATRIX,LA_PC_IDN_MATRIX, IDN_MatrixInit, IDN_MatrixDestroy, IDN_MatrixVisit, IDN_MatrixEval, laMatrixNode);
+
+}
+
+
+
+void laSetExtraNodeFunctions(laUiDefineFunc AddInputNodes, laUiDefineFunc AddDriverNodes, laGetBaseNodeTypeF GetInputNodeType, laGetBaseNodeTypeF GetDriverNodeType){
+    MAIN.ExtraAddInputNodes=AddInputNodes; MAIN.ExtraAddDriverNodes=AddDriverNodes;
+    MAIN.ExtraGetInputNodeType=GetInputNodeType; MAIN.ExtraGetDriverNodeType=GetDriverNodeType;
+}
+
+void laMappingRequestRebuild(){ MAIN.MappingNeedRebuild=1; }
+void laMappingRequestEval(){ MAIN.MappingNeedEval=1; }
+
+int la_RunInputMapping(){
+    MAIN.MappingNeedEval = 0;
+    for(laListItemPointer*lip=MAIN.InputMappingEval.pFirst;lip;lip=lip->pNext){
+        laBaseNode* n=lip->p; n->Type->Eval(n);
+    }
+    return 1;
+}
+int la_RebuildInputMapping(){
+    MAIN.MappingNeedRebuild = 0;
+    while(lstPopPointer(&MAIN.InputMappingEval));
+    laListHandle pending={0};
+    for(laNodeRack* ir=MAIN.InputMappingRacks.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,&MAIN.InputMappingEval); if(result==LA_DAG_FLAG_ERR){ while(lstPopPointer(&pending)); break; }
+    }
+    if(result==LA_DAG_FLAG_ERR){ while(lstPopPointer(&MAIN.InputMappingEval)); return LA_DAG_FLAG_ERR; }
+    return LA_DAG_FLAG_PERM;
+}

+ 14 - 2
source/lagui/resources/la_properties.c

@@ -478,6 +478,10 @@ void laread_ConditionNodeType(laUiConditionNode *ucn, int Type){
     ucn->Type = Type;
 }
 
+void* laget_FirstDriverPage(void* unused, void* unused2){
+    return MAIN.DriverPages.pFirst;
+}
+
 void *tnsget_TnsMain(void *unused){
     return T;
 }
@@ -886,16 +890,24 @@ void la_RegisterInternalProps(){
             laSubGroupDetachable(sp, laget_DetachedControllerFirst, laget_ListNext);
             LA_PROP_CONTROLLER=sp;
             
-            laAddSubGroup(p, "input_racks", "Input Racks", "Input Mapping Racks","la_input_rack",0,0,0,-1,0,0,0,0,0,0,offsetof(LA,InputMappingRacks),0);
+            laAddSubGroup(p, "input_racks", "Input Racks", "Input Mapping Racks","la_node_rack",0,0,0,-1,0,0,0,0,0,0,offsetof(LA,InputMappingRacks),0);
             
+            sp=laAddSubGroup(p, "driver_pages", "Driver Pages", "Pages of driver nodes","la_driver_page",0,0,0,offsetof(LA,CurrentDriverPage),0,0,0,0,0,0,offsetof(LA,DriverPages),0);
+            laSubGroupDetachable(sp, laget_FirstDriverPage, laget_ListNext);
+
             laAddStringProperty(p, "identifier", "Identifier", "Identifier", 0, 0, 0, 0, 0, 0, 0, laget_MainIdentifier, 0, 0, LA_AS_IDENTIFIER|LA_READ_ONLY);
             laAddSubGroup(p, "test_ucn", "TEST UCN", "---", "udf_content_node",0, 0, 0, offsetof(LA, TEST_Link), 0, 0, 0, 0, 0, 0, 0, 0);
             
             laAddIntProperty(p, "example_int", "Example Integer", "Example integer", 0, 0, 0, 100, 0, 1, 50, 0, offsetof(LA, example_int), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
             laAddStringProperty(p, "example_string", "Example String", "Example string", 0, 0, 0, 0, 1, offsetof(LA,example_string), 0, 0, 0, 0, 0);
+        }
 
-            //_LA_PROP_NODE_GRAPH = laAddSubGroup(p, "Node Graphs", "All node graphs in manage", "node_item",0, 0, 0, 0, 0, 0, laget_List2Next, 0, 0, 0, 0, 0, offsetof(LA, NodeGraphs), 0);
+        p = laAddPropertyContainer("la_driver_page", "Driver Page", "A page of driver nodes", 0, laui_IdentifierOnly, sizeof(laDriverPage), 0, 0, 1);{
+            laAddStringProperty(p, "name", "Name", "Name of the page", 0, 0, 0, 0, 1, offsetof(laDriverPage, Name), 0, 0, 0, 0, LA_AS_IDENTIFIER);
+            laAddSubGroup(p, "racks", "Racks", "Racks for nodes","la_node_rack",0,0,0,-1,0,0,0,0,0,0,offsetof(laDriverPage,Racks),0);
+            laAddOperatorProperty(p,"add_rack","Add Rack", "Add a rack into the page", "LA_add_driver_rack", '+', 0);
         }
+
         //p = laAddPropertyContainer("udf_fail_node", "UDF Failed Node", "Single Wild Data Block Reference", laui_UDFFailNodeItem, sizeof(laUDFFailNode), 0, 0, 0, 0, 0); {
         //	laAddSubGroup(p, "instance", "Instance", "Actual Data Block Instance", "property_trash_item",0, 0, 0 Item.p), 0, 0, 0, 0, 0, 0, 0, 0,
         //		0, 0,

+ 41 - 3
source/lagui/resources/la_templates.c

@@ -1414,22 +1414,59 @@ void laui_GameController(laUiList *uil, laPropPack *This, laPropPack *Extra, laC
 void lauidetached_GameController(laPanel* p){
     la_MakeDetachedProp(p, "la.controllers", "controllers");
 }
-void laui_InputMapperRack(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+void laui_NodeRack(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laNodeRack* r=This->EndInstance;
     laColumn* c=laFirstColumn(uil),*cl, *cr;
     laShowItem(uil,c,This,"name")->Flags|=LA_UI_FLAGS_NO_DECAL|LA_TEXT_ALIGN_CENTER;
     laShowItem(uil,c,This,"nodes")->Flags|=LA_UI_FLAGS_NO_DECAL;
-    laShowItem(uil,c,This,"add_node");
+    if(r->RackType==LA_RACK_TYPE_INPUT){
+        laShowItem(uil,c,This,"add_node_input");
+    }else{
+        laShowItem(uil,c,This,"add_node_driver");
+    }
 }
 void laui_InputMapper(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
     laColumn* c=laFirstColumn(uil),*cl, *cr;
 
     laUiItem* g=laMakeGroup(uil,c,"Racks",0);{ g->Flags|=/*LA_UI_FLAGS_NO_GAP|LA_UI_FLAGS_NO_DECAL|*/LA_UI_FLAGS_NODE_CONTAINER;
         laUiList* gu=g->Page; laColumn* gc=laFirstColumn(gu); gu->AllowScale=1; gu->HeightCoeff=-3; g->State=LA_UI_ACTIVE;
-        laUiItem* hui=laShowItemFull(gu,gc,0,"la.input_racks",0,0,laui_InputMapperRack,0); hui->Expand=15; hui->Flags|=LA_UI_FLAGS_NO_DECAL;
+        laUiItem* hui=laShowItemFull(gu,gc,0,"la.input_racks",0,0,laui_NodeRack,0); hui->Expand=15; hui->Flags|=LA_UI_FLAGS_NO_DECAL;
     }
     laShowItem(uil,c,0,"LA_add_input_mapper_rack");
 }
 
+void lauidetached_Drivers(laPanel* p){
+    la_MakeDetachedProp(p, "la.driver_pages", "page");
+}
+
+void laui_DriverPage(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil);
+    laUiItem* g=laMakeGroup(uil,c,"Racks",0);{ g->Flags|=/*LA_UI_FLAGS_NO_GAP|LA_UI_FLAGS_NO_DECAL|*/LA_UI_FLAGS_NODE_CONTAINER;
+        laUiList* gu=g->Page; laColumn* gc=laFirstColumn(gu); gu->AllowScale=1; gu->HeightCoeff=-3; g->State=LA_UI_ACTIVE;
+        laUiItem* hui=laShowItemFull(gu,gc,This,"racks",0,0,laui_NodeRack,0); hui->Expand=15; hui->Flags|=LA_UI_FLAGS_NO_DECAL;
+    }
+    laShowItem(uil,c,This,"add_rack");
+}
+void laui_Drivers(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil),*cl, *cr;
+    laSplitColumn(uil,c,0.6); cl=laLeftColumn(c,0); cr=laRightColumn(c,0);
+    
+    laUiItem* b=laBeginRow(uil,cl,0,0);
+    laUiItem* b2=laOnConditionThat(uil,cl,laPropExpression(0,"la.driver_pages"));{
+        laUiItem* b3=laOnConditionThat(uil,cl,laPropExpression(Extra,"page"));{
+            laShowItem(uil,cr,Extra,"page.name")->Expand=1;
+            laShowItem(uil,c,Extra,"LA_driver_rebuild")->Flags|=LA_UI_FLAGS_ICON;
+        }laEndCondition(uil,b3);
+    }laEndCondition(uil,b2);
+    laShowItem(uil,cl,0,"LA_add_driver_page")->Flags|=LA_UI_FLAGS_ICON;
+    laEndRow(uil,b);
+    b2=laOnConditionThat(uil,cr,laPropExpression(0,"la.driver_pages"));{
+        laShowItemFull(uil,cr,Extra,"page",LA_WIDGET_COLLECTION_SELECTOR,0,0,0);
+    }laEndCondition(uil,b2);
+    
+    laShowItemFull(uil,c,Extra,"page",LA_WIDGET_COLLECTION_SINGLE,0,laui_DriverPage,0);
+}
+
 
 void la_RegisterBuiltinTemplates(){
     //laRegisterUiTemplate("LAUI_default_subwindow_menubar", "Menu", laui_DefaultSubWindowMenuBarActual, 0);
@@ -1441,4 +1478,5 @@ void la_RegisterBuiltinTemplates(){
     laRegisterUiTemplate("LAUI_terminal", "Terminal", laui_terminal, 0, 0);
     laRegisterUiTemplate("LAUI_controllers", "Controllers", laui_GameController, lauidetached_GameController, 0);
     laRegisterUiTemplate("LAUI_input_mapper","Input Mapper",laui_InputMapper,0,0);
+    laRegisterUiTemplate("LAUI_drivers","Drivers",laui_Drivers,lauidetached_Drivers,0);
 }

+ 253 - 0
source/lagui/resources/la_tns_drivers.c

@@ -0,0 +1,253 @@
+#include "../la_5.h"
+
+extern LA MAIN;
+extern struct _tnsMain *T;
+
+laBaseNodeType TNS_IDN_TRANSFORM;
+laBaseNodeType TNS_IDN_MAKE_TRANSFORM;
+
+laPropContainer* TNS_PC_IDN_TRANSFORM;
+laPropContainer* TNS_PC_IDN_MAKE_TRANSFORM;
+
+void IDN_TransformInit(tnsTransformNode* n){
+    n->Mat=laCreateInSocket("mat",0); strSafeSet(&n->Base.Name,"Transform");
+}
+void IDN_TransformDestroy(tnsTransformNode* n){
+    laDestroyInSocket(n->Mat); strSafeDestroy(&n->Base.Name);
+}
+int IDN_TransformVisit(tnsTransformNode* n, laListHandle* l){
+    if(LA_SRC_AND_PARENT(n->Mat)){ laBaseNode*bn=n->Mat->Source->Parent; LA_VISIT_NODE(bn); }
+    n->Base.Eval=LA_DAG_FLAG_PERM;
+    lstAppendPointer(l, n);
+    return LA_DAG_FLAG_PERM;
+}
+int IDN_TransformEval(tnsTransformNode* n){
+    if(!n->Target) return 0;
+    if((!n->Mat->Source) || (n->Mat->Source->DataType!=(LA_PROP_FLOAT|LA_PROP_ARRAY)) || (n->Mat->Source->ArrLen!=16)){
+        tnsLoadIdentity44d(&n->Target->DeltaTransform); tnsSelfMatrixChanged(n->Target, 1);
+    }else{
+        memcpy(n->Target->DeltaTransform, n->Mat->Source->Data, sizeof(tnsMatrix44d)); tnsSelfMatrixChanged(n->Target, 1);
+    }
+    laNotifyInstanceUsers(n->Target);
+    return 1;
+}
+void tnsui_TransformNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); tnsTransformNode*n=This->EndInstance;
+    laColumn* cl,*cr; laSplitColumn(uil,c,0.3); cl=laLeftColumn(c,0); cr=laRightColumn(c,0);
+
+    LA_BASE_NODE_HEADER(uil,c,This);
+
+    laUiItem* b=laBeginRow(uil,cl,0,0);
+    laShowNodeSocket(uil,cl,This,"mat",0);
+    laEndRow(uil,b);
+
+    laShowItem(uil,cr,This,"target");
+}
+
+void IDN_MakeTransformInit(tnsMakeTransformNode* n){
+    n->Out=laCreateOutSocket(n,"mat",0); strSafeSet(&n->Base.Name,"Make Transform");
+    n->Loc=laCreateInSocket("loc",0); 
+    n->Rot=laCreateInSocket("rot",0);  n->Angle=laCreateInSocket("angle",0); n->UseRot[2]=1;
+    n->Sca=laCreateInSocket("scale",0);  n->UseSca=1;
+}
+void IDN_MakeTransformDestroy(tnsMakeTransformNode* n){
+    laDestroyOutSocket(n->Out); strSafeDestroy(&n->Base.Name);
+    laDestroyInSocket(n->Loc);
+    laDestroyInSocket(n->Rot); laDestroyInSocket(n->Angle);
+    laDestroyInSocket(n->Sca);
+}
+int IDN_MakeTransformVisit(tnsMakeTransformNode* n, laListHandle* l){
+    LA_GUARD_THIS_NODE(n);
+    if(LA_SRC_AND_PARENT(n->Loc)){ laBaseNode*bn=n->Loc->Source->Parent; LA_VISIT_NODE(bn); }
+    if(LA_SRC_AND_PARENT(n->Rot)){ laBaseNode*bn=n->Rot->Source->Parent; LA_VISIT_NODE(bn); }
+    if(LA_SRC_AND_PARENT(n->Sca)){ laBaseNode*bn=n->Sca->Source->Parent; LA_VISIT_NODE(bn); }
+    if(LA_SRC_AND_PARENT(n->Angle)){ laBaseNode*bn=n->Angle->Source->Parent;  LA_VISIT_NODE(bn); }
+    n->Base.Eval=LA_DAG_FLAG_PERM; lstAppendPointer(l, n);
+    return LA_DAG_FLAG_PERM;
+}
+int IDN_MakeTransformEval(tnsMakeTransformNode* n){
+    tnsMatrix44d mm,mt,mr,ms; real* UseT,*UseR,*UseA,*UseS; tnsVector3d nr;
+    if(LA_SRC_AND_PARENT(n->Loc) && n->Loc->Source->ArrLen>=3 && n->Loc->Source->DataType==LA_PROP_FLOAT|LA_PROP_ARRAY){ UseT=n->Loc->Source->Data; }else{ UseT=n->UseLoc; }
+    if(LA_SRC_AND_PARENT(n->Rot) && n->Rot->Source->ArrLen>=3 && n->Rot->Source->DataType==LA_PROP_FLOAT|LA_PROP_ARRAY){ UseR=n->Rot->Source->Data; }else{ UseR=n->UseRot; }
+    if(LA_SRC_AND_PARENT(n->Angle) && n->Angle->Source->DataType&LA_PROP_FLOAT){ UseA=n->Angle->Source->Data; }else{ UseA=&n->UseAngle; }
+    if(LA_SRC_AND_PARENT(n->Sca) && n->Sca->Source->DataType&LA_PROP_FLOAT){ UseS=n->Sca->Source->Data; }else{ UseS=&n->UseSca; }
+    tnsMakeTranslationMatrix44d(mt,LA_COLOR3(UseT));
+    tnsNormalize3d(nr, UseR); tnsMakeRotationMatrix44d(mr,*UseA, LA_COLOR3(nr));
+    tnsMakeScaleMatrix44d(ms,*UseS,*UseS,*UseS);
+    tnsMultiply44d(mm,mt,mr); tnsMultiply44d(n->Mat,mm,ms);
+    n->Out->ArrLen=16; n->Out->Data=n->Mat; n->Out->DataType=LA_PROP_FLOAT|LA_PROP_ARRAY;
+    return 1;
+}
+void tnsui_MakeTransformNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); tnsMakeTransformNode*n=This->EndInstance;
+    laColumn* cl,*cr; laSplitColumn(uil,c,0.3); cl=laLeftColumn(c,0); cr=laRightColumn(c,0);
+    laUiItem* b2, *rui;
+
+    LA_BASE_NODE_HEADER(uil,c,This);
+
+    laUiItem* b=laBeginRow(uil,c,0,0);
+    laShowNodeSocket(uil,c,This,"loc",0); laShowLabel(uil,c,"Loc",0,0); b2=laOnConditionThat(uil,c,laNot(laPropExpression(This,"loc.source")));{
+        laShowItem(uil,c,This,"use_loc")->Expand=1;
+    }laEndCondition(uil,b2);
+    laEndRow(uil,b);
+
+    b=laBeginRow(uil,c,0,0);
+    laShowNodeSocket(uil,c,This,"rot",0); laShowLabel(uil,c,"Rot",0,0); b2=laOnConditionThat(uil,c,laNot(laPropExpression(This,"rot.source")));{
+        laShowItem(uil,c,This,"use_rot")->Expand=1;
+    }laEndCondition(uil,b2);
+    laEndRow(uil,b);
+
+    b=laBeginRow(uil,c,0,0);
+    laShowNodeSocket(uil,c,This,"angle",0); laShowLabel(uil,c,"Angle",0,0); b2=laOnConditionThat(uil,c,laNot(laPropExpression(This,"angle.source")));{
+        laShowItem(uil,c,This,"use_angle")->Expand=1;
+    }laEndCondition(uil,b2);
+    laEndRow(uil,b);
+
+    b=laBeginRow(uil,c,0,0);
+    laShowNodeSocket(uil,c,This,"sca",0); laShowLabel(uil,c,"Scale",0,0); b2=laOnConditionThat(uil,c,laNot(laPropExpression(This,"sca.source")));{
+        laShowItem(uil,c,This,"use_sca")->Expand=1;
+    }laEndCondition(uil,b2);
+    laEndRow(uil,b);
+
+    b=laBeginRow(uil,c,0,0);
+    laShowSeparator(uil,c)->Expand=1;;
+    laShowNodeSocket(uil,c,This,"out",0);
+    laEndRow(uil,b);
+}
+
+
+int OPINV_AddDriverPage(laOperator* a, laEvent *e){
+    laDriverPage* dp=memAcquire(sizeof(laDriverPage));
+    strSafeSet(&dp->Name,"New Page");
+    lstAppendItem(&MAIN.DriverPages, dp); MAIN.CurrentDriverPage=dp;
+    laNotifyUsers("la.driver_pages");
+    return LA_FINISHED;
+}
+int OPINV_AddDriverRack(laOperator* a, laEvent *e){
+    laDriverPage* dp=a->This?a->This->EndInstance:0; if(!dp) return LA_FINISHED;
+    laNodeRack* ir=memAcquire(sizeof(laNodeRack)); ir->RackType=LA_RACK_TYPE_DRIVER;
+    strSafeSet(&ir->Name,"New Rack");
+    lstAppendItem(&dp->Racks, ir);
+    laNotifyUsers("la.driver_pages");
+    return LA_FINISHED;
+}
+
+laBaseNode* la_CreateDriverNode(laNodeRack* ir, laBaseNodeType* NodeType){
+    laBaseNode* bn=memAcquire(NodeType->NodeSize);
+    bn->Type=NodeType; NodeType->Init(bn); lstAppendItem(&ir->Nodes, bn); bn->InRack=ir;
+    laNotifyUsers("la.driver_pages");
+    return bn;
+}
+void la_DestroyDriverNode(laBaseNode* bn){
+    lstRemoveItem(bn->InRack, bn); bn->Type->Destroy(bn);
+    laNotifyUsers("la.driver_pages");
+    memFree(bn);
+}
+int OPINV_AddDriverNode(laOperator* a, laEvent *e){
+    laNodeRack* ir=a->This?a->This->EndInstance:0; if(!ir) return LA_CANCELED;
+    laBaseNodeType* bnt=0;
+
+    char* type=strGetArgumentString(a->ExtraInstructionsP,"type");
+    if(!type){ laEnableOperatorPanel(a,a->This,e->x,e->y,200,200,0,0,0,0,0,0,0,0,e); return LA_RUNNING; }
+    elif(strSame(type, "TRANSFORM")){ la_CreateDriverNode(ir, &TNS_IDN_TRANSFORM); }
+    elif(strSame(type, "MAKE_TRANSFORM")){ la_CreateDriverNode(ir, &TNS_IDN_MAKE_TRANSFORM); }
+    elif(strSame(type, "VISUALIZER")){ la_CreateDriverNode(ir, &LA_IDN_VISUALIZER); }
+    elif(strSame(type, "SPLIT")){ la_CreateDriverNode(ir, &LA_IDN_SPLIT); }
+    elif(strSame(type, "SWITCH")){ la_CreateDriverNode(ir, &LA_IDN_SWITCH); }
+    elif(strSame(type, "COMBINE")){ la_CreateDriverNode(ir, &LA_IDN_COMBINE); }
+    elif(strSame(type, "VALUES")){ la_CreateDriverNode(ir, &LA_IDN_VALUES); }
+    elif(strSame(type, "MATRIX")){ la_CreateDriverNode(ir, &LA_IDN_MATRIX); }
+    elif(MAIN.ExtraGetDriverNodeType && (bnt=MAIN.ExtraGetDriverNodeType(type))){ la_CreateDriverNode(ir, bnt); }
+
+    return LA_FINISHED;
+}
+void laui_AddDriverNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil);
+    if(MAIN.ExtraAddDriverNodes){
+        MAIN.ExtraAddDriverNodes(uil,This,Extra,0,0);
+    }
+    laShowLabel(uil,c,"Operations:",0,0);
+    laShowItemFull(uil,c,This,"add_node_driver",0,"type=SPLIT;text=Split",0,0);
+    laShowItemFull(uil,c,This,"add_node_driver",0,"type=SWITCH;text=Switch",0,0);
+    laShowItemFull(uil,c,This,"add_node_driver",0,"type=COMBINE;text=Combine",0,0);
+    laShowItemFull(uil,c,This,"add_node_driver",0,"type=VALUES;text=Values",0,0);
+    laShowLabel(uil,c,"Visualizations:",0,0);
+    laShowItemFull(uil,c,This,"add_node_driver",0,"type=VISUALIZER;text=Visualizer",0,0);
+    laShowLabel(uil,c,"Math:",0,0);
+    laShowItemFull(uil,c,This,"add_node_driver",0,"type=MAKE_TRANSFORM;text=Make Transform",0,0);
+    laShowItemFull(uil,c,This,"add_node_driver",0,"type=MATRIX;text=Matrix",0,0);
+    laShowLabel(uil,c,"Drivers:",0,0);
+    laShowItemFull(uil,c,This,"add_node_driver",0,"type=TRANSFORM;text=Transform Objects",0,0);
+}
+
+int OPINV_RebuildDrivers(laOperator* a, laEvent *e){
+    laDriverRequestRebuild();
+    return LA_FINISHED;
+}
+
+
+tnsObject* tnsget_FirstObject(void* unused, void* unused2){
+    return T->World.AllObjects.pFirst;
+}
+
+void tns_RegisterNodes(){
+    laPropContainer *pc; laProp *p;
+    laOperatorType *at;
+    laEnumProp *ep;
+
+    laCreateOperatorType("LA_add_driver_page", "New Page", "Add a driver page", 0,0,0,OPINV_AddDriverPage,0,'+',0);
+    laCreateOperatorType("LA_add_driver_rack", "Add Rack", "Add a rack for driver nodes", 0,0,0,OPINV_AddDriverRack,0,'+',0);
+    at=laCreateOperatorType("LA_add_driver_node", "Add Node", "Add a input mapper node",0,0,0,OPINV_AddDriverNode,OPMOD_FinishOnData,'+',0);
+    at->UiDefine=laui_AddDriverNode;
+    laCreateOperatorType("LA_driver_rebuild", "Rebuild Drivers", "Rebuild drivers for evaluation",0,0,0,OPINV_RebuildDrivers,0,L'⭮',0);
+
+    pc=laAddPropertyContainer("tns_transform_node", "Transform", "Transform objects",0,tnsui_TransformNode,sizeof(tnsTransformNode),0,0,1);
+    TNS_PC_IDN_TRANSFORM=pc;
+    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,"mat", "Mat","Input matrix","la_in_socket",0,0,0,offsetof(tnsTransformNode,Mat),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"target", "Target","Target object","tns_object",0,LA_WIDGET_COLLECTION_SELECTOR,laui_IdentifierOnly,offsetof(tnsTransformNode,Target),tnsget_FirstObject,0,laget_ListNext,0,0,0,0,LA_UDF_REFER);
+    
+    pc=laAddPropertyContainer("tns_make_transform_node", "Make Transform", "Make Transform matrix",0,tnsui_MakeTransformNode,sizeof(tnsMakeTransformNode),0,0,1);
+    TNS_PC_IDN_MAKE_TRANSFORM=pc;
+    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,"out", "Out","Output matrix","la_out_socket",0,0,0,offsetof(tnsMakeTransformNode,Out),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"loc", "Location","Location","la_in_socket",0,0,0,offsetof(tnsMakeTransformNode,Loc),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"rot", "Rotation","Rotation","la_in_socket",0,0,0,offsetof(tnsMakeTransformNode,Rot),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"sca", "Scale","Scale","la_in_socket",0,0,0,offsetof(tnsMakeTransformNode,Sca),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddSubGroup(pc,"angle", "Angle","Rotation Angle","la_in_socket",0,0,0,offsetof(tnsMakeTransformNode,Angle),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddFloatProperty(pc,"use_loc", "Location", "Use Location",0,"X,Y,Z",0,0,0,0.05,0,0,offsetof(tnsMakeTransformNode, UseLoc),0,0,3,0,0,0,0,0,0,0,0);
+    laAddFloatProperty(pc,"use_rot", "Rotation", "Use Rotation",0,"X,Y,Z",0,0,0,0.05,0,0,offsetof(tnsMakeTransformNode, UseRot),0,0,3,0,0,0,0,0,0,0,0);
+    laAddFloatProperty(pc,"use_sca", "Scale", "Use Scale",0,0,0,0,0,0.05,0,0,offsetof(tnsMakeTransformNode, UseSca),0,0,0,0,0,0,0,0,0,0,0);
+    laAddFloatProperty(pc,"use_angle", "Angle", "Use Angle",0,0,0,0,0,0.05,0,0,offsetof(tnsMakeTransformNode, UseAngle),0,0,0,0,0,0,0,0,0,0,0);
+    
+    LA_IDN_REGISTER(TNS_IDN_TRANSFORM,TNS_PC_IDN_TRANSFORM, IDN_TransformInit, IDN_TransformDestroy, IDN_TransformVisit, IDN_TransformEval, tnsTransformNode);
+    LA_IDN_REGISTER(TNS_IDN_MAKE_TRANSFORM,TNS_PC_IDN_MAKE_TRANSFORM, IDN_MakeTransformInit, IDN_MakeTransformDestroy, IDN_MakeTransformVisit, IDN_MakeTransformEval, tnsMakeTransformNode);
+}
+
+void laDriverRequestRebuild(){ MAIN.DriverNeedRebuild=1; }
+void laDriverRequestEval(){ MAIN.DriverNeedEval=1; }
+
+int la_RunDrivers(){
+    MAIN.MappingNeedEval = 0;
+    for(laListItemPointer*lip=MAIN.DriverEval.pFirst;lip;lip=lip->pNext){
+        laBaseNode* n=lip->p; n->Type->Eval(n);
+    }
+    return 1;
+}
+int la_RebuildDriverEval(){
+    MAIN.MappingNeedRebuild = 0;
+    while(lstPopPointer(&MAIN.DriverEval));
+    laListHandle pending={0};
+    for(laDriverPage* dp=MAIN.DriverPages.pFirst;dp;dp=dp->Item.pNext){
+        for(laNodeRack* ir=dp->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,&MAIN.DriverEval); if(result==LA_DAG_FLAG_ERR){ while(lstPopPointer(&pending)); break; }
+    }
+    if(result==LA_DAG_FLAG_ERR){ while(lstPopPointer(&MAIN.DriverEval)); return LA_DAG_FLAG_ERR; }
+    return LA_DAG_FLAG_PERM;
+}

+ 3 - 3
source/lagui/resources/la_widgets_viewers.c

@@ -1029,9 +1029,9 @@ void la_RegisterUiTypesViewerWidgets(){
     laAssignNewKey(km, 0, "M_rotate", LA_KM_SEL_UI_EXTRA, 0, LA_KEY_DOWN, 'r', 0);
     laAssignNewKey(km, 0, "M_make_parent", LA_KM_SEL_UI_EXTRA, LA_KEY_CTRL, LA_KEY_DOWN, 'p', 0);
     laAssignNewKey(km, 0, "M_unparent", LA_KM_SEL_UI_EXTRA, LA_KEY_ALT, LA_KEY_DOWN, 'p', 0);
-    laAssignNewKey(km, 0, "M_clear_transformations", 0, LA_KEY_ALT, LA_KEY_DOWN, 'g', "location=true;text=Clear Location;");
-    laAssignNewKey(km, 0, "M_clear_transformations", 0, LA_KEY_ALT, LA_KEY_DOWN, 'r', "rotation=true;text=Clear Rotation;");
-    laAssignNewKey(km, 0, "M_clear_transformations", 0, LA_KEY_ALT, LA_KEY_DOWN, 's', "scale=true;text=Clear Scale;");
+    laAssignNewKey(km, 0, "M_clear_transformations", LA_KM_SEL_UI_EXTRA, LA_KEY_ALT, LA_KEY_DOWN, 'g', "location=true;text=Clear Location;");
+    laAssignNewKey(km, 0, "M_clear_transformations", LA_KM_SEL_UI_EXTRA, LA_KEY_ALT, LA_KEY_DOWN, 'r', "rotation=true;text=Clear Rotation;");
+    laAssignNewKey(km, 0, "M_clear_transformations", LA_KM_SEL_UI_EXTRA, LA_KEY_ALT, LA_KEY_DOWN, 's', "scale=true;text=Clear Scale;");
     laAssignNewKey(km, 0, "M_extrude", LA_KM_SEL_UI_EXTRA, 0, LA_KEY_DOWN, 'e', 0);
     laAssignNewKey(km, 0, "M_extrude", LA_KM_SEL_UI_EXTRA, LA_KEY_SHIFT, LA_KEY_DOWN, 'd', "duplicate_only=true;text=Duplicate;");
     laAssignNewKey(km, 0, "M_delete", LA_KM_SEL_UI_EXTRA, 0, LA_KEY_DOWN, 'x', 0);