1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-10-28 19:04:08 +01:00
Files
openlara/src/cache.h
2018-08-26 20:34:52 +03:00

1060 lines
37 KiB
C

#ifndef H_CACHE
#define H_CACHE
#include "core.h"
#include "format.h"
#include "controller.h"
#include "camera.h"
#define NO_CLIP_PLANE 1000000.0f
struct ShaderCache {
enum Effect { FX_NONE = 0, FX_UNDERWATER = 1, FX_ALPHA_TEST = 2, FX_CLIP_PLANE = 4 };
Shader *shaders[Core::passMAX][Shader::MAX][(FX_UNDERWATER | FX_ALPHA_TEST | FX_CLIP_PLANE) + 1];
PSO *pso[Core::passMAX][Shader::MAX][(FX_UNDERWATER | FX_ALPHA_TEST | FX_CLIP_PLANE) + 1][bmMAX];
ShaderCache() {
memset(shaders, 0, sizeof(shaders));
LOG("shader: cache warm-up...\n");
prepareCompose(FX_NONE);
if (Core::settings.detail.water > Core::Settings::LOW)
prepareCompose(FX_CLIP_PLANE);
prepareAmbient(FX_NONE);
if (Core::settings.detail.shadows > Core::Settings::LOW)
prepareShadows(FX_NONE);
if (Core::settings.detail.water > Core::Settings::LOW)
prepareWater(FX_NONE);
prepareFilter(FX_NONE);
prepareGUI(FX_NONE);
Core::resetTime();
LOG("shader: cache is ready\n");
}
~ShaderCache() {
for (int pass = 0; pass < Core::passMAX; pass++)
for (int type = 0; type < Shader::MAX; type++)
for (int fx = 0; fx < sizeof(shaders[pass][Shader::MAX]) / sizeof(shaders[pass][Shader::MAX][FX_NONE]); fx++)
delete shaders[pass][type][fx];
}
#define rsBase (RS_COLOR_WRITE | RS_DEPTH_TEST | RS_DEPTH_WRITE | RS_CULL_FRONT)
#define rsBlend (RS_BLEND_ALPHA | RS_BLEND_ADD)
#define rsFull (rsBase | rsBlend)
#define rsShadow (RS_DEPTH_TEST | RS_DEPTH_WRITE | RS_CULL_BACK)
void prepareCompose(int fx) {
compile(Core::passCompose, Shader::SPRITE, fx, rsBase);
compile(Core::passCompose, Shader::MIRROR, fx, rsBase);
compile(Core::passCompose, Shader::ROOM, fx, rsFull);
compile(Core::passCompose, Shader::ROOM, fx, rsFull | RS_DISCARD);
compile(Core::passCompose, Shader::ROOM, fx | FX_UNDERWATER, rsFull);
compile(Core::passCompose, Shader::ROOM, fx | FX_UNDERWATER, rsFull | RS_DISCARD);
compile(Core::passCompose, Shader::ENTITY, fx, rsFull);
compile(Core::passCompose, Shader::ENTITY, fx | FX_UNDERWATER, rsFull);
compile(Core::passCompose, Shader::ENTITY, fx | FX_UNDERWATER, rsFull | RS_DISCARD);
compile(Core::passCompose, Shader::ENTITY, fx, rsFull | RS_DISCARD);
compile(Core::passCompose, Shader::SPRITE, fx, rsFull);
compile(Core::passCompose, Shader::SPRITE, fx | FX_UNDERWATER, rsFull);
compile(Core::passCompose, Shader::SPRITE, fx, rsFull | RS_DISCARD);
compile(Core::passCompose, Shader::SPRITE, fx | FX_UNDERWATER, rsFull | RS_DISCARD);
compile(Core::passCompose, Shader::FLASH, fx, rsFull | RS_BLEND_MULT);
compile(Core::passCompose, Shader::FLASH, fx, rsFull | RS_BLEND_MULT | RS_DISCARD);
}
void prepareAmbient(int fx) {
compile(Core::passAmbient, Shader::ROOM, fx, rsFull);
compile(Core::passAmbient, Shader::ROOM, fx, rsFull | RS_DISCARD);
compile(Core::passAmbient, Shader::ROOM, fx | FX_UNDERWATER, rsFull);
compile(Core::passAmbient, Shader::ROOM, fx | FX_UNDERWATER, rsFull | RS_DISCARD);
compile(Core::passAmbient, Shader::SPRITE, fx, rsFull | RS_DISCARD);
compile(Core::passAmbient, Shader::SPRITE, fx | FX_UNDERWATER, rsFull | RS_DISCARD);
compile(Core::passAmbient, Shader::FLASH, fx, rsFull);
}
void prepareShadows(int fx) {
compile(Core::passShadow, Shader::ENTITY, fx, rsShadow);
compile(Core::passShadow, Shader::ENTITY, fx, rsShadow | RS_DISCARD);
compile(Core::passShadow, Shader::MIRROR, fx, rsShadow);
compile(Core::passShadow, Shader::MIRROR, fx, rsShadow | RS_DISCARD);
}
void prepareWater(int fx) {
compile(Core::passWater, Shader::WATER_MASK, fx, RS_COLOR_WRITE_A | RS_DEPTH_TEST);
compile(Core::passWater, Shader::WATER_STEP, fx, RS_COLOR_WRITE);
compile(Core::passWater, Shader::WATER_DROP, fx, RS_COLOR_WRITE);
compile(Core::passWater, Shader::WATER_CAUSTICS, fx, RS_COLOR_WRITE);
compile(Core::passWater, Shader::WATER_COMPOSE, fx, RS_COLOR_WRITE | RS_DEPTH_TEST);
}
void prepareFilter(int fx) {
compile(Core::passFilter, Shader::FILTER_UPSCALE, fx, RS_COLOR_WRITE);
compile(Core::passFilter, Shader::FILTER_DOWNSAMPLE, fx, RS_COLOR_WRITE);
compile(Core::passFilter, Shader::FILTER_GRAYSCALE, fx, RS_COLOR_WRITE);
compile(Core::passFilter, Shader::FILTER_BLUR, fx, RS_COLOR_WRITE);
}
void prepareGUI(int fx) {
compile(Core::passGUI, Shader::DEFAULT, fx, RS_COLOR_WRITE | RS_BLEND_ALPHA);
}
#undef rsBase
#undef rsBlend
#undef rsFull
#undef rsShadow
Shader* compile(Core::Pass pass, Shader::Type type, int fx, uint32 rs) {
if (rs & RS_DISCARD)
fx |= FX_ALPHA_TEST;
#ifndef FFP
if (shaders[pass][type][fx])
return shaders[pass][type][fx];
int def[SD_MAX], defCount = 0;
#define SD_ADD(x) (def[defCount++] = SD_##x)
if (Core::settings.detail.shadows) {
if (Core::support.shadowSampler) {
SD_ADD(SHADOW_SAMPLER);
} else {
if (Core::support.depthTexture)
SD_ADD(SHADOW_DEPTH);
else
SD_ADD(SHADOW_COLOR);
}
}
switch (pass) {
case Core::passCompose :
case Core::passShadow :
case Core::passAmbient : {
def[defCount++] = SD_TYPE_SPRITE + type;
if (fx & FX_UNDERWATER) SD_ADD(UNDERWATER);
if (fx & FX_ALPHA_TEST) SD_ADD(ALPHA_TEST);
if (pass == Core::passCompose) {
if (fx & FX_CLIP_PLANE)
SD_ADD(CLIP_PLANE);
if (Core::settings.detail.lighting > Core::Settings::MEDIUM && (type == Shader::ENTITY))
SD_ADD(OPT_AMBIENT);
if (Core::settings.detail.shadows > Core::Settings::LOW && (type == Shader::ENTITY || type == Shader::ROOM))
SD_ADD(OPT_SHADOW);
if (Core::settings.detail.shadows > Core::Settings::MEDIUM && (type == Shader::ROOM))
SD_ADD(OPT_CONTACT);
if (Core::settings.detail.water > Core::Settings::MEDIUM && (type == Shader::ENTITY || type == Shader::ROOM) && (fx & FX_UNDERWATER))
SD_ADD(OPT_CAUSTICS);
}
break;
}
case Core::passWater : def[defCount++] = SD_WATER_DROP + type; break;
case Core::passFilter : def[defCount++] = SD_FILTER_UPSCALE + type; break;
case Core::passGUI : break;
default : ASSERT(false);
}
#undef SD_ADD
LOG("shader: %s(%d) %s%s%s\n", Core::passNames[pass], type, (fx & FX_UNDERWATER) ? "u" : "", (fx & FX_ALPHA_TEST) ? "a" : "", (fx & FX_CLIP_PLANE) ? "c" : "");
return shaders[pass][type][fx] = new Shader(pass, type, def, defCount);
#else
return NULL;
#endif
}
Shader *getShader(Core::Pass pass, Shader::Type type, int fx) {
Shader *shader = shaders[pass][type][fx];
#ifndef FFP
if (shader == NULL)
LOG("! NULL shader: %d %d %d\n", int(pass), int(type), int(fx));
ASSERT(shader != NULL);
#endif
return shader;
}
void bind(Core::Pass pass, Shader::Type type, int fx) {
Core::pass = pass;
Shader *shader = getShader(pass, type, fx);
if (shader)
shader->setup();
Core::setAlphaTest((fx & FX_ALPHA_TEST) != 0);
}
};
struct AmbientCache {
IGame *game;
TR::Level *level;
struct Cube {
enum int32 {
BLANK, WAIT, READY
} status;
vec4 colors[6]; // TODO: ubyte4[6]
} *items;
int *offsets;
struct Task {
int room;
int flip;
int sector;
Cube *cube;
} tasks[32];
int tasksCount;
Texture *textures[6 * 4]; // 64, 16, 4, 1
AmbientCache(IGame *game) : game(game), level(game->getLevel()), tasksCount(0) {
items = NULL;
offsets = new int[level->roomsCount];
int sectors = 0;
for (int i = 0; i < level->roomsCount; i++) {
TR::Room &r = level->rooms[i];
offsets[i] = sectors;
sectors += r.xSectors * r.zSectors * (r.alternateRoom > -1 ? 2 : 1); // x2 for flipped rooms
}
// init cache buffer
items = new Cube[sectors];
memset(items, 0, sizeof(Cube) * sectors);
// init downsample textures
for (int j = 0; j < 6; j++)
for (int i = 0; i < 4; i++)
textures[j * 4 + i] = new Texture(64 >> (i << 1), 64 >> (i << 1), FMT_RGBA, OPT_TARGET | OPT_NEAREST);
}
~AmbientCache() {
delete[] items;
delete[] offsets;
for (int i = 0; i < 6 * 4; i++)
delete textures[i];
}
void addTask(int room, int sector) {
if (tasksCount >= COUNT(tasks)) return;
Task &task = tasks[tasksCount++];
task.room = room;
task.flip = level->state.flags.flipped && level->rooms[room].alternateRoom > -1;
task.sector = sector;
task.cube = &items[offsets[room] + sector];
task.cube->status = Cube::WAIT;
}
void renderAmbient(int room, int sector, vec4 *colors) {
PROFILE_MARKER("PASS_AMBIENT");
TR::Room &r = level->rooms[room];
TR::Room::Sector &s = r.sectors[sector];
vec3 pos = vec3(float((sector / r.zSectors) * 1024 + 512 + r.info.x),
float(max((s.floor - 2) * 256, (s.floor + s.ceiling) * 256 / 2)),
float((sector % r.zSectors) * 1024 + 512 + r.info.z));
Core::setClearColor(vec4(0, 0, 0, 1));
// first pass - render environment from position (room geometry & static meshes)
game->renderEnvironment(room, pos, textures, 4);
// second pass - downsample it
Core::setDepthTest(false);
game->setShader(Core::passFilter, Shader::FILTER_DOWNSAMPLE);
for (int i = 1; i < 4; i++) {
int size = 64 >> (i << 1);
Core::active.shader->setParam(uParam, vec4(1.0f / (size << 2), 0.0f, 0.0f, 0.0f));
for (int j = 0; j < 6; j++) {
Texture *src = textures[j * 4 + i - 1];
Texture *dst = textures[j * 4 + i];
Core::setTarget(dst, RT_STORE_COLOR);
src->bind(sDiffuse);
game->getMesh()->renderQuad();
}
}
// get result color from 1x1 textures
for (int j = 0; j < 6; j++) {
Core::setTarget(textures[j * 4 + 3], RT_LOAD_COLOR);
colors[j] = Core::copyPixel(0, 0);
}
Core::setDepthTest(true);
Core::setClearColor(vec4(0, 0, 0, 0));
}
void processQueue() {
game->setupBinding();
for (int i = 0; i < tasksCount; i++) {
Task &task = tasks[i];
bool needFlip = task.flip != level->state.flags.flipped;
if (needFlip) game->flipMap(false);
int sector = task.sector;
if (task.flip) {
TR::Room &r = level->rooms[task.room];
sector -= r.xSectors * r.zSectors;
}
renderAmbient(task.room, sector, &task.cube->colors[0]);
if (needFlip) game->flipMap(false);
task.cube->status = Cube::READY;
}
tasksCount = 0;
}
Cube* getAmbient(int roomIndex, int sector) {
TR::Room &r = level->rooms[roomIndex];
if (level->state.flags.flipped && r.alternateRoom > -1)
sector += r.xSectors * r.zSectors;
Cube *cube = &items[offsets[roomIndex] + sector];
if (cube->status == Cube::BLANK)
addTask(roomIndex, sector);
return cube->status == Cube::READY ? cube : NULL;
}
void getAmbient(int room, const vec3 &pos, Cube &value) {
TR::Room &r = level->rooms[room];
int sx = clamp((int(pos.x) - r.info.x) / 1024, 0, r.xSectors - 1);
int sz = clamp((int(pos.z) - r.info.z) / 1024, 0, r.zSectors - 1);
int sector = sx * r.zSectors + sz;
Cube *a = getAmbient(room, sector);
if (a)
value = *a;
else
value.status = Cube::BLANK;
}
};
struct WaterCache {
#define MAX_SURFACES 16
#define MAX_INVISIBLE_TIME 5.0f
#define SIMULATE_TIMESTEP (1.0f / 40.0f)
#define DETAIL (64.0f / 1024.0f)
#define MAX_DROPS 32
IGame *game;
TR::Level *level;
Texture *screen;
Texture *refract;
Texture *reflect;
struct Item {
int from, to, caust;
float timer;
float waterLevel;
bool flip;
bool visible;
bool blank;
vec3 pos, size;
Texture *mask;
Texture *caustics;
#ifdef BLUR_CAUSTICS
Texture *caustics_tmp;
#endif
Texture *data[2];
Item() {
mask = caustics = data[0] = data[1] = NULL;
}
Item(int from, int to) : from(from), to(to), caust(to), timer(SIMULATE_TIMESTEP), visible(true), blank(true) {
mask = caustics = data[0] = data[1] = NULL;
}
void init(IGame *game) {
TR::Level *level = game->getLevel();
TR::Room &r = level->rooms[to]; // underwater room
ASSERT(r.flags.water);
int minX = r.xSectors, minZ = r.zSectors, maxX = 0, maxZ = 0;
int posY = level->rooms[to].waterLevel;
if (posY == -1)
posY = level->rooms[from].waterLevel;
ASSERT(posY != -1); // underwater room without reaching the surface
int caustY = posY;
for (int z = 0; z < r.zSectors; z++)
for (int x = 0; x < r.xSectors; x++) {
TR::Room::Sector &s = r.sectors[x * r.zSectors + z];
if (s.roomAbove != TR::NO_ROOM && !level->rooms[s.roomAbove].flags.water) {
minX = min(minX, x);
minZ = min(minZ, z);
maxX = max(maxX, x);
maxZ = max(maxZ, z);
if (s.roomBelow != TR::NO_ROOM) {
int16 caustRoom = s.roomBelow;
int floor = int(level->getFloor(&s, vec3(float(r.info.x + x * 1024), float(posY), float(r.info.z + z * 1024)), &caustRoom));
if (floor > caustY) {
caustY = floor;
caust = caustRoom;
}
}
}
}
maxX++;
maxZ++;
int w = nextPow2(maxX - minX);
int h = nextPow2(maxZ - minZ);
uint16 *m = new uint16[w * h];
memset(m, 0, w * h * sizeof(m[0]));
for (int z = minZ; z < maxZ; z++)
for (int x = minX; x < maxX; x++) {
TR::Room::Sector &s = r.sectors[x * r.zSectors + z];
bool hasWater = s.roomAbove != TR::NO_ROOM && !level->rooms[s.roomAbove].flags.water;
if (hasWater) {
TR::Room &rt = level->rooms[s.roomAbove];
int xt = int(r.info.x + x * 1024 - rt.info.x) / 1024;
int zt = int(r.info.z + z * 1024 - rt.info.z) / 1024;
TR::Room::Sector &st = rt.sectors[xt * rt.zSectors + zt];
hasWater = s.ceiling > st.ceiling; // TODO fix for LEVEL10A, use slant
}
m[(x - minX) + w * (z - minZ)] = hasWater ? 0xF800 : 0;
}
mask = new Texture(w, h, FMT_RGB16, OPT_NEAREST, m);
delete[] m;
size = vec3(float((maxX - minX) * 512), 1.0f, float((maxZ - minZ) * 512)); // half size
pos = vec3(r.info.x + minX * 1024 + size.x, float(posY), r.info.z + minZ * 1024 + size.z);
int *mf = new int[4 * w * 64 * h * 64];
memset(mf, 0, sizeof(int) * 4 * w * 64 * h * 64);
data[0] = new Texture(w * 64, h * 64, FMT_RGBA_HALF, OPT_TARGET | OPT_VERTEX, mf);
data[1] = new Texture(w * 64, h * 64, FMT_RGBA_HALF, OPT_TARGET | OPT_VERTEX);
delete[] mf;
caustics = Core::settings.detail.water > Core::Settings::MEDIUM ? new Texture(512, 512, FMT_RGBA, OPT_TARGET) : NULL;
#ifdef BLUR_CAUSTICS
caustics_tmp = Core::settings.detail.water > Core::Settings::MEDIUM ? new Texture(512, 512, Texture::RGBA) : NULL;
#endif
blank = false;
}
void deinit() {
delete data[0];
delete data[1];
delete caustics;
#ifdef BLUR_CAUSTICS
delete caustics_tmp;
#endif
delete mask;
mask = caustics = data[0] = data[1] = NULL;
}
} items[MAX_SURFACES];
int count, visible;
int dropCount;
struct Drop {
vec3 pos;
float radius;
float strength;
Drop() {}
Drop(const vec3 &pos, float radius, float strength) : pos(pos), radius(radius), strength(strength) {}
} drops[MAX_DROPS];
WaterCache(IGame *game) : game(game), level(game->getLevel()), screen(NULL), refract(NULL), count(0), dropCount(0) {
reflect = new Texture(512, 512, FMT_RGBA, OPT_TARGET);
}
~WaterCache() {
delete screen;
delete refract;
delete reflect;
for (int i = 0; i < count; i++)
items[i].deinit();
}
void update() {
int i = 0;
while (i < count) {
Item &item = items[i];
if (item.timer > MAX_INVISIBLE_TIME) {
items[i].deinit();
items[i] = items[--count];
continue;
}
item.timer += Core::deltaTime;
i++;
}
}
void reset() {
for (int i = 0; i < count; i++)
items[i].visible = false;
visible = 0;
}
void flipMap() {
for (int i = 0; i < level->roomsCount && count; i++)
if (level->rooms[i].alternateRoom > -1) {
int j = 0;
while (j < count) {
if (items[j].from == i || items[j].to == i) {
items[j].deinit();
items[j] = items[--count];
} else
j++;
}
}
}
void setVisible(int roomIndex, int nextRoom = TR::NO_ROOM) {
if (nextRoom == TR::NO_ROOM) { // setVisible(underwaterRoom) for caustics update
for (int i = 0; i < count; i++)
if (items[i].caust == roomIndex) {
nextRoom = items[i].from;
if (!items[i].visible) {
items[i].visible = true;
visible++;
}
break;
}
return;
}
int from, to; // from surface room to underwater room
if (level->rooms[roomIndex].flags.water) {
from = nextRoom;
to = roomIndex;
} else {
from = roomIndex;
to = nextRoom;
}
if (level->rooms[to].waterLevel == -1 && level->rooms[from].waterLevel == -1) // not have water surface
return;
for (int i = 0; i < count; i++) {
Item &item = items[i];
if (item.from == from && item.to == to) {
if (!item.visible) {
visible++;
item.visible = true;
}
return;
}
}
if (count == MAX_SURFACES) return;
items[count++] = Item(from, to);
visible++;
}
void bindCaustics(int roomIndex) {
Item *item = NULL;
for (int i = 0; i < count; i++)
if (items[i].caust == roomIndex) {
item = &items[i];
break;
}
if (item && item->caustics) {
item->caustics->bind(sReflect);
Core::active.shader->setParam(uRoomSize, vec4(item->pos.x - item->size.x, item->pos.z - item->size.z, item->size.x * 2.0f, item->size.z * 2.0f));
game->setWaterParams(item->pos.y);
} else {
Core::active.shader->setParam(uRoomSize, vec4(0, 0, 1, 1));
Core::blackTex->bind(sReflect);
}
}
void addDrop(const vec3 &pos, float radius, float strength) {
if (dropCount >= MAX_DROPS) return;
drops[dropCount++] = Drop(pos, radius, strength);
}
void drop(Item &item) {
if (!dropCount) return;
vec2 s(item.size.x * DETAIL * 2.0f, item.size.z * DETAIL * 2.0f);
game->setShader(Core::passWater, Shader::WATER_DROP);
vec4 rPosScale[2] = { vec4(0.0f), vec4(1.0f) };
Core::active.shader->setParam(uPosScale, rPosScale[0], 2);
Core::active.shader->setParam(uTexParam, vec4(1.0f / item.data[0]->width, 1.0f / item.data[0]->height, s.x / item.data[0]->width, s.y / item.data[0]->height));
for (int i = 0; i < dropCount; i++) {
Drop &drop = drops[i];
vec3 p;
p.x = (drop.pos.x - (item.pos.x - item.size.x)) * DETAIL;
p.z = (drop.pos.z - (item.pos.z - item.size.z)) * DETAIL;
Core::active.shader->setParam(uParam, vec4(p.x, p.z, drop.radius * DETAIL, -drop.strength));
item.data[0]->bind(sDiffuse);
Core::setTarget(item.data[1], RT_STORE_COLOR);
Core::setViewport(0, 0, int(s.x + 0.5f), int(s.y + 0.5f));
game->getMesh()->renderQuad();
swap(item.data[0], item.data[1]);
}
}
void step(Item &item) {
if (item.timer < SIMULATE_TIMESTEP) return;
vec2 s(item.size.x * DETAIL * 2.0f, item.size.z * DETAIL * 2.0f);
game->setShader(Core::passWater, Shader::WATER_STEP);
Core::active.shader->setParam(uParam, vec4(0.995f, 1.0f, 0, Core::params.x));
Core::active.shader->setParam(uTexParam, vec4(1.0f / item.data[0]->width, 1.0f / item.data[0]->height, s.x / item.data[0]->width, s.y / item.data[0]->height));
while (item.timer >= SIMULATE_TIMESTEP) {
// water step
item.data[0]->bind(sDiffuse);
Core::setTarget(item.data[1], RT_STORE_COLOR);
Core::setViewport(0, 0, int(s.x + 0.5f), int(s.y + 0.5f));
game->getMesh()->renderQuad();
swap(item.data[0], item.data[1]);
item.timer -= SIMULATE_TIMESTEP;
}
if (Core::settings.detail.water < Core::Settings::HIGH)
return;
// calc caustics
game->setShader(Core::passWater, Shader::WATER_CAUSTICS);
vec4 rPosScale[2] = { vec4(0.0f), vec4(32767.0f / PLANE_DETAIL) };
Core::active.shader->setParam(uPosScale, rPosScale[0], 2);
float sx = item.size.x * DETAIL / (item.data[0]->width / 2);
float sz = item.size.z * DETAIL / (item.data[0]->height / 2);
Core::active.shader->setParam(uTexParam, vec4(0.0f, 0.0f, sx, sz));
Core::whiteTex->bind(sReflect);
item.data[0]->bind(sNormal);
Core::setTarget(item.caustics, RT_CLEAR_COLOR | RT_STORE_COLOR);
Core::setViewport(1, 1, item.caustics->width - 2, item.caustics->width - 2); // leave 1px for black border
game->getMesh()->renderPlane();
#ifdef BLUR_CAUSTICS
// v blur
Core::setTarget(item.caustics_tmp, CLEAR_ALL);
game->setShader(Core::passFilter, Shader::FILTER_BLUR, false, false);
Core::active.shader->setParam(uParam, vec4(0, 1, 1.0f / item.caustics->width, 0));;
item.caustics->bind(sDiffuse);
game->getMesh()->renderQuad();
Core::invalidateTarget(false, true);
// h blur
Core::setTarget(item.caustics, CLEAR_ALL);
game->setShader(Core::passFilter, Shader::FILTER_BLUR, false, false);
Core::active.shader->setParam(uParam, vec4(1, 0, 1.0f / item.caustics->width, 0));;
item.caustics_tmp->bind(sDiffuse);
game->getMesh()->renderQuad();
Core::invalidateTarget(false, true);
#endif
}
void renderMask() {
if (!visible) return;
PROFILE_MARKER("WATER_RENDER_MASK");
// mask underwater geometry by zero alpha
game->setShader(Core::passWater, Shader::WATER_MASK);
Core::active.shader->setParam(uTexParam, vec4(1.0f));
Core::setColorWrite(false, false, false, true);
Core::setDepthWrite(false);
Core::setCullMode(cmNone);
for (int i = 0; i < count; i++) {
Item &item = items[i];
if (!item.visible) continue;
vec4 rPosScale[2] = { vec4(item.pos, 0.0f), vec4(item.size, 1.0) };
Core::active.shader->setParam(uPosScale, rPosScale[0], 2);
game->getMesh()->renderQuad();
}
Core::setColorWrite(true, true, true, true);
Core::setDepthWrite(true);
Core::setCullMode(cmFront);
}
Texture* getScreenTex() {
int w = Core::viewportDef.width;
int h = Core::viewportDef.height;
// get refraction texture
if (!refract || w != refract->origWidth || h != refract->origHeight) {
PROFILE_MARKER("WATER_REFRACT_INIT");
delete refract;
refract = new Texture(w, h, FMT_RGBA, OPT_TARGET);
#ifdef _OS_IOS
delete screen;
screen = new Texture(w, h, FMT_RGBA, OPT_TARGET);
#endif
}
return screen;
}
void copyScreenToRefract() {
PROFILE_MARKER("WATER_REFRACT_COPY");
// get refraction texture
int x, y;
if (!screen) {
x = Core::viewportDef.x;
y = Core::viewportDef.y;
} else
x = y = 0;
Core::copyTarget(refract, 0, 0, x, y, Core::viewportDef.width, Core::viewportDef.height); // copy framebuffer into refraction texture
}
void simulate() {
PROFILE_MARKER("WATER_SIMULATE");
// simulate water
Core::setDepthTest(false);
Core::setBlendMode(bmNone);
for (int i = 0; i < count; i++) {
Item &item = items[i];
if (!item.visible) continue;
if (item.timer >= SIMULATE_TIMESTEP || dropCount) {
item.mask->bind(sMask);
// add water drops
drop(item);
// simulation step
step(item);
}
}
Core::setDepthTest(true);
}
void renderReflect() {
if (!visible) return;
PROFILE_MARKER("WATER_REFLECT");
for (int i = 0; i < count; i++) {
Item &item = items[i];
if (item.visible && item.blank)
item.init(game);
}
// render mirror reflection
Core::setTarget(reflect, RT_CLEAR_COLOR | RT_CLEAR_DEPTH | RT_STORE_COLOR);
Camera *camera = (Camera*)game->getCamera();
game->setupBinding();
// merge visible rooms for all items
int roomsList[256];
int roomsCount = 0;
for (int i = 0; i < level->roomsCount; i++)
level->rooms[i].flags.visible = false;
bool underwater = level->rooms[camera->getRoomIndex()].flags.water;
vec4 reflectPlane;
for (int i = 0; i < count; i++) {
Item &item = items[i];
if (!item.visible) continue;
reflectPlane = vec4(0, 1, 0, -item.pos.y);
camera->reflectPlane = &reflectPlane;
camera->setup(true);
game->getVisibleRooms(roomsList, roomsCount, TR::NO_ROOM, underwater ? item.from : item.to, vec4(-1.0f, -1.0f, 1.0f, 1.0f), false);
}
if (roomsCount) {
// select optimal water plane
float waterDist = 10000000.0f;
int waterItem = 0;
for (int i = 0; i < count; i++) {
Item &item = items[i];
if (!item.visible) continue;
float d = fabsf(item.pos.x - camera->eye.pos.x) + fabsf(item.pos.z - camera->eye.pos.z);
if (d < waterDist) {
waterDist = d;
waterItem = i;
}
}
float waterLevel = items[waterItem].pos.y;
reflectPlane = vec4(0, 1, 0, -waterLevel);
camera->reflectPlane = &reflectPlane;
camera->setup(true);
// render reflections frame
float sign = underwater ? -1.0f : 1.0f;
game->setClipParams(sign, waterLevel * sign);
game->renderView(TR::NO_ROOM, false, false, roomsCount, roomsList);
}
game->setClipParams(1.0f, NO_CLIP_PLANE);
camera->reflectPlane = NULL;
camera->setup(true);
}
void render() {
if (!visible) return;
PROFILE_MARKER("WATER_RENDER");
for (int i = 0; i < count; i++) {
Item &item = items[i];
if (!item.visible) continue;
// render water plane
game->setShader(Core::passWater, Shader::WATER_COMPOSE);
Core::active.shader->setParam(uLightPos, Core::lightPos[0], 1);
Core::active.shader->setParam(uLightColor, Core::lightColor[0], 1);
Core::active.shader->setParam(uParam, vec4(float(refract->origWidth) / refract->width, float(refract->origHeight) / refract->height, 0.05f, 0.03f));
float sx = item.size.x * DETAIL / (item.data[0]->width / 2);
float sz = item.size.z * DETAIL / (item.data[0]->height / 2);
Core::active.shader->setParam(uTexParam, vec4(0.0f, 0.0f, sx, sz));
refract->bind(sDiffuse);
reflect->bind(sReflect);
item.mask->bind(sMask);
item.data[0]->bind(sNormal);
Core::setCullMode(cmNone);
Core::setBlendMode(bmAlpha);
#ifdef WATER_USE_GRID
vec4 rPosScale[2] = { vec4(item.pos, 0.0f), vec4(item.size * vec3(1.0f / PLANE_DETAIL, 512.0f, 1.0f / PLANE_DETAIL), 1.0f) };
Core::active.shader->setParam(uPosScale, rPosScale[0], 2);
game->getMesh()->renderPlane();
#else
vec4 rPosScale[2] = { vec4(item.pos, 0.0f), vec4(item.size, 1.0) };
Core::active.shader->setParam(uPosScale, rPosScale[0], 2);
game->getMesh()->renderQuad();
#endif
Core::setCullMode(cmFront);
Core::setBlendMode(bmNone);
}
dropCount = 0;
}
void blitScreen() {
ASSERT(screen);
Core::setDepthTest(false);
Core::setBlendMode(bmNone);
game->setShader(Core::passGUI, Shader::DEFAULT);
Core::mView.identity();
Core::mProj = GAPI::ortho(0.0f, float(screen->origWidth), 0.0f, float(screen->origHeight), 0.0f, 1.0f);
Core::setViewProj(Core::mView, Core::mProj);
Core::active.shader->setParam(uViewProj, Core::mViewProj);
Core::active.shader->setParam(uMaterial, vec4(1.0f));
screen->bind(0);
int w = screen->width;
int h = screen->height;
Index indices[6] = { 0, 1, 2, 0, 2, 3 };
Vertex vertices[4];
vertices[0].coord = short4(0, h, 0, 0);
vertices[1].coord = short4(w, h, 0, 0);
vertices[2].coord = short4(w, 0, 0, 0);
vertices[3].coord = short4(0, 0, 0, 0);
vertices[0].light =
vertices[1].light =
vertices[2].light =
vertices[3].light = ubyte4(255, 255, 255, 255);
vertices[0].texCoord = short4( 0, 32767, 0, 0);
vertices[1].texCoord = short4(32767, 32767, 0, 0);
vertices[2].texCoord = short4(32767, 0, 0, 0);
vertices[3].texCoord = short4( 0, 0, 0, 0);
game->getMesh()->renderBuffer(indices, COUNT(indices), vertices, COUNT(vertices));
Core::setDepthTest(true);
}
#undef MAX_WATER_SURFACES
#undef MAX_WATER_INVISIBLE_TIME
#undef WATER_SIMULATE_TIMESTEP
#undef DETAIL
};
struct ZoneCache {
struct Item {
uint16 zone;
uint16 count;
uint16 *zones;
uint16 *boxes;
Item *next;
Item(uint16 zone, uint16 count, uint16 *zones, uint16 *boxes, Item *next) :
zone(zone), count(count), zones(zones), boxes(boxes), next(next) {}
~Item() {
delete[] boxes;
delete next;
}
} *items;
IGame *game;
// dummy arrays for path search
uint16 *nodes;
uint16 *parents;
uint16 *weights;
ZoneCache(IGame *game) : items(NULL), game(game) {
TR::Level *level = game->getLevel();
nodes = new uint16[level->boxesCount * 3];
parents = nodes + level->boxesCount;
weights = nodes + level->boxesCount * 2;
}
~ZoneCache() {
delete items;
delete[] nodes;
}
Item *getBoxes(uint16 zone, uint16 *zones) {
Item *item = items;
while (item) {
if (item->zone == zone && item->zones == zones)
return item;
item = item->next;
}
int count = 0;
TR::Level *level = game->getLevel();
for (int i = 0; i < level->boxesCount; i++)
if (zones[i] == zone)
nodes[count++] = i;
ASSERT(count > 0);
uint16 *boxes = new uint16[count];
memcpy(boxes, nodes, sizeof(uint16) * count);
return items = new Item(zone, count, zones, boxes, items);
}
uint16 findPath(int ascend, int descend, bool big, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) {
if (boxStart == 0xFFFF || boxEnd == 0xFFFF)
return 0;
TR::Level *level = game->getLevel();
memset(parents, 0xFF, sizeof(uint16) * level->boxesCount); // fill parents by 0xFFFF
memset(weights, 0x00, sizeof(uint16) * level->boxesCount); // zeroes weights
uint16 count = 0;
nodes[count++] = boxEnd;
uint16 zone = zones[boxStart];
if (zone != zones[boxEnd])
return 0;
TR::Box &b = level->boxes[boxStart];
int sx = (b.minX + b.maxX) >> 11; // box center / 1024
int sz = (b.minZ + b.maxZ) >> 11;
while (count) {
// get min weight
int minI = 0;
int minW = weights[nodes[minI]];
for (int i = 1; i < count; i++)
if (weights[nodes[i]] < minW) {
minI = i;
minW = weights[nodes[i]];
}
int cur = nodes[minI];
// peek min weight item from array
count--;
for (int i = minI; i < count; i++)
nodes[i] = nodes[i + 1];
// check for end of path
if (cur == boxStart) {
count = 0;
while (cur != boxEnd) {
nodes[count++] = cur;
cur = parents[cur];
}
nodes[count++] = cur;
*boxes = nodes;
return count;
}
// add overlap boxes
TR::Box &b = game->getLevel()->boxes[cur];
TR::Overlap *overlap = &level->overlaps[b.overlap.index];
do {
uint16 index = overlap->boxIndex;
// unvisited yet
if (parents[index] != 0xFFFF)
continue;
// has same zone
if (zones[index] != zone)
continue;
// check passability
if (big && level->boxes[index].overlap.blockable)
continue;
// check blocking (doors)
if (level->boxes[index].overlap.block)
continue;
// check for height difference
int d = level->boxes[index].floor - b.floor;
if (d > ascend || d < descend)
continue;
int dx = sx - ((b.minX + b.maxX) >> 11);
int dz = sz - ((b.minZ + b.maxZ) >> 11);
int w = abs(dx) + abs(dz);
ASSERT(count < level->boxesCount);
nodes[count++] = index;
parents[index] = cur;
weights[index] = weights[cur] + w;
} while (!(overlap++)->end);
}
return 0;
}
};
#undef UNDERWATER_COLOR
#endif