+static void R_Water_StartFrame(void)
+{
+ int i;
+ int waterwidth, waterheight, texturewidth, textureheight;
+ r_waterstate_waterplane_t *p;
+
+ // set waterwidth and waterheight to the water resolution that will be
+ // used (often less than the screen resolution for faster rendering)
+ waterwidth = (int)bound(1, r_refdef.view.width * r_water_resolutionmultiplier.value, r_refdef.view.width);
+ waterheight = (int)bound(1, r_refdef.view.height * r_water_resolutionmultiplier.value, r_refdef.view.height);
+
+ // calculate desired texture sizes
+ // can't use water if the card does not support the texture size
+ if (!r_water.integer || !r_glsl.integer || !gl_support_fragment_shader || waterwidth > gl_max_texture_size || waterheight > gl_max_texture_size || r_showsurfaces.integer)
+ texturewidth = textureheight = waterwidth = waterheight = 0;
+ else if (gl_support_arb_texture_non_power_of_two)
+ {
+ texturewidth = waterwidth;
+ textureheight = waterheight;
+ }
+ else
+ {
+ for (texturewidth = 1;texturewidth < waterwidth ;texturewidth *= 2);
+ for (textureheight = 1;textureheight < waterheight;textureheight *= 2);
+ }
+
+ // allocate textures as needed
+ if (r_waterstate.waterwidth != waterwidth || r_waterstate.waterheight != waterheight || r_waterstate.texturewidth != texturewidth || r_waterstate.textureheight != textureheight)
+ {
+ r_waterstate.maxwaterplanes = MAX_WATERPLANES;
+ for (i = 0, p = r_waterstate.waterplanes;i < r_waterstate.maxwaterplanes;i++, p++)
+ {
+ if (p->texture_refraction)
+ R_FreeTexture(p->texture_refraction);
+ p->texture_refraction = NULL;
+ if (p->texture_reflection)
+ R_FreeTexture(p->texture_reflection);
+ p->texture_reflection = NULL;
+ }
+ memset(&r_waterstate, 0, sizeof(r_waterstate));
+ r_waterstate.waterwidth = waterwidth;
+ r_waterstate.waterheight = waterheight;
+ r_waterstate.texturewidth = texturewidth;
+ r_waterstate.textureheight = textureheight;
+ }
+
+ if (r_waterstate.waterwidth)
+ {
+ r_waterstate.enabled = true;
+
+ // set up variables that will be used in shader setup
+ r_waterstate.screenscale[0] = 0.5f * (float)waterwidth / (float)texturewidth;
+ r_waterstate.screenscale[1] = 0.5f * (float)waterheight / (float)textureheight;
+ r_waterstate.screencenter[0] = 0.5f * (float)waterwidth / (float)texturewidth;
+ r_waterstate.screencenter[1] = 0.5f * (float)waterheight / (float)textureheight;
+ }
+
+ r_waterstate.maxwaterplanes = MAX_WATERPLANES;
+ r_waterstate.numwaterplanes = 0;
+}
+
+static void R_Water_AddWaterPlane(msurface_t *surface)
+{
+ int triangleindex, planeindex;
+ const int *e;
+ vec3_t vert[3];
+ vec3_t normal;
+ vec3_t center;
+ mplane_t plane;
+ r_waterstate_waterplane_t *p;
+ // just use the first triangle with a valid normal for any decisions
+ VectorClear(normal);
+ for (triangleindex = 0, e = rsurface.modelelement3i + surface->num_firsttriangle * 3;triangleindex < surface->num_triangles;triangleindex++, e += 3)
+ {
+ Matrix4x4_Transform(&rsurface.matrix, rsurface.modelvertex3f + e[0]*3, vert[0]);
+ Matrix4x4_Transform(&rsurface.matrix, rsurface.modelvertex3f + e[1]*3, vert[1]);
+ Matrix4x4_Transform(&rsurface.matrix, rsurface.modelvertex3f + e[2]*3, vert[2]);
+ TriangleNormal(vert[0], vert[1], vert[2], normal);
+ if (VectorLength2(normal) >= 0.001)
+ break;
+ }
+
+ VectorCopy(normal, plane.normal);
+ VectorNormalize(plane.normal);
+ plane.dist = DotProduct(vert[0], plane.normal);
+ PlaneClassify(&plane);
+ if (PlaneDiff(r_refdef.view.origin, &plane) < 0)
+ {
+ // skip backfaces (except if nocullface is set)
+ if (!(surface->texture->currentframe->currentmaterialflags & MATERIALFLAG_NOCULLFACE))
+ return;
+ VectorNegate(plane.normal, plane.normal);
+ plane.dist *= -1;
+ PlaneClassify(&plane);
+ }
+
+
+ // find a matching plane if there is one
+ for (planeindex = 0, p = r_waterstate.waterplanes;planeindex < r_waterstate.numwaterplanes;planeindex++, p++)
+ if (fabs(PlaneDiff(vert[0], &p->plane)) < 1 && fabs(PlaneDiff(vert[1], &p->plane)) < 1 && fabs(PlaneDiff(vert[2], &p->plane)) < 1)
+ break;
+ if (planeindex >= r_waterstate.maxwaterplanes)
+ return; // nothing we can do, out of planes
+
+ // if this triangle does not fit any known plane rendered this frame, add one
+ if (planeindex >= r_waterstate.numwaterplanes)
+ {
+ // store the new plane
+ r_waterstate.numwaterplanes++;
+ p->plane = plane;
+ // clear materialflags and pvs
+ p->materialflags = 0;
+ p->pvsvalid = false;
+ }
+ // merge this surface's materialflags into the waterplane
+ p->materialflags |= surface->texture->currentframe->currentmaterialflags;
+ // merge this surface's PVS into the waterplane
+ VectorMAM(0.5f, surface->mins, 0.5f, surface->maxs, center);
+ if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION) && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.FatPVS
+ && r_refdef.scene.worldmodel->brush.PointInLeaf && r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, center)->clusterindex >= 0)
+ {
+ r_refdef.scene.worldmodel->brush.FatPVS(r_refdef.scene.worldmodel, center, 2, p->pvsbits, sizeof(p->pvsbits), p->pvsvalid);
+ p->pvsvalid = true;
+ }
+}
+
+static void R_Water_ProcessPlanes(void)
+{
+ r_refdef_view_t originalview;
+ int planeindex;
+ r_waterstate_waterplane_t *p;
+
+ originalview = r_refdef.view;
+
+ // make sure enough textures are allocated
+ for (planeindex = 0, p = r_waterstate.waterplanes;planeindex < r_waterstate.numwaterplanes;planeindex++, p++)
+ {
+ if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION))
+ {
+ if (!p->texture_refraction)
+ p->texture_refraction = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_refraction", planeindex), r_waterstate.texturewidth, r_waterstate.textureheight, NULL, TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP | TEXF_ALWAYSPRECACHE, NULL);
+ if (!p->texture_refraction)
+ goto error;
+ }
+
+ if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION))
+ {
+ if (!p->texture_reflection)
+ p->texture_reflection = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_reflection", planeindex), r_waterstate.texturewidth, r_waterstate.textureheight, NULL, TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP | TEXF_ALWAYSPRECACHE, NULL);
+ if (!p->texture_reflection)
+ goto error;
+ }
+ }
+
+ // render views
+ for (planeindex = 0, p = r_waterstate.waterplanes;planeindex < r_waterstate.numwaterplanes;planeindex++, p++)
+ {
+ r_refdef.view.showdebug = false;
+ r_refdef.view.width = r_waterstate.waterwidth;
+ r_refdef.view.height = r_waterstate.waterheight;
+ r_refdef.view.useclipplane = true;
+ r_waterstate.renderingscene = true;
+
+ // render the normal view scene and copy into texture
+ // (except that a clipping plane should be used to hide everything on one side of the water, and the viewer's weapon model should be omitted)
+ if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION))
+ {
+ r_refdef.view.clipplane = p->plane;
+ VectorNegate(r_refdef.view.clipplane.normal, r_refdef.view.clipplane.normal);
+ r_refdef.view.clipplane.dist = -r_refdef.view.clipplane.dist;
+ PlaneClassify(&r_refdef.view.clipplane);
+
+ R_RenderScene(false);
+
+ // copy view into the screen texture
+ R_Mesh_TexBind(0, R_GetTexture(p->texture_refraction));
+ GL_ActiveTexture(0);
+ CHECKGLERROR
+ qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, r_refdef.view.x, vid.height - (r_refdef.view.y + r_refdef.view.height), r_refdef.view.width, r_refdef.view.height);CHECKGLERROR
+ }
+
+ if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION))
+ {
+ // render reflected scene and copy into texture
+ Matrix4x4_Reflect(&r_refdef.view.matrix, p->plane.normal[0], p->plane.normal[1], p->plane.normal[2], p->plane.dist, -2);
+ // update the r_refdef.view.origin because otherwise the sky renders at the wrong location (amongst other problems)
+ Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, r_refdef.view.origin);
+ r_refdef.view.clipplane = p->plane;
+ // reverse the cullface settings for this render
+ r_refdef.view.cullface_front = GL_FRONT;
+ r_refdef.view.cullface_back = GL_BACK;
+ if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.num_pvsclusterbytes)
+ {
+ r_refdef.view.usecustompvs = true;
+ if (p->pvsvalid)
+ memcpy(r_refdef.viewcache.world_pvsbits, p->pvsbits, r_refdef.scene.worldmodel->brush.num_pvsclusterbytes);
+ else
+ memset(r_refdef.viewcache.world_pvsbits, 0xFF, r_refdef.scene.worldmodel->brush.num_pvsclusterbytes);
+ }
+
+ R_ResetViewRendering3D();
+ R_ClearScreen(r_refdef.fogenabled);
+ if (r_timereport_active)
+ R_TimeReport("viewclear");
+
+ R_RenderScene(false);
+
+ R_Mesh_TexBind(0, R_GetTexture(p->texture_reflection));
+ GL_ActiveTexture(0);
+ CHECKGLERROR
+ qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, r_refdef.view.x, vid.height - (r_refdef.view.y + r_refdef.view.height), r_refdef.view.width, r_refdef.view.height);CHECKGLERROR
+
+ R_ResetViewRendering3D();
+ R_ClearScreen(r_refdef.fogenabled);
+ if (r_timereport_active)
+ R_TimeReport("viewclear");
+ }