LCOV - code coverage report
Current view: top level - source/graphics - HeightMipmap.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 83 134 61.9 %
Date: 2023-01-19 00:18:29 Functions: 10 13 76.9 %

          Line data    Source code
       1             : /* Copyright (C) 2021 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "HeightMipmap.h"
      21             : 
      22             : #include "lib/bits.h"
      23             : #include "lib/timer.h"
      24             : #include "lib/allocators/shared_ptr.h"
      25             : #include "lib/tex/tex.h"
      26             : #include "maths/MathUtil.h"
      27             : #include "ps/Filesystem.h"
      28             : 
      29             : #include <cmath>
      30             : 
      31          19 : CHeightMipmap::CHeightMipmap()
      32             : {
      33          19 : }
      34             : 
      35          38 : CHeightMipmap::~CHeightMipmap()
      36             : {
      37          19 :     ReleaseData();
      38          19 : }
      39             : 
      40          96 : void CHeightMipmap::ReleaseData()
      41             : {
      42         253 :     for (size_t i = 0; i < m_Mipmap.size(); ++i)
      43             :     {
      44         157 :         delete[] m_Mipmap[i].m_Heightmap;
      45         157 :         m_Mipmap[i].m_MapSize = 0;
      46             :     }
      47          96 :     m_Mipmap.clear();
      48          96 : }
      49             : 
      50          93 : void CHeightMipmap::Update(const u16* ptr)
      51             : {
      52          93 :     ENSURE(ptr != 0);
      53             : 
      54          93 :     Update(ptr, 0, 0, m_MapSize, m_MapSize);
      55          93 : }
      56             : 
      57          93 : void CHeightMipmap::Update(const u16* ptr, size_t left, size_t bottom, size_t right, size_t top)
      58             : {
      59          93 :     ENSURE(ptr != 0);
      60             : 
      61          93 :     size_t mapSize = m_MapSize;
      62             : 
      63         634 :     for (size_t i = 0; i < m_Mipmap.size(); ++i)
      64             :     {
      65             :         // update window
      66         541 :         left = Clamp<size_t>(floorf(static_cast<float>(left) / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize - 1);
      67         541 :         bottom = Clamp<size_t>(floorf(static_cast<float>(bottom) / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize - 1);
      68             : 
      69         541 :         right = Clamp<size_t>(ceilf(static_cast<float>(right) / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize);
      70         541 :         top = Clamp<size_t>(ceilf(static_cast<float>(top) / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize);
      71             : 
      72             :         // TODO: should verify that the bounds calculations are actually correct
      73             : 
      74             :         // update mipmap
      75         541 :         BilinearUpdate(m_Mipmap[i], mapSize, ptr, left, bottom, right, top);
      76             : 
      77         541 :         mapSize = m_Mipmap[i].m_MapSize;
      78         541 :         ptr = m_Mipmap[i].m_Heightmap;
      79             :     }
      80          93 : }
      81             : 
      82          29 : void CHeightMipmap::Initialize(size_t mapSize, const u16* ptr)
      83             : {
      84          29 :     ENSURE(ptr != 0);
      85          29 :     ENSURE(mapSize > 0);
      86             : 
      87          29 :     ReleaseData();
      88             : 
      89          29 :     m_MapSize = mapSize;
      90          29 :     size_t mipmapSize = round_down_to_pow2(mapSize);
      91             : 
      92         343 :     while (mipmapSize > 1)
      93             :     {
      94         157 :         m_Mipmap.push_back(SMipmap(mipmapSize, new u16[mipmapSize*mipmapSize]));
      95         157 :         mipmapSize >>= 1;
      96             :     };
      97             : 
      98          29 :     Update(ptr);
      99          29 : }
     100             : 
     101           0 : float CHeightMipmap::GetTrilinearGroundLevel(float x, float z, float radius) const
     102             : {
     103             :     float y;
     104           0 :     if (radius <= 0.0f) // avoid logf of non-positive value
     105           0 :         y = 0.0f;
     106             :     else
     107           0 :         y = Clamp<float>(logf(radius * m_Mipmap[0].m_MapSize) / logf(2), 0.0f, m_Mipmap.size());
     108             : 
     109           0 :     const size_t iy = static_cast<size_t>(Clamp<ssize_t>(floorf(y), 0, m_Mipmap.size() - 2));
     110             : 
     111           0 :     const float fy = y - iy;
     112             : 
     113           0 :     const float h0 = BilinearFilter(m_Mipmap[iy], x, z);
     114           0 :     const float h1 = BilinearFilter(m_Mipmap[iy + 1], x, z);
     115             : 
     116           0 :     return (1 - fy) * h0 + fy * h1;
     117             : }
     118             : 
     119           0 : float CHeightMipmap::BilinearFilter(const SMipmap &mipmap, float x, float z) const
     120             : {
     121           0 :     x *= mipmap.m_MapSize;
     122           0 :     z *= mipmap.m_MapSize;
     123             : 
     124           0 :     const size_t xi = static_cast<size_t>(Clamp<ssize_t>(floor(x), 0, mipmap.m_MapSize - 2));
     125           0 :     const size_t zi = static_cast<size_t>(Clamp<ssize_t>(floor(z), 0, mipmap.m_MapSize - 2));
     126             : 
     127           0 :     const float xf = Clamp<float>(x-xi, 0.0f, 1.0f);
     128           0 :     const float zf = Clamp<float>(z-zi, 0.0f, 1.0f);
     129             : 
     130           0 :     const float h00 = mipmap.m_Heightmap[zi*mipmap.m_MapSize + xi];
     131           0 :     const float h01 = mipmap.m_Heightmap[(zi+1)*mipmap.m_MapSize + xi];
     132           0 :     const float h10 = mipmap.m_Heightmap[zi*mipmap.m_MapSize + (xi+1)];
     133           0 :     const float h11 = mipmap.m_Heightmap[(zi+1)*mipmap.m_MapSize + (xi+1)];
     134             : 
     135             :     return
     136           0 :         (1.f - xf) * (1.f - zf) * h00 +
     137           0 :                xf  * (1.f - zf) * h10 +
     138           0 :         (1.f - xf) *        zf  * h01 +
     139           0 :                xf  *        zf  * h11;
     140             : }
     141             : 
     142         448 : void CHeightMipmap::HalfResizeUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top)
     143             : {
     144             :     // specialized, faster version of BilinearUpdate for powers of 2
     145             : 
     146         448 :     ENSURE(out_mipmap.m_MapSize != 0);
     147             : 
     148         448 :     if (out_mipmap.m_MapSize * 2 != mapSize)
     149           0 :         debug_warn(L"wrong size");
     150             : 
     151             :     // valid update window
     152         448 :     ENSURE(left < out_mipmap.m_MapSize);
     153         448 :     ENSURE(bottom < out_mipmap.m_MapSize);
     154         448 :     ENSURE(right > left && right <= out_mipmap.m_MapSize);
     155         448 :     ENSURE(top > bottom && top <= out_mipmap.m_MapSize);
     156             : 
     157        5686 :     for (size_t dstZ = bottom; dstZ < top; ++dstZ)
     158             :     {
     159      115450 :         for (size_t dstX = left; dstX < right; ++dstX)
     160             :         {
     161      110212 :             size_t srcX = dstX << 1;
     162      110212 :             size_t srcZ = dstZ << 1;
     163             : 
     164      110212 :             u16 h00 = ptr[srcX + 0 + srcZ * mapSize];
     165      110212 :             u16 h10 = ptr[srcX + 1 + srcZ * mapSize];
     166      110212 :             u16 h01 = ptr[srcX + 0 + (srcZ + 1) * mapSize];
     167      110212 :             u16 h11 = ptr[srcX + 1 + (srcZ + 1) * mapSize];
     168             : 
     169      110212 :             out_mipmap.m_Heightmap[dstX + dstZ * out_mipmap.m_MapSize] = (h00 + h10 + h01 + h11) / 4;
     170             :         }
     171             :     }
     172         448 : }
     173             : 
     174         541 : void CHeightMipmap::BilinearUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top)
     175             : {
     176         541 :     ENSURE(out_mipmap.m_MapSize != 0);
     177             : 
     178             :     // filter should have full coverage
     179         541 :     ENSURE(out_mipmap.m_MapSize <= mapSize && out_mipmap.m_MapSize * 2 >= mapSize);
     180             : 
     181             :     // valid update window
     182         541 :     ENSURE(left < out_mipmap.m_MapSize);
     183         541 :     ENSURE(bottom < out_mipmap.m_MapSize);
     184         541 :     ENSURE(right > left && right <= out_mipmap.m_MapSize);
     185         541 :     ENSURE(top > bottom && top <= out_mipmap.m_MapSize);
     186             : 
     187         541 :     if (out_mipmap.m_MapSize * 2 == mapSize)
     188             :     {
     189             :         // optimized for powers of 2
     190         448 :         HalfResizeUpdate(out_mipmap, mapSize, ptr, left, bottom, right, top);
     191             :     }
     192             :     else
     193             :     {
     194        5517 :         for (size_t dstZ = bottom; dstZ < top; ++dstZ)
     195             :         {
     196      336432 :             for (size_t dstX = left; dstX < right; ++dstX)
     197             :             {
     198      331008 :                 const float x = ((float)dstX / (float)out_mipmap.m_MapSize) * mapSize;
     199      331008 :                 const float z = ((float)dstZ / (float)out_mipmap.m_MapSize) * mapSize;
     200             : 
     201      331008 :                 const size_t srcX = Clamp<size_t>(x, 0, mapSize - 2);
     202      331008 :                 const size_t srcZ = Clamp<size_t>(z, 0, mapSize - 2);
     203             : 
     204      331008 :                 const float fx = Clamp<float>(x - srcX, 0.0f, 1.0f);
     205      331008 :                 const float fz = Clamp<float>(z - srcZ, 0.0f, 1.0f);
     206             : 
     207      331008 :                 const float h00 = ptr[srcX + 0 + srcZ * mapSize];
     208      331008 :                 const float h10 = ptr[srcX + 1 + srcZ * mapSize];
     209      331008 :                 const float h01 = ptr[srcX + 0 + (srcZ + 1) * mapSize];
     210      331008 :                 const float h11 = ptr[srcX + 1 + (srcZ + 1) * mapSize];
     211             : 
     212      331008 :                 out_mipmap.m_Heightmap[dstX + dstZ * out_mipmap.m_MapSize] = (u16)
     213      662016 :                     ((1.f - fx) * (1.f - fz) * h00 +
     214      662016 :                             fx  * (1.f - fz) * h10 +
     215      662016 :                      (1.f - fx) *        fz  * h01 +
     216      331008 :                             fx  *        fz  * h11);
     217             :             }
     218             :         }
     219             :     }
     220         541 : }
     221             : 
     222           0 : void CHeightMipmap::DumpToDisk(const VfsPath& filename) const
     223             : {
     224           0 :     const size_t w = m_MapSize;
     225           0 :     const size_t h = m_MapSize * 2;
     226           0 :     const size_t bpp = 8;
     227           0 :     int flags = TEX_GREY|TEX_TOP_DOWN;
     228             : 
     229           0 :     const size_t img_size = w * h * bpp/8;
     230           0 :     const size_t hdr_size = tex_hdr_size(filename);
     231           0 :     std::shared_ptr<u8> buf;
     232           0 :     AllocateAligned(buf, hdr_size+img_size, maxSectorSize);
     233           0 :     void* img = buf.get() + hdr_size;
     234           0 :     Tex t;
     235           0 :     WARN_IF_ERR(t.wrap(w, h, bpp, flags, buf, hdr_size));
     236             : 
     237           0 :     memset(img, 0x00, img_size);
     238           0 :     size_t yoff = 0;
     239           0 :     for (size_t i = 0; i < m_Mipmap.size(); ++i)
     240             :     {
     241           0 :         size_t size = m_Mipmap[i].m_MapSize;
     242           0 :         u16* heightmap = m_Mipmap[i].m_Heightmap;
     243           0 :         ENSURE(size+yoff <= h);
     244           0 :         for (size_t y = 0; y < size; ++y)
     245             :         {
     246           0 :             for (size_t x = 0; x < size; ++x)
     247             :             {
     248           0 :                 u16 val = heightmap[x + y*size];
     249           0 :                 ((u8*)img)[x + (y+yoff)*w] = val >> 8;
     250             :             }
     251             :         }
     252           0 :         yoff += size;
     253             :     }
     254             : 
     255             :     DynArray da;
     256           0 :     WARN_IF_ERR(t.encode(filename.Extension(), &da));
     257           0 :     g_VFS->CreateFile(filename, DummySharedPtr(da.base), da.pos);
     258           0 :     ignore_result(da_free(&da));
     259           3 : }

Generated by: LCOV version 1.13