Fix Molten VK printing too many log messages
[0ad.git] / source / graphics / HFTracer.cpp
blob5217c0cd50949a6fc19de0da43184475399dbaa3
1 /* Copyright (C) 2021 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
19 * Determine intersection of rays with a heightfield.
22 #include "precompiled.h"
24 #include "HFTracer.h"
26 #include "graphics/Patch.h"
27 #include "graphics/Terrain.h"
28 #include "maths/BoundingBoxAligned.h"
29 #include "maths/MathUtil.h"
30 #include "maths/Vector3D.h"
32 #include <cfloat>
34 // To cope well with points that are slightly off the edge of the map,
35 // we act as if there's an N-tile margin around the edges of the heightfield.
36 // (N shouldn't be too huge else it'll hurt performance a little when
37 // RayIntersect loops through it all.)
38 // CTerrain::CalcPosition implements clamp-to-edge behaviour so the tracer
39 // will have that behaviour.
40 static const int MARGIN_SIZE = 64;
42 ///////////////////////////////////////////////////////////////////////////////
43 // CHFTracer constructor
44 CHFTracer::CHFTracer(CTerrain *pTerrain):
45 m_pTerrain(pTerrain),
46 m_Heightfield(m_pTerrain->GetHeightMap()),
47 m_MapSize(m_pTerrain->GetVerticesPerSide()),
48 m_CellSize((float)TERRAIN_TILE_SIZE),
49 m_HeightScale(HEIGHT_SCALE)
54 ///////////////////////////////////////////////////////////////////////////////
55 // RayTriIntersect: intersect a ray with triangle defined by vertices
56 // v0,v1,v2; return true if ray hits triangle at distance less than dist,
57 // or false otherwise
58 static bool RayTriIntersect(const CVector3D& v0, const CVector3D& v1, const CVector3D& v2,
59 const CVector3D& origin, const CVector3D& dir, float& dist)
61 const float EPSILON=0.00001f;
63 // calculate edge vectors
64 CVector3D edge0=v1-v0;
65 CVector3D edge1=v2-v0;
67 // begin calculating determinant - also used to calculate U parameter
68 CVector3D pvec=dir.Cross(edge1);
70 // if determinant is near zero, ray lies in plane of triangle
71 float det = edge0.Dot(pvec);
72 if (fabs(det)<EPSILON)
73 return false;
75 float inv_det = 1.0f/det;
77 // calculate vector from vert0 to ray origin
78 CVector3D tvec=origin-v0;
80 // calculate U parameter, test bounds
81 float u=tvec.Dot(pvec)*inv_det;
82 if (u<-0.01f || u>1.01f)
83 return false;
85 // prepare to test V parameter
86 CVector3D qvec=tvec.Cross(edge0);
88 // calculate V parameter and test bounds
89 float v=dir.Dot(qvec)*inv_det;
90 if (v<0.0f || u+v>1.0f)
91 return false;
93 // calculate distance to intersection point from ray origin
94 float d=edge1.Dot(qvec)*inv_det;
95 if (d>=0 && d<dist) {
96 dist=d;
97 return true;
100 return false;
103 ///////////////////////////////////////////////////////////////////////////////
104 // CellIntersect: test if ray intersects either of the triangles in the given
105 // cell - return hit result, and distance to hit, if hit occurred
106 bool CHFTracer::CellIntersect(int cx, int cz, const CVector3D& origin, const CVector3D& dir, float& dist) const
108 bool res=false;
110 // get vertices for this cell
111 CVector3D vpos[4];
112 m_pTerrain->CalcPosition(cx,cz,vpos[0]);
113 m_pTerrain->CalcPosition(cx+1,cz,vpos[1]);
114 m_pTerrain->CalcPosition(cx+1,cz+1,vpos[2]);
115 m_pTerrain->CalcPosition(cx,cz+1,vpos[3]);
117 dist=1.0e30f;
118 if (RayTriIntersect(vpos[0],vpos[1],vpos[2],origin,dir,dist)) {
119 res=true;
122 if (RayTriIntersect(vpos[0],vpos[2],vpos[3],origin,dir,dist)) {
123 res=true;
126 return res;
129 ///////////////////////////////////////////////////////////////////////////////
130 // RayIntersect: intersect ray with this heightfield; return true if
131 // intersection occurs (and fill in grid coordinates of intersection), or false
132 // otherwise
133 bool CHFTracer::RayIntersect(const CVector3D& origin, const CVector3D& dir, int& x, int& z, CVector3D& ipt) const
135 // If the map is empty (which should never happen),
136 // return early before we crash when reading zero-sized heightmaps
137 if (!m_MapSize)
139 debug_warn(L"CHFTracer::RayIntersect called with zero-size map");
140 return false;
143 // intersect first against bounding box
144 CBoundingBoxAligned bound;
145 bound[0] = CVector3D(-MARGIN_SIZE * m_CellSize, 0, -MARGIN_SIZE * m_CellSize);
146 bound[1] = CVector3D((m_MapSize + MARGIN_SIZE) * m_CellSize, 65535 * m_HeightScale, (m_MapSize + MARGIN_SIZE) * m_CellSize);
148 float tmin,tmax;
149 if (!bound.RayIntersect(origin,dir,tmin,tmax)) {
150 // ray missed world bounds; no intersection
151 return false;
154 // project origin onto grid, if necessary, to get starting point for traversal
155 CVector3D traversalPt;
156 if (tmin>0) {
157 traversalPt=origin+dir*tmin;
158 } else {
159 traversalPt=origin;
162 // setup traversal variables
163 int sx=dir.X<0 ? -1 : 1;
164 int sz=dir.Z<0 ? -1 : 1;
166 float invCellSize=1.0f/float(m_CellSize);
168 float fcx=traversalPt.X*invCellSize;
169 int cx=(int)floor(fcx);
171 float fcz=traversalPt.Z*invCellSize;
172 int cz=(int)floor(fcz);
174 float invdx = 1.0e20f;
175 float invdz = 1.0e20f;
177 if (fabs(dir.X) > 1.0e-20)
178 invdx = float(1.0/fabs(dir.X));
179 if (fabs(dir.Z) > 1.0e-20)
180 invdz = float(1.0/fabs(dir.Z));
182 do {
183 // test current cell
184 if (cx >= -MARGIN_SIZE && cx < int(m_MapSize + MARGIN_SIZE - 1) && cz >= -MARGIN_SIZE && cz < int(m_MapSize + MARGIN_SIZE - 1))
186 float dist;
188 if (CellIntersect(cx,cz,origin,dir,dist)) {
189 x=cx;
190 z=cz;
191 ipt=origin+dir*dist;
192 return true;
195 else
197 // Degenerate case: y close to zero
198 // catch travelling off the map
199 if ((cx < -MARGIN_SIZE) && (sx < 0))
200 return false;
201 if ((cx >= (int)(m_MapSize + MARGIN_SIZE - 1)) && (sx > 0))
202 return false;
203 if ((cz < -MARGIN_SIZE) && (sz < 0))
204 return false;
205 if ((cz >= (int)(m_MapSize + MARGIN_SIZE - 1)) && (sz > 0))
206 return false;
209 // get coords of current cell
210 fcx=traversalPt.X*invCellSize;
211 fcz=traversalPt.Z*invCellSize;
213 // get distance to next cell in x,z
214 float dx=(sx==-1) ? fcx-float(cx) : 1-(fcx-float(cx));
215 dx*=invdx;
216 float dz=(sz==-1) ? fcz-float(cz) : 1-(fcz-float(cz));
217 dz*=invdz;
219 // advance ..
220 float dist;
221 if (dx<dz) {
222 cx+=sx;
223 dist=dx;
224 } else {
225 cz+=sz;
226 dist=dz;
229 traversalPt+=dir*dist;
230 } while (traversalPt.Y>=0);
232 // fell off end of heightmap with no intersection; return a miss
233 return false;
236 static bool TestTile(u16* heightmap, int stride, int i, int j, const CVector3D& pos, const CVector3D& dir, CVector3D& isct)
238 u16 y00 = heightmap[i + j*stride];
239 u16 y10 = heightmap[i+1 + j*stride];
240 u16 y01 = heightmap[i + (j+1)*stride];
241 u16 y11 = heightmap[i+1 + (j+1)*stride];
243 CVector3D p00( i * TERRAIN_TILE_SIZE, y00 * HEIGHT_SCALE, j * TERRAIN_TILE_SIZE);
244 CVector3D p10((i+1) * TERRAIN_TILE_SIZE, y10 * HEIGHT_SCALE, j * TERRAIN_TILE_SIZE);
245 CVector3D p01( i * TERRAIN_TILE_SIZE, y01 * HEIGHT_SCALE, (j+1) * TERRAIN_TILE_SIZE);
246 CVector3D p11((i+1) * TERRAIN_TILE_SIZE, y11 * HEIGHT_SCALE, (j+1) * TERRAIN_TILE_SIZE);
248 int mid1 = y00+y11;
249 int mid2 = y01+y10;
250 int triDir = (mid1 < mid2);
252 float dist = FLT_MAX;
254 if (triDir)
256 if (RayTriIntersect(p00, p10, p01, pos, dir, dist) || // lower-left triangle
257 RayTriIntersect(p11, p01, p10, pos, dir, dist)) // upper-right triangle
259 isct = pos + dir * dist;
260 return true;
263 else
265 if (RayTriIntersect(p00, p11, p01, pos, dir, dist) || // upper-left triangle
266 RayTriIntersect(p00, p10, p11, pos, dir, dist)) // lower-right triangle
268 isct = pos + dir * dist;
269 return true;
272 return false;
275 bool CHFTracer::PatchRayIntersect(CPatch* patch, const CVector3D& origin, const CVector3D& dir, CVector3D* out)
277 // (TODO: This largely duplicates RayIntersect - some refactoring might be
278 // nice in the future.)
280 // General approach:
281 // Given the ray defined by origin + dir * t, we increase t until it
282 // enters the patch's bounding box. The x,z coordinates identify which
283 // tile it is currently above/below. Do an intersection test vs the tile's
284 // two triangles. If it doesn't hit, do a 2D line rasterisation to find
285 // the next tiles the ray will pass through, and test each of them.
287 // Start by jumping to the point where the ray enters the bounding box
288 CBoundingBoxAligned bound = patch->GetWorldBounds();
289 float tmin, tmax;
290 if (!bound.RayIntersect(origin, dir, tmin, tmax))
292 // Ray missed patch; no intersection
293 return false;
296 int heightmapStride = patch->m_Parent->GetVerticesPerSide();
298 // Get heightmap, offset to start at this patch
299 u16* heightmap = patch->m_Parent->GetHeightMap() +
300 patch->m_X * PATCH_SIZE +
301 patch->m_Z * PATCH_SIZE * heightmapStride;
303 // Get patch-space position of ray origin and bbox entry point
304 CVector3D patchPos(
305 patch->m_X * PATCH_SIZE * TERRAIN_TILE_SIZE,
306 0.0f,
307 patch->m_Z * PATCH_SIZE * TERRAIN_TILE_SIZE);
308 CVector3D originPatch = origin - patchPos;
309 CVector3D entryPatch = originPatch + dir * tmin;
311 // We want to do a simple 2D line rasterisation (with the 3D ray projected
312 // down onto the Y plane). That will tell us which cells are intersected
313 // in 2D dimensions, then we can do a more precise 3D intersection test.
315 // WLOG, assume the ray has direction dir.x > 0, dir.z > 0, and starts in
316 // cell (i,j). The next cell intersecting the line must be either (i+1,j)
317 // or (i,j+1). To tell which, just check whether the point (i+1,j+1) is
318 // above or below the ray. Advance into that cell and repeat.
320 // (If the ray passes precisely through (i+1,j+1), we can pick either.
321 // If the ray is parallel to Y, only the first cell matters, then we can
322 // carry on rasterising in any direction (a bit of a waste of time but
323 // should be extremely rare, and it's safe and simple).)
325 // Work out which tile we're starting in
326 int i = Clamp<int>(entryPatch.X / TERRAIN_TILE_SIZE, 0, PATCH_SIZE - 1);
327 int j = Clamp<int>(entryPatch.Z / TERRAIN_TILE_SIZE, 0, PATCH_SIZE - 1);
329 // Work out which direction the ray is going in
330 int di = (dir.X >= 0 ? 1 : 0);
331 int dj = (dir.Z >= 0 ? 1 : 0);
335 CVector3D isct;
336 if (TestTile(heightmap, heightmapStride, i, j, originPatch, dir, isct))
338 if (out)
339 *out = isct + patchPos;
340 return true;
343 // Get the vertex between the two possible next cells
344 float nx = (i + di) * (int)TERRAIN_TILE_SIZE;
345 float nz = (j + dj) * (int)TERRAIN_TILE_SIZE;
347 // Test which side of the ray the vertex is on, and advance into the
348 // appropriate cell, using a test that works for all 4 combinations
349 // of di,dj
350 float dot = dir.Z * (nx - originPatch.X) - dir.X * (nz - originPatch.Z);
351 if ((di == dj) == (dot > 0.0f))
352 j += dj*2-1;
353 else
354 i += di*2-1;
356 while (i >= 0 && j >= 0 && i < PATCH_SIZE && j < PATCH_SIZE);
358 // Ran off the edge of the patch, so no intersection
359 return false;