summaryrefslogtreecommitdiff
path: root/src/engine/water.cpp
diff options
context:
space:
mode:
authorxolatile2025-07-16 23:07:43 +0200
committerxolatile2025-07-16 23:07:43 +0200
commit7256502afa0babe60fcafbd2888cd3e33c3f9b6b (patch)
tree8a8495662a69bdadc4b5d9152656b9f02a44d668 /src/engine/water.cpp
parentbc596ac9d4cdd00abf537b88d3c544be161330cc (diff)
downloadxolatile-badassbug-7256502afa0babe60fcafbd2888cd3e33c3f9b6b.tar.xz
xolatile-badassbug-7256502afa0babe60fcafbd2888cd3e33c3f9b6b.tar.zst
Source code, broken...
Diffstat (limited to 'src/engine/water.cpp')
-rw-r--r--src/engine/water.cpp1061
1 files changed, 1061 insertions, 0 deletions
diff --git a/src/engine/water.cpp b/src/engine/water.cpp
new file mode 100644
index 0000000..07c24c2
--- /dev/null
+++ b/src/engine/water.cpp
@@ -0,0 +1,1061 @@
+#include "engine.h"
+
+VARFP(waterreflect, 0, 1, 1, { cleanreflections(); preloadwatershaders(); });
+VARFP(waterrefract, 0, 1, 1, { cleanreflections(); preloadwatershaders(); });
+VARFP(waterenvmap, 0, 1, 1, { cleanreflections(); preloadwatershaders(); });
+VARFP(waterfallrefract, 0, 0, 1, { cleanreflections(); preloadwatershaders(); });
+
+/* vertex water */
+VARP(watersubdiv, 0, 2, 3);
+VARP(waterlod, 0, 1, 3);
+
+static int wx1, wy1, wx2, wy2, wz, wsize, wsubdiv;
+static float whoffset, whphase;
+
+static inline float vertwangle(int v1, int v2)
+{
+ static const float whscale = 59.0f/23.0f/(2*M_PI);
+ v1 &= wsize-1;
+ v2 &= wsize-1;
+ return v1*v2*whscale+whoffset;
+}
+
+static inline float vertwphase(float angle)
+{
+ float s = angle - int(angle) - 0.5f;
+ s *= 8 - fabs(s)*16;
+ return WATER_AMPLITUDE*s-WATER_OFFSET;
+}
+
+static inline void vertw(int v1, int v2, int v3)
+{
+ float h = vertwphase(vertwangle(v1, v2));
+ gle::attribf(v1, v2, v3+h);
+}
+
+static inline void vertwq(float v1, float v2, float v3)
+{
+ gle::attribf(v1, v2, v3+whphase);
+}
+
+static inline void vertwn(float v1, float v2, float v3)
+{
+ float h = -WATER_OFFSET;
+ gle::attribf(v1, v2, v3+h);
+}
+
+struct waterstrip
+{
+ int x1, y1, x2, y2, z;
+ ushort size, subdiv;
+
+ int numverts() const { return 2*((y2-y1)/subdiv + 1)*((x2-x1)/subdiv); }
+
+ void save()
+ {
+ x1 = wx1;
+ y1 = wy1;
+ x2 = wx2;
+ y2 = wy2;
+ z = wz;
+ size = wsize;
+ subdiv = wsubdiv;
+ }
+
+ void restore()
+ {
+ wx1 = x1;
+ wy1 = y1;
+ wx2 = x2;
+ wy2 = y2;
+ wz = z;
+ wsize = size;
+ wsubdiv = subdiv;
+ }
+};
+vector<waterstrip> waterstrips;
+
+void flushwaterstrips()
+{
+ if(gle::attribbuf.length()) xtraverts += gle::end();
+ gle::defvertex();
+ int numverts = 0;
+ loopv(waterstrips) numverts += waterstrips[i].numverts();
+ gle::begin(GL_TRIANGLE_STRIP, numverts);
+ loopv(waterstrips)
+ {
+ waterstrips[i].restore();
+ for(int x = wx1; x < wx2; x += wsubdiv)
+ {
+ for(int y = wy1; y <= wy2; y += wsubdiv)
+ {
+ vertw(x, y, wz);
+ vertw(x+wsubdiv, y, wz);
+ }
+ x += wsubdiv;
+ if(x >= wx2) break;
+ for(int y = wy2; y >= wy1; y -= wsubdiv)
+ {
+ vertw(x, y, wz);
+ vertw(x+wsubdiv, y, wz);
+ }
+ }
+ gle::multidraw();
+ }
+ waterstrips.setsize(0);
+ wsize = 0;
+ xtraverts += gle::end();
+}
+
+void flushwater(int mat = MAT_WATER, bool force = true)
+{
+ if(wsize)
+ {
+ if(wsubdiv >= wsize)
+ {
+ if(gle::attribbuf.empty()) { gle::defvertex(); gle::begin(GL_QUADS); }
+ vertwq(wx1, wy1, wz);
+ vertwq(wx2, wy1, wz);
+ vertwq(wx2, wy2, wz);
+ vertwq(wx1, wy2, wz);
+ }
+ else waterstrips.add().save();
+ wsize = 0;
+ }
+
+ if(force)
+ {
+ if(gle::attribbuf.length()) xtraverts += gle::end();
+ if(waterstrips.length()) flushwaterstrips();
+ }
+}
+
+void rendervertwater(int subdiv, int xo, int yo, int z, int size, int mat)
+{
+ if(wsize == size && wsubdiv == subdiv && wz == z)
+ {
+ if(wx2 == xo)
+ {
+ if(wy1 == yo && wy2 == yo + size) { wx2 += size; return; }
+ }
+ else if(wy2 == yo && wx1 == xo && wx2 == xo + size) { wy2 += size; return; }
+ }
+
+ flushwater(mat, false);
+
+ wx1 = xo;
+ wy1 = yo;
+ wx2 = xo + size,
+ wy2 = yo + size;
+ wz = z;
+ wsize = size;
+ wsubdiv = subdiv;
+
+ ASSERT((wx1 & (subdiv - 1)) == 0);
+ ASSERT((wy1 & (subdiv - 1)) == 0);
+}
+
+int calcwatersubdiv(int x, int y, int z, int size)
+{
+ float dist;
+ if(camera1->o.x >= x && camera1->o.x < x + size &&
+ camera1->o.y >= y && camera1->o.y < y + size)
+ dist = fabs(camera1->o.z - float(z));
+ else
+ dist = vec(x + size/2, y + size/2, z + size/2).dist(camera1->o) - size*1.42f/2;
+ int subdiv = watersubdiv + int(dist) / (32 << waterlod);
+ return subdiv >= 31 ? INT_MAX : 1<<subdiv;
+}
+
+int renderwaterlod(int x, int y, int z, int size, int mat)
+{
+ if(size <= (32 << waterlod))
+ {
+ int subdiv = calcwatersubdiv(x, y, z, size);
+ if(subdiv < size * 2) rendervertwater(min(subdiv, size), x, y, z, size, mat);
+ return subdiv;
+ }
+ else
+ {
+ int subdiv = calcwatersubdiv(x, y, z, size);
+ if(subdiv >= size)
+ {
+ if(subdiv < size * 2) rendervertwater(size, x, y, z, size, mat);
+ return subdiv;
+ }
+ int childsize = size / 2,
+ subdiv1 = renderwaterlod(x, y, z, childsize, mat),
+ subdiv2 = renderwaterlod(x + childsize, y, z, childsize, mat),
+ subdiv3 = renderwaterlod(x + childsize, y + childsize, z, childsize, mat),
+ subdiv4 = renderwaterlod(x, y + childsize, z, childsize, mat),
+ minsubdiv = subdiv1;
+ minsubdiv = min(minsubdiv, subdiv2);
+ minsubdiv = min(minsubdiv, subdiv3);
+ minsubdiv = min(minsubdiv, subdiv4);
+ if(minsubdiv < size * 2)
+ {
+ if(minsubdiv >= size) rendervertwater(size, x, y, z, size, mat);
+ else
+ {
+ if(subdiv1 >= size) rendervertwater(childsize, x, y, z, childsize, mat);
+ if(subdiv2 >= size) rendervertwater(childsize, x + childsize, y, z, childsize, mat);
+ if(subdiv3 >= size) rendervertwater(childsize, x + childsize, y + childsize, z, childsize, mat);
+ if(subdiv4 >= size) rendervertwater(childsize, x, y + childsize, z, childsize, mat);
+ }
+ }
+ return minsubdiv;
+ }
+}
+
+void renderflatwater(int x, int y, int z, int rsize, int csize, int mat)
+{
+ if(gle::attribbuf.empty()) { gle::defvertex(); gle::begin(GL_QUADS); }
+ vertwn(x, y, z);
+ vertwn(x+rsize, y, z);
+ vertwn(x+rsize, y+csize, z);
+ vertwn(x, y+csize, z);
+}
+
+VARFP(vertwater, 0, 1, 1, allchanged());
+
+static inline void renderwater(const materialsurface &m, int mat = MAT_WATER)
+{
+ if(!vertwater || drawtex == DRAWTEX_MINIMAP) renderflatwater(m.o.x, m.o.y, m.o.z, m.rsize, m.csize, mat);
+ else if(renderwaterlod(m.o.x, m.o.y, m.o.z, m.csize, mat) >= int(m.csize) * 2)
+ rendervertwater(m.csize, m.o.x, m.o.y, m.o.z, m.csize, mat);
+}
+
+void setuplava(Texture *tex, float scale)
+{
+ float xk = TEX_SCALE/(tex->xs*scale);
+ float yk = TEX_SCALE/(tex->ys*scale);
+ float scroll = lastmillis/1000.0f;
+ LOCALPARAMF(lavatexgen, xk, yk, scroll, scroll);
+ gle::normal(vec(0, 0, 1));
+ whoffset = fmod(float(lastmillis/2000.0f/(2*M_PI)), 1.0f);
+ whphase = vertwphase(whoffset);
+}
+
+void renderlava(const materialsurface &m)
+{
+ renderwater(m, MAT_LAVA);
+}
+
+void flushlava()
+{
+ flushwater(MAT_LAVA);
+}
+
+/* reflective/refractive water */
+
+#define MAXREFLECTIONS 16
+
+struct Reflection
+{
+ GLuint tex, refracttex;
+ int material, height, depth, age;
+ bool init;
+ matrix4 projmat;
+ occludequery *query, *prevquery;
+ vector<materialsurface *> matsurfs;
+
+ Reflection() : tex(0), refracttex(0), material(-1), height(-1), depth(0), age(0), init(false), query(NULL), prevquery(NULL)
+ {}
+};
+
+VARP(reflectdist, 0, 2000, 10000);
+
+#define WATERVARS(name) \
+ bvec name##color(0x14, 0x46, 0x50), name##fallcolor(0, 0, 0); \
+ HVARFR(name##colour, 0, 0x144650, 0xFFFFFF, \
+ { \
+ if(!name##colour) name##colour = 0x144650; \
+ name##color = bvec((name##colour>>16)&0xFF, (name##colour>>8)&0xFF, name##colour&0xFF); \
+ }); \
+ VARR(name##fog, 0, 150, 10000); \
+ VARR(name##spec, 0, 150, 1000); \
+ HVARFR(name##fallcolour, 0, 0, 0xFFFFFF, \
+ { \
+ name##fallcolor = bvec((name##fallcolour>>16)&0xFF, (name##fallcolour>>8)&0xFF, name##fallcolour&0xFF); \
+ });
+
+WATERVARS(water)
+WATERVARS(water2)
+WATERVARS(water3)
+WATERVARS(water4)
+
+GETMATIDXVAR(water, colour, int)
+GETMATIDXVAR(water, color, const bvec &)
+GETMATIDXVAR(water, fallcolour, int)
+GETMATIDXVAR(water, fallcolor, const bvec &)
+GETMATIDXVAR(water, fog, int)
+GETMATIDXVAR(water, spec, int)
+
+#define LAVAVARS(name) \
+ bvec name##color(0xFF, 0x40, 0x00); \
+ HVARFR(name##colour, 0, 0xFF4000, 0xFFFFFF, \
+ { \
+ if(!name##colour) name##colour = 0xFF4000; \
+ name##color = bvec((name##colour>>16)&0xFF, (name##colour>>8)&0xFF, name##colour&0xFF); \
+ }); \
+ VARR(name##fog, 0, 50, 10000);
+
+LAVAVARS(lava)
+LAVAVARS(lava2)
+LAVAVARS(lava3)
+LAVAVARS(lava4)
+
+GETMATIDXVAR(lava, colour, int)
+GETMATIDXVAR(lava, color, const bvec &)
+GETMATIDXVAR(lava, fog, int)
+
+void setprojtexmatrix(Reflection &ref)
+{
+ if(ref.init)
+ {
+ ref.init = false;
+ (ref.projmat = camprojmatrix).projective();
+ }
+
+ LOCALPARAM(watermatrix, ref.projmat);
+}
+
+Reflection reflections[MAXREFLECTIONS];
+Reflection waterfallrefraction;
+GLuint reflectionfb = 0, reflectiondb = 0;
+
+GLuint getwaterfalltex() { return waterfallrefraction.refracttex ? waterfallrefraction.refracttex : notexture->id; }
+
+VAR(oqwater, 0, 2, 2);
+VARFP(waterfade, 0, 1, 1, { cleanreflections(); preloadwatershaders(); });
+
+void preloadwatershaders(bool force)
+{
+ static bool needwater = false;
+ if(force) needwater = true;
+ if(!needwater) return;
+
+ useshaderbyname("waterglare");
+
+ if(waterenvmap && !waterreflect)
+ useshaderbyname(waterrefract ? (waterfade ? "waterenvfade" : "waterenvrefract") : "waterenv");
+ else useshaderbyname(waterrefract ? (waterfade ? "waterfade" : "waterrefract") : (waterreflect ? "waterreflect" : "water"));
+
+ useshaderbyname(waterrefract ? (waterfade ? "underwaterfade" : "underwaterrefract") : "underwater");
+
+ extern int waterfallenv;
+ useshaderbyname(waterfallenv ? "waterfallenv" : "waterfall");
+ if(waterfallrefract) useshaderbyname(waterfallenv ? "waterfallenvrefract" : "waterfallrefract");
+}
+
+void renderwater()
+{
+ if(editmode && showmat && !drawtex) return;
+ if(!rplanes) return;
+
+ glDisable(GL_CULL_FACE);
+
+ if(!glaring && drawtex != DRAWTEX_MINIMAP)
+ {
+ if(waterrefract)
+ {
+ if(waterfade)
+ {
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ }
+ }
+ else
+ {
+ glDepthMask(GL_FALSE);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_SRC_ALPHA);
+ }
+ }
+
+ GLOBALPARAM(camera, camera1->o);
+ GLOBALPARAMF(millis, lastmillis/1000.0f);
+
+ #define SETWATERSHADER(which, name) \
+ do { \
+ static Shader *name##shader = NULL; \
+ if(!name##shader) name##shader = lookupshaderbyname(#name); \
+ which##shader = name##shader; \
+ } while(0)
+
+ Shader *aboveshader = NULL;
+ if(glaring) SETWATERSHADER(above, waterglare);
+ else if(drawtex == DRAWTEX_MINIMAP) aboveshader = notextureshader;
+ else if(waterenvmap && !waterreflect)
+ {
+ if(waterrefract)
+ {
+ if(waterfade) SETWATERSHADER(above, waterenvfade);
+ else SETWATERSHADER(above, waterenvrefract);
+ }
+ else SETWATERSHADER(above, waterenv);
+ }
+ else if(waterrefract)
+ {
+ if(waterfade) SETWATERSHADER(above, waterfade);
+ else SETWATERSHADER(above, waterrefract);
+ }
+ else if(waterreflect) SETWATERSHADER(above, waterreflect);
+ else SETWATERSHADER(above, water);
+
+ Shader *belowshader = NULL;
+ if(!glaring && drawtex != DRAWTEX_MINIMAP)
+ {
+ if(waterrefract)
+ {
+ if(waterfade) SETWATERSHADER(below, underwaterfade);
+ else SETWATERSHADER(below, underwaterrefract);
+ }
+ else SETWATERSHADER(below, underwater);
+ }
+
+ vec ambient(max(skylightcolor[0], ambientcolor[0]), max(skylightcolor[1], ambientcolor[1]), max(skylightcolor[2], ambientcolor[2]));
+ float offset = -WATER_OFFSET;
+ loopi(MAXREFLECTIONS)
+ {
+ Reflection &ref = reflections[i];
+ if(ref.height<0 || ref.age || ref.matsurfs.empty()) continue;
+ if(!glaring && oqfrags && oqwater && ref.query && ref.query->owner==&ref)
+ {
+ if(!ref.prevquery || ref.prevquery->owner!=&ref || checkquery(ref.prevquery))
+ {
+ if(checkquery(ref.query)) continue;
+ }
+ }
+
+ bool below = camera1->o.z < ref.height+offset;
+ if(below)
+ {
+ if(!belowshader) continue;
+ belowshader->set();
+ }
+ else aboveshader->set();
+
+ if(!glaring && drawtex != DRAWTEX_MINIMAP)
+ {
+ if(waterreflect || waterrefract)
+ {
+ if(waterreflect || !waterenvmap) glBindTexture(GL_TEXTURE_2D, waterreflect ? ref.tex : ref.refracttex);
+ setprojtexmatrix(ref);
+ }
+
+ if(waterrefract)
+ {
+ glActiveTexture_(GL_TEXTURE3);
+ glBindTexture(GL_TEXTURE_2D, ref.refracttex);
+ if(waterfade)
+ {
+ float fadeheight = ref.height+offset+(below ? -2 : 2);
+ LOCALPARAMF(waterheight, fadeheight);
+ }
+ }
+ }
+
+ MSlot &mslot = lookupmaterialslot(ref.material);
+ glActiveTexture_(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, mslot.sts.inrange(2) ? mslot.sts[2].t->id : notexture->id);
+ glActiveTexture_(GL_TEXTURE2);
+ glBindTexture(GL_TEXTURE_2D, mslot.sts.inrange(3) ? mslot.sts[3].t->id : notexture->id);
+ glActiveTexture_(GL_TEXTURE0);
+ if(!glaring && waterenvmap && !waterreflect && drawtex != DRAWTEX_MINIMAP)
+ {
+ glBindTexture(GL_TEXTURE_CUBE_MAP, lookupenvmap(mslot));
+ }
+
+ whoffset = fmod(float(lastmillis/600.0f/(2*M_PI)), 1.0f);
+ whphase = vertwphase(whoffset);
+
+ gle::color(getwatercolor(ref.material));
+ int wfog = getwaterfog(ref.material), wspec = getwaterspec(ref.material);
+
+ const entity *lastlight = (const entity *)-1;
+ int lastdepth = -1;
+ loopvj(ref.matsurfs)
+ {
+ materialsurface &m = *ref.matsurfs[j];
+
+ entity *light = (m.light && m.light->type==ET_LIGHT ? m.light : NULL);
+ if(light!=lastlight)
+ {
+ flushwater();
+ vec lightpos = light ? light->o : vec(worldsize/2, worldsize/2, worldsize);
+ float lightrad = light && light->attr1 ? light->attr1 : worldsize*8.0f;
+ vec lightcol = (light ? vec(light->attr2, light->attr3, light->attr4) : vec(ambient)).div(255.0f).mul(wspec/100.0f);
+ LOCALPARAM(lightpos, lightpos);
+ LOCALPARAM(lightcolor, lightcol);
+ LOCALPARAMF(lightradius, lightrad);
+ lastlight = light;
+ }
+
+ if(!glaring && !waterrefract && m.depth!=lastdepth)
+ {
+ flushwater();
+ float depth = !wfog ? 1.0f : min(0.75f*m.depth/wfog, 0.95f);
+ depth = max(depth, !below && (waterreflect || waterenvmap) ? 0.3f : 0.6f);
+ LOCALPARAMF(depth, depth, 1.0f-depth);
+ lastdepth = m.depth;
+ }
+
+ renderwater(m);
+ }
+ flushwater();
+ }
+
+ if(!glaring && drawtex != DRAWTEX_MINIMAP)
+ {
+ if(waterrefract)
+ {
+ if(waterfade) glDisable(GL_BLEND);
+ }
+ else
+ {
+ glDepthMask(GL_TRUE);
+ glDisable(GL_BLEND);
+ }
+ }
+
+ glEnable(GL_CULL_FACE);
+}
+
+void setupwaterfallrefract()
+{
+ glBindTexture(GL_TEXTURE_2D, waterfallrefraction.refracttex ? waterfallrefraction.refracttex : notexture->id);
+ setprojtexmatrix(waterfallrefraction);
+}
+
+void cleanreflection(Reflection &ref)
+{
+ ref.material = -1;
+ ref.height = -1;
+ ref.init = false;
+ ref.query = ref.prevquery = NULL;
+ ref.matsurfs.setsize(0);
+ if(ref.tex)
+ {
+ glDeleteTextures(1, &ref.tex);
+ ref.tex = 0;
+ }
+ if(ref.refracttex)
+ {
+ glDeleteTextures(1, &ref.refracttex);
+ ref.refracttex = 0;
+ }
+}
+
+void cleanreflections()
+{
+ loopi(MAXREFLECTIONS) cleanreflection(reflections[i]);
+ cleanreflection(waterfallrefraction);
+ if(reflectionfb)
+ {
+ glDeleteFramebuffers_(1, &reflectionfb);
+ reflectionfb = 0;
+ }
+ if(reflectiondb)
+ {
+ glDeleteRenderbuffers_(1, &reflectiondb);
+ reflectiondb = 0;
+ }
+}
+
+VARFP(reflectsize, 6, 8, 11, cleanreflections());
+
+void genwatertex(GLuint &tex, GLuint &fb, GLuint &db, bool refract = false)
+{
+ static const GLenum colorfmts[] = { GL_RGBA, GL_RGBA8, GL_RGB, GL_RGB8, GL_FALSE },
+ depthfmts[] = { GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT32, GL_FALSE };
+ static GLenum reflectfmt = GL_FALSE, refractfmt = GL_FALSE, depthfmt = GL_FALSE;
+ static bool usingalpha = false;
+ bool needsalpha = refract && waterrefract && waterfade;
+ if(refract && usingalpha!=needsalpha)
+ {
+ usingalpha = needsalpha;
+ refractfmt = GL_FALSE;
+ }
+ int size = 1<<reflectsize;
+ while(size>hwtexsize) size /= 2;
+
+ glGenTextures(1, &tex);
+ char *buf = new char[size*size*4];
+ memset(buf, 0, size*size*4);
+
+ GLenum &colorfmt = refract ? refractfmt : reflectfmt;
+ if(colorfmt && fb && db)
+ {
+ createtexture(tex, size, size, buf, 3, 1, colorfmt);
+ delete[] buf;
+ return;
+ }
+
+ if(!fb) glGenFramebuffers_(1, &fb);
+ int find = needsalpha ? 0 : 2;
+ do
+ {
+ createtexture(tex, size, size, buf, 3, 1, colorfmt ? colorfmt : colorfmts[find]);
+ glBindFramebuffer_(GL_FRAMEBUFFER, fb);
+ glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
+ if(glCheckFramebufferStatus_(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE) break;
+ }
+ while(!colorfmt && colorfmts[++find]);
+ if(!colorfmt) colorfmt = colorfmts[find];
+
+ delete[] buf;
+
+ if(!db) { glGenRenderbuffers_(1, &db); depthfmt = GL_FALSE; }
+ if(!depthfmt) glBindRenderbuffer_(GL_RENDERBUFFER, db);
+ find = 0;
+ do
+ {
+ if(!depthfmt) glRenderbufferStorage_(GL_RENDERBUFFER, depthfmts[find], size, size);
+ glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, db);
+ if(glCheckFramebufferStatus_(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE) break;
+ }
+ while(!depthfmt && depthfmts[++find]);
+ if(!depthfmt)
+ {
+ glBindRenderbuffer_(GL_RENDERBUFFER, 0);
+ depthfmt = depthfmts[find];
+ }
+
+ glBindFramebuffer_(GL_FRAMEBUFFER, 0);
+}
+
+void addwaterfallrefraction(materialsurface &m)
+{
+ Reflection &ref = waterfallrefraction;
+ if(ref.age>=0)
+ {
+ ref.age = -1;
+ ref.init = false;
+ ref.matsurfs.setsize(0);
+ ref.material = MAT_WATER;
+ ref.height = INT_MAX;
+ }
+ ref.matsurfs.add(&m);
+
+ if(!ref.refracttex) genwatertex(ref.refracttex, reflectionfb, reflectiondb);
+}
+
+void addreflection(materialsurface &m)
+{
+ int mat = m.material, height = m.o.z;
+ Reflection *ref = NULL, *oldest = NULL;
+ loopi(MAXREFLECTIONS)
+ {
+ Reflection &r = reflections[i];
+ if(r.height<0)
+ {
+ if(!ref) ref = &r;
+ }
+ else if(r.height==height && r.material==mat)
+ {
+ r.matsurfs.add(&m);
+ r.depth = max(r.depth, int(m.depth));
+ if(r.age<0) return;
+ ref = &r;
+ break;
+ }
+ else if(!oldest || r.age>oldest->age) oldest = &r;
+ }
+ if(!ref)
+ {
+ if(!oldest || oldest->age<0) return;
+ ref = oldest;
+ }
+ if(ref->height!=height || ref->material!=mat)
+ {
+ ref->material = mat;
+ ref->height = height;
+ ref->prevquery = NULL;
+ }
+ rplanes++;
+ ref->age = -1;
+ ref->init = false;
+ ref->matsurfs.setsize(0);
+ ref->matsurfs.add(&m);
+ ref->depth = m.depth;
+ if(drawtex == DRAWTEX_MINIMAP) return;
+
+ if(waterreflect && !ref->tex) genwatertex(ref->tex, reflectionfb, reflectiondb);
+ if(waterrefract && !ref->refracttex) genwatertex(ref->refracttex, reflectionfb, reflectiondb, true);
+}
+
+static void drawmaterialquery(const materialsurface &m, float offset, float border = 0, float reflect = -1)
+{
+ if(gle::attribbuf.empty())
+ {
+ gle::defvertex();
+ gle::begin(GL_QUADS);
+ }
+ float x = m.o.x, y = m.o.y, z = m.o.z, csize = m.csize + border, rsize = m.rsize + border;
+ if(reflect >= 0) z = 2*reflect - z;
+ switch(m.orient)
+ {
+#define GENFACEORIENT(orient, v0, v1, v2, v3) \
+ case orient: v0 v1 v2 v3 break;
+#define GENFACEVERT(orient, vert, mx,my,mz, sx,sy,sz) \
+ gle::attribf(mx sx, my sy, mz sz);
+ GENFACEVERTS(x, x, y, y, z, z, - border, + csize, - border, + rsize, + offset, - offset)
+#undef GENFACEORIENT
+#undef GENFACEVERT
+ }
+}
+
+extern void drawreflection(float z, bool refract, int fogdepth = -1, const bvec &col = bvec(0, 0, 0));
+
+int rplanes = 0;
+
+void queryreflection(Reflection &ref, bool init)
+{
+ if(init)
+ {
+ nocolorshader->set();
+ glDepthMask(GL_FALSE);
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+ glDisable(GL_CULL_FACE);
+ }
+ startquery(ref.query);
+ loopvj(ref.matsurfs)
+ {
+ materialsurface &m = *ref.matsurfs[j];
+ float offset = 0.1f;
+ if(m.orient==O_TOP)
+ {
+ offset = WATER_OFFSET +
+ (vertwater ? WATER_AMPLITUDE*(camera1->pitch > 0 || m.depth < WATER_AMPLITUDE+0.5f ? -1 : 1) : 0);
+ if(fabs(m.o.z-offset - camera1->o.z) < 0.5f && m.depth > WATER_AMPLITUDE+1.5f)
+ offset += camera1->pitch > 0 ? -1 : 1;
+ }
+ drawmaterialquery(m, offset);
+ }
+ xtraverts += gle::end();
+ endquery(ref.query);
+}
+
+void queryreflections()
+{
+ rplanes = 0;
+
+ static int lastsize = 0;
+ int size = 1<<reflectsize;
+ while(size>hwtexsize) size /= 2;
+ if(size!=lastsize) { if(lastsize) cleanreflections(); lastsize = size; }
+
+ for(vtxarray *va = visibleva; va; va = va->next)
+ {
+ if(!va->matsurfs || va->occluded >= OCCLUDE_BB || va->curvfc >= VFC_FOGGED) continue;
+ int lastmat = -1;
+ loopi(va->matsurfs)
+ {
+ materialsurface &m = va->matbuf[i];
+ if(m.material != lastmat)
+ {
+ if((m.material&MATF_VOLUME) != MAT_WATER || m.orient == O_BOTTOM) { i += m.skip; continue; }
+ if(m.orient != O_TOP)
+ {
+ if(!waterfallrefract || !getwaterfog(m.material)) { i += m.skip; continue; }
+ }
+ lastmat = m.material;
+ }
+ if(m.orient==O_TOP) addreflection(m);
+ else addwaterfallrefraction(m);
+ }
+ }
+
+ loopi(MAXREFLECTIONS)
+ {
+ Reflection &ref = reflections[i];
+ ++ref.age;
+ if(ref.height>=0 && !ref.age && ref.matsurfs.length())
+ {
+ if(waterpvsoccluded(ref.height)) ref.matsurfs.setsize(0);
+ }
+ }
+ if(waterfallrefract)
+ {
+ Reflection &ref = waterfallrefraction;
+ ++ref.age;
+ if(ref.height>=0 && !ref.age && ref.matsurfs.length())
+ {
+ if(waterpvsoccluded(-1)) ref.matsurfs.setsize(0);
+ }
+ }
+
+ if((editmode && showmat && !drawtex) || !oqfrags || !oqwater || drawtex == DRAWTEX_MINIMAP) return;
+
+ int refs = 0;
+ if(waterreflect || waterrefract) loopi(MAXREFLECTIONS)
+ {
+ Reflection &ref = reflections[i];
+ ref.prevquery = oqwater > 1 ? ref.query : NULL;
+ ref.query = ref.height>=0 && !ref.age && ref.matsurfs.length() ? newquery(&ref) : NULL;
+ if(ref.query) queryreflection(ref, !refs++);
+ }
+ if(waterfallrefract)
+ {
+ Reflection &ref = waterfallrefraction;
+ ref.prevquery = oqwater > 1 ? ref.query : NULL;
+ ref.query = ref.height>=0 && !ref.age && ref.matsurfs.length() ? newquery(&ref) : NULL;
+ if(ref.query) queryreflection(ref, !refs++);
+ }
+
+ if(refs)
+ {
+ glDepthMask(GL_TRUE);
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ glEnable(GL_CULL_FACE);
+ }
+
+ glFlush();
+}
+
+VARP(maxreflect, 1, 2, 8);
+
+int refracting = 0, refractfog = 0;
+bvec refractcolor(0, 0, 0);
+bool reflecting = false, fading = false, fogging = false;
+float reflectz = 1e16f;
+
+VAR(maskreflect, 0, 2, 16);
+
+void maskreflection(Reflection &ref, float offset, bool reflect, bool clear = false)
+{
+ const bvec &wcol = getwatercolor(ref.material);
+ vec color = wcol.tocolor();
+ if(!maskreflect)
+ {
+ if(clear) glClearColor(color.r, color.g, color.b, 1);
+ glClear(GL_DEPTH_BUFFER_BIT | (clear ? GL_COLOR_BUFFER_BIT : 0));
+ return;
+ }
+ glClearDepth(0);
+ glClear(GL_DEPTH_BUFFER_BIT);
+ glClearDepth(1);
+ glDepthRange(1, 1);
+ glDepthFunc(GL_ALWAYS);
+ glDisable(GL_CULL_FACE);
+ if(clear)
+ {
+ notextureshader->set();
+ gle::color(color);
+ }
+ else
+ {
+ nocolorshader->set();
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+ }
+ float reflectheight = reflect ? ref.height + offset : -1;
+ loopv(ref.matsurfs)
+ {
+ materialsurface &m = *ref.matsurfs[i];
+ drawmaterialquery(m, -offset, maskreflect, reflectheight);
+ }
+ xtraverts += gle::end();
+ if(!clear) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ glEnable(GL_CULL_FACE);
+ glDepthFunc(GL_LESS);
+ glDepthRange(0, 1);
+}
+
+VAR(reflectscissor, 0, 1, 1);
+VAR(reflectvfc, 0, 1, 1);
+
+static bool calcscissorbox(Reflection &ref, int size, vec &clipmin, vec &clipmax, int &sx, int &sy, int &sw, int &sh)
+{
+ materialsurface &m0 = *ref.matsurfs[0];
+ int dim = dimension(m0.orient), r = R[dim], c = C[dim];
+ ivec bbmin = m0.o, bbmax = bbmin;
+ bbmax[r] += m0.rsize;
+ bbmax[c] += m0.csize;
+ loopvj(ref.matsurfs)
+ {
+ materialsurface &m = *ref.matsurfs[j];
+ bbmin[r] = min(bbmin[r], m.o[r]);
+ bbmin[c] = min(bbmin[c], m.o[c]);
+ bbmax[r] = max(bbmax[r], m.o[r] + m.rsize);
+ bbmax[c] = max(bbmax[c], m.o[c] + m.csize);
+ bbmin[dim] = min(bbmin[dim], m.o[dim]);
+ bbmax[dim] = max(bbmax[dim], m.o[dim]);
+ }
+
+ vec4 v[8];
+ float sx1 = 1, sy1 = 1, sx2 = -1, sy2 = -1;
+ loopi(8)
+ {
+ vec4 &p = v[i];
+ camprojmatrix.transform(vec(i&1 ? bbmax.x : bbmin.x, i&2 ? bbmax.y : bbmin.y, (i&4 ? bbmax.z + WATER_AMPLITUDE : bbmin.z - WATER_AMPLITUDE) - WATER_OFFSET), p);
+ if(p.z >= -p.w)
+ {
+ float x = p.x / p.w, y = p.y / p.w;
+ sx1 = min(sx1, x);
+ sy1 = min(sy1, y);
+ sx2 = max(sx2, x);
+ sy2 = max(sy2, y);
+ }
+ }
+ if(sx1 >= sx2 || sy1 >= sy2) return false;
+ loopi(8)
+ {
+ const vec4 &p = v[i];
+ if(p.z >= -p.w) continue;
+ loopj(3)
+ {
+ const vec4 &o = v[i^(1<<j)];
+ if(o.z <= -o.w) continue;
+ float t = (p.z + p.w)/(p.z + p.w - o.z - o.w),
+ w = p.w + t*(o.w - p.w),
+ x = (p.x + t*(o.x - p.x))/w,
+ y = (p.y + t*(o.y - p.y))/w;
+ sx1 = min(sx1, x);
+ sy1 = min(sy1, y);
+ sx2 = max(sx2, x);
+ sy2 = max(sy2, y);
+ }
+ }
+ if(sx1 <= -1 && sy1 <= -1 && sx2 >= 1 && sy2 >= 1) return false;
+ sx1 = max(sx1, -1.0f);
+ sy1 = max(sy1, -1.0f);
+ sx2 = min(sx2, 1.0f);
+ sy2 = min(sy2, 1.0f);
+ if(reflectvfc)
+ {
+ clipmin.x = clamp(clipmin.x, sx1, sx2);
+ clipmin.y = clamp(clipmin.y, sy1, sy2);
+ clipmax.x = clamp(clipmax.x, sx1, sx2);
+ clipmax.y = clamp(clipmax.y, sy1, sy2);
+ }
+ sx = int(floor((sx1+1)*0.5f*size));
+ sy = int(floor((sy1+1)*0.5f*size));
+ sw = max(int(ceil((sx2+1)*0.5f*size)) - sx, 0);
+ sh = max(int(ceil((sy2+1)*0.5f*size)) - sy, 0);
+ return true;
+}
+
+VARR(refractclear, 0, 0, 1);
+
+void drawreflections()
+{
+ if((editmode && showmat && !drawtex) || drawtex == DRAWTEX_MINIMAP) return;
+
+ static int lastdrawn = 0;
+ int refs = 0, n = lastdrawn;
+ float offset = -WATER_OFFSET;
+ int size = 1<<reflectsize;
+ while(size>hwtexsize) size /= 2;
+
+ if(waterreflect || waterrefract) loopi(MAXREFLECTIONS)
+ {
+ Reflection &ref = reflections[++n%MAXREFLECTIONS];
+ if(ref.height<0 || ref.age || ref.matsurfs.empty()) continue;
+ if(oqfrags && oqwater && ref.query && ref.query->owner==&ref)
+ {
+ if(!ref.prevquery || ref.prevquery->owner!=&ref || checkquery(ref.prevquery))
+ {
+ if(checkquery(ref.query)) continue;
+ }
+ }
+
+ if(!refs)
+ {
+ glViewport(0, 0, size, size);
+ glBindFramebuffer_(GL_FRAMEBUFFER, reflectionfb);
+ }
+ refs++;
+ ref.init = true;
+ lastdrawn = n;
+
+ vec clipmin(-1, -1, -1), clipmax(1, 1, 1);
+ int sx, sy, sw, sh;
+ bool scissor = reflectscissor && calcscissorbox(ref, size, clipmin, clipmax, sx, sy, sw, sh);
+ if(scissor) glScissor(sx, sy, sw, sh);
+ else
+ {
+ sx = sy = 0;
+ sw = sh = size;
+ }
+
+ const bvec &wcol = getwatercolor(ref.material);
+ int wfog = getwaterfog(ref.material);
+
+ if(waterreflect && ref.tex && camera1->o.z >= ref.height+offset)
+ {
+ glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ref.tex, 0);
+ if(scissor) glEnable(GL_SCISSOR_TEST);
+ maskreflection(ref, offset, true);
+ savevfcP();
+ setvfcP(ref.height+offset, clipmin, clipmax);
+ drawreflection(ref.height+offset, false);
+ restorevfcP();
+ if(scissor) glDisable(GL_SCISSOR_TEST);
+ }
+
+ if(waterrefract && ref.refracttex)
+ {
+ glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ref.refracttex, 0);
+ if(scissor) glEnable(GL_SCISSOR_TEST);
+ maskreflection(ref, offset, false, refractclear || !wfog || (ref.depth>=10000 && camera1->o.z >= ref.height + offset));
+ if(wfog || waterfade)
+ {
+ savevfcP();
+ setvfcP(-1, clipmin, clipmax);
+ drawreflection(ref.height+offset, true, wfog, wcol);
+ restorevfcP();
+ }
+ if(scissor) glDisable(GL_SCISSOR_TEST);
+ }
+
+ if(refs>=maxreflect) break;
+ }
+
+ if(waterfallrefract && waterfallrefraction.refracttex)
+ {
+ Reflection &ref = waterfallrefraction;
+
+ if(ref.height<0 || ref.age || ref.matsurfs.empty()) goto nowaterfall;
+ if(oqfrags && oqwater && ref.query && ref.query->owner==&ref)
+ {
+ if(!ref.prevquery || ref.prevquery->owner!=&ref || checkquery(ref.prevquery))
+ {
+ if(checkquery(ref.query)) goto nowaterfall;
+ }
+ }
+
+ if(!refs)
+ {
+ glViewport(0, 0, size, size);
+ glBindFramebuffer_(GL_FRAMEBUFFER, reflectionfb);
+ }
+ refs++;
+ ref.init = true;
+
+ vec clipmin(-1, -1, -1), clipmax(1, 1, 1);
+ int sx, sy, sw, sh;
+ bool scissor = reflectscissor && calcscissorbox(ref, size, clipmin, clipmax, sx, sy, sw, sh);
+ if(scissor) glScissor(sx, sy, sw, sh);
+ else
+ {
+ sx = sy = 0;
+ sw = sh = size;
+ }
+
+ glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ref.refracttex, 0);
+ if(scissor) glEnable(GL_SCISSOR_TEST);
+ maskreflection(ref, -0.1f, false);
+ savevfcP();
+ setvfcP(-1, clipmin, clipmax);
+ drawreflection(-1, true);
+ restorevfcP();
+ if(scissor) glDisable(GL_SCISSOR_TEST);
+ }
+nowaterfall:
+
+ if(!refs) return;
+ glViewport(0, 0, screenw, screenh);
+ glBindFramebuffer_(GL_FRAMEBUFFER, 0);
+}
+