LCOV - code coverage report
Current view: top level - engine/render - normal.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 5.3 % 225 12
Test Date: 2026-06-04 05:03:08 Functions: 18.8 % 16 3

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

Generated by: LCOV version 2.0-1