*/}}
Browse Source

Material system and a simple halftone shader.

YimingWu 8 months ago
parent
commit
0af919fe94
10 changed files with 339 additions and 126 deletions
  1. 2 1
      la_kernel.c
  2. 20 19
      la_tns.h
  3. 34 14
      la_tns_kernel.c
  4. 40 13
      la_tns_mesh.c
  5. 64 1
      resources/la_modelling.c
  6. 21 70
      resources/la_operators.c
  7. 69 1
      resources/la_properties.c
  8. 69 6
      resources/la_templates.c
  9. 15 0
      resources/la_tns_shaders.cpp
  10. 5 1
      resources/la_widgets.c

+ 2 - 1
la_kernel.c

@@ -4444,6 +4444,7 @@ int la_DoExpression(laUiConditionNode *Expression, int *IResult, real *FResult,
         default:
             return 0;
         }
+        break;
     case LA_CONDITION_INT:
         *IResult = Expression->IntValue;
         if (*IResult) return 1;
@@ -6623,7 +6624,7 @@ int laInvokePCreateThis(laOperator *From, laOperatorType *at, laEvent *e, laProp
     created->LastPs = memAcquireSimple(sizeof(laPropStep));
     created->Go = created->LastPs;
     created->LastPs->p = OrigionalThis->LastPs->p;
-    created->LastPs->UseInstance = FromInstance;
+    created->LastPs->UseInstance = OrigionalThis->LastPs->UseInstance;
     created->EndInstance = FromInstance;
     created->LastIndex = OrigionalThis->LastIndex;
 

+ 20 - 19
la_tns.h

@@ -493,6 +493,7 @@ STRUCTURE(tnsEvaluateData){
 
 NEED_STRUCTURE(laRackPageCollection);
 
+NEED_STRUCTURE(tnsMaterial);
 STRUCTURE(tnsObject){
     laListItem Item;
 
@@ -566,26 +567,11 @@ STRUCTURE(tnsMakeTransformNode){
 #define TNS_MATERIAL_DRAW_MODE_SOLID 0
 #define TNS_MATERIAL_DRAW_MODE_WIRE 1
 
-typedef struct _tnsMaterial tnsMaterial;
-struct _tnsMaterial
-{
+STRUCTURE(tnsMaterial){
     laListItem Item;
-
     laSafeString *Name;
-
-    int ID;
-
-    int DrawMode;
-
     real Color[4];
-    real LinearColor[4];
-    real SpectacularColor[4];
-    real ReflexThreshold;
-    real ReflexStrengh;
-    real ReflexSharpeness;
-
-    real LowBrightnessColor[4]; //Windows are lighten up in the evenings
-    u8bit HaloMode;
+    int Colorful;
 };
 
 #define TNS_CAMERA_PERSPECTIVE 0
@@ -668,6 +654,7 @@ STRUCTURE(tnsFace){
     int flags;
     tnsVector3f n;
     short looplen;
+    short mat;
 };
 
 STRUCTURE(tnsBatchCommand){
@@ -704,6 +691,8 @@ extern laPropContainer* TNS_PC_OBJECT_INSTANCER;
 extern laPropContainer* TNS_PC_OBJECT_CAMERA;
 extern laPropContainer* TNS_PC_OBJECT_LIGHT;
 extern laPropContainer* TNS_PC_OBJECT_MESH;
+extern laPropContainer* TNS_PC_MATERIAL;
+extern laPropContainer* TNS_PC_MATERIAL_SLOT;
 
 #define TNS_HINT_TRANSFORM (1<<1)
 #define TNS_HINT_GEOMETRY  (1<<0)
@@ -715,6 +704,13 @@ STRUCTURE(tnsMeshFaceStorage){
     int len; int arr;
 };
 
+STRUCTURE(tnsMaterialSlot){
+    laListItem Item;
+    tnsMaterial* Material;
+    tnsObject* Parent;
+    short Index;
+};
+
 STRUCTURE(tnsMeshObject){
     tnsObject Base;
 
@@ -735,6 +731,9 @@ STRUCTURE(tnsMeshObject){
     tnsBatch *Batch;
     tnsBatch *ExtraBatch;
 
+    tnsMaterialSlot* CurrentMaterial;
+    laListHandle Materials;
+
     int Mode;
 };
 
@@ -871,7 +870,7 @@ void tnsCommandUseUniformColor(tnsBatchCommand*c,real* color);
 void tnsCommandOverrideColorArray(tnsBatchCommand*c, int VertCount, int ColorDimension, float* colors);
 tnsBatchCommand *tnsCreateCommand(tnsBatch *b, const char* name, u32bit ElementCount, int Dimension, GLenum DrawAs, u32bit *Elements, int HiddenByDefault);
 void tnsDeleteBatch(tnsBatch *b);
-void tnsDrawBatch(tnsBatch* batch, const char* OverrideCommand, real* OverrideUniformColor, int OverrideAsArray);
+int tnsDrawBatch(tnsBatch* batch, const char* OverrideCommand, real* OverrideUniformColor, int OverrideAsArray);
 
 void tnsVertex3d(real x, real y, real z);
 void tnsVertex2d(real x, real y);
@@ -1168,8 +1167,10 @@ void tnsDrawScene(int W, int H, tnsObject *root);
 void tnsDrawWorld(int W, int H);
 
 tnsMaterial *tnsCreateMaterial(char *Name);
-tnsMaterial *tnsFindMaterialByIndex(int index);
 tnsMaterial *tnsFindMaterial(char *name);
+tnsMaterialSlot* tnsNewMaterialSlot(tnsMeshObject* mo);
+void tnsRemoveMaterialSlot(tnsMeshObject* mo, tnsMaterialSlot* ms);
+void tnsAssignMaterialSlot(tnsMeshObject* mo, tnsMaterialSlot* ms);
 
 void tnsClearAll();
 void tnsClearColorv(real *rgba);

+ 34 - 14
la_tns_kernel.c

@@ -1559,10 +1559,11 @@ void tnsDeleteBatch(tnsBatch *b){
 
     FreeMem(b);
 }
-void tnsDrawBatch(tnsBatch* batch, const char* OverrideCommand, real* OverrideUniformColor, int OverrideAsArray) {
+int tnsDrawBatch(tnsBatch* batch, const char* OverrideCommand, real* OverrideUniformColor, int OverrideAsArray) {
 	//int Mode = batch->DrawMode;
 	if (!batch) return;
     tnsShader* cs=T->BindedShader;
+    int Drawn=0;
 
 	//glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
 
@@ -1612,10 +1613,12 @@ void tnsDrawBatch(tnsBatch* batch, const char* OverrideCommand, real* OverrideUn
             glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bc->EBO);
             glDrawElements(bc->DrawAs, bc->ElementCount*bc->Dimension, GL_UNSIGNED_INT, 0);
         }else{ glDrawArrays(bc->DrawAs,0,bc->ElementCount); }
+        Drawn++;
         if(PointSizeChanged){ glPointSize(1); } if(LineWidthChanged){ glLineWidth(1); }
 	}
 
 	//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+    return Drawn;
 }
 
 //==========================*=======================[Texture]
@@ -3491,6 +3494,8 @@ void tnsDestroyObject(tnsObject *o){
         if(mo->f){ for(int i=0;i<mo->totf;i++){ free(mo->f[i].loop); mo->f[i].loop=0; } arrFree(&mo->f, &mo->maxf); }
         mo->totv=mo->tote=mo->totf=0;
         tnsInvalidateMeshBatch(o);
+        tnsMaterialSlot* ms;
+        while(ms=lstPopItem(&mo->Materials)){ memLeave(ms); } memAssignRef(mo,&mo->CurrentMaterial,0);
     }
 
     strSafeDestroy(&o->Name);
@@ -4076,27 +4081,42 @@ void tnsDrawCursor(tnsObject* root){
 
 tnsMaterial *tnsCreateMaterial(char *Name){
     tnsMaterial *m; if(!Name || !Name[0]) return 0;
-
     m = memAcquireHyper(sizeof(tnsMaterial));
-    strSafeSet(&m->Name, Name);
+    strSafeSet(&m->Name, Name); tnsVectorSet4(m->Color,0.8,0.8,0.8,1);
     lstAppendItem(&T->World->Materials, m);
     return m;
 }
-tnsMaterial *tnsFindMaterialByIndex(int index){
-    tnsMaterial *m; int i = 0;
-    for (m = T->World->Materials.pFirst; m; m = m->Item.pNext){
-        if (i == index){ m->ID = i; return m; } i++;
-    }
-    return 0;
-}
 tnsMaterial *tnsFindMaterial(char *name){
     tnsMaterial *m;
-    int i = 0;
-    for (m = T->World->Materials.pFirst; m; m = m->Item.pNext){
-        if (strSame(m->Name->Ptr, name)){ return m; }
-    }
+    for (m = T->World->Materials.pFirst; m; m = m->Item.pNext){ if(strSame(m->Name->Ptr, name)) return m; }
     return 0;
 }
+void tnsRemoveMaterial(tnsMaterial* mat){
+    lstRemoveItem(&T->World->Materials,mat); memLeave(mat);
+}
+tnsMaterialSlot* tnsNewMaterialSlot(tnsMeshObject* mo){
+    if(mo->Base.Type!=TNS_OBJECT_MESH) return 0; short nextid=0,found=1;
+    while(found){ found=0;
+        for(tnsMaterialSlot* msi=mo->Materials.pFirst;msi;msi=msi->Item.pNext){
+            if(nextid==msi->Index){ found=1; nextid++; continue; }
+        }
+    }
+    tnsMaterialSlot* ms=memAcquire(sizeof(tnsMaterialSlot)); lstAppendItem(&mo->Materials,ms);
+    ms->Index=nextid; ms->Parent=mo; memAssignRef(mo,&mo->CurrentMaterial,ms);
+    return ms;
+}
+void tnsRemoveMaterialSlot(tnsMeshObject* mo, tnsMaterialSlot* ms){
+    if(mo->Base.Type!=TNS_OBJECT_MESH) return;
+    memAssignRef(mo,&mo->CurrentMaterial,ms->Item.pNext?ms->Item.pNext:ms->Item.pPrev);
+    lstRemoveItem(&mo->Materials,ms); memLeave(ms);
+}
+void tnsAssignMaterialSlot(tnsMeshObject* mo, tnsMaterialSlot* ms){
+    if(mo->Mode==TNS_MESH_OBJECT_MODE){
+        for(int i=0;i<mo->totf;i++){ mo->f[i].mat=ms->Index; }
+    }elif(mo->Mode==TNS_MESH_EDIT_MODE){
+        for(tnsMFace* f=mo->mf.pFirst;f;f=f->Item.pNext){ if(f->flags&TNS_MESH_FLAG_SELECTED) f->mat=ms->Index; }
+    }
+}
 
 //==================================================================[Util]
 

+ 40 - 13
la_tns_mesh.c

@@ -125,8 +125,15 @@ tnsMeshObject* tnsDuplicateMeshObject(tnsMeshObject* from){
     arrInitLength(&to->e, to->tote, &to->maxe, sizeof(tnsEdge)); if(from->tote) memcpy(to->e,from->e,sizeof(tnsEdge)*from->tote);
     arrInitLength(&to->f, to->totf, &to->maxf, sizeof(tnsFace)); if(from->totf) memcpy(to->f,from->f,sizeof(tnsFace)*from->totf);
     for(int i=0;i<to->totf;i++){ to->f[i].loop=0; arrInitLength(&to->f[i].loop, to->f[i].looplen, &to->f[i].looplen, sizeof(int));
-        for(int l=0;l<to->f[i].looplen;l++){ to->f[i].loop[l]=from->f[i].loop[l]; }
+        for(int l=0;l<to->f[i].looplen;l++){ to->f[i].loop[l]=from->f[i].loop[l]; } to->f[i].mat=from->f[i].mat;
     }
+    tnsMaterialSlot* new_current=0;
+    for(tnsMaterialSlot* ms=from->Materials.pFirst;ms;ms=ms->Item.pNext){
+        tnsMaterialSlot* nms=tnsNewMaterialSlot(to); nms->Index=ms->Index;
+        memAssignRef(nms,&nms->Material,ms->Material);
+        if(ms==from->CurrentMaterial) new_current=nms;
+    }
+    memAssignRef(to,&to->CurrentMaterial,new_current);
     return to;
 }
 
@@ -173,14 +180,23 @@ tnsMVert* tnsGetMFaceLastVert(tnsMFace* mf){
     laListItemPointer* lip=mf->l.pLast; laListItemPointer* next=mf->l.pFirst; tnsMEdge* me0=lip->p, *me1=next->p;
     return tnsMMeshEdgeStartingVert(me0,me1);
 }
-int* tnsGetTriangulatedBatch(tnsMeshObject* mo, int* totelem){
-    int tottri=0;
-    if(mo->Mode==TNS_MESH_OBJECT_MODE){ for(int i=0;i<mo->totf;i++){ tottri+=(mo->f[i].looplen-2); } }
-    else{ for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ tottri+=mf->looplen-2; } }
+int* tnsGetTriangulatedBatch(tnsMeshObject* mo, int* totelem, int mat){
+    int tottri=0; int filter=1; if(mat<0){ filter=0; }
+    if(mo->Mode==TNS_MESH_OBJECT_MODE){
+        for(int i=0;i<mo->totf;i++){ if(filter && (mo->f[i].mat!=mat)) continue; tottri+=(mo->f[i].looplen-2); }
+    }else{ for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ 
+        if(filter && (mf->mat!=mat)) continue; tottri+=mf->looplen-2; }
+    }
     if(!tottri) return 0;
     int* ebuf=calloc(1,sizeof(int)*tottri*3); int* pebuf=ebuf;
-    if(mo->Mode==TNS_MESH_OBJECT_MODE){ for(int i=0;i<mo->totf;i++){ tnsTrangulateFaceSimple(mo, i, &mo->f[i], pebuf); pebuf+=(mo->f[i].looplen-2)*3; } }
-    else{ int i=0; for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ tnsTrangulateFaceSimpleM(mo, i, mf, pebuf); pebuf+=(mf->looplen-2)*3; i++; } }
+    if(mo->Mode==TNS_MESH_OBJECT_MODE){
+        for(int i=0;i<mo->totf;i++){ if(filter && (mo->f[i].mat!=mat)) continue;
+            tnsTrangulateFaceSimple(mo, i, &mo->f[i], pebuf); pebuf+=(mo->f[i].looplen-2)*3;
+        }
+    }else{ int i=0; for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){
+        if(filter && (mf->mat!=mat)){ i++; continue; }
+        tnsTrangulateFaceSimpleM(mo, i, mf, pebuf); pebuf+=(mf->looplen-2)*3; i++; }
+    }
     *totelem = tottri;
     return ebuf;
 }
@@ -246,9 +262,9 @@ void tnsInvalidateMeshBatch(tnsMeshObject* mo){
 void tnsRegenerateMeshBatch(tnsMeshObject* mo){
     if(!mo) return;
     if(mo->Batch) tnsDeleteBatch(mo->Batch); mo->Batch=0;
-    real meshcolor[4]={0.8,0.8,0.8,1};
+    real meshcolor[4]={0.8,0.8,0.8,0.5};
 
-    int tottri; int* elem = tnsGetTriangulatedBatch(mo, &tottri);
+    int tottri; int* elem = tnsGetTriangulatedBatch(mo, &tottri,-1);
     float* idcolors=0,*editcolors=0; int docolors=mo->Mode==TNS_MESH_EDIT_MODE;
     int totv,tote; int* eelems=0; float* n=0;
     float* v = tnsGetDrawingVertArray(mo,&totv,&n,&idcolors,&editcolors,&eelems,&tote);
@@ -259,6 +275,14 @@ void tnsRegenerateMeshBatch(tnsMeshObject* mo){
     if(elem){
         c=tnsCreateCommand(mo->Batch, "body", tottri, 3, GL_TRIANGLES, elem, 0);
         tnsCommandUseUniformColor(c,meshcolor);
+        for(tnsMaterialSlot* ms=mo->Materials.pFirst;ms;ms=ms->Item.pNext){
+            int* melem,mtot; melem=tnsGetTriangulatedBatch(mo, &mtot, ms->Index);
+            if(melem){
+                c=tnsCreateCommand(mo->Batch, "mat", mtot, 3, GL_TRIANGLES, melem, 0); 
+                tnsCommandUseUniformColor(c,ms->Material?ms->Material->Color:meshcolor);
+                free(melem);
+            }
+        }
     }
     free(elem); free(v); free(n);
 
@@ -314,8 +338,11 @@ void tnsDrawMeshObjectOutline(tnsEvaluatedInstance* ei, void* unused){
     tnsDrawBatch(((tnsMeshObject*)ei->Object)->Batch, "body", color, 0);
 }
 void tnsDrawMeshObject(tnsEvaluatedInstance* ei, void* unused){
-    tnsEnsureMeshBatch(ei->Object);
-    tnsUseNormal(1); tnsDrawBatch(((tnsMeshObject*)ei->Object)->Batch,"body",0,0); tnsUseNormal(0);
+    tnsEnsureMeshBatch(ei->Object); tnsUseNormal(1);
+    if(!tnsDrawBatch(((tnsMeshObject*)ei->Object)->Batch,"mat",0,0)){
+        tnsDrawBatch(((tnsMeshObject*)ei->Object)->Batch,"body",0,0);
+    }
+    tnsUseNormal(0);
 }
 void tnsEvaluateMeshObject(tnsMeshObject* mo, tnsEvaluateData* ed){
     tnsEnsureMeshBatch(mo);
@@ -599,7 +626,7 @@ void tnsMMeshFromMesh(tnsMeshObject* mo){
         for(int j=0;j<f->looplen;j++){ int v2=j+1; if(j==f->looplen-1) v2=0;
             tnsEdgeHashEdge* ehe=tnsEdgeHashGetEdge(eh,f->loop[j],f->loop[v2]);
             tnsMEdge* me=ehe->me; tnsMMeshEdgeAssignVerts(me,eh->vl[f->loop[j]].mv,eh->vl[f->loop[v2]].mv);
-            tnsMMeshFaceAddEdge(mf,me); tnsVectorSet3v(mf->n,f->n);
+            tnsMMeshFaceAddEdge(mf,me); tnsVectorSet3v(mf->n,f->n); mf->mat=f->mat;
         }
     }
     tnsMMeshEnsureSelectionFromVerts(mo);
@@ -616,7 +643,7 @@ void tnsMeshFromMMesh(tnsMeshObject* mo){
             laListItemPointer* next=lip->pNext; if(!next) next=mf->l.pFirst; tnsMEdge* me0=lip->p, *me1=next->p;
             tnsFillFaceLoop(f, j, tnsMMeshEdgeStartingVert(me0,me1)->i); j++;
         }
-        tnsFillFaceNormal(f,mf->n);
+        tnsFillFaceNormal(f,mf->n); f->mat=mf->mat;
     }
     mo->totv=mo->totmv; mo->totf=mo->totmf;
     mo->maxv=mo->totv; mo->maxe=mo->tote; mo->maxf=mo->totf;

+ 64 - 1
resources/la_modelling.c

@@ -902,7 +902,7 @@ void la_ExtrudeMakeDuplication(MExtrudeExtra* ee){
         if(!(mf->flags&TNS_MESH_FLAG_SELECTED)) continue;
         arrEnsureLength(&ee->df, ee->nextf, &ee->maxf, sizeof(MEDupFace));
         MEDupFace* df=&ee->df[ee->nextf];
-        tnsMFace* nmf=tnsMMeshNewFace(mo); df->oi=mf->i; mf->i=ee->nextf; df->nmf=nmf; df->omf=mf;
+        tnsMFace* nmf=tnsMMeshNewFace(mo); df->oi=mf->i; mf->i=ee->nextf; df->nmf=nmf; df->omf=mf; nmf->mat=mf->mat;
         for(laListItemPointer*lip=mf->l.pFirst;lip;lip=lip->pNext){
             tnsMEdge* ome=lip->p; tnsMMeshFaceAddEdge(nmf,ee->de[ome->i].nme);
         }
@@ -1619,6 +1619,7 @@ void laui_Merge(laUiList *uil, laPropPack *pp, laPropPack *actinst, laColumn *ex
 
 int OPINV_NewRootObject(laOperator *a, laEvent *e){
     tnsCreateRootObject("Root"); laNotifyUsers("tns.world.root_objects"); laNotifyUsers("tns.world.active_root");
+    laRecordDifferences(0,"tns.world"); laPushDifferences("New root object",0);
     return LA_FINISHED;
 }
 int OPCHK_RemoveRootObjects(laPropPack *This, laStringSplitor *ss){
@@ -1636,6 +1637,62 @@ int OPINV_RemoveRootObject(laOperator *a, laEvent *e){
     return LA_FINISHED;
 }
 
+int OPINV_NewMaterial(laOperator *a, laEvent *e){
+    tnsMaterial* mat=tnsCreateMaterial("Material"); laNotifyUsers("tns.world.materials");
+    if(a->This && a->This->EndInstance && la_EnsureSubTarget(a->This->LastPs->p,a->This->EndInstance)==TNS_PC_OBJECT_MESH){
+        tnsMeshObject* mo=a->This->EndInstance; if(mo->CurrentMaterial){
+            memAssignRef(mo->CurrentMaterial,&mo->CurrentMaterial->Material,mat);
+            laRecordInstanceDifferences(mo,"tns_mesh_object"); laNotifyInstanceUsers(mo);
+        }
+    }
+    laRecordDifferences(0,"tns.world.materials"); laPushDifferences("New material",0);
+    return LA_FINISHED;
+}
+int OPCHK_RemoveMaterial(laPropPack *This, laStringSplitor *ss){
+    if(This && This->EndInstance){ if(la_EnsureSubTarget(This->LastPs->p,0)==TNS_PC_MATERIAL) return 1; }
+    return 0;
+}
+int OPINV_RemoveMaterial(laOperator *a, laEvent *e){
+    if(!a->This || !a->This->EndInstance){ return LA_FINISHED; }
+    tnsRemoveMaterial("Material"); laNotifyUsers("tns.world.materials");
+    laRecordDifferences(0,"tns.world.materials"); laPushDifferences("Removed material",0);
+    return LA_FINISHED;
+}
+int OPCHK_NewMaterialSlot(laPropPack *This, laStringSplitor *ss){
+    if(This && This->EndInstance){ if(la_EnsureSubTarget(This->LastPs->p,This->EndInstance)==TNS_PC_OBJECT_MESH) return 1; } return 0;
+}
+int OPINV_NewMaterialSlot(laOperator *a, laEvent *e){
+    if(!a->This || !a->This->EndInstance){ return LA_FINISHED; } tnsMeshObject* mo=a->This->EndInstance;
+    tnsNewMaterialSlot(mo); laNotifyInstanceUsers(mo);
+    laRecordDifferences(0,"tns.world.objects"); laPushDifferences("New material slot",0);
+    return LA_FINISHED;
+}
+int OPCHK_RemoveMaterialSlot(laPropPack *This, laStringSplitor *ss){
+    if(This && This->EndInstance){ if(la_EnsureSubTarget(This->LastPs->p,0)==TNS_PC_MATERIAL_SLOT) return 1; } return 0;
+}
+int OPINV_RemoveMaterialSlot(laOperator *a, laEvent *e){
+    if(!a->This || !a->This->EndInstance){ return LA_FINISHED; } tnsMaterialSlot* ms=a->This->EndInstance;
+    laNotifyInstanceUsers(ms->Parent); tnsRemoveMaterialSlot(ms->Parent,ms);
+    laRecordDifferences(0,"tns.world.objects"); laPushDifferences("Remove material slot",0);
+    return LA_FINISHED;
+}
+
+int OPCHK_AssignMaterialSlot(laPropPack *This, laStringSplitor *ss){
+    if(This && This->EndInstance){
+        if(la_EnsureSubTarget(This->LastPs->p,This->EndInstance)==TNS_PC_OBJECT_MESH){
+            tnsMeshObject* mo=This->EndInstance; if(mo->Mode==TNS_MESH_EDIT_MODE && mo->CurrentMaterial) return 1;
+        }
+    } return 0;
+}
+int OPINV_AssignMaterialSlot(laOperator *a, laEvent *e){
+    if(!a->This || !a->This->EndInstance){ return LA_FINISHED; }
+    tnsMeshObject* mo=a->This->EndInstance; tnsMaterialSlot* ms=mo->CurrentMaterial;
+    if(mo->Mode!=TNS_MESH_EDIT_MODE || !ms) return LA_FINISHED;
+    tnsAssignMaterialSlot(mo,ms); tnsInvalidateMeshBatch(mo);
+    laNotifyInstanceUsers(mo); laNotifyUsers("tns.world");
+    laRecordInstanceDifferences(mo,"tns_mesh_object"); laPushDifferences("Assign material slot",0);
+    return LA_FINISHED;
+}
 
 void la_RegisterModellingOperators(){
     laPropContainer *pc; laProp *p;
@@ -1644,6 +1701,12 @@ void la_RegisterModellingOperators(){
     laCreateOperatorType("M_new_root", "New Root", "Create a new root object", 0, 0, 0, OPINV_NewRootObject, 0, '+', 0);
     laCreateOperatorType("M_remove_root", "Remove Root", "Remove root object", OPCHK_RemoveRootObjects, 0, 0, OPINV_RemoveRootObject, 0 ,L'🗴', 0);
 
+    laCreateOperatorType("M_new_material", "New Material", "Create a new material", 0, 0, 0, OPINV_NewMaterial, 0, '+', 0);
+    laCreateOperatorType("M_remove_material", "Remove Material", "Remove a material", OPCHK_RemoveMaterial, 0, 0, OPINV_RemoveMaterial, 0 ,L'🗴', 0);
+    laCreateOperatorType("M_new_material_slot", "New Material Slot", "Create a new material slot", OPCHK_NewMaterialSlot, 0, 0, OPINV_NewMaterialSlot, 0, '+', 0);
+    laCreateOperatorType("M_remove_material_slot", "Remove Material Slot", "Remove a material slot", OPCHK_RemoveMaterialSlot, 0, 0, OPINV_RemoveMaterialSlot, 0 ,L'🗴', 0);
+    laCreateOperatorType("M_assign_material_slot", "Assign Material Slot", "Assign faces to a material slot", OPCHK_AssignMaterialSlot, 0, 0, OPINV_AssignMaterialSlot, 0 ,L'🖌', 0);
+
     laCreateOperatorType("M_set_cursor", "Set Cursor", "Set cursor in the viewport", OPCHK_ViewportAndSceneExists, 0, 0, OPINV_SetCursor, 0, 0, LA_EXTRA_TO_PANEL);
     laCreateOperatorType("M_toggle_edit_mode", "Toggle Edit Mode", "Toggle edit mode of the active object", OPCHK_ThereIsActiveObject, 0, 0, OPINV_ToggleEdit, 0, 0, 0);
     laCreateOperatorType("M_select", "Select", "Select things in the viewport", OPCHK_ViewportAndSceneExists, 0, 0, OPINV_Select, OPMOD_Select, 0, LA_EXTRA_TO_PANEL);

+ 21 - 70
resources/la_operators.c

@@ -482,7 +482,7 @@ int OPCHK_IsFileBrowser(laPropPack *This, laStringSplitor *ss){
 }
 int OPINV_FileBrowserUpLevel(laOperator *a, laEvent *e){
     if (a->This){
-        la_FileBrowserUpLevel(a->This->LastPs->UseInstance);
+        la_FileBrowserUpLevel(a->This->EndInstance);
         laNotifyUsersPPPath(a->This, "path");
         laRecalcCurrentPanel();
     }
@@ -497,7 +497,7 @@ int OPCHK_FileBrowserCanConfirm(laPropPack *This, laStringSplitor *ss){
 }
 int OPINV_FileBrowserConfirm(laOperator *a, laEvent *e){
     if (a->This){
-        laFileBrowser* fb=a->This->LastPs->UseInstance;
+        laFileBrowser* fb=a->This->EndInstance;
         if(fb->WarnFileExists){
             char path[2048]; la_FileBrowserGetFullPath(fb, path);
 #ifdef __linux__
@@ -516,7 +516,7 @@ int OPINV_FileBrowserConfirm(laOperator *a, laEvent *e){
     return LA_FINISHED_PASS;
 }
 int OPMOD_FileBrowserConfirm(laOperator *a, laEvent *e){
-    laFileBrowser* fb=a->This->LastPs->UseInstance;
+    laFileBrowser* fb=a->This->EndInstance;
     if (a->ConfirmData){
         if (a->ConfirmData->Mode == LA_CONFIRM_CANCEL){
             if(fb->StatusWaitingWarning){ fb->StatusWaitingWarning=0; return LA_FINISHED; }
@@ -681,8 +681,8 @@ int OPINV_AddResourceFolder(laOperator *a, laEvent *e){
     return LA_FINISHED;
 }
 int OPINV_RemoveResourceFolder(laOperator *a, laEvent *e){
-    if(!a->This || !a->This->LastPs->UseInstance) return LA_CANCELED;
-    laResourceFolder* rf=a->This->LastPs->UseInstance;
+    if(!a->This || !a->This->EndInstance) return LA_CANCELED;
+    laResourceFolder* rf=a->This->EndInstance;
     laRemoveResourceFolder(rf);
     laRefreshUDFRegistries();
     laNotifyUsers("la.user_preferences.resource_folders");
@@ -762,6 +762,16 @@ int OPCHK_StringSetValue(laPropPack *This, laStringSplitor *Instructions){
     if (This && (This->LastPs->p->PropertyType == LA_PROP_STRING) && (!This->LastPs->p->ReadOnly)) return 1;
     else return 0;
 }
+int OPCHK_CollectionSetValue(laPropPack *This, laStringSplitor *Instructions){
+    laSubProp*sp=This->LastPs->p;
+    if (This && (This->LastPs->p->PropertyType == LA_PROP_SUB) && (!This->LastPs->p->ReadOnly) &&
+        (sp->Base.UDFIsRefer && (!sp->Base.UDFIsSingle) && (sp->Base.Offset>=0||sp->Set||sp->SetState))) return 1;
+    else return 0;
+}
+int OPINV_CollectionClearSelection(laOperator *a, laEvent *e){
+    if(!a->This) return LA_CANCELED; laSubProp* sp=a->This->LastPs->p;
+    laSetActiveInstance(sp, a->This->LastPs->UseInstance, 0);
+}
 int OPINV_PropSetDefault(laOperator *a, laEvent *e){
     if(!a->This) return LA_CANCELED; laProp* p=a->This->LastPs->p;
     if (p->PropertyType==LA_PROP_ENUM){
@@ -855,61 +865,6 @@ int OPINV_StringPaste(laOperator *a, laEvent *e){
     return LA_FINISHED;
 }
 
-int OPCHK_SubPutDataBlock(laPropPack *This, laStringSplitor *Instructions){
-    //laListItem* Inst;
-    //void* Actuall=0;
-    //laProp* p;
-    //if (!MAIN.RestoreInstance || !This || !Instructions) return 0;
-    //p = la_PropLookup(&This->LastPs->p->SubProp->Props, strGetArgumentString(Instructions, "identifier"));
-    //if (!p) return 0;
-    //for (Inst = &p->SubProp->TrashBin.pFirst; Inst; Inst = Inst->pNext) {
-    //	if (Inst == MAIN.RestoreInstance) {
-    //		Actuall = Inst; break;
-    //	}
-    //}
-    //if(!Actuall)
-    //	for (Inst = &p->SubProp->FailedNodes.pFirst; Inst; Inst = Inst->pNext) {
-    //		if (Inst == MAIN.RestoreInstance) {
-    //			Actuall = Inst; break;
-    //		}
-    //	}
-    //if (!Actuall) return 0;
-
-    return 0;
-}
-int OPINV_SubPutDataBlock(laOperator *a, laEvent *e){
-    //laProp* p = la_PropLookup(&a->This->LastPs->p->SubProp->Props, strGetArgumentString(a->ExtraInstructionsP, "identifier"));
-
-    //lstRemoveItem(&p->SubProp->TrashBin, MAIN.RestoreInstance);
-    //lstRemoveItem(&p->SubProp->FailedNodes, MAIN.RestoreInstance);
-
-    ////laNotifySubPropUsers(p->SubProp, MAIN.RestoreInstance);
-
-    //laAppendInstance(p, a->This->EndInstance, MAIN.RestoreInstance);
-
-    //laNotifySubPropUsers(p, a->This->EndInstance);
-    //laNotifySubPropUsers(_LA_PROP_TRASH_ITEM, p->SubProp);
-    //laNotifySubPropUsers(_LA_PROP_FAILED_ITEM, p->SubProp);
-
-    return LA_FINISHED;
-}
-int OPCHK_SubRestoreDataBlock(laPropPack *This, laStringSplitor *Instructions){
-    if (This) return 1;
-    return 0;
-}
-int OPINV_SubRestoreDataBlock(laOperator *a, laEvent *e){
-    //if (a->This->EndInstance) {
-    //	MAIN.RestoreInstance = a->This->EndInstance;
-    //}
-
-    //laEnableOperatorPanel(a, 0, e->x, e->y, 600, 600, 2000, 1000, 100, 100, 0, 0, 0, 0, e);
-
-    return LA_FINISHED; //will directly finish after actuator panel close or any feedback event and don't pass any feedback.
-}
-int OPEXT_SubRestoreDataBlock(laOperator *a, int unused){
-    return 0;
-}
-
 int OPCHK_CombineChildBlocks(laPropPack *This, laStringSplitor *Instructions){
     laLayout *l = MAIN.CurrentWindow->CurrentLayout;
     laBlock *b = l->OnBlockSeperator;
@@ -1103,7 +1058,7 @@ int OPCHK_BlockHasMorePanels(laPropPack *This, laStringSplitor *Instructions){
     return 1;
 }
 int OPINV_BlockClosePanel(laOperator *a, laEvent *e){
-    laPanel*p = a->This?a->This->LastPs->UseInstance:0;
+    laPanel*p = a->This?a->This->EndInstance:0;
     if(p && p->Mode == LA_PANEL_FLOATING_TOP){
         laDestroySinglePanel(p,0);
         return LA_FINISHED;
@@ -1179,7 +1134,7 @@ void la_ClearDockingTarget(){
     for(laWindow* w=MAIN.Windows.pFirst;w;w=w->Item.pNext){  w->CurrentLayout->DropToBlock=0; }
 }
 int OPINV_DockPanel(laOperator* a, laEvent* e){
-    laPanel*p = a->This?a->This->LastPs->UseInstance:0;
+    laPanel*p = a->This?a->This->EndInstance:0;
     if(!p) return LA_CANCELED;
 
     la_StartDocking(MAIN.CurrentWindow, p);
@@ -1988,7 +1943,7 @@ int OPCHK_IsPanelNoInstructions(laPropPack *This, laStringSplitor *ss){
     return 0;
 }
 int OPINV_HidePanel(laOperator *a, laEvent *e){
-    laPanel *p = a->This->LastPs->UseInstance;
+    laPanel *p = a->This?a->This->EndInstance:0;
 
     laHidePanelWithDissoveEffect(p);
 
@@ -2004,7 +1959,7 @@ int OPINV_ActivatePanel(laOperator *a, laEvent *e){
     return LA_FINISHED;
 }
 int OPINV_PanPanel(laOperator *a, laEvent *e){
-    laPanel *p = a->This->LastPs->UseInstance;
+    laPanel *p = a->This?a->This->EndInstance:0;
 
     //laHidePanel(p);
 
@@ -2110,14 +2065,10 @@ void la_RegisterBuiltinOperators(){
     laCreateOperatorType("LA_string_get_folder_path", "Get folder Path", "get folder path", OPCHK_StringSetValue, 0, 0, OPINV_StringGetFolderPath, OPMOD_StringGetFolderOrFilePath, U'📁', LA_ACTUATOR_SYSTEM);
     laCreateOperatorType("LA_string_get_file_path", "Get folder Path", "get file path", OPCHK_StringSetValue, 0, 0, OPINV_StringGetFilePath, OPMOD_StringGetFolderOrFilePath, U'🖹', LA_ACTUATOR_SYSTEM);
     
+    laCreateOperatorType("LA_collection_clear_selection", "Clear Selection", "Clear collection selection", OPCHK_CollectionSetValue, 0, 0, OPINV_CollectionClearSelection, 0, U'🧹', LA_ACTUATOR_SYSTEM);
+    
     laCreateOperatorType("LA_prop_insert_key", "Insert Key Frame", "Insert key frame in the active action", OPCHK_PropInsertKey, 0, 0, OPINV_PropInsertKey, 0, U'🔑', LA_ACTUATOR_SYSTEM);
 
-    laCreateOperatorType("LA_sub_put_data_block", "Put Data Block", "Put Pending Data Block Here",
-                          OPCHK_SubPutDataBlock, 0, 0, OPINV_SubPutDataBlock, 0, U'🡮', LA_ACTUATOR_SYSTEM | LA_ACTUATOR_HIDDEN);
-    at = laCreateOperatorType("LA_sub_restore_data_block", "Put Data Block", "Put Pending Data Block Here",
-                               OPCHK_SubRestoreDataBlock, 0, OPEXT_SubRestoreDataBlock, OPINV_SubRestoreDataBlock, 0, U'🔗', LA_ACTUATOR_SYSTEM);
-    at->UiDefine = laui_DataRestorePage;
-
     laCreateOperatorType("LA_view_hyper_data", "View Hyper Data", "Show Properties Of Specific Data Block", OPCHK_IsHyper, 0, 0, OPINV_ViewHyperData, 0, U'🛈', LA_ACTUATOR_SYSTEM);
     
     laCreateOperatorType("LA_file_dialog_up", "Up", "Select Upper Folder Level", OPCHK_IsFileBrowser, 0, 0, OPINV_FileBrowserUpLevel, 0, U'🢰', LA_ACTUATOR_SYSTEM | LA_ACTUATOR_HIDDEN);

+ 69 - 1
resources/la_properties.c

@@ -585,6 +585,8 @@ void tnsset_ObjectDLocationARR(tnsObject* o, real* arr){ tnsVectorCopy3d(arr,o->
 void tnsset_ObjectDRotationARR(tnsObject* o, real* arr){ tnsVectorCopy3d(arr,o->DRotation); tnsDeltaTransformValueChanged(o); laNotifyUsers("tns.world"); }
 void tnsset_ObjectDScale(tnsObject* o, real val){ o->DScale=val; tnsDeltaTransformValueChanged(o); laNotifyUsers("tns.world"); }
 
+tnsMeshObject* tnsget_ObjectAsMesh(tnsObject* o){ if(!o || o->Type!=TNS_OBJECT_MESH) return 0; return o; }
+
 void laget_UiTemplateIdentifier(laUiTemplate *uit, char *result, char** here){
     *here=uit->Identifier->Ptr;
 }
@@ -786,7 +788,10 @@ int lafilter_NodeCategory(void* unused, laNodeCategory* c){
 }
 
 void tnspost_Object(tnsObject *o){
-    /* what */
+    laListItemPointer* NextLip;
+    for(laListItemPointer* lip=o->ChildObjects.pFirst;lip;lip=NextLip){
+        NextLip=lip->pNext; if(!lip->p){ lstRemoveItem(&o->ChildObjects,lip); memFree(lip); }
+    }
 }
 void tnspropagate_Object(tnsObject* o, laUDF* udf, int force){
     for(laListItemPointer* lip=o->ChildObjects.pFirst;lip;lip=lip->pNext){ if(!lip->p) continue;
@@ -816,6 +821,7 @@ void* tnsget_MeshObjectFaceRaw(tnsMeshObject* o, int* r_size, int* r_is_copy){
         arrEnsureLength(&arr, i+o->f[f].looplen+2, &max, sizeof(int));
         arr[i]=o->f[f].looplen; i++;
         arr[i]=o->f[f].flags; i++;
+        arr[i]=o->f[f].mat; i++;
         for(int l=0;l<o->f[f].looplen;l++){
             arr[i]=o->f[f].loop[l]; i++;
         }
@@ -838,6 +844,7 @@ void tnsset_MeshObjectFaceRaw(tnsMeshObject* o, int* data, int s){
         tnsFace*f=&o->f[fi];
         f->looplen=data[i]; i++;
         f->flags=data[i]; i++;
+        f->mat=data[i]; i++;
         f->loop=calloc(1,sizeof(int)*f->looplen);
         for(int li=0;li<f->looplen;li++){
             f->loop[li]=data[i]; i++;
@@ -845,6 +852,39 @@ void tnsset_MeshObjectFaceRaw(tnsMeshObject* o, int* data, int s){
     }
 }
 
+void tns_InvalidateMeshWithMaterial(tnsMaterial* m){
+    for(tnsMeshObject* o=T->World->AllObjects.pFirst;o;o=o->Base.Item.pNext){
+        if(o->Base.Type!=TNS_OBJECT_MESH) continue;
+        for(tnsMaterialSlot* ms=o->Materials.pFirst;ms;ms=ms->Item.pNext){
+            if(ms->Material==m){ tnsInvalidateMeshBatch(o); break; }
+        }
+    }
+}
+void tnsset_MaterialColor(tnsMaterial* m, real* c){
+    tnsVectorCopy4d(c,m->Color); tns_InvalidateMeshWithMaterial(m); laNotifyUsers("tns.world");
+}
+void tnsget_MaterialSlotname(tnsMaterialSlot* ms, char *result, char** here){
+    if(!ms){ strcpy(result,"?"); return; }
+    if(ms->Material&&ms->Material->Name&&ms->Material->Name->Ptr){
+        sprintf(result,"%d: %s",ms->Index,ms->Material->Name->Ptr); return;
+    }
+    sprintf(result,"%d: %s",ms->Index,transLate("<Default Material>"));
+}
+tnsMaterialSlot* tnsget_FirstMaterialSlot(tnsMeshObject* mo, void* unused){
+    return mo->Materials.pFirst;
+}
+tnsMaterialSlot* tnsget_ActiveMaterialSlot(tnsMeshObject* mo){
+    return mo->CurrentMaterial;
+}
+void tnsset_ActiveMaterialSlot(tnsMeshObject* mo, tnsMaterialSlot* ms){
+    memAssignRef(mo,&mo->CurrentMaterial,ms);
+}
+tnsMaterial* tnsget_FirstMaterial(void* unused1, void* unused2){
+    return T->World->Materials.pFirst;
+}
+tnsMaterial* tnsgetactive_SlotMaterial(tnsMaterialSlot* ms, void* unused){
+    return ms->Material;
+}
 
 laPropContainer* tnsget_ObjectType(tnsObject* o){
     switch(o->Type){
@@ -886,6 +926,8 @@ laPropContainer* TNS_PC_OBJECT_INSTANCER;
 laPropContainer* TNS_PC_OBJECT_CAMERA;
 laPropContainer* TNS_PC_OBJECT_LIGHT;
 laPropContainer* TNS_PC_OBJECT_MESH;
+laPropContainer* TNS_PC_MATERIAL;
+laPropContainer* TNS_PC_MATERIAL_SLOT;
 
 laProp* LA_PROP_CONTROLLER;
 
@@ -945,6 +987,7 @@ void la_RegisterGeneralProps(){
     p = la_SetGeneralRoot(&MAIN.GeneralCollectionSub, "__general_collection__", "Genral Collection Operations", "Genral Collection Operations");
     laAddOperatorProperty(p, "put_data_block", "Put", "Append Pending Data Block Here", "LA_sub_put_data_block", U'🔗', 0);
     laAddOperatorProperty(p, "save_instance", "Save Instance", "Save instance as a udf block", "LA_udf_save_instance", 0,0);
+    laAddOperatorProperty(p, "clear_selection", "Clear Selection", "Clear selected instance", "LA_collection_clear_selection", U'🧹',0);
 }
 
 void lareset_RackPage(laRackPage* rp){
@@ -999,12 +1042,31 @@ void la_RegisterTNSProps(){
         laAddSubGroup(p, "active_root", "Active Root Object", "Global active root object", "tns_object",0,0,0,offsetof(tnsWorld,ActiveRoot),tnsget_detached_FirstRootObject,0,laget_ListNext,0,0,0,0,LA_UDF_REFER);
         sp = laAddSubGroup(p, "objects", "Objects", "List of all objects", "tns_object",tnsget_ObjectType, 0,0,-1,0,0,0,0,0,0,offsetof(tnsWorld, AllObjects), 0);
         laSubGroupExtraFunctions(sp,tnsfilter_SavableObject,tnsfilter_SavableObject,0,0,0);
+        laAddSubGroup(p, "materials", "Materials", "List of all materials", "tns_material",0,0,0,-1,0,0,0,0,0,0,offsetof(tnsWorld, Materials), 0);
     }
 
     p = laAddPropertyContainer("tns_child_object", "Child Object", "Child object linker", 0,0,sizeof(laListItemPointer), 0,0,0);{
         laAddSubGroup(p, "object", "Object", "Object link", "tns_object",tnsget_ObjectType, 0,0,offsetof(laListItemPointer, p), 0,0,0,0,0,0,0,LA_UDF_REFER);
     }
 
+    p = laAddPropertyContainer("tns_material", "Material" "Object material", 0,0,0,sizeof(tnsMaterial),0,0,2);{
+        TNS_PC_MATERIAL=p;
+        laAddStringProperty(p, "name", "Material Name", "The name ff the material", 0,0,0,0,1, offsetof(tnsMaterial, Name), 0,0,0,0,LA_AS_IDENTIFIER);
+        laAddFloatProperty(p, "color", "Color", "Base color of the material", LA_WIDGET_FLOAT_COLOR, "R,G,B,A", 0,1,0,0.025, 1, 0,offsetof(tnsMaterial, Color), 0,0,4, 0,0,0,0,tnsset_MaterialColor, 0,0,0);
+        ep = laAddEnumProperty(p, "colorful", "Colorful", "Use colorful display", 0,0,0,0,0,offsetof(tnsMaterial, Colorful), 0,0,0,0,0,0,0,0,0,0);{
+            laAddEnumItemAs(ep, "NONE", "None", "Display materials normally",0,0);
+            laAddEnumItemAs(ep, "COLORFUL", "Colorful", "Display material with colorful halftone",1,0);
+        }
+    }
+
+    p = laAddPropertyContainer("tns_material_slot", "Material Slot" "Material Slot", 0,0,0,sizeof(tnsMaterialSlot),0,0,1);{
+        TNS_PC_MATERIAL_SLOT=p;
+        laAddStringProperty(p,"name","Name","Name of the material slot",LA_WIDGET_STRING_PLAIN,0,0,0,0,0,0,tnsget_MaterialSlotname,0,0,LA_READ_ONLY|LA_UDF_IGNORE);
+        laAddSubGroup(p, "material", "Material", "Material reference of this slot","tns_material",0,0,0,offsetof(tnsMaterialSlot,Material),tnsget_FirstMaterial,tnsgetactive_SlotMaterial,laget_ListNext,0,0,0,0,LA_UDF_REFER);
+        laAddIntProperty(p,"index","Index","Index of the material slot",LA_WIDGET_INT_PLAIN,0,0,0,0,0,0,0,offsetof(tnsMaterialSlot,Index),0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
+        laAddOperatorProperty(p,"remove","Remove","Remove this material slot","M_remove_material_slot",L'🗴',0);
+    }
+
     p = laAddPropertyContainer("tns_object", "Object", "3D Object Item", 0,tnsui_BaseObjectProperties,sizeof(tnsObject), tnspost_Object, 0,2);{
         laPropContainerExtraFunctions(p,0,0,0,tnspropagate_Object,tnsui_DefaultObjectPropUiDefine);
         laContainerAnimationFunctions(p,laaction_VerifyRootObject);
@@ -1051,6 +1113,7 @@ void la_RegisterTNSProps(){
         laAddSubGroup(p, "drivers", "Drivers", "Driver page collection","la_driver_collection",0,0,0,offsetof(tnsObject,Drivers),0,0,0,0,0,0,0,LA_UDF_SINGLE|LA_HIDE_IN_SAVE);
         laAddOperatorProperty(p, "add_driver_page", "Add Page", "Add a driver page","LA_add_driver_page",'+',0);
         laAddOperatorProperty(p, "remove_root", "Remove root", "Remove the root node","M_remove_root",L'🗴',0);
+        laAddSubGroup(p, "as_mesh", "As Mesh", "As mesh object", "tns_mesh_object",0,0,0,-1,0,tnsget_ObjectAsMesh,0,0,0,0,0,LA_UDF_REFER|LA_READ_ONLY|LA_UDF_IGNORE);
     }
     p = laAddPropertyContainer("tns_instancer", "Instancer", "Instance placeholder object", U'📎', tnsui_InstancerObjectProperties,sizeof(tnsInstancer), 0,0,2);{
         laPropContainerExtraFunctions(p,0,0,tnstouched_Object,0/*tnspropagate_Object*/,0);
@@ -1084,6 +1147,11 @@ void la_RegisterTNSProps(){
         //laAddIntProperty(p, "maxv", "Max Vert", "Max Vert count", 0,0,0,0,0,0,0,0,offsetof(tnsMeshObject, maxv),0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
         //laAddIntProperty(p, "maxe", "Max Edge", "Max Edge count", 0,0,0,0,0,0,0,0,offsetof(tnsMeshObject, maxe),0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
         //laAddIntProperty(p, "maxf", "Max Face", "Max Face count", 0,0,0,0,0,0,0,0,offsetof(tnsMeshObject, maxf),0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
+        laAddSubGroup(p, "current_material", "Current Material", "Current material slot in this object", "tns_material_slot",0,0,0,offsetof(tnsMeshObject, CurrentMaterial),tnsget_FirstMaterialSlot,0,laget_ListNext,0,0,0,0,LA_UDF_REFER);
+        laAddSubGroup(p, "materials", "Materials", "Material slots in this mesh object", "tns_material_slot",0,0,0,-1,0,tnsget_ActiveMaterialSlot,0,tnsset_ActiveMaterialSlot,0,0,offsetof(tnsMeshObject, Materials),0);
+        laAddOperatorProperty(p,"add_material_slot","Add Material Slot","Add a material slot into this object","M_new_material_slot",L'+',0);
+        laAddOperatorProperty(p,"add_material","Add Material","Add a new material to this material slot","M_new_material",'+',0);
+        laAddOperatorProperty(p,"assign_material_slot","Assign Material Slot","Assign faces to a current slot","M_assign_material_slot",L'🖌',0);
     }
     p = laAddPropertyContainer("tns_camera", "Camera", "Camera object", U'📷', tnsui_CameraObjectProperties,sizeof(tnsCamera), 0,0,2);{
         //laPropContainerExtraFunctions(p,0,0,0,tnspropagate_Object,0);

+ 69 - 6
resources/la_templates.c

@@ -1732,11 +1732,74 @@ void tnsui_InstancerObjectProperties(laUiList *uil, laPropPack *This, laPropPack
     laSplitColumn(uil,c,0.5); cl=laLeftColumn(c,5); cr=laRightColumn(c,0);
     laShowLabel(uil,cl,"Instance",0,0); laShowItemFull(uil,cr,This,"instance",LA_WIDGET_COLLECTION_SELECTOR,0,laui_IdentifierOnly,0);
 }
+void tnsui_Material(laUiList *uil, laPropPack *This, laPropPack *UNUSED_Extra, laColumn *UNUSED_Colums, int context){
+    laColumn* c=laFirstColumn(uil),*cl,*cr; laUiItem* b,*b1;
+    laSplitColumn(uil,c,0.4); cl=laLeftColumn(c,8); cr=laRightColumn(c,0);
+    if(!context){
+        laShowLabel(uil,cl,"Name",0,0); laShowItem(uil,cr,This,"name");
+    }
+    laShowLabel(uil,cl,"Color",0,0); laShowItemFull(uil,cr,This,"color",LA_WIDGET_FLOAT_COLOR,0,0,0);
+    laShowItemFull(uil,cr,This,"colorful",LA_WIDGET_ENUM_HIGHLIGHT,0,0,0);
+}
+void tnsui_MaterialListItem(laUiList *uil, laPropPack *This, laPropPack *UNUSED_Extra, laColumn *UNUSED_Colums, int context){
+    laColumn* c=laFirstColumn(uil); laUiItem* b,*b1;
+    b=laBeginRow(uil,c,0,0);
+    laShowItemFull(uil,c,This,"color",LA_WIDGET_FLOAT_COLOR,0,0,0)->Flags|=LA_UI_FLAGS_NO_EVENT|LA_UI_FLAGS_ICON;
+    laUiItem* ui=laShowItem(uil,c,This,"name"); ui->Flags|=LA_UI_FLAGS_PLAIN; ui->Expand=1;
+    laEndRow(uil,b);
+}
+void tnsui_MaterialSlot(laUiList *uil, laPropPack *This, laPropPack *UNUSED_Extra, laColumn *UNUSED_Colums, int context){
+    laColumn* c=laFirstColumn(uil); laUiItem* b,*b1;
+    b1=laOnConditionThat(uil,c,laPropExpression(This,"material"));{
+        b=laBeginRow(uil,c,0,0);
+        laShowItemFull(uil,c,This,"material.color",LA_WIDGET_FLOAT_COLOR,0,0,0)->Flags|=LA_UI_FLAGS_NO_EVENT|LA_UI_FLAGS_ICON;
+        laUiItem* ui=laShowItem(uil,c,This,"material.name"); ui->Flags|=LA_UI_FLAGS_PLAIN; ui->Expand=1;
+        laEndRow(uil,b);
+    }laElse(uil,b1);{
+        laShowItem(uil,c,This,"name")->Flags|=LA_UI_FLAGS_DISABLED;
+    }laEndCondition(uil,b1);
+}
 void tnsui_BaseObjectProperties(laUiList *uil, laPropPack *This, laPropPack *UNUSED_Extra, laColumn *UNUSED_Colums, int context){
-    laColumn* c=laFirstColumn(uil); laUiItem* g; laUiList* gu; laColumn* gc,*gcl,*gcr;
-    g=laMakeGroup(uil,c,"Object",0); gu=g->Page; gc=laFirstColumn(gu);
+    laColumn* c=laFirstColumn(uil); laUiItem* g,*tg; laUiList* gu,*tu; laColumn* gc,*gcl,*gcr,*tc; laUiItem* b1,*b2,*b3,*b4,*b5;
+
+    b1=laOnConditionThat(uil,c,laEqual(laPropExpression(This,"__self.type"),laIntExpression(TNS_OBJECT_MESH)));{
+        g=laMakeGroup(uil,c,"Materials",0); gu=g->Page; gc=laFirstColumn(gu); laSplitColumn(gu,gc,0.7);
+        gcl=laLeftColumn(gc,0); gcr=laRightColumn(gc,1);
+        tg=laMakeEmptyGroup(gu,gcl,"Slots",0); tu=tg->Page; tc=laFirstColumn(tu); tu->HeightCoeff=4;
+        laShowItemFull(tu,tc,This,"as_mesh.materials",0,0,tnsui_MaterialSlot,0)->Flags|=LA_UI_FLAGS_NO_DECAL;
+        laShowItem(gu,gcr,This,"as_mesh.add_material_slot");
+        b2=laOnConditionThat(gu,gc,laPropExpression(This,"as_mesh.current_material"));{
+            laUiItem* mat=laShowInvisibleItem(gu,gc,This,"as_mesh.current_material");
+            laShowItem(gu,gcr,&mat->PP,"remove");
+            b3=laBeginRow(gu,gc,0,0); laUiItem* sel;
+            laShowItem(gu,gc,This,"as_mesh.add_material")->Flags|=LA_UI_FLAGS_ICON;
+            b4=laOnConditionThat(gu,gc,laPropExpression(&mat->PP,"material"));{
+                sel=laShowItemFull(gu,gc,&mat->PP,"material",LA_WIDGET_COLLECTION_SELECTOR,0,tnsui_MaterialListItem,0);
+                sel->Flags|=LA_UI_COLLECTION_SIMPLE_SELECTOR;
+                laShowItem(gu,gc,&mat->PP,"material.name")->Expand=1;
+                laShowItem(gu,gc,&sel->PP,"clear_selection")->Flags|=LA_UI_FLAGS_ICON;
+            }laElse(gu,b4);{
+                sel=laShowItemFull(gu,gc,&mat->PP,"material",LA_WIDGET_COLLECTION_SELECTOR,0,tnsui_MaterialListItem,0);
+                sel->Flags|=LA_UI_COLLECTION_SIMPLE_SELECTOR; sel->Expand=1;
+            }laEndCondition(gu,b4);
+            laEndRow(gu,b3);
+            b4=laOnConditionThat(gu,gc,laEqual(laPropExpression(This,"as_mesh.mode"),laIntExpression(TNS_MESH_EDIT_MODE)));{
+                b5=laBeginRow(gu,gc,0,0);
+                laShowLabel(gu,gc,"Edit:",0,0);
+                laShowItemFull(gu,gc,This,"as_mesh.assign_material_slot",0,"text=Assign",0,0);
+                laEndRow(gu,b5);
+            }laEndCondition(gu,b4);
+            laShowSeparator(gu,gc);
+            b4=laOnConditionThat(gu,gc,laPropExpression(&mat->PP,"material"));{
+                laShowItemFull(gu,gc,&mat->PP,"material",LA_WIDGET_COLLECTION_SINGLE,0,tnsui_Material,1)->Flags|=LA_UI_FLAGS_NO_DECAL;
+            }laEndCondition(gu,b4);
+        }laElse(gu,b2);{
+            laShowLabel(gu,gc,"No active material slot.",0,0);
+        }laEndCondition(gu,b2);
+    }laEndCondition(uil,b1);
 
-    laUiItem* b1=laOnConditionToggle(gu,gc,0,0,0,0,0);{ strSafeSet(&b1->ExtraInstructions,"text=Transformations");
+    g=laMakeGroup(uil,c,"Object",0); gu=g->Page; gc=laFirstColumn(gu);
+    b1=laOnConditionToggle(gu,gc,0,0,0,0,0);{ strSafeSet(&b1->ExtraInstructions,"text=Transformations");
         //b1->State=LA_UI_ACTIVE;
         laSplitColumn(gu,gc,0.5); gcl=laLeftColumn(gc,0); gcr=laRightColumn(gc,0);
         laShowLabel(gu,gcl,"Location",0,0); laShowItem(gu,gcl,This,"location")->Flags|=LA_UI_FLAGS_TRANSPOSE;
@@ -1783,14 +1846,14 @@ void tnsui_ObjectProperties(laUiList *uil, laPropPack *This, laPropPack *Extra,
 
     laUiItem* b0=laOnConditionThat(uil,c,laPropExpression(Extra,"detached"));{
         ADD_PAGE1(Extra,"root_object")
-        laUiItem* actui=laShowItem(gu,gc,&rb->PP,"active"); actui->Flags|=LA_UI_COLLECTION_NO_HIGHLIGHT|LA_UI_FLAGS_NO_DECAL;
+        laUiItem* actui=laShowInvisibleItem(gu,gc,&rb->PP,"active");
         ADD_PAGE2
     }laElse(uil,b0);{
         ADD_PAGE1(0,"tns.world.active_root")
-        laUiItem* actui=laShowItemFull(gu,gc,0,"tns.world.active_root.active",LA_WIDGET_COLLECTION_SINGLE,0,0,0);
-        actui->Flags|=LA_UI_COLLECTION_NO_HIGHLIGHT|LA_UI_FLAGS_NO_DECAL;
+        laUiItem* actui=laShowInvisibleItem(gu,gc,0,"tns.world.active_root.active");
         ADD_PAGE2
     }laEndCondition(uil,b0);
+
 #undef ADD_PAGE1
 #undef ADD_PAGE2
 }

+ 15 - 0
resources/la_tns_shaders.cpp

@@ -662,6 +662,20 @@ vec3 ConvertColorSpace(vec3 color){
     }
     return color;
 }
+vec4 rgb2cmyk(vec3 rgb);
+float HalftoneSingle(float a,float theta){
+	float psize=5; vec2 ctr=vec2(psize/2,psize/2); vec2 pt=vec2(psize,psize);
+    vec2 xy=gl_FragCoord.xy; xy=vec2(sin(theta)*xy.x-cos(theta)*xy.y,cos(theta)*xy.x+sin(theta)*xy.y);
+	ivec2 xyi=ivec2(int(xy.x/psize),int(xy.y/psize));
+	vec2 xyf=mod(xy,pt);
+	float px1=0.000001; //2.0f/psize;
+	float cmp=(pow(a,1.5)*psize/2*(1.414+px1)); float fac=distance(xyf,ctr)/cmp;
+	return smoothstep(1+px1,1-px1,fac);
+}
+vec4 halftone(vec4 color){
+	color.a=HalftoneSingle(color.a,15.0/180.0*3.14);
+	return color;
+}
 void main(){
     vec4 color=vec4(1,0,1,1);
     if(TextureMode==0){ color = fColor;}
@@ -677,6 +691,7 @@ void main(){
         if(MultiplyColor!=0){color*=fColor;}
     }
     if(UseNormal!=0){
+		color=halftone(color); if(color.a==0) discard;
         float light_factor=dot(fNormal,vec3(0,0,1));
         float view=dot(fNormal,fGPos-uViewPos);
         float factor=abs(light_factor);

+ 5 - 1
resources/la_widgets.c

@@ -202,6 +202,7 @@ int la_ImageGetHeight(laUiItem *ui){
 }
 
 int la_ColorSelectorGetMinWidth(laUiItem *ui){
+    if(ui->Flags&LA_UI_FLAGS_ICON) return LA_RH;
     return 5*LA_RH;
 }
 int la_ValueGetMinWidth(laUiItem *ui){
@@ -389,7 +390,10 @@ void la_CollectionSelectorDraw(laUiItem *ui, int h){
         tnsDrawStringAuto("🧹", laThemeColor(bt,ui->State|LA_BT_TEXT), ui->R - LA_RH, ui->R, ui->U, 0);
     }
     if(Simple){
-        tnsDrawStringAuto("⯆", laThemeColor(bt,ui->State|LA_BT_TEXT), ui->L, ui->R, ui->U, LA_TEXT_ALIGN_CENTER);
+        int R=ui->R; if(ui->Expand){
+            R=ui->L+LA_RH; tnsDrawStringAuto(transLate("Select"), laThemeColor(bt,ui->State|LA_BT_TEXT), R+bt->LM, ui->R, ui->U, 0);
+        }
+        tnsDrawStringAuto("⯆", laThemeColor(bt,ui->State|LA_BT_TEXT), ui->L, R, ui->U, LA_TEXT_ALIGN_CENTER);
     }
 }
 void la_EmptyDraw(laUiItem *ui, int h){