mirror of
https://github.com/XProger/OpenLara.git
synced 2025-10-28 19:04:08 +01:00
1060 lines
37 KiB
C
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
|