UFO: Alien Invasion
Loading...
Searching...
No Matches
r_grass.cpp
Go to the documentation of this file.
1
5
6/*
7Copyright (C) 2013-2020 UFO: Alien Invasion.
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18See the GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program; if not, write to the Free Software
22Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24*/
25
26#include "r_local.h"
27#include "r_grass.h"
28
29#define GRASS_MARK -956 /* arbitrary number from top of my head -- Sandro */
30
31#define TRIS_PER_CLUMP 10
32#define MAX_CLUMPS 2046
33
34#define MAX_CLUMP_TRIS (TRIS_PER_CLUMP * MAX_CLUMPS)
35
36struct Clump {
37 int firstTriangle, numTriangles; /* populated only when actual geometry is generated */
40 float rotation;
41 int level;
42};
43
44static int clumpCount = 0;
46
49
50static int clumpTriangleCount = 0;
51
52/* gfv -- grass fragment vertex */
55
63
64static void R_PlantGrass (Clump& clump)
65{
68 clump.numTriangles = 0;
69 return;
70 }
71
73 clump.numTriangles = 0; /* safeguard */
74
75 vec3_t rot[3]; /* rotation matrix for the plant */
76#define xt (rot[0])
77#define yt (rot[1])
78#define zt (rot[2])
79
80#if 0
81 /* horisontal planting */
82 VectorSet(xt, 1, 0, 0);
83 VectorSet(zt, 0, 0, 1);
84#else
85 /* normal-based planting */
86
87 /* we can calculate the downslope vector and use it instead;
88 * a bit more of math, but call allow us to create plants that interact with the slope in various ways */
89 VectorCopy(clump.normal, zt);
90 VectorSet(xt, zt[2], 0.0, -zt[0]); /* short-circuit CrossProduct(yaxis, normal, xt) since it degenerates to a simple shuffle */
92#endif
93
94 /* generate geometry */
95#if 0
96 /* prebuilt mesh or debug grass marker */
97
98 /* randomly rotate plant around the base */
100 CrossProduct(yt, zt, xt);
101
102 /* randomly mirror the plant, too */
103 if (rand() & 1)
105
106 /* marker */
107 vec_t* ptr = gfv_pos[clumpTriangleCount * 3];
108 VectorMA(clump.position, 1, zt, ptr);
109 VectorMA(ptr, 16, yt, ptr + 3);
110 VectorMA(ptr, 16, xt, ptr + 6);
111
113 Vector2Set(texc, 0, 0);
114 Vector2Set(texc + 2, 1, 0);
115 Vector2Set(texc + 4, 0, 1);
116
118#else
119 /* programmatically generated clump */
120 CrossProduct(zt, xt, yt);
121 for (int i = 0; i < TRIS_PER_CLUMP; i += 2) {
122 vec3_t sdir, tdir;
123 vec2_t sprrot;
124 vec3_t tmp;
125
126 sprrot[0] = frand() * 360;
127 sprrot[1] = frand() * 60 + 15;
128
129 PolarToVec(sprrot, tmp);
130 VectorRotate(rot, tmp, tdir);
131
132 sprrot[0] += 90;
133 sprrot[1] = 0;
134
135 PolarToVec(sprrot, tmp);
136 VectorRotate(rot, tmp, sdir);
137#if 0
138 /* debug marker */
139 vec_t* ptr = gfv_pos[clumpTriangleCount * 3];
140 VectorCopy(clump.position, ptr);
141 VectorMA(ptr, 16, sdir, ptr + 3);
142 VectorMA(ptr, 16, tdir, ptr + 6);
143
145 Vector2Set(texc, 0, 0);
146 Vector2Set(texc + 2, 1, 0);
147 Vector2Set(texc + 4, 0, 1);
148
150#else
151 /* billboard sprite */
152 vec_t* ptr = gfv_pos[clumpTriangleCount * 3];
153 VectorCopy(clump.position, ptr);
155 VectorMA(clump.position, -24, sdir, ptr); /* quad vertex 0 */
156 VectorMA(ptr, 32, tdir, ptr + 3); /* quad vertex 1 */
157 VectorMA(ptr + 3, 48, sdir, ptr + 6); /* quad vertex 2 */
158
159 VectorCopy(ptr, ptr + 9); /* quad vertex 0 */
160 VectorCopy(ptr + 6, ptr + 12); /* quad vertex 2 */
161 VectorMA(ptr + 6, -32, tdir, ptr + 15); /* quad vertex 3 */
162
164 Vector2Set(texc, 0, 1);
165 Vector2Set(texc + 2, 0, 0);
166 Vector2Set(texc + 4, 1, 0);
167
168 Vector2Set(texc + 6, 0, 1);
169 Vector2Set(texc + 8, 1, 0);
170 Vector2Set(texc + 12, 1, 1);
171
173#endif
174 }
175#endif
176
177#undef xt
178#undef yt
179#undef zt
181}
182
183static void R_AddClump (const vec3_t pos, const vec3_t normal, int level)
184{
185 if (clumpCount >= MAX_CLUMPS)
186 return;
187
188 Clump& cp = clumps[clumpCount];
189
190 VectorCopy(pos, cp.position);
191 VectorCopy(normal, cp.normal);
192
193 cp.rotation = frand() * 360;
194 cp.level = level;
195
196 clumpCount++;
197}
198
199static int ClumpOrder (const void* a, const void* b)
200{
201 const Clump* pa = static_cast <const Clump*>(a);
202 const Clump* pb = static_cast <const Clump*>(b);
203
204 if (pa->level != pb->level)
205 return pa->level - pb->level;
206
208 return 0;
209}
210
211static void R_OrganizeClumps ()
212{
213 qsort(clumps, clumpCount, sizeof(Clump), ClumpOrder);
214
215 int lastLevel = 0, i;
216 for (i = 0; i < clumpCount; i++)
217 while (lastLevel < clumps[i].level) {
218 clumpsForLevel[lastLevel] = i;
219 lastLevel++;
220 }
221
222 while (lastLevel < PATHFINDING_HEIGHT) {
223 clumpsForLevel[lastLevel] = i;
224 lastLevel++;
225 }
226}
227
229{
230 float density = 1.0f; /* count of clumps per 32x32 unit tile */
231 double area = 0.0; /* float does not provide enough mantissa precision for this; also note that area is doubled */
232
233 Com_Printf("Planting grass ...\n");
234
235 /* 1st pass: walk through all brushes and determine which surfaces to use and their total area */
236 for (int tile = 0; tile < r_numMapTiles; tile++) {
237 /* ignore weaponclip, actorclip and stepon */
238 for (int i = 0; i <= LEVEL_LASTVISIBLE; i++) {
239 const mBspModel_t* const bspModel = &r_mapTiles[tile]->bsp;
240 const mBspHeader_t* const header = &bspModel->submodels[i];
241
242 if (!header->numfaces)
243 continue;
244
245 for (int j = 0; j < header->numfaces; j++) {
246 mBspSurface_t* const surf = &bspModel->surfaces[header->firstface + j];
247 const cBspPlane_t* const plane = surf->plane;
248
249 if (surf->frame == GRASS_MARK)
250 continue; /* already processed it */
251
252 if (!(surf->texinfo->flags & SURF_FOLIAGE))
253 continue; /* not tagged as overgrown */
254
255 if (surf->firstTriangle < 0 || surf->numTriangles == 0)
256 continue; /* no geometry for this surface, skip it */
257
258 /* reject way too inclined or downward facing planes */
259 if (plane->normal[2] < 0.5f) /* cutoff angle is 60 degrees */
260 continue;
261
262 /* walk triangle list and sum areas */
263 double surfArea = 0.0;
264
265 for (int k = 0; k < surf->numTriangles; k++) {
266 const int vofs = (k + surf->firstTriangle) * 3;
267 const int indo = bspModel->indexes[vofs] & 0xffff;
268 const int inda = bspModel->indexes[vofs + 1] & 0xffff;
269 const int indb = bspModel->indexes[vofs + 2] & 0xffff;
270 vec3_t vo, va, vb;
271 vec3_t cross;
272
273 /* calculate barycentric origin and coordinate axes */
274 VectorCopy(&bspModel->verts[indo * 3], vo);
275 VectorSubtract(&bspModel->verts[inda * 3], vo, va);
276 VectorSubtract(&bspModel->verts[indb * 3], vo, vb);
277
278 /* calculate area */
279 CrossProduct(va, vb, cross);
280 surfArea += VectorLength(cross);
281 }
282
283 if (surfArea < 80.0)
284 continue; /* skip tiny surfaces */
285
286 area += surfArea;
287 surf->frame = GRASS_MARK;
288 }
289 }
290 }
291 Com_Printf("Total grassy area is %7.0f units (%i cells)\n", area / 2, (int)(area / 2048));
292
293 double areaPerClump = area / MAX_CLUMPS;
294 if (areaPerClump < 2048 / (density * density))
295 areaPerClump = 2048 / (density * density);
296
297 /* 2nd pass: actually plant the grass */
299 double clumpsToPlant = 0.0;
300 int planted = 0;
301
302 for (int tile = 0; tile < r_numMapTiles; tile++) {
303 /* ignore weaponclip, actorclip and stepon */
304 for (int i = 0; i <= LEVEL_LASTVISIBLE; i++) {
305 const mBspModel_t* const bspModel = &r_mapTiles[tile]->bsp;
306 const mBspHeader_t* const header = &bspModel->submodels[i];
307
308 if (!header->numfaces)
309 continue;
310
311 int level;
312
313 for (level = 0; level < PATHFINDING_HEIGHT - 1; level++)
314 if (!i || ((1 << level) & i))
315 break;
316
317 for (int j = 0; j < header->numfaces; j++) {
318 mBspSurface_t* const surf = &bspModel->surfaces[header->firstface + j];
319 const cBspPlane_t* const plane = surf->plane;
320
321 if (surf->frame != GRASS_MARK)
322 continue; /* not suitable for grass or got grass already generated */
323
324 surf->frame--; /* screen from any future stumblings upon it */
325
326 /* walk triangle list and plant grass */
327 for (int k = 0; k < surf->numTriangles; k++) {
328 const int vofs = (k + surf->firstTriangle) * 3;
329 const int indo = bspModel->indexes[vofs] & 0xffff;
330 const int inda = bspModel->indexes[vofs + 1] & 0xffff;
331 const int indb = bspModel->indexes[vofs + 2] & 0xffff;
332 vec3_t vo, va, vb;
333 vec3_t cross;
334
335 /* calculate barycentric origin and coordinate axes */
336 VectorCopy(&bspModel->verts[indo * 3], vo);
337 VectorSubtract(&bspModel->verts[inda * 3], vo, va);
338 VectorSubtract(&bspModel->verts[indb * 3], vo, vb);
339
340 /* calculate area and convert it to clump count */
341 CrossProduct(va, vb, cross);
342 clumpsToPlant += VectorLength(cross) / areaPerClump;
343
344 while (clumpsToPlant >= 1.0) {
345 /* generate random point within a triangle using barycentic coordinates */
346 float u = frand();
347 float v = frand();
348
349 /* if barycentric coordinates are outside of triangle, rotate them 180 deg by flipping both coords
350 * explanation: they just got on other half of parallelogram defined by va and vb, which half complements this triangle to said parallelogram
351 */
352 if (u + v > 1.0f) {
353 u = 1.0f - u;
354 v = 1.0f - v;
355 }
356
357 vec3_t pos;
358
359 VectorMA(vo, u, va, pos);
360 VectorMA(pos, v, vb, pos);
361
362 R_AddClump(pos, plane->normal, level);
363
364 clumpsToPlant -= 1.0;
365 planted++;
366 }
367 }
368 }
369 }
370 }
371
373
374 for (int i = 0; i < clumpCount; i++)
376
377 if (clumpTriangleCount <= 0) {
378 /* no grass geometry generated, so zero triangle counts */
379 for (int i = 0; i < PATHFINDING_HEIGHT; i++)
381 } else {
382 /* generate triangle counts to render the grass in a single OpenGL call */
383 int lastClumpCount = 0;
384 int triangles = 0;
385 for (int i = 0; i < PATHFINDING_HEIGHT; i++) {
386 if (clumpsForLevel[i] > lastClumpCount) {
387 lastClumpCount = clumpsForLevel[i];
388 const Clump& clump = clumps[lastClumpCount - 1];
389 triangles = clump.firstTriangle + clump.numTriangles;
390 }
391 clumpTrianglesForLevel[i] = triangles;
392 Com_Printf("%i triangles for level %i (%i clumps)\n", triangles, i + 1, lastClumpCount);
393 }
394 }
395
396 Com_Printf("Planted %i clumps of grass\n", planted);
397}
398
400{
401 if (clumpTriangleCount <= 0)
402 return;
403
404 R_BindTexture(R_FindImage("models/objects/vegi/plants2/plant_skin3", it_pic)->texnum);
405
406 R_EnableAlphaTest(true);
407
408 R_BindArray(GL_TEXTURE_COORD_ARRAY, GL_FLOAT, gfv_texcoord);
409 R_BindArray(GL_VERTEX_ARRAY, GL_FLOAT, gfv_pos);
410 glDrawArrays(GL_TRIANGLES, 0, clumpTrianglesForLevel[refdef.worldlevel] * 3);
411 R_BindDefaultArray(GL_VERTEX_ARRAY);
412 R_BindDefaultArray(GL_TEXTURE_COORD_ARRAY);
413
414 R_EnableAlphaTest(false);
415
416 refdef.batchCount++;
417}
rendererData_t refdef
Definition r_main.cpp:45
void Com_Printf(const char *const fmt,...)
Definition common.cpp:428
#define SURF_FOLIAGE
Definition defines.h:267
#define LEVEL_LASTVISIBLE
Definition defines.h:348
#define PATHFINDING_HEIGHT
15 max, adjusting above 8 will require a rewrite to the DV code
Definition defines.h:294
level_locals_t level
Definition g_main.cpp:38
vec_t VectorLength(const vec3_t v)
Calculate the length of a vector.
Definition mathlib.cpp:434
void VectorMA(const vec3_t veca, const float scale, const vec3_t vecb, vec3_t outVector)
Sets vector_out (vc) to vevtor1 (va) + scale * vector2 (vb).
Definition mathlib.cpp:261
void VectorInverse(vec3_t v)
Inverse a vector.
Definition mathlib.cpp:460
void RotatePointAroundVector(vec3_t dst, const vec3_t dir, const vec3_t point, float degrees)
Rotate a point around a given vector.
Definition mathlib.cpp:849
void PolarToVec(const vec2_t a, vec3_t v)
Converts longitude and latitude to a 3D vector in Euclidean coordinates.
Definition mathlib.cpp:910
void VectorRotate(vec3_t m[3], const vec3_t va, vec3_t vb)
Rotate a vector with a rotation matrix.
Definition mathlib.cpp:395
void CrossProduct(const vec3_t v1, const vec3_t v2, vec3_t cross)
binary operation on vectors in a three-dimensional space
Definition mathlib.cpp:820
float frand(void)
Return random values between 0 and 1.
Definition mathlib.cpp:506
void VectorNormalizeFast(vec3_t v)
fast vector normalize routine that does not check to make sure that length != 0, nor does it return l...
Definition mathlib.cpp:762
QGL_EXTERN int GLboolean GLfloat * v
Definition r_gl.h:120
QGL_EXTERN GLint i
Definition r_gl.h:113
void R_DrawGrass()
Definition r_grass.cpp:399
static void R_OrganizeClumps()
Definition r_grass.cpp:211
#define MAX_CLUMP_TRIS
Definition r_grass.cpp:34
static int ClumpOrder(const void *a, const void *b)
Definition r_grass.cpp:199
static void R_PlantGrass(Clump &clump)
Definition r_grass.cpp:64
#define MAX_CLUMPS
Definition r_grass.cpp:32
#define xt
void R_GenerateGrass()
Definition r_grass.cpp:228
#define yt
static vec3_t gfv_pos[MAX_CLUMP_TRIS *3]
Definition r_grass.cpp:53
static int clumpCount
Definition r_grass.cpp:44
static int clumpTrianglesForLevel[PATHFINDING_HEIGHT]
Definition r_grass.cpp:48
static void R_AddClump(const vec3_t pos, const vec3_t normal, int level)
Definition r_grass.cpp:183
static vec2_t gfv_texcoord[MAX_CLUMP_TRIS *3]
Definition r_grass.cpp:54
void R_ClearGrass()
Definition r_grass.cpp:56
#define GRASS_MARK
Definition r_grass.cpp:29
#define zt
static Clump clumps[MAX_CLUMPS]
Definition r_grass.cpp:45
static int clumpsForLevel[PATHFINDING_HEIGHT]
Definition r_grass.cpp:47
#define TRIS_PER_CLUMP
Definition r_grass.cpp:31
static int clumpTriangleCount
Definition r_grass.cpp:50
Pseudoinstanced grass generation and rendering.
image_t * R_FindImage(const char *pname, imagetype_t type)
Finds or loads the given image.
Definition r_image.cpp:603
@ it_pic
Definition r_image.h:45
local graphics definitions
int r_numMapTiles
Definition r_model.cpp:33
model_t * r_mapTiles[MAX_MAPTILES]
The world model(s).
Definition r_model.cpp:32
void R_EnableAlphaTest(bool enable)
Definition r_state.cpp:277
void R_BindDefaultArray(GLenum target)
Binds the appropriate shared vertex array to the specified target.
Definition r_state.cpp:182
void R_BindArray(GLenum target, GLenum type, const void *array)
Definition r_state.cpp:148
#define R_BindTexture(tn)
Definition r_state.h:184
#define OBJZERO(obj)
Definition shared.h:178
const char * va(const char *format,...)
does a varargs printf into a temp buffer, so I don't need to have varargs versions of all text functi...
Definition shared.cpp:410
vec3_t normal
Definition r_grass.cpp:39
int firstTriangle
Definition r_grass.cpp:37
vec3_t position
Definition r_grass.cpp:38
int numTriangles
Definition r_grass.cpp:37
float rotation
Definition r_grass.cpp:40
int level
Definition r_grass.cpp:41
plane_t structure
Definition typedefs.h:20
vec3_t normal
Definition typedefs.h:21
brush model
glElementIndex_t * indexes
mBspSurface_t * surfaces
mBspHeader_t * submodels
mBspTexInfo_t * texinfo
unsigned int numTriangles
cBspPlane_t * plane
float vec_t
Definition ufotypes.h:37
vec_t vec3_t[3]
Definition ufotypes.h:39
vec_t vec2_t[2]
Definition ufotypes.h:38
#define VectorSubtract(a, b, dest)
Definition vector.h:45
#define VectorCopy(src, dest)
Definition vector.h:51
#define Vector2Set(v, x, y)
Definition vector.h:61
#define VectorSet(v, x, y, z)
Definition vector.h:59