LCOV - code coverage report
Current view: top level - engine/render - normal.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 4.5 % 223 10
Test Date: 2025-07-03 06:37:36 Functions: 18.8 % 16 3

            Line data    Source code
       1              : /**
       2              :  * @file normal.cpp: cube geometry normal interpolation
       3              :  *
       4              :  * cube geometry in the libprimis engine is faceted, only allowing 8 ticks of
       5              :  * movement; as a result, normal vectors of geometry are not very smooth
       6              :  *
       7              :  * to resolve this, adjacent cube faces with low differences in their angle can
       8              :  * have their faces "merged" by interpolating the normal maps of their respective
       9              :  * faces
      10              :  *
      11              :  * this is controlled by the lerp variables and is generally uniformly done for
      12              :  * all geometry on the map; see `lerpangle` for the threshold variable
      13              :  */
      14              : #include "../libprimis-headers/cube.h"
      15              : #include "../../shared/geomexts.h"
      16              : 
      17              : #include "octarender.h"
      18              : 
      19              : #include "world/octaworld.h"
      20              : #include "world/world.h"
      21              : 
      22              : struct normalkey final
      23              : {
      24              :     vec pos;
      25              :     int smooth;
      26              : 
      27            0 :     bool operator==(const normalkey &k) const
      28              :     {
      29            0 :         return k.pos == pos && smooth == k.smooth;
      30              :     }
      31              : };
      32              : 
      33              : template<>
      34              : struct std::hash<normalkey>
      35              : {
      36            0 :     size_t operator()(const normalkey &k) const
      37              :     {
      38              :         auto vechash = std::hash<vec>();
      39            0 :         return vechash(k.pos);
      40              :     }
      41              : };
      42              : 
      43              : namespace //internal functionality not seen by other files
      44              : {
      45              :     struct normalgroup final
      46              :     {
      47              :         vec pos;
      48              :         int smooth, flat, normals, tnormals;
      49              : 
      50              :         normalgroup() : smooth(0), flat(0), normals(-1), tnormals(-1) {}
      51            0 :         normalgroup(const normalkey &key) : pos(key.pos), smooth(key.smooth), flat(0), normals(-1), tnormals(-1) {}
      52              :     };
      53              : 
      54              :     struct normal final
      55              :     {
      56              :         int next;
      57              :         vec surface;
      58              :     };
      59              : 
      60              :     struct tnormal final
      61              :     {
      62              :         int next;
      63              :         float offset;
      64              :         std::array<int, 2> normals;
      65              :         std::array<normalgroup *, 2> groups;
      66              :     };
      67              : 
      68              :     std::unordered_map<normalkey, normalgroup> normalgroups;
      69              :     std::vector<normal> normals;
      70              :     std::vector<tnormal> tnormals;
      71              :     std::vector<int> smoothgroups;
      72              : 
      73              :     VARR(lerpangle, 0, 44, 180); //max angle to merge octree faces' normals smoothly
      74              : 
      75              :     bool usetnormals = true;
      76              : 
      77            0 :     int addnormal(const vec &pos, int smooth, const vec &surface)
      78              :     {
      79            0 :         normalkey key = { pos, smooth };
      80            0 :         auto itr = normalgroups.find(key);
      81            0 :         if(itr == normalgroups.end())
      82              :         {
      83            0 :             itr = normalgroups.insert( { key, normalgroup(key) } ).first;
      84              :         }
      85            0 :         normal n;
      86            0 :         n.next = (*itr).second.normals;
      87            0 :         n.surface = surface;
      88            0 :         normals.push_back(n);
      89            0 :         return (*itr).second.normals = normals.size()-1;
      90              :     }
      91              : 
      92            0 :     void addtnormal(const vec &pos, int smooth, float offset, int normal1, int normal2, const vec &pos1, const vec &pos2)
      93              :     {
      94            0 :         normalkey key = { pos, smooth };
      95            0 :         auto itr = normalgroups.find(key);
      96            0 :         if(itr == normalgroups.end())
      97              :         {
      98            0 :             itr = normalgroups.insert( { key, normalgroup(key) } ).first;
      99              :         }
     100              :         tnormal n;
     101            0 :         n.next = (*itr).second.tnormals;
     102            0 :         n.offset = offset;
     103            0 :         n.normals[0] = normal1;
     104            0 :         n.normals[1] = normal2;
     105            0 :         normalkey key1 = { pos1, smooth },
     106            0 :                   key2 = { pos2, smooth };
     107            0 :         n.groups[0] = &((*normalgroups.find(key1)).second);
     108            0 :         n.groups[1] = &((*normalgroups.find(key2)).second);
     109            0 :         tnormals.push_back(n);
     110            0 :         (*itr).second.tnormals = tnormals.size()-1;
     111            0 :     }
     112              : 
     113            0 :     int addnormal(const vec &pos, int smooth, int axis)
     114              :     {
     115            0 :         normalkey key = { pos, smooth };
     116            0 :         auto itr = normalgroups.find(key);
     117            0 :         if(itr == normalgroups.end())
     118              :         {
     119            0 :             itr = normalgroups.insert( { key, normalgroup(key) } ).first;
     120              :         }
     121            0 :         (*itr).second.flat += 1<<(4*axis);
     122            0 :         return axis - 6;
     123              :     }
     124              : 
     125            0 :     void findnormal(const normalgroup &g, float lerpthreshold, const vec &surface, vec &v)
     126              :     {
     127            0 :         v = vec(0, 0, 0);
     128            0 :         int total = 0;
     129              :         //check if abs value of x component of the surface normal is greater than the lerp threshold
     130              :         //note the assignments to the int n are bitshifted to pack all three axes within a single int
     131            0 :         if(surface.x >= lerpthreshold)
     132              :         {
     133            0 :             int n = (g.flat>>4)&0xF;
     134            0 :             v.x += n; total += n;
     135              :         }
     136            0 :         else if(surface.x <= -lerpthreshold)
     137              :         {
     138            0 :             int n = g.flat&0xF;
     139            0 :             v.x -= n;
     140            0 :             total += n;
     141              :         }
     142              :         //ditto y component
     143            0 :         if(surface.y >= lerpthreshold)
     144              :         {
     145            0 :             int n = (g.flat>>12)&0xF;
     146            0 :             v.y += n;
     147            0 :             total += n;
     148              :         }
     149            0 :         else if(surface.y <= -lerpthreshold)
     150              :         {
     151            0 :             int n = (g.flat>>8)&0xF;
     152            0 :             v.y -= n;
     153            0 :             total += n;
     154              :         }
     155              :         //ditto z component
     156            0 :         if(surface.z >= lerpthreshold)
     157              :         {
     158            0 :             int n = (g.flat>>20)&0xF;
     159            0 :             v.z += n;
     160            0 :             total += n;
     161              :         }
     162            0 :         else if(surface.z <= -lerpthreshold)
     163              :         {
     164            0 :             int n = (g.flat>>16)&0xF;
     165            0 :             v.z -= n;
     166            0 :             total += n;
     167              :         }
     168              : 
     169            0 :         for(int cur = g.normals; cur >= 0;)
     170              :         {
     171            0 :             normal &o = normals[cur];
     172            0 :             if(o.surface.dot(surface) >= lerpthreshold)
     173              :             {
     174            0 :                 v.add(o.surface);
     175            0 :                 total++;
     176              :             }
     177            0 :             cur = o.next;
     178              :         }
     179            0 :         if(total > 1)
     180              :         {
     181            0 :             v.normalize();
     182              :         }
     183            0 :         else if(!total)
     184              :         {
     185            0 :             v = surface;
     186              :         }
     187            0 :     }
     188              : 
     189            0 :     bool findtnormal(const normalgroup &g, float lerpthreshold, const vec &surface, vec &v)
     190              :     {
     191            0 :         float bestangle = lerpthreshold;
     192            0 :         const tnormal *bestnorm = nullptr;
     193            0 :         for(int cur = g.tnormals; cur >= 0;)
     194              :         {
     195            0 :             const tnormal &o = tnormals[cur];
     196              :             const std::array<vec, 6> flats = { vec(-1,  0,  0),
     197              :                                                vec( 1,  0,  0),
     198              :                                                vec( 0, -1,  0),
     199              :                                                vec( 0,  1,  0),
     200              :                                                vec( 0,  0, -1),
     201            0 :                                                vec( 0,  0,  1) };
     202            0 :             vec n1 = o.normals[0] < 0 ? flats[o.normals[0]+6] : normals[o.normals[0]].surface,
     203            0 :                 n2 = o.normals[1] < 0 ? flats[o.normals[1]+6] : normals[o.normals[1]].surface,
     204            0 :                 nt;
     205            0 :             nt.lerp(n1, n2, o.offset).normalize();
     206            0 :             float tangle = nt.dot(surface);
     207            0 :             if(tangle >= bestangle)
     208              :             {
     209            0 :                 bestangle = tangle;
     210            0 :                 bestnorm = &o;
     211              :             }
     212            0 :             cur = o.next;
     213              :         }
     214            0 :         if(!bestnorm)
     215              :         {
     216            0 :             return false;
     217              :         }
     218            0 :         vec n1, n2;
     219            0 :         findnormal(*bestnorm->groups[0], lerpthreshold, surface, n1);
     220            0 :         findnormal(*bestnorm->groups[1], lerpthreshold, surface, n2);
     221            0 :         v.lerp(n1, n2, bestnorm->offset).normalize();
     222            0 :         return true;
     223              :     }
     224              : 
     225              :     VARR(lerpsubdiv, 0, 2, 4);      //Linear intERPolation SUBDIVisions
     226              :     VARR(lerpsubdivsize, 4, 4, 128);//Linear intERPolation SUBDIVision cube SIZE
     227              : 
     228            0 :     void addnormals(const cube &c, const ivec &o, int size)
     229              :     {
     230            0 :         if(c.children)
     231              :         {
     232            0 :             size >>= 1;
     233            0 :             for(size_t i = 0; i < c.children->size(); ++i)
     234              :             {
     235            0 :                 addnormals((*c.children)[i], ivec(i, o, size), size);
     236              :             }
     237            0 :             return;
     238              :         }
     239            0 :         else if(c.isempty())
     240              :         {
     241            0 :             return;
     242              :         }
     243            0 :         vec pos[Face_MaxVerts];
     244              :         int norms[Face_MaxVerts],
     245            0 :             tj = usetnormals && c.ext ? c.ext->tjoints : -1, vis;
     246            0 :         for(int i = 0; i < 6; ++i)
     247              :         {
     248            0 :             if((vis = visibletris(c, i, o, size)))
     249              :             {
     250            0 :                 if(c.texture[i] == Default_Sky)
     251              :                 {
     252            0 :                     continue;
     253              :                 }
     254              : 
     255            0 :                 std::array<vec, 2> planes;
     256            0 :                 int numverts = c.ext ? c.ext->surfaces[i].numverts&Face_MaxVerts : 0,
     257            0 :                     convex = 0,
     258            0 :                     numplanes = 0;
     259            0 :                 if(numverts)
     260              :                 {
     261            0 :                     vertinfo *verts = c.ext->verts() + c.ext->surfaces[i].verts;
     262            0 :                     vec vo(static_cast<ivec>(o).mask(~0xFFF));
     263            0 :                     for(int j = 0; j < numverts; ++j)
     264              :                     {
     265            0 :                         vertinfo &v = verts[j];
     266            0 :                         pos[j] = vec(v.x, v.y, v.z).mul(1.0f/8).add(vo);
     267              :                     }
     268            0 :                     if(!(c.merged&(1<<i)) && !flataxisface(c, i))
     269              :                     {
     270            0 :                         convex = faceconvexity(verts, numverts, size);
     271              :                     }
     272              :                 }
     273            0 :                 else if(c.merged&(1<<i))
     274              :                 {
     275            0 :                     continue;
     276              :                 }
     277              :                 else
     278              :                 {
     279            0 :                     std::array<ivec, 4> v;
     280            0 :                     genfaceverts(c, i, v);
     281            0 :                     if(!flataxisface(c, i))
     282              :                     {
     283            0 :                         convex = faceconvexity(v);
     284              :                     }
     285            0 :                     int order = vis&4 || convex < 0 ? 1 : 0;
     286            0 :                     vec vo(o);
     287            0 :                     pos[numverts++] = static_cast<vec>(v[order]).mul(size/8.0f).add(vo);
     288            0 :                     if(vis&1)
     289              :                     {
     290            0 :                         pos[numverts++] = static_cast<vec>(v[order+1]).mul(size/8.0f).add(vo);
     291              :                     }
     292            0 :                     pos[numverts++] = static_cast<vec>(v[order+2]).mul(size/8.0f).add(vo);
     293            0 :                     if(vis&2)
     294              :                     {
     295            0 :                         pos[numverts++] = static_cast<vec>(v[(order+3)&3]).mul(size/8.0f).add(vo);
     296              :                     }
     297              :                 }
     298              : 
     299            0 :                 if(!flataxisface(c, i))
     300              :                 {
     301            0 :                     planes[numplanes++].cross(pos[0], pos[1], pos[2]).normalize();
     302            0 :                     if(convex)
     303              :                     {
     304            0 :                         planes[numplanes++].cross(pos[0], pos[2], pos[3]).normalize();
     305              :                     }
     306              :                 }
     307              : 
     308            0 :                 const VSlot &vslot = lookupvslot(c.texture[i], false);
     309            0 :                 int smooth = vslot.slot->smooth;
     310              : 
     311            0 :                 if(!numplanes)
     312              :                 {
     313            0 :                     for(int k = 0; k < numverts; ++k)
     314              :                     {
     315            0 :                         norms[k] = addnormal(pos[k], smooth, i);
     316              :                     }
     317              :                 }
     318            0 :                 else if(numplanes==1)
     319              :                 {
     320            0 :                     for(int k = 0; k < numverts; ++k)
     321              :                     {
     322            0 :                         norms[k] = addnormal(pos[k], smooth, planes[0]);
     323              :                     }
     324              :                 }
     325              :                 else
     326              :                 {
     327            0 :                     vec avg = vec(planes[0]).add(planes[1]).normalize();
     328            0 :                     norms[0] = addnormal(pos[0], smooth, avg);
     329            0 :                     norms[1] = addnormal(pos[1], smooth, planes[0]);
     330            0 :                     norms[2] = addnormal(pos[2], smooth, avg);
     331            0 :                     for(int k = 3; k < numverts; k++)
     332              :                     {
     333            0 :                         norms[k] = addnormal(pos[k], smooth, planes[1]);
     334              :                     }
     335              :                 }
     336              : 
     337            0 :                 while(tj >= 0 && tjoints[tj].edge < i*(Face_MaxVerts+1))
     338              :                 {
     339            0 :                     tj = tjoints[tj].next;
     340              :                 }
     341            0 :                 while(tj >= 0 && tjoints[tj].edge < (i+1)*(Face_MaxVerts+1))
     342              :                 {
     343            0 :                     int edge = tjoints[tj].edge,
     344            0 :                         e1 = edge%(Face_MaxVerts+1),
     345            0 :                         e2 = (e1+1)%numverts;
     346            0 :                     const vec &v1 = pos[e1],
     347            0 :                               &v2 = pos[e2];
     348            0 :                     ivec d(vec(v2).sub(v1).mul(8));
     349            0 :                     int axis = std::abs(d.x) > std::abs(d.y) ? (std::abs(d.x) > std::abs(d.z) ? 0 : 2) : (std::abs(d.y) > std::abs(d.z) ? 1 : 2);
     350            0 :                     if(d[axis] < 0)
     351              :                     {
     352            0 :                         d.neg();
     353              :                     }
     354            0 :                     reduceslope(d);
     355            0 :                     int origin  =  static_cast<int>(std::min(v1[axis], v2[axis])*8)&~0x7FFF,
     356            0 :                         offset1 = (static_cast<int>(v1[axis]*8) - origin) / d[axis],
     357            0 :                         offset2 = (static_cast<int>(v2[axis]*8) - origin) / d[axis];
     358            0 :                     vec o = vec(v1).sub(vec(d).mul(offset1/8.0f)),
     359            0 :                         n1, n2;
     360            0 :                     float doffset = 1.0f / (offset2 - offset1);
     361            0 :                     while(tj >= 0)
     362              :                     {
     363            0 :                         tjoint &t = tjoints[tj];
     364            0 :                         if(t.edge != edge)
     365              :                         {
     366            0 :                             break;
     367              :                         }
     368            0 :                         float offset = (t.offset - offset1) * doffset;
     369            0 :                         vec tpos = vec(d).mul(t.offset/8.0f).add(o);
     370            0 :                         addtnormal(tpos, smooth, offset, norms[e1], norms[e2], v1, v2);
     371            0 :                         tj = t.next;
     372              :                     }
     373              :                 }
     374              :             }
     375              :         }
     376              :     }
     377              : }
     378              : 
     379              : /* externally relevant functionality */
     380              : ///////////////////////////////////////
     381              : 
     382            0 : void findnormal(const vec &pos, int smooth, const vec &surface, vec &v)
     383              : {
     384            0 :     normalkey key = { pos, smooth };
     385            0 :     auto itr = normalgroups.find(key);
     386            0 :     if(smooth < 0)
     387              :     {
     388            0 :         smooth = 0;
     389              :     }
     390            0 :     bool usegroup = (static_cast<int>(smoothgroups.size()) > smooth) && smoothgroups[smooth] >= 0;
     391            0 :     if(itr != normalgroups.end())
     392              :     {
     393            0 :         int angle = usegroup ? smoothgroups[smooth] : lerpangle;
     394            0 :         float lerpthreshold = cos360(angle) - 1e-5f;
     395            0 :         if((*itr).second.tnormals < 0 || !findtnormal((*itr).second, lerpthreshold, surface, v))
     396              :         {
     397            0 :             findnormal((*itr).second, lerpthreshold, surface, v);
     398              :         }
     399              :     }
     400              :     else
     401              :     {
     402            0 :         v = surface;
     403              :     }
     404            0 : }
     405              : 
     406            0 : void cubeworld::calcnormals(bool lerptjoints)
     407              : {
     408            0 :     usetnormals = lerptjoints;
     409            0 :     if(usetnormals)
     410              :     {
     411            0 :         findtjoints();
     412              :     }
     413            0 :     for(size_t i = 0; i < worldroot->size(); ++i)
     414              :     {
     415            0 :         addnormals((*worldroot)[i], ivec(i, ivec(0, 0, 0), mapsize()/2), mapsize()/2);
     416              :     }
     417            0 : }
     418              : 
     419            0 : void clearnormals()
     420              : {
     421            0 :     normalgroups.clear();
     422            0 :     normals.clear();
     423            0 :     tnormals.clear();
     424            0 : }
     425              : 
     426            0 : void resetsmoothgroups()
     427              : {
     428            0 :     smoothgroups.clear();
     429            0 : }
     430              : 
     431              : static constexpr int maxsmoothgroups = 10000;
     432              : //returns the smoothgroup at the idth location in the smoothgroups vector
     433              : //returns -1 (failure) if you try to ask for an id greater than 10,000
     434            1 : int smoothangle(int id, int angle)
     435              : {
     436            1 :     if(id < 0)
     437              :     {
     438            0 :         id = smoothgroups.size();
     439              :     }
     440            1 :     if(id >= maxsmoothgroups)
     441              :     {
     442            0 :         return -1;
     443              :     }
     444            2 :     while(static_cast<int>(smoothgroups.size()) <= id)
     445              :     {
     446            1 :         smoothgroups.push_back(-1);
     447              :     }
     448            1 :     if(angle >= 0)
     449              :     {
     450            0 :         smoothgroups[id] = std::min(angle, 180);
     451              :     }
     452            1 :     return id;
     453              : }
     454              : 
     455            1 : void initnormalcmds()
     456              : {
     457            2 :     addcommand("smoothangle", reinterpret_cast<identfun>(+[] (int *id, int *angle) {intret(smoothangle(*id, *angle));}), "ib", Id_Command);
     458            1 : }
        

Generated by: LCOV version 2.0-1