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 : }
|