EBLAN_ENGINE/editor/src/lighting_format.h

97 lines
4.5 KiB
C++

#pragma once
#include "level_format.h"
#include <array>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
struct LevelLighting {
LightingSettings settings;
std::vector<EditorLightSource> lights;
std::array<uint32_t,1024> rgb{}; // 00RRGGBB, baked static contribution + ambient
LevelLighting() { rgb.fill(0x282828); }
};
inline bool ValidateLighting(const LightingSettings& s, const EditorLightSource* lights, size_t count, std::string& error) {
if (s.mode>2 || s.ambient>255 || s.flashlight>1 || s.flashlightRadius<32 || s.flashlightRadius>2048 ||
s.flashlightIntensity>255 || s.flashlightCone<1 || s.flashlightCone>96 || s.baked!=1 || s.reserved || count>MAX_EDITOR_LIGHTS) {
error="Неверные параметры освещения"; return false;
}
for(size_t i=0;i<count;++i) {
const auto& l=lights[i];
if(l.x<0 || l.x>=2048 || l.y<0 || l.y>=2048 || l.radius<1 || l.radius>2048 || l.intensity<0 || l.intensity>255 ||
l.curIntensity<0 || l.curIntensity>255 || (l.lightType!=LIGHT_TYPE_STATIC && l.lightType!=LIGHT_TYPE_POINT && l.lightType!=LIGHT_TYPE_PULSE) || (l.flags&~7u) || (l.color&0xFF000000)) {
error="Неверные координаты, цвет или параметры источника света"; return false;
}
}
return true;
}
inline bool LightingWalkable(uint8_t cell) { return cell==0 || cell==6 || cell==10; }
// Exact supercover grid traversal; corner crossings cannot leak diagonally through walls.
inline bool LightingVisible(const uint8_t* cells,int x1,int y1,int x2,int y2) {
if(x1<0||x1>=2048||y1<0||y1>=2048||x2<0||x2>=2048||y2<0||y2>=2048) return false;
int cx=x1/64,cy=y1/64,ex=x2/64,ey=y2/64;
const int dx=std::abs(x2-x1),dy=std::abs(y2-y1),sx=x2>=x1?1:-1,sy=y2>=y1?1:-1;
int nx=sx>0?(cx+1)*64-x1:x1-cx*64,ny=sy>0?(cy+1)*64-y1:y1-cy*64;
auto free=[&](int x,int y){return x>=0&&x<32&&y>=0&&y<32&&LightingWalkable(cells[y*32+x]);};
// A wall-mounted source may start in a wall; every subsequent cell is tested.
for(int steps=0;steps<65;++steps) {
if(cx==ex&&cy==ey) return free(cx,cy);
const int a=dx?nx*dy:0x3FFFFFFF,b=dy?ny*dx:0x3FFFFFFF;
if(a==b) {
if(!free(cx+sx,cy)||!free(cx,cy+sy)) return false;
cx+=sx;cy+=sy;nx+=64;ny+=64;
} else if(a<b) {cx+=sx;nx+=64;} else {cy+=sy;ny+=64;}
if(!free(cx,cy)) return false;
}
return false;
}
inline int LightDistance(int dx,int dy) {
dx=std::abs(dx);dy=std::abs(dy);
return (std::max)(dx,dy)+(3*(std::min)(dx,dy))/8;
}
inline std::array<uint32_t,1024> BakeLightingRGB(const uint8_t* cells,const std::vector<EditorLightSource>& lights,int ambient) {
std::array<uint32_t,1024> rgb;
const uint32_t base=ambient*0x010101u;rgb.fill(base);
for(int i=0;i<1024;++i) {
if(!LightingWalkable(cells[i])) continue;
int x=(i%32)*64+32,y=(i/32)*64+32,r=ambient,g=ambient,b=ambient;
for(const auto& l:lights) {
if(!(l.flags&LIGHT_FLAG_ACTIVE)||l.lightType!=LIGHT_TYPE_STATIC||l.radius<=0) continue;
int d=LightDistance(x-l.x,y-l.y);if(d>=l.radius) continue;
if((l.flags&LIGHT_FLAG_SHADOWS)&&!LightingVisible(cells,l.x,l.y,x,y)) continue;
int a=(l.radius-d)*l.intensity/l.radius;
r=(std::min)(255,r+a*int((l.color>>16)&255)/255);
g=(std::min)(255,g+a*int((l.color>>8)&255)/255);
b=(std::min)(255,b+a*int(l.color&255)/255);
}
rgb[i]=(r<<16)|(g<<8)|b;
}
auto smooth=rgb;
for(int y=0;y<32;++y)for(int x=0;x<32;++x) {
int i=y*32+x;if(!LightingWalkable(cells[i])) continue;
int r=((rgb[i]>>16)&255)*2,g=((rgb[i]>>8)&255)*2,b=(rgb[i]&255)*2,n=2;
for(auto delta: {std::pair<int,int>{-1,0},{1,0},{0,-1},{0,1}}) {
int xx=x+delta.first,yy=y+delta.second;
if(xx<0||xx>=32||yy<0||yy>=32||!LightingWalkable(cells[yy*32+xx])) continue;
uint32_t c=rgb[yy*32+xx];r+=(c>>16)&255;g+=(c>>8)&255;b+=c&255;++n;
}
smooth[i]=((r/n)<<16)|((g/n)<<8)|(b/n);
}
return smooth;
}
inline uint64_t LightingBakeSignature(const uint8_t* cells,const std::vector<EditorLightSource>& lights,int ambient) {
uint64_t hash=1469598103934665603ull;
auto add=[&](uint32_t v){hash^=v;hash*=1099511628211ull;};
for(int i=0;i<1024;++i)add(cells[i]);add(ambient);
for(const auto& l:lights)if(l.lightType==LIGHT_TYPE_STATIC) {
add(l.x);add(l.y);add(l.radius);add(l.intensity);add(l.flags&3);add(l.color);
}
return hash;
}