*/}}
Pārlūkot izejas kodu

Flat shading and basics of deferred shading

Yiming Wu 2 gadi atpakaļ
vecāks
revīzija
cb16ff562b

+ 1 - 1
source/lagui/la_interface.h

@@ -733,7 +733,7 @@ STRUCTURE(laCanvasTemplate){
 STRUCTURE(laCanvasExtra){
     laListItem Item;
     tnsOffscreen *OffScr;
-    int NoOffScreen;
+    tnsOffscreen *DeferredOffScr;
 
     laUiItem *ParentUi;
 

+ 20 - 5
source/lagui/la_tns.h

@@ -53,8 +53,9 @@ struct _tnsShader{
     int iShadow;
 
     int iVertex, iNormal, iColor, iUV;
+    int iUseNormal;
 
-    int iTexColor;
+    int iTexColor,iTexNormal,iTexGPos;
     int iTexColorMS;
     int iMultiplyColor, StateMultiplyColor;
     int iTextureMode, StateTextureMode;
@@ -194,6 +195,8 @@ struct _tnsMain
     tnsTexture *StateTexColor;
     int StateTextureMode;
     int StateMultiplyColor;
+    int StateUseNormal;
+    int SetUseNormal;
     
     tnsShader* StateShader;
 
@@ -615,6 +618,7 @@ STRUCTURE(tnsGroup){
 
 #define TNS_MESH_FLAG_SELECTED (1<<0)
 #define TNS_MESH_FLAG_PICKED   (1<<1)
+#define TNS_MESH_FLAG_LOOP_REVERSE (1<<2)
 
 STRUCTURE(tnsMVert){
     laListItem Item;
@@ -639,7 +643,6 @@ STRUCTURE(tnsMFace){
     laListItem Item;
     laListHandle l; //list item pointer of edges
     tnsVector3d n;
-    tnsVector3d gn;
     tnsVector3d c;
     short looplen;
     short mat;
@@ -649,6 +652,7 @@ STRUCTURE(tnsMFace){
 
 STRUCTURE(tnsVert){
     tnsVector3f p;
+    tnsVector3f n;
     int flags;
 };
 STRUCTURE(tnsEdge){
@@ -657,8 +661,9 @@ STRUCTURE(tnsEdge){
 };
 STRUCTURE(tnsFace){
     int* loop;
-    short looplen;
     int flags;
+    tnsVector3f n;
+    short looplen;
 };
 
 STRUCTURE(tnsBatchCommand){
@@ -955,9 +960,13 @@ void tnsConvert44df(tnsMatrix44d from, tnsMatrix44f to);
 int tnsTrangleLineBoundBoxTest(tnsRenderTriangle *rt, tnsRenderLine *rl);
 
 #define tnsVectorSet3(to, x,y,z)\
-    {to[0]=x;to[1]=y;to[2]=z;}
+    {(to)[0]=x;(to)[1]=y;(to)[2]=z;}
+#define tnsVectorSet4(to, x,y,z,w)\
+    {(to)[0]=x;(to)[1]=y;(to)[2]=z;(to)[3]=w;}
 #define tnsVectorSet3v(to, from)\
     tnsVectorSet3(to,from[0],from[1],from[2])
+#define tnsVectorSet4v(to, from)\
+    tnsVectorSet4(to,from[0],from[1],from[2],from[3])
 
 real tnsDistIdv2(real x1, real y1, real x2, real y2);
 real tnsDist3dv(tnsVector3d l, tnsVector3d r);
@@ -1077,6 +1086,8 @@ void tnsSelectObject(tnsObject* o, int Select, int Toggle);
 tnsMFace* tnsMMeshNewFace(tnsMeshObject* mo);
 tnsMEdge* tnsMMeshNewEdge(tnsMeshObject* mo);
 tnsMVert* tnsMMeshNewVert(tnsMeshObject* mo);
+void tnsMMeshCalculateNormalFrom(tnsMFace* mf);
+int tnsMMeshCalculateNormal(tnsMeshObject* mo);
 void tnsMMeshEdgeAssignVerts(tnsMEdge* me,tnsMVert* mv1,tnsMVert* mv2);
 tnsMEdge* tnsMMeshVertShareEdge(tnsMVert* mv0, tnsMVert* mv1);
 int tnsMMeshEdgeHasVert(tnsMEdge* me, tnsMVert* MV);
@@ -1100,7 +1111,7 @@ void tnsMMeshRefreshIndex(tnsMeshObject* mo);
 void tnsMeshEnterEditMode(tnsMeshObject* mo);
 void tnsMeshLeaveEditMode(tnsMeshObject* mo);
 int tnsMMeshAnySelected(tnsMeshObject* mo);
-void tnsMMeshClearPickedFlags(tnsMeshObject* mo);
+void tnsMMeshClearExtraFlags(tnsMeshObject* mo);
 void tnsMMeshDeselectAll(tnsMeshObject* mo);
 void tnsMMeshSelectAll(tnsMeshObject* mo);
 void tnsMMeshSelectVert(tnsMeshObject* mo, tnsMVert* mv, int select, int toggle);
@@ -1148,6 +1159,9 @@ void tnsLoadSystemFontMono(char* mono);
 void tnsLoadSystemFont(int num_fonts, ...);
 int tnsGetTextureMemoryComponetCount(tnsTexture *t);
 
+void tnsUseNormal(int Use);
+
+void tnsSetRayShaderUniformTextures(tnsOffscreen* doff);
 int tnsInit2DTexture(tnsTexture *t, GLint glInternalFormat, int w, int h, int Multisample);
 tnsTexture *tnsCreate2DTexture(GLint glInternalFormat, int w, int h, int Multisample);
 tnsTexture *tnsCreate3DTexture(GLint glInternalFormat, int w, int h, int slices);
@@ -1155,6 +1169,7 @@ void tnsConfigure2DTexture(tnsTexture *t);
 void tnsConfigure3DTexture(tnsTexture *t);
 void tnsReconfigureTextureParameters(int Multisample);
 tnsOffscreen *tnsCreate2DOffscreen(int glInternalFormat, int w, int h, int Multisample, int WithDepth);
+tnsOffscreen *tnsCreateDeferredOffscreen(int w, int h);
 void tnsCopyScreenTo2DTexture(tnsTexture *target, int x_lower_left, int y_lower_left, int w, int h);
 void tnsActiveTexture(GLenum tex);
 void tnsBindTexture(tnsTexture *t);

+ 74 - 27
source/lagui/la_tns_kernel.c

@@ -100,14 +100,18 @@ uniform mat4 mModel;\
 uniform mat4 mView;\
 in vec4 vVertex;\
 in vec4 vColor;\
-/*in vec4 vNormal;*/\
+in vec3 vNormal;\
 in vec2 vUV;\
 out vec4 fColor;\
 out vec2 fUV;\
+flat out vec3 fNormal;\
+out vec3 fGPos;\
 void main(){\
     gl_Position = mProjection * mView * mModel * vVertex;\
     fColor = vColor;\
     fUV=vUV;\
+    fGPos=vec3((mModel * vVertex).xyz);\
+    fNormal= (mModel * vec4(vNormal,0.)).xyz;\
 }";
 const char LA_IMM_FRAGMENT_SHADER[] = "#version 330\n\
 uniform sampler2D TexColor;\
@@ -115,8 +119,15 @@ uniform sampler2DMS TexColorMS;\
 uniform int TextureMode;\n\
 uniform int MultiplyColor;\n\
 uniform int SampleAmount;\n\
+uniform int UseNormal;\n\
+uniform vec3 uViewPos;\n\
 in vec4 fColor;\n\
 in vec2 fUV;\n\
+flat in vec3 fNormal;\n\
+in vec3 fGPos;\n\
+layout(location = 0) out vec4 outColor;\n\
+layout(location = 1) out vec3 outNormal;\n\
+layout(location = 2) out vec3 outGPos;\n\
 void main(){\n\
     vec4 color=vec4(1,0,1,1);\n\
     if(TextureMode==0){ color = fColor;}\n\
@@ -131,7 +142,17 @@ void main(){\n\
         color/=SampleAmount;\n\
         if(MultiplyColor!=0){color*=fColor;}\n\
     }\n\
-    gl_FragColor = color;\n\
+    if(UseNormal!=0){\n\
+        float light_factor=dot(fNormal,vec3(0,0,1));\n\
+        float view=dot(fNormal,fGPos-uViewPos);\n\
+        float factor=abs(light_factor);\n\
+        if(light_factor*view>0){ factor=0; }\n\
+        color=vec4(color.rgb*mix(0.2,1.,factor),color.a);\n\
+        vec3 oNormal=fNormal; if(view<0){ oNormal=-fNormal; }\n\
+        outNormal = oNormal;\n\
+    }\n\
+    outColor = color;\n\
+    outGPos = fGPos;\n\
 }";
 const char LA_RAY_VERTEX_SHADER[] = "#version 330\n\
 in vec3 vUV;\n\
@@ -146,12 +167,17 @@ uniform vec3 uViewDir;\n\
 uniform vec3 uViewPos;\n\
 uniform float uFOV;\n\
 in vec3 fViewDir;\n\
+uniform sampler2D TexColor;\n\
+uniform sampler2D TexNormal;\n\
+uniform sampler2D TexGPos;\n\
 void main(){\n\
     float d=dot(uViewDir,normalize(fViewDir));\n\
     float target=cos(uFOV/2.);\n\
     vec4 color=vec4(1.,1.,1.,1.); float mul=0.;\n\
     if(d<(target+0.005)&&d>target) mul=1.0;\n\
-    gl_FragColor = mul*color;\n\
+    vec2 uv=gl_FragCoord.xy/textureSize(TexColor,0);\n\
+    vec4 buffer_color=texture2D(TexColor,uv);\n\
+    gl_FragColor = mul*color+buffer_color;\n\
 }";
 const char LA_SCENE_VERTEX_SHADER[] = "#version 330\n\
 uniform mat4 mProjection;\n\
@@ -389,7 +415,7 @@ void tnsShaderMakeIndex(tnsShader *tns){
     tns->iProjection = glGetUniformLocation(program, "mProjection");
     tns->iProjectionInverse = glGetUniformLocation(program, "mProjectionInverse");
     tns->iView = glGetUniformLocation(program, "mView");
-    tns->iNormal = glGetUniformLocation(program, "mNormalScaler");
+    //tns->iNormal = glGetUniformLocation(program, "mNormalScaler");
     tns->iShadow = glGetUniformLocation(program, "mShadow");
 
     tns->iVertex = glGetAttribLocation(program, "vVertex");
@@ -398,10 +424,13 @@ void tnsShaderMakeIndex(tnsShader *tns){
     tns->iUV = glGetAttribLocation(program, "vUV");
 
     tns->iTexColor = glGetUniformLocation(program, "TexColor");
+    tns->iTexNormal = glGetUniformLocation(program, "TexNormal");
+    tns->iTexGPos = glGetUniformLocation(program, "TexGPos");
     tns->iTexColorMS = glGetUniformLocation(program, "TexColorMS");
     tns->iMultiplyColor = glGetUniformLocation(program, "MultiplyColor");
     tns->iTextureMode = glGetUniformLocation(program, "TextureMode");
     tns->iSampleAmount = glGetUniformLocation(program, "SampleAmount");
+    tns->iUseNormal = glGetUniformLocation(program, "UseNormal");
     if(tns->iTexColor>=0){glUniform1i(tns->iTexColor, 0);}
     if(tns->iTexColorMS>=0){glUniform1i(tns->iTexColorMS, 1);}
 
@@ -1368,6 +1397,14 @@ void tnsPopMatrix(){
 
 //========================================[KNL]
 
+void tnsSetRayShaderUniformTextures(tnsOffscreen* doff){
+    if(!doff)return;
+    tnsActiveTexture(GL_TEXTURE0); glBindTexture(doff->pColor[0]->GLTexType, doff->pColor[0]->GLTexHandle);
+    tnsActiveTexture(GL_TEXTURE1); glBindTexture(doff->pColor[1]->GLTexType, doff->pColor[1]->GLTexHandle);
+    tnsActiveTexture(GL_TEXTURE2); glBindTexture(doff->pColor[2]->GLTexType, doff->pColor[2]->GLTexHandle);
+    glUniform1i(T->RayShader->iTexColor, 0); glUniform1i(T->RayShader->iTexNormal, 1); glUniform1i(T->RayShader->iTexGPos, 2);
+}
+
 void tnsInitRenderKernel(int matrixStackLevel){
     tnsCommand *c;
     T = memAcquireHyper(sizeof(tnsMain));
@@ -1658,8 +1695,8 @@ void tnsDrawBatch(tnsBatch* batch, const char* OverrideCommand, real* OverrideUn
     if(cs->iNormal>=0){
         if(batch->HasNormal){
             glBindBuffer(GL_ARRAY_BUFFER, batch->NBO); glEnableVertexAttribArray(cs->iNormal);
-            glVertexAttribPointer(cs->iNormal, batch->NormalDimension, GL_FLOAT, 0, 0, 0);
-        }else{ glDisableVertexAttribArray(cs->iNormal); glVertexAttrib3f(cs->iNormal,0,0,1); }
+            glVertexAttribPointer(cs->iNormal, batch->NormalDimension, GL_FLOAT, 0, 0, 0); tnsUniformUseNormal(cs,T->SetUseNormal);
+        }else{ glDisableVertexAttribArray(cs->iNormal); glVertexAttrib3f(cs->iNormal,0,0,1); tnsUniformUseNormal(cs,0); }
     }
     if(cs->iColor>=0){
         if(batch->HasColor){
@@ -1844,6 +1881,11 @@ void tnsUseMultiplyColor(int enable){
     T->StateMultiplyColor=enable?1:0;
 }
 
+void tnsUniformUseNormal(tnsShader* s, int Use){
+    if(T->StateUseNormal!=Use){ T->StateUseNormal=Use; glUniform1i(s->iUseNormal,Use); }
+}
+void tnsUseNormal(int Use){ T->SetUseNormal=Use; }
+
 void tnsActiveTexture(GLenum tex){
     if (T->GlTextureSets != tex) glActiveTexture(tex);
     T->GlTextureSets = tex;
@@ -2336,11 +2378,11 @@ void tnsFlush(){
         if (cs->iNormal != -1){
             glBindBuffer(GL_ARRAY_BUFFER, T->NormalBufObject);
             if (c->UseNormal){
-                glEnableVertexAttribArray(cs->iNormal);
+                glEnableVertexAttribArray(cs->iNormal); tnsUniformUseNormal(cs,1);
             }
             glVertexAttribPointer(cs->iNormal, 3, GL_FLOAT, 0, 0, c->NormalBegin * sizeof(GLfloat));
-            if (!c->UseNormal && c->Dimensions == 2){
-                glDisableVertexAttribArray(cs->iNormal);
+            if (!c->UseNormal || c->Dimensions == 2){
+                glDisableVertexAttribArray(cs->iNormal); tnsUniformUseNormal(cs,0);
                 glVertexAttrib3f(cs->iNormal, 0, 0, -1);
             }
         }
@@ -2473,8 +2515,7 @@ void tnsDetach2DOffscreenBuffer(tnsOffscreen *target, GLuint which_attach_point)
 
 tnsOffscreen *tnsCreate2DOffscreen(int glInternalFormat, int w, int h, int Multisample, int WithDepth){
     tnsOffscreen *toff = tnsCreateOffscreenHandle();
-    tnsTexture *color;
-    tnsTexture *depth;
+    tnsTexture *color; tnsTexture *depth;
 
     if(glInternalFormat){
         color = tnsCreate2DTexture(glInternalFormat, w, h, Multisample);
@@ -2487,6 +2528,18 @@ tnsOffscreen *tnsCreate2DOffscreen(int glInternalFormat, int w, int h, int Multi
 
     return toff;
 }
+tnsOffscreen *tnsCreateDeferredOffscreen(int w, int h){
+    tnsOffscreen *toff = tnsCreateOffscreenHandle();
+    tnsTexture *color,*normal,*gpos; tnsTexture *depth;
+
+    color  = tnsCreate2DTexture(GL_RGBA8, w, h, 0);  tnsAttach2DOffscreenBuffer(toff, GL_COLOR_ATTACHMENT0, color);
+    normal = tnsCreate2DTexture(GL_RGB8, w, h, 0);   tnsAttach2DOffscreenBuffer(toff, GL_COLOR_ATTACHMENT1, normal);
+    gpos   = tnsCreate2DTexture(GL_RGB32F, w, h, 0); tnsAttach2DOffscreenBuffer(toff, GL_COLOR_ATTACHMENT2, gpos);
+
+    depth = tnsCreate2DTexture(GL_DEPTH_COMPONENT, w, h, 0); tnsAttach2DOffscreenBuffer(toff, GL_DEPTH_ATTACHMENT, depth);
+
+    return toff;
+}
 
 void tnsConfigureOffscreen(tnsOffscreen *off, int w, int h){
     tnsTexture* t=off->pColor[0];
@@ -2502,25 +2555,18 @@ void tnsDelete2DOffscreen(tnsOffscreen *o){
 
     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
 
-    if (o->pColor[1]){
-        tnsDetach2DOffscreenBuffer(o, GL_COLOR_ATTACHMENT1);
-        tnsDeleteTexture(o->pColor[1]);
-    }
-    if (o->pColor[2]){
-        tnsDetach2DOffscreenBuffer(o, GL_COLOR_ATTACHMENT2);
-        tnsDeleteTexture(o->pColor[2]);
+    for(int i=0;i<16;i++){
+        if(o->pColor[i]){
+            tnsDetach2DOffscreenBuffer(o, GL_COLOR_ATTACHMENT0+i);
+            tnsDeleteTexture(o->pColor[0]);
+        }
     }
-    if (o->pColor[3]){
-        tnsDetach2DOffscreenBuffer(o, GL_COLOR_ATTACHMENT3);
-        tnsDeleteTexture(o->pColor[3]);
+    
+    if(o->pDepth){
+        tnsDetach2DOffscreenBuffer(o, GL_DEPTH_ATTACHMENT);
+        tnsDeleteTexture(o->pDepth);
     }
 
-    tnsDetach2DOffscreenBuffer(o, GL_COLOR_ATTACHMENT0);
-    tnsDetach2DOffscreenBuffer(o, GL_DEPTH_ATTACHMENT);
-
-    tnsDeleteTexture(o->pColor[0]);
-    tnsDeleteTexture(o->pDepth);
-
     glDeleteFramebuffers(1, &o->FboHandle);
     //tnsDeleteTexture(o->pStencil);
 
@@ -3448,6 +3494,7 @@ void tnsApplyCameraView(int W, int H, tnsCamera *Camera){
 
     if (current_shader = tKnlGetActiveShader()){
         tnsShaderApplyView(current_shader, result);
+        if(current_shader->uViewPos>-1) glUniform3f(current_shader->uViewPos,LA_COLOR3(Camera->Base.Location));
     }
 }
 void tnsApplyShadowCameraView(tnsLight *Light){

+ 103 - 45
source/lagui/la_tns_mesh.c

@@ -60,6 +60,8 @@ tnsFace* tnsFillFace(tnsMeshObject* mo, int vertcount, ...){
     mo->totf++; return &mo->f[mo->totf-1];
 }
 void tnsFillFaceLoop(tnsFace* f, int i, int v){ f->loop[i]=v; }
+void tnsFillFaceNormal(tnsFace* f, real* normal){ tnsVectorSet3v(f->n, normal); }
+void tnsFillVertNormal(tnsVert* v, real* normal){ tnsVectorSet3v(v->n, normal); }
 
 int tnsMergeMeshObjects(tnsMeshObject* into, tnsMeshObject* mo){
     if(into->Base.Type!=TNS_OBJECT_MESH||mo->Base.Type!=TNS_OBJECT_MESH) return 0;
@@ -81,7 +83,7 @@ int tnsMergeMeshObjects(tnsMeshObject* into, tnsMeshObject* mo){
 tnsMeshObject* tnsDuplicateMeshObjects(tnsMeshObject* from){
     if(from->Base.Type!=TNS_OBJECT_MESH) return 0;
     tnsMeshObject* to = memAcquireHyper(sizeof(tnsMeshObject));
-    tnsInitObjectBase(to, from->Base.ParentObject?from->Base.ParentObject:from->Base.InRoot, from->Base.Name, TNS_OBJECT_MESH,0,0,0,0,0,0,0,0,1);
+    tnsInitObjectBase(to, from->Base.ParentObject?from->Base.ParentObject:from->Base.InRoot, from->Base.Name->Ptr, TNS_OBJECT_MESH,0,0,0,0,0,0,0,0,1);
     tnsCopyObjectTransformationsLocal(to,from);
     if(!from->totv){ return to; }
     to->totv=from->totv; to->tote=from->tote; to->totf=from->totf;
@@ -98,7 +100,7 @@ void tnsInitMeshPlane(tnsMeshObject* mo, real size){
     tnsInitMesh(mo, 4,0,1);
     tnsFillVert(mo, size, size,0); tnsFillVert(mo,-size, size,0);
     tnsFillVert(mo,-size,-size,0); tnsFillVert(mo, size,-size,0);
-    tnsFillFace(mo, 4, 0,1,2,3);
+    tnsFace* f=tnsFillFace(mo, 4, 0,1,2,3); tnsVector3d n={0,0,1}; tnsFillFaceNormal(f,n);
     mo->v[0].flags|=TNS_MESH_FLAG_SELECTED;
     mo->v[1].flags|=TNS_MESH_FLAG_SELECTED;
     mo->v[2].flags|=TNS_MESH_FLAG_SELECTED;
@@ -114,68 +116,79 @@ void tnsAddMMeshPlane(tnsMeshObject* mo, real size){
     tnsMMeshMakeFace4v(mo,mv1,mv2,mv3,mv4);
 }
 
-void tnsTrangulateFaceSimple(tnsMeshObject* mo, tnsFace* f, int* ebuf){
+void tnsTrangulateFaceSimple(tnsMeshObject* mo, int fi, tnsFace* f, int* ebuf){
     for(int i=0;i<f->looplen-2;i++){
-        ebuf[i*3]=f->loop[0];
+        ebuf[i*3]=f->loop[i];
         ebuf[i*3+1]=f->loop[i+1];
-        ebuf[i*3+2]=f->loop[i+2];
+        ebuf[i*3+2]=fi+mo->totv; //f->loop[i+2];
     }
 }
-void tnsTrangulateFaceSimpleM(tnsMeshObject* mo, tnsMFace* mf, int* ebuf){
+void tnsTrangulateFaceSimpleM(tnsMeshObject* mo, int fi, tnsMFace* mf, int* ebuf){
     tnsMVert* mv=0,*mvs; int i=0;
     for(laListItemPointer* lip=mf->l.pFirst;lip;lip=lip->pNext){
         laListItemPointer* next=lip->pNext; if(!next) next=mf->l.pFirst; tnsMEdge* me0=lip->p, *me1=next->p;
         if(next==mf->l.pLast){ break; }
         mvs=tnsMMeshEdgeStartingVert(me0,me1);
         if(!mv) mv=mvs;
-        ebuf[i*3]=mv->i; tnsMVert*mm=tnsMMeshEdgeAnotherVert(me0,mvs);
+        ebuf[i*3]=mvs->i; tnsMVert*mm=tnsMMeshEdgeAnotherVert(me0,mvs);
         ebuf[i*3+1]=mm->i;
-        ebuf[i*3+2]=tnsMMeshEdgeAnotherVert(me1,mm)->i; i++;
+        ebuf[i*3+2]=fi+mo->totmv; /*tnsMMeshEdgeAnotherVert(me1,mm)->i;*/ i++;
     }
 }
+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; } }
     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, &mo->f[i], pebuf); pebuf+=(mo->f[i].looplen-2)*3; } }
-    else{ for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ tnsTrangulateFaceSimpleM(mo, mf, pebuf); pebuf+=(mf->looplen-2)*3; } }
+    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++; } }
     *totelem = tottri;
     return ebuf;
 }
-float* tnsGetDrawingVertArray(tnsMeshObject* mo, int* r_totv, int DoIdColors, float** idcolors, int DoEditModeColors, float** editcolors, int** edgeelems, int* r_tote){
+float* tnsGetDrawingVertArray(tnsMeshObject* mo, int* r_tot_render_v, float** r_normals, float** idcolors, float** editcolors, int** edgeelems, int* r_tote){
     if(!mo->totv&&!mo->totmv) return 0;
-    int totv=mo->Mode==TNS_MESH_EDIT_MODE?mo->totmv:mo->totv; 
-    int tote=mo->Mode==TNS_MESH_EDIT_MODE?mo->totme:mo->tote; *r_tote=tote;
-    int extraverts=DoIdColors?mo->totme*2:0;
-    float* p=calloc(1,(totv+extraverts)*3*sizeof(float)); *r_totv=(totv+extraverts);
-    if(DoIdColors){ (*idcolors)=calloc(1,(totv+extraverts)*3*sizeof(float)); (*edgeelems)=calloc(1,(extraverts)*2*sizeof(int));}
-    if(DoEditModeColors){ (*editcolors)=calloc(1,totv*4*sizeof(float)*extraverts); }
-    if(mo->Mode==TNS_MESH_OBJECT_MODE){ for(int i=0;i<totv;i++){ p[i*3]=mo->v[i].p[0]; p[i*3+1]=mo->v[i].p[1]; p[i*3+2]=mo->v[i].p[2]; } }
-    else{ for(tnsMVert*mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ int i=mv->i;
-            p[i*3]=mv->p[0]; p[i*3+1]=mv->p[1]; p[i*3+2]=mv->p[2];
-            if(DoIdColors){
-                int id=i+1; real r=(real)((id & 0x000000FF)>>0)/255.0; real g=(real)((id & 0x0000FF00)>>8)/255.0; real b=(real)((id & 0x00FF0000)>>16)/255.0;
-                (*idcolors)[i*3]=r; (*idcolors)[i*3+1]=g; (*idcolors)[i*3+2]=b;
-            }
-            if(DoEditModeColors){
-                real* c=(mv->flags&TNS_MESH_FLAG_SELECTED)?laAccentColor(LA_BT_SVERTEX):laAccentColor(LA_BT_VERTEX);
-                (*editcolors)[i*4]=c[0]; (*editcolors)[i*4+1]=c[1]; (*editcolors)[i*4+2]=c[2]; (*editcolors)[i*4+3]=c[3];
-            }
+    int objmode=mo->Mode==TNS_MESH_OBJECT_MODE;
+    int totv=objmode?mo->totv:mo->totmv;
+    int tote=objmode?mo->tote:mo->totme; *r_tote=tote;
+    int totf=objmode?mo->totf:mo->totmf;
+    int extraverts=objmode?mo->totf:(mo->totme*2+mo->totmf);
+    float* p=calloc(1,(totv+extraverts)*3*sizeof(float)); *r_tot_render_v=(totv+extraverts);
+    float* n=calloc(1,(totv+extraverts)*3*sizeof(float)); *r_normals = n;
+
+    if(objmode){
+        for(int i=0;i<totv;i++){ tnsVectorSet3v(&p[i*3],mo->v[i].p); tnsVectorSet3v(&n[i*3],mo->v[i].n); }
+        int start=mo->totv*3; for(int i=0;i<totf;i++){
+            tnsVectorSet3v(&p[start+i*3],mo->v[mo->f[i].loop[mo->f[i].looplen-1]].p); tnsVectorSet3v(&n[start+i*3],mo->f[i].n);
+        }
+    }else{
+        (*idcolors)=calloc(1,(totv+extraverts)*3*sizeof(float));
+        (*edgeelems)=calloc(1,(extraverts)*2*sizeof(int));
+        (*editcolors)=calloc(1,totv*4*sizeof(float)*extraverts);
+        for(tnsMVert*mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ int i=mv->i;
+            tnsVectorSet3v(&p[i*3],mv->p); tnsVectorSet3v(&n[i*3],mv->n);
+            int id=i+1; real r=(real)((id & 0x000000FF)>>0)/255.0; real g=(real)((id & 0x0000FF00)>>8)/255.0; real b=(real)((id & 0x00FF0000)>>16)/255.0;
+            (*idcolors)[i*3]=r; (*idcolors)[i*3+1]=g; (*idcolors)[i*3+2]=b;
+            real* c=(mv->flags&TNS_MESH_FLAG_SELECTED)?laAccentColor(LA_BT_SVERTEX):laAccentColor(LA_BT_VERTEX);
+            (*editcolors)[i*4]=c[0]; (*editcolors)[i*4+1]=c[1]; (*editcolors)[i*4+2]=c[2]; (*editcolors)[i*4+3]=c[3];
         }
         for(tnsMEdge*me=mo->me.pFirst;me;me=me->Item.pNext){ int ei=me->i;
             (*edgeelems)[ei*2]=mo->totmv+ei*2; (*edgeelems)[ei*2+1]=mo->totmv+ei*2+1;
             float* eidcolor1=&(*idcolors)[(*edgeelems)[ei*2]*3], *eidcolor2=&(*idcolors)[(*edgeelems)[ei*2+1]*3];
             int id=ei+1; real r=(real)((id & 0x000000FF)>>0)/255.0; real g=(real)((id & 0x0000FF00)>>8)/255.0; real b=(real)((id & 0x00FF0000)>>16)/255.0;
-            eidcolor1[0]=r;eidcolor1[1]=g;eidcolor1[2]=b;
-            eidcolor2[0]=r;eidcolor2[1]=g;eidcolor2[2]=b;
-            int se1=(*edgeelems)[ei*2];   p[se1*3]=me->vl->p[0]; p[se1*3+1]=me->vl->p[1]; p[se1*3+2]=me->vl->p[2];
-            int se2=(*edgeelems)[ei*2+1]; p[se2*3]=me->vr->p[0]; p[se2*3+1]=me->vr->p[1]; p[se2*3+2]=me->vr->p[2];
+            tnsVectorSet3(eidcolor1,r,g,b); tnsVectorSet3(eidcolor2,r,g,b);
+            int se1=(*edgeelems)[ei*2];   tnsVectorSet3v(&p[se1*3],me->vl->p);
+            int se2=(*edgeelems)[ei*2+1]; tnsVectorSet3v(&p[se2*3],me->vr->p);
             float* eedcolor1=&(*editcolors)[(*edgeelems)[ei*2]*4], *eedcolor2=&(*editcolors)[(*edgeelems)[ei*2+1]*4];
             real* c=(me->flags&TNS_MESH_FLAG_SELECTED)?laAccentColor(LA_BT_SEDGE):laAccentColor(LA_BT_EDGE);
-            eedcolor1[0]=c[0]; eedcolor1[1]=c[1]; eedcolor1[2]=c[2]; eedcolor1[3]=c[3];
-            eedcolor2[0]=c[0]; eedcolor2[1]=c[1]; eedcolor2[2]=c[2]; eedcolor2[3]=c[3];
+            tnsVectorSet4v(eedcolor1,c); tnsVectorSet4v(eedcolor2,c);
+        }
+        int start=mo->totmv*3; int fi=0; for(tnsMFace*mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){
+            tnsMVert* sv=tnsGetMFaceLastVert(mf); tnsVectorSet3v(&p[start+fi*3],sv->p); tnsVectorSet3v(&n[start+fi*3],mf->n); fi++;
         }
     }
     return p;
@@ -194,18 +207,18 @@ 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,0.6};
+    real meshcolor[4]={0.8,0.8,0.8,1};
 
     int tottri; int* elem = tnsGetTriangulatedBatch(mo, &tottri);
     float* idcolors=0,*editcolors=0; int docolors=mo->Mode==TNS_MESH_EDIT_MODE;
-    int totv,tote; int* eelems=0;
-    float* v = tnsGetDrawingVertArray(mo,&totv,docolors,&idcolors,docolors,&editcolors,&eelems,&tote);
+    int totv,tote; int* eelems=0; float* n=0;
+    float* v = tnsGetDrawingVertArray(mo,&totv,&n,&idcolors,&editcolors,&eelems,&tote);
     if(!v){ if(elem){free(elem);} if(eelems){free(eelems);} return; }
 
-    mo->Batch = tnsCreateBatch(totv, 3, v, 0, 0, 4, editcolors);
+    mo->Batch = tnsCreateBatch(totv, 3, v, 3, n, 4, editcolors);
     tnsBatchCommand*c=tnsCreateCommand(mo->Batch, "body", tottri, 3, GL_TRIANGLES, elem, 0);
     tnsCommandUseUniformColor(c,meshcolor);
-    free(elem); free(v);
+    free(elem); free(v); free(n);
 
     if(mo->Mode==TNS_MESH_EDIT_MODE){
         elem=tnsGetEdgeBatch(mo); if(elem) {
@@ -243,7 +256,9 @@ void tnsDrawMeshObject(tnsMeshObject* mo, int DrawAsObjectSelection, int MeshSel
             color[2]=(real)((i & 0x00FF0000)>>16)/255.0;
             tnsDrawBatch(mo->Batch,"body",color,0);
         }else{
+            tnsUseNormal(1);
             tnsDrawBatch(mo->Batch,"body",0,0);
+            tnsUseNormal(0);
             if(mo->Mode==TNS_MESH_EDIT_MODE){
                 if(MeshSelectionMode==LA_CANVAS_SELECT_MODE_VERTS){
                     tnsDrawBatch(mo->Batch,"verts",0,0); tnsDrawBatch(mo->Batch,"lines",0,0);
@@ -258,6 +273,48 @@ void tnsDrawMeshObject(tnsMeshObject* mo, int DrawAsObjectSelection, int MeshSel
 tnsMFace* tnsMMeshNewFace(tnsMeshObject* mo){ tnsMFace* mf=memAcquireSimple(sizeof(tnsMFace)); mf->i=mo->totmf; mo->totmf++; lstAppendItem(&mo->mf,mf); return mf; }
 tnsMEdge* tnsMMeshNewEdge(tnsMeshObject* mo){ tnsMEdge* me=memAcquireSimple(sizeof(tnsMEdge)); me->i=mo->totme; mo->totme++; lstAppendItem(&mo->me,me); return me; }
 tnsMVert* tnsMMeshNewVert(tnsMeshObject* mo){ tnsMVert* mv=memAcquireSimple(sizeof(tnsMVert)); mv->i=mo->totmv; mo->totmv++; lstAppendItem(&mo->mv,mv); return mv; }
+void tnsMMeshCalculateNormalFrom(tnsMFace* mf){
+    tnsVector3d accum={0}; tnsVector3d d0,d1; tnsVector3d naccum={0},nn={0};if(mf->flags&TNS_MESH_FLAG_PICKED) return;
+    for(laListItemPointer* lip=mf->l.pFirst;lip;lip=lip->pNext){
+        laListItemPointer* next=lip->pNext; if(!next) next=mf->l.pFirst; tnsMEdge* me0=lip->p, *me1=next->p;
+        tnsMVert* v0=tnsMMeshEdgeStartingVert(me0,me1); tnsMVert* v1=tnsMMeshEdgeShareVert(me0,me1); tnsMVert* v2=tnsMMeshEdgeAnotherVert(me1, v1);
+        tnsVectorAccum3d(accum, v0->p); tnsVectorAccum3d(accum, v1->p); tnsVectorAccum3d(accum, v2->p);
+        tnsVectorMinus3d(d0,v0->p,v1->p); tnsVectorMinus3d(d1,v2->p,v1->p); real len=tnsVectorCross3d(nn,d0,d1); tnsVectorMultiSelf3d(nn, len);
+        tnsVectorAccum3d(naccum, nn); 
+        //if(v0==me0->vr){ me0->flags|=TNS_MESH_FLAG_LOOP_REVERSE; } // TODO: consistency not implemented.
+    }
+    tnsNormalize3d(mf->n, naccum); tnsVectorMulti3d(mf->c, accum, 1.0/(mf->looplen+2));
+    mf->flags|=TNS_MESH_FLAG_PICKED;
+    if(mf->flags&TNS_MESH_FLAG_SELECTED){
+        for(laListItemPointer* lip=mf->l.pFirst;lip;lip=lip->pNext){
+            tnsMEdge* me=lip->p; tnsMFace* of=((mf==me->fl)?me->fr:me->fl);
+            if(of) tnsMMeshCalculateNormalFrom(of);
+        }
+    }
+}
+void tnsMMeshCalculateFaceVertNormal(tnsMFace* mf){
+    tnsVector3d accum={0};
+    for(laListItemPointer* lip=mf->l.pFirst;lip;lip=lip->pNext){
+        laListItemPointer* next=lip->pNext; if(!next) next=mf->l.pFirst; tnsMEdge* me0=lip->p, *me1=next->p;
+        tnsMVert* mv=tnsMMeshEdgeStartingVert(me0,me1); int fcount=0;
+        for(laListItemPointer* el=mv->elink.pFirst;el;el=el->pNext){ tnsMEdge* ve=lip->p;
+            if(ve->fl){ tnsVectorAccum3d(accum, ve->fl->n); fcount++; }
+            if(ve->fr){ tnsVectorAccum3d(accum, ve->fr->n); fcount++; }
+        }
+        if(!fcount) accum[2]=1; else tnsNormalizeSelf3d(accum); 
+        tnsVectorSet3v(mv->n, accum);
+    }
+}
+int tnsMMeshCalculateNormal(tnsMeshObject* mo){
+    tnsMMeshClearExtraFlags(mo); int ran=0;
+    for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ if((!(mf->flags&TNS_MESH_FLAG_SELECTED))||(mf->flags&TNS_MESH_FLAG_PICKED)) continue;
+        tnsMMeshCalculateNormalFrom(mf); ran=1;
+    }
+    for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ if(!(mf->flags&TNS_MESH_FLAG_PICKED)) continue;
+        tnsMMeshCalculateFaceVertNormal(mf); ran=1;
+    }
+    return ran;
+}
 void tnsMMeshEdgeAssignVerts(tnsMEdge* me,tnsMVert* mv1,tnsMVert* mv2){
     if(me->vl||me->vr){ return; }  //if((me->vl==mv1&&me->vr=mv2) || (me->vl==mv2&&me->vr=mv1))
     me->vl=mv1; me->vr=mv2;
@@ -452,13 +509,13 @@ void tnsMMeshFromMesh(tnsMeshObject* mo){
         for(int j=0;j<ehv->next;j++){ tnsMEdge*me=tnsMMeshNewEdge(mo); ehv->e[j].me=me; }
     }
     for(int i=0;i<mo->totv;i++){ tnsVert*v=&mo->v[i]; tnsMVert*mv=tnsMMeshNewVert(mo); eh->vl[i].mv=mv;
-        mv->p[0]=mo->v[i].p[0]; mv->p[1]=mo->v[i].p[1]; mv->p[2]=mo->v[i].p[2]; mv->flags=mo->v[i].flags; }
+        tnsVectorSet3v(mv->p,mo->v[i].p); mv->flags=mo->v[i].flags; tnsVectorSet3v(mv->n,v->n); }
     for(int i=0;i<mo->totf;i++){
         tnsFace* f=&mo->f[i]; tnsMFace* mf=tnsMMeshNewFace(mo); mf->flags=f->flags;
         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);
+            tnsMMeshFaceAddEdge(mf,me); tnsVectorSet3v(mf->n,f->n);
         }
     }
     tnsMMeshEnsureSelectionFromVerts(mo);
@@ -469,12 +526,13 @@ void tnsMeshFromMMesh(tnsMeshObject* mo){
     tnsInitMesh(mo, mo->totmv, 0, mo->totmf); int i=0;
     /* Vertex index should already correct. */
     //for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ mv->i=i; i++; }
-    for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ tnsVert* v=tnsFillVert(mo, mv->p[0], mv->p[1], mv->p[2]); v->flags=mv->flags; }
-    for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ tnsFace* f=tnsFillFace(mo, mf->looplen, -1);  f->flags=mf->flags;
+    for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ tnsVert* v=tnsFillVert(mo, LA_COLOR3(mv->p)); tnsFillVertNormal(v,mv->n); v->flags=mv->flags; }
+    for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ tnsFace* f=tnsFillFace(mo, mf->looplen, -1); f->flags=mf->flags;
         int j=0; for(laListItemPointer* lip=mf->l.pFirst;lip;lip=lip->pNext){
             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);
     }
     mo->totv=mo->totmv; mo->totf=mo->totmf;
     mo->maxv=mo->totv; mo->maxe=mo->tote; mo->maxf=mo->totf;
@@ -502,9 +560,9 @@ int tnsMMeshAnySelected(tnsMeshObject* mo){
     for(tnsMEdge* me=mo->me.pFirst;me;me=me->Item.pNext){ if(me->flags&TNS_MESH_FLAG_SELECTED) return 1; }
     for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ if(mf->flags&TNS_MESH_FLAG_SELECTED) return 1; } return 0;
 }
-void tnsMMeshClearPickedFlags(tnsMeshObject* mo){
+void tnsMMeshClearExtraFlags(tnsMeshObject* mo){
     for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ mv->flags&=(~TNS_MESH_FLAG_PICKED); }
-    for(tnsMEdge* me=mo->me.pFirst;me;me=me->Item.pNext){ me->flags&=(~TNS_MESH_FLAG_PICKED); }
+    for(tnsMEdge* me=mo->me.pFirst;me;me=me->Item.pNext){ me->flags&=(~TNS_MESH_FLAG_PICKED); /*me->flags&=(~TNS_MESH_FLAG_LOOP_REVERSE);*/ }
     for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ mf->flags&=(~TNS_MESH_FLAG_PICKED); }
 }
 void tnsMMeshDeselectAll(tnsMeshObject* mo){

+ 16 - 1
source/lagui/resources/la_modelling.c

@@ -962,7 +962,7 @@ void la_FillIslandFromVert(MIslandInfo* ii, tnsMVert* mv, int SelectMode){
     if(connections>2) ii->HasBranches=1;
 }
 void la_GetSelectionIslands(tnsMeshObject* mo, MMakeData* md, int SelectMode){
-    tnsMMeshClearPickedFlags(mo);
+    tnsMMeshClearExtraFlags(mo);
     for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){
         if(M_SHOULD_INCL_PRIM(mv)){ MIslandInfo* ii=la_NewMIsland(md); la_FillIslandFromVert(ii,mv,SelectMode); }
     }
@@ -1193,6 +1193,20 @@ int OPINV_Duplicate(laOperator *a, laEvent *e){
     return LA_FINISHED;
 }
 
+int OPINV_RecalculateNormals(laOperator *a, laEvent *e){
+    if(!a->This || !a->This->EndInstance){ return 0; }
+    laCanvasExtra* ex=a->This->EndInstance; tnsCamera*c=ex->ViewingCamera; laUiItem* ui=ex->ParentUi;
+    tnsObject*root=ui?ui->PP.EndInstance:0; if(!root) return LA_CANCELED;
+    tnsMeshObject* mo=root->Active; int ran=0;
+    
+    if(!mo || mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode!=TNS_MESH_EDIT_MODE){ return LA_CANCELED; }
+
+    ran=tnsMMeshCalculateNormal(mo);
+    
+    if(ran){ tnsInvalidateMeshBatch(mo); laRecordAndPush(0,"tns.world","Recalculate Normals",TNS_HINT_GEOMETRY); laNotifyUsers("tns.world"); }
+    
+    return LA_FINISHED;
+}
 
 void la_RegisterModellingOperators(){
     laPropContainer *pc; laProp *p;
@@ -1219,4 +1233,5 @@ void la_RegisterModellingOperators(){
     laCreateOperatorType("M_separate", "Separate", "Separate mesh parts", 0, 0, 0, OPINV_Separate, 0, 0, 0);
     laCreateOperatorType("M_combine", "Combine", "Combine mesh objects", 0, 0, 0, OPINV_Combine, 0, 0, 0);
     laCreateOperatorType("M_duplicate", "Duplicate", "Duplicate objects", 0, 0, 0, OPINV_Duplicate, OPMOD_Transformation, 0, 0);
+    laCreateOperatorType("M_recalculate_normals", "Recalculate Normals", "Recalculate normals", 0, 0, 0, OPINV_RecalculateNormals, 0, 0, 0);
 }

+ 1 - 1
source/lagui/resources/la_properties.c

@@ -1370,7 +1370,7 @@ void la_RegisterInternalProps(){
         laAddSubGroup(p, "l", "Loop", "Loop", "tns_loop",0,0,0,-1,0,0,0,0,0,0,offsetof(tnsMFace, l),0);
         laAddIntProperty(p,"looplen","Loop Length","Loop length",0,0,0,0,0,0,0,0,offsetof(tnsMFace, looplen),0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY)->ElementBytes=2;
         laAddFloatProperty(p, "n", "Normal", "Normal", 0, "X,Y,Z", 0,0,0,0,0,0, offsetof(tnsMFace, n),0,0,3,0,0,0,0,0,0,0,0);
-        laAddFloatProperty(p, "gn", "Global Normal", "Global normal", 0, "X,Y,Z", 0,0,0,0,0,0, offsetof(tnsMFace, gn),0,0,3,0,0,0,0,0,0,0,0);
+        //laAddFloatProperty(p, "gn", "Global Normal", "Global normal", 0, "X,Y,Z", 0,0,0,0,0,0, offsetof(tnsMFace, gn),0,0,3,0,0,0,0,0,0,0,0);
         laAddFloatProperty(p, "c", "Center", "Center", 0, "X,Y,Z", 0,0,0,0,0,0, offsetof(tnsMFace, c),0,0,3,0,0,0,0,0,0,0,0);
     }
     p = laAddPropertyContainer("tns_medge_link", "MEdge Link", "MEdge Link", 0, 0, sizeof(laListItemPointer), 0, 0, 0);{

+ 13 - 9
source/lagui/resources/la_widgets_viewers.c

@@ -8,7 +8,7 @@ extern struct _tnsMain *T;
 extern tnsFontManager *FM;
 
 
-void la_RootObjectDrawFullscreenQuad(tnsCamera* c, real Aspect){
+void la_RootObjectDrawFullscreenQuad(tnsOffscreen* DeferredOffScr,tnsCamera* c, real Aspect){
     real FOV = c->FOV;
     tnsMatrix44d mat;
     tnsGetMVMatrix(mat);
@@ -22,6 +22,7 @@ void la_RootObjectDrawFullscreenQuad(tnsCamera* c, real Aspect){
     vv[0]=tan(-fovh);vv[1]=tan(fovv); tnsApplyRotation43d(vdTL,mat,vv);
     tnsUseRayShader();
     tnsEnableShaderv(T->RayShader);
+    tnsSetRayShaderUniformTextures(DeferredOffScr);
     real uv[12]; tnsMakeQuad3d(uv, LA_COLOR3(vdTL), LA_COLOR3(vdTR), LA_COLOR3(vdBR), LA_COLOR3(vdBL));
     real vt[8]; tnsMakeQuad2d(vt, -1,1, 1,1, 1,-1, -1,-1);
     tnsTexCoordArray3d(uv,4);
@@ -49,7 +50,9 @@ void la_RootObjectDraw(laBoxedTheme *bt, tnsObject *root, laUiItem* ui){
 
     if (!e->OffScr || e->OffScr->pColor[0]->Height != ui->B - ui->U || e->OffScr->pColor[0]->Width != ui->R - ui->L){
         if (e->OffScr) tnsDelete2DOffscreen(e->OffScr);
+        if (e->DeferredOffScr) tnsDelete2DOffscreen(e->DeferredOffScr);
         e->OffScr = tnsCreate2DOffscreen(GL_RGBA, W, H, MAIN.PanelMultisample ,1);
+        e->DeferredOffScr = tnsCreateDeferredOffscreen(W,H);
     }
 
     if (0){//(e->CurrentScene && e->CurrentScene->ActiveSun){
@@ -117,7 +120,7 @@ void la_RootObjectDraw(laBoxedTheme *bt, tnsObject *root, laUiItem* ui){
     //    tnsClearAll();
     //}
 
-    tnsDrawToOffscreen(e->OffScr,1,0);
+    tnsDrawToOffscreen(e->DeferredOffScr,3,TNS_ATTACHMENT_ARRAY_0_1_2);
     tnsViewportWithScissor(0, 0, W, H);
     tnsClearColorv(laThemeColor(bt,LA_BT_NORMAL));
     tnsClearAll();
@@ -126,21 +129,18 @@ void la_RootObjectDraw(laBoxedTheme *bt, tnsObject *root, laUiItem* ui){
     tnsPushMatrix();
     tnsPopMatrix(); //those are necessary when ui is the first in list;
 
-    //glEnable(GL_DEPTH_TEST);
-        
+    glEnable(GL_DEPTH_TEST); glDisable(GL_BLEND);
     tnsEnableShaderv(T->immShader);
     tnsUnbindTexture(); tnsUniformUseTexture(T->immShader,0,0); tnsUseMultiplyColor(0);
     glPointSize(6); glLineWidth(3);
     tnsDrawObjectTree(root, 0, 0, e->SelectMode);
-    glPointSize(1); glLineWidth(10);
+    glPointSize(1); glLineWidth(2);
 
     glDepthMask(GL_FALSE); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); //glEnable(GL_POLYGON_OFFSET_LINE); glPolygonOffset(1,1);
     tnsDrawObjectTree(root, root->Active, 1, 0);
     glDepthMask(GL_TRUE); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
     glLineWidth(1);
 
-    tnsUseImmShader();
-
     //laInvoke(0,"M_select",e,&ui->ExtraPP,0,0);
 
     //glColorMask(GL_FALSE, GL_FALSE,GL_FALSE,GL_FALSE);
@@ -156,9 +156,10 @@ void la_RootObjectDraw(laBoxedTheme *bt, tnsObject *root, laUiItem* ui){
         tnsFlush();
     }
 
-    //glDisable(GL_DEPTH_TEST);
+    glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND);
 
-    //la_RootObjectDrawFullscreenQuad(c, (real)W/(real)H);
+    tnsDrawToOffscreen(e->OffScr, 1,0);
+    la_RootObjectDrawFullscreenQuad(e->DeferredOffScr, c, (real)W/(real)H);
 }
 void la_RootObjectDrawOverlay(laUiItem *ui, int h){
     laCanvasExtra *e = ui->Extra;
@@ -456,6 +457,7 @@ void laDefault3DViewOverlay(laUiItem *ui){
         laShowItem(gu,gc,&ui->ExtraPP,"_this_M_separate");
         laShowItem(gu,gc,&ui->ExtraPP,"_this_M_combine");
         laShowItem(gu,gc,&ui->ExtraPP,"_this_M_duplicate");
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_recalculate_normals");
     }
 
     g = laMakeFoldableGroup(uil, cr, "Scene Info", 0, 1);{
@@ -1003,6 +1005,7 @@ void la_RegisterUiTypesViewerWidgets(){
         laAddOperatorProperty(pc, "_this_M_separate", "Separate", "Separate mesh parts", "M_separate", 0, 0);
         laAddOperatorProperty(pc, "_this_M_combine", "Combine", "Combine mesh objects", "M_combine", 0, 0);
         laAddOperatorProperty(pc, "_this_M_duplicate", "Duplicate", "Duplicate objects", "M_duplicate", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_recalculate_normals", "Recalculate Normals", "Recalculate normals", "M_recalculate_normals", 0, 0);
     }
 
     km = &ct->KeyMapper;
@@ -1038,4 +1041,5 @@ void la_RegisterUiTypesViewerWidgets(){
     laAssignNewKey(km, 0, "M_separate", LA_KM_SEL_UI_EXTRA, 0, LA_KEY_DOWN, 'p', 0);
     laAssignNewKey(km, 0, "M_combine", LA_KM_SEL_UI_EXTRA, LA_KEY_CTRL, LA_KEY_DOWN, 'j', 0);
     laAssignNewKey(km, 0, "M_duplicate", LA_KM_SEL_UI_EXTRA, LA_KEY_SHIFT, LA_KEY_DOWN, 'd', 0);
+    laAssignNewKey(km, 0, "M_recalculate_normals", LA_KM_SEL_UI_EXTRA, LA_KEY_CTRL, LA_KEY_DOWN, 'n', 0);
 }