2023-08-24 11:59:05 +02:00
|
|
|
// PC3D rendering shader
|
|
|
|
|
2023-08-14 19:22:04 +02:00
|
|
|
varying vec2 v_vTexcoord;
|
|
|
|
varying vec4 v_vColour;
|
|
|
|
varying vec3 v_vNormal;
|
|
|
|
|
2023-08-23 20:01:09 +02:00
|
|
|
varying vec4 v_worldPosition;
|
2023-08-24 11:59:05 +02:00
|
|
|
varying vec3 v_viewPosition;
|
2023-08-23 20:01:09 +02:00
|
|
|
varying float v_cameraDistance;
|
2023-08-14 19:22:04 +02:00
|
|
|
|
2023-08-24 11:59:05 +02:00
|
|
|
#define PI 3.14159265359
|
|
|
|
#define TAU 6.28318530718
|
|
|
|
|
2023-12-06 13:51:22 +01:00
|
|
|
uniform int use_8bit;
|
|
|
|
|
2023-08-16 20:16:31 +02:00
|
|
|
#region ---- light ----
|
|
|
|
uniform vec4 light_ambient;
|
2023-08-22 11:51:45 +02:00
|
|
|
uniform float shadowBias;
|
|
|
|
|
2024-09-10 05:40:05 +02:00
|
|
|
#ifdef _YY_HLSL11_
|
|
|
|
#define LIGHT_DIR_LIMIT 16
|
|
|
|
#define LIGHT_PNT_LIMIT 16
|
|
|
|
#define LIGHT_PNT_LIMIT6 16*6
|
|
|
|
#else
|
|
|
|
#define LIGHT_DIR_LIMIT 8
|
|
|
|
#define LIGHT_PNT_LIMIT 8
|
|
|
|
#define LIGHT_PNT_LIMIT6 8*6
|
|
|
|
#endif
|
|
|
|
|
2023-08-16 20:16:31 +02:00
|
|
|
uniform int light_dir_count;
|
|
|
|
uniform vec3 light_dir_direction[LIGHT_DIR_LIMIT];
|
|
|
|
uniform vec4 light_dir_color[LIGHT_DIR_LIMIT];
|
|
|
|
uniform float light_dir_intensity[LIGHT_DIR_LIMIT];
|
2023-08-22 11:51:45 +02:00
|
|
|
|
|
|
|
uniform mat4 light_dir_view[LIGHT_DIR_LIMIT];
|
|
|
|
uniform mat4 light_dir_proj[LIGHT_DIR_LIMIT];
|
|
|
|
uniform int light_dir_shadow_active[LIGHT_DIR_LIMIT];
|
2023-08-23 20:01:09 +02:00
|
|
|
uniform float light_dir_shadow_bias[LIGHT_DIR_LIMIT];
|
2023-08-22 11:51:45 +02:00
|
|
|
uniform sampler2D light_dir_shadowmap_0;
|
|
|
|
uniform sampler2D light_dir_shadowmap_1;
|
2023-09-05 18:05:18 +02:00
|
|
|
//uniform sampler2D light_dir_shadowmap_2;
|
|
|
|
//uniform sampler2D light_dir_shadowmap_3;
|
2023-08-22 11:51:45 +02:00
|
|
|
|
2023-08-16 20:16:31 +02:00
|
|
|
uniform int light_pnt_count;
|
|
|
|
uniform vec3 light_pnt_position[LIGHT_PNT_LIMIT];
|
|
|
|
uniform vec4 light_pnt_color[LIGHT_PNT_LIMIT];
|
|
|
|
uniform float light_pnt_intensity[LIGHT_PNT_LIMIT];
|
|
|
|
uniform float light_pnt_radius[LIGHT_PNT_LIMIT];
|
2023-08-22 11:51:45 +02:00
|
|
|
|
2024-09-10 05:40:05 +02:00
|
|
|
uniform mat4 light_pnt_view[LIGHT_PNT_LIMIT6];
|
2023-08-22 11:51:45 +02:00
|
|
|
uniform mat4 light_pnt_proj[LIGHT_PNT_LIMIT];
|
|
|
|
uniform int light_pnt_shadow_active[LIGHT_PNT_LIMIT];
|
2023-08-23 20:01:09 +02:00
|
|
|
uniform float light_pnt_shadow_bias[LIGHT_DIR_LIMIT];
|
2023-08-22 11:51:45 +02:00
|
|
|
uniform sampler2D light_pnt_shadowmap_0;
|
|
|
|
uniform sampler2D light_pnt_shadowmap_1;
|
2023-09-05 18:05:18 +02:00
|
|
|
//uniform sampler2D light_pnt_shadowmap_2;
|
|
|
|
//uniform sampler2D light_pnt_shadowmap_3;
|
2023-08-22 11:51:45 +02:00
|
|
|
#endregion
|
|
|
|
|
2023-08-24 11:59:05 +02:00
|
|
|
#region ---- material ----
|
|
|
|
vec4 mat_baseColor;
|
|
|
|
|
|
|
|
uniform float mat_diffuse;
|
|
|
|
uniform float mat_specular;
|
|
|
|
uniform float mat_shine;
|
|
|
|
uniform int mat_metalic;
|
2023-08-24 19:44:12 +02:00
|
|
|
uniform float mat_reflective;
|
2024-10-05 02:49:33 +02:00
|
|
|
uniform vec2 mat_texScale;
|
|
|
|
uniform vec2 mat_texShift;
|
2023-08-24 19:44:12 +02:00
|
|
|
|
2023-08-30 16:40:45 +02:00
|
|
|
uniform int mat_defer_normal;
|
2023-08-24 19:44:12 +02:00
|
|
|
uniform float mat_normal_strength;
|
|
|
|
uniform sampler2D mat_normal_map;
|
2023-08-29 19:26:18 +02:00
|
|
|
|
|
|
|
uniform int mat_flip;
|
2023-08-24 11:59:05 +02:00
|
|
|
#endregion
|
|
|
|
|
2023-08-22 11:51:45 +02:00
|
|
|
#region ---- rendering ----
|
2023-08-24 11:59:05 +02:00
|
|
|
uniform vec3 cameraPosition;
|
|
|
|
uniform int gammaCorrection;
|
2023-08-24 19:44:12 +02:00
|
|
|
|
|
|
|
uniform int env_use_mapping;
|
|
|
|
uniform sampler2D env_map;
|
|
|
|
uniform vec2 env_map_dimension;
|
2023-08-30 16:40:45 +02:00
|
|
|
|
|
|
|
uniform mat4 viewProjMat;
|
2023-08-22 11:51:45 +02:00
|
|
|
#endregion
|
|
|
|
|
2023-12-07 15:08:09 +01:00
|
|
|
#region ++++ mapping ++++
|
|
|
|
vec2 equirectangularUv(vec3 dir) {
|
|
|
|
vec3 n = normalize(dir);
|
|
|
|
return vec2((atan(n.x, n.y) / TAU) + 0.5, 1. - acos(n.z) / PI);
|
|
|
|
}
|
|
|
|
|
|
|
|
float unormToFloat(vec3 v) {
|
|
|
|
v *= 256.;
|
|
|
|
return (v.r * 65536. + v.g * 256. + v.b) / (65536.);
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
2023-08-24 11:59:05 +02:00
|
|
|
#region ++++ matrix ++++
|
2023-08-22 11:51:45 +02:00
|
|
|
float matrixGet(mat4 matrix, int index) {
|
|
|
|
if(index < 0 || index > 15) return 0.;
|
|
|
|
|
|
|
|
int _x = int(floor(float(index) / 4.));
|
|
|
|
int _y = int(mod(float(index), 4.));
|
|
|
|
return matrix[_x][_y];
|
|
|
|
}
|
|
|
|
|
|
|
|
mat4 matrixSet(mat4 matrix, int index, float value) {
|
|
|
|
if(index < 0 || index > 15) return matrix;
|
|
|
|
|
|
|
|
int _x = int(floor(float(index) / 4.));
|
|
|
|
int _y = int(mod(float(index), 4.));
|
|
|
|
matrix[_x][_y] = value;
|
|
|
|
return matrix;
|
|
|
|
}
|
2023-08-16 20:16:31 +02:00
|
|
|
#endregion
|
2023-08-14 19:22:04 +02:00
|
|
|
|
2023-08-24 11:59:05 +02:00
|
|
|
#region ++++ shadow sampler ++++
|
|
|
|
float sampleDirShadowMap(int index, vec2 position) {
|
2023-12-07 15:08:09 +01:00
|
|
|
vec4 d;
|
|
|
|
|
|
|
|
if(index == 0) d = texture2D(light_dir_shadowmap_0, position);
|
|
|
|
else if(index == 1) d = texture2D(light_dir_shadowmap_1, position);
|
|
|
|
//else if(index == 2) d = texture2D(light_dir_shadowmap_2, position);
|
|
|
|
//else if(index == 3) d = texture2D(light_dir_shadowmap_3, position);
|
|
|
|
|
2024-10-07 07:50:21 +02:00
|
|
|
if(use_8bit == 1) return unormToFloat(d.rgb);
|
2023-12-07 15:08:09 +01:00
|
|
|
return d.r;
|
2023-08-22 11:51:45 +02:00
|
|
|
}
|
2023-08-24 11:59:05 +02:00
|
|
|
|
|
|
|
float samplePntShadowMap(int index, vec2 position, int side) {
|
2024-10-07 07:50:21 +02:00
|
|
|
// -x, x, -y, y, -z, z
|
|
|
|
// r0, b0, g0, r1, g1, b1
|
|
|
|
|
2023-12-07 15:08:09 +01:00
|
|
|
float d = 0.;
|
|
|
|
|
2023-08-24 11:59:05 +02:00
|
|
|
position.x /= 2.;
|
|
|
|
if(side >= 3) {
|
|
|
|
position.x += 0.5;
|
|
|
|
side -= 3;
|
|
|
|
}
|
2023-08-22 11:51:45 +02:00
|
|
|
|
2023-12-07 15:08:09 +01:00
|
|
|
if(index == 0) d = texture2D(light_pnt_shadowmap_0, position)[side];
|
|
|
|
else if(index == 1) d = texture2D(light_pnt_shadowmap_1, position)[side];
|
|
|
|
//else if(index == 2) d = texture2D(light_pnt_shadowmap_2, position)[side];
|
|
|
|
//else if(index == 3) d = texture2D(light_pnt_shadowmap_3, position)[side];
|
|
|
|
|
|
|
|
return d;
|
2023-08-24 11:59:05 +02:00
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region ++++ Phong shading ++++
|
|
|
|
vec3 phongLight(vec3 normal, vec3 lightVec, vec3 viewVec, vec3 light) {
|
|
|
|
vec3 lightDir = normalize(lightVec);
|
|
|
|
vec3 viewDir = normalize(viewVec);
|
|
|
|
vec3 refcDir = reflect(-lightDir, normal);
|
|
|
|
|
2023-08-24 12:43:03 +02:00
|
|
|
float kD = 1., kS = 0.;
|
2023-08-24 11:59:05 +02:00
|
|
|
|
2023-08-24 12:43:03 +02:00
|
|
|
if(mat_diffuse + mat_specular != 0.) {
|
2023-08-24 11:59:05 +02:00
|
|
|
kD = mat_diffuse / (mat_diffuse + mat_specular);
|
|
|
|
kS = mat_specular / (mat_diffuse + mat_specular);
|
|
|
|
}
|
|
|
|
|
|
|
|
vec3 lLambert = max(0., dot(normal, lightDir)) * light;
|
|
|
|
|
|
|
|
float specular = pow(max(dot(viewDir, refcDir), 0.), max(0.001, mat_shine));
|
|
|
|
vec3 lSpecular = specular * light;
|
|
|
|
if(mat_metalic == 1) lSpecular *= mat_baseColor.rgb;
|
|
|
|
|
|
|
|
return kD * lLambert + kS * lSpecular;
|
|
|
|
}
|
|
|
|
#endregion
|
2023-08-22 11:51:45 +02:00
|
|
|
|
2023-08-14 19:22:04 +02:00
|
|
|
void main() {
|
2023-08-29 19:26:18 +02:00
|
|
|
vec2 uv_coord = v_vTexcoord;
|
|
|
|
if(mat_flip == 1) uv_coord.y = -uv_coord.y;
|
|
|
|
|
2024-10-05 02:49:33 +02:00
|
|
|
uv_coord = fract(uv_coord * mat_texScale + mat_texShift);
|
2023-08-29 19:26:18 +02:00
|
|
|
mat_baseColor = texture2D( gm_BaseTexture, uv_coord );
|
2023-08-24 11:59:05 +02:00
|
|
|
mat_baseColor *= v_vColour;
|
|
|
|
|
|
|
|
vec4 final_color = mat_baseColor;
|
|
|
|
vec3 viewDirection = normalize(cameraPosition - v_worldPosition.xyz);
|
2023-08-23 20:01:09 +02:00
|
|
|
|
2023-08-30 16:40:45 +02:00
|
|
|
vec4 viewProjPos = viewProjMat * vec4(v_worldPosition.xyz, 1.);
|
|
|
|
viewProjPos /= viewProjPos.w;
|
|
|
|
viewProjPos = viewProjPos * 0.5 + 0.5;
|
|
|
|
|
2023-08-24 19:44:12 +02:00
|
|
|
#region ++++ normal ++++
|
|
|
|
vec3 _norm = v_vNormal;
|
|
|
|
|
2023-12-07 15:08:09 +01:00
|
|
|
if(mat_defer_normal == 1)
|
2023-08-30 16:40:45 +02:00
|
|
|
_norm = texture2D(mat_normal_map, viewProjPos.xy).rgb;
|
|
|
|
|
|
|
|
vec3 normal = normalize(_norm);
|
2023-08-24 19:44:12 +02:00
|
|
|
#endregion
|
2023-08-14 19:22:04 +02:00
|
|
|
|
2023-08-28 12:56:00 +02:00
|
|
|
#region ++++ environment ++++
|
|
|
|
if(env_use_mapping == 1 && mat_reflective > 0.) {
|
|
|
|
vec3 reflectDir = reflect(viewDirection, normal);
|
|
|
|
|
|
|
|
float refRad = mix(16., 0., mat_reflective);
|
|
|
|
vec2 tx = 1. / env_map_dimension;
|
|
|
|
vec2 reflect_sample_pos = equirectangularUv(reflectDir);
|
|
|
|
vec4 env_sampled = vec4(0.);
|
|
|
|
float weight = 0.;
|
|
|
|
|
|
|
|
for(float i = -refRad; i <= refRad; i++)
|
|
|
|
for(float j = -refRad; j <= refRad; j++) {
|
|
|
|
vec2 _map_pos = reflect_sample_pos + vec2(i, j) * tx;
|
|
|
|
if(_map_pos.y < 0.) _map_pos.y = -_map_pos.y;
|
|
|
|
else if(_map_pos.y > 1.) _map_pos.y = 1. - (_map_pos.y - 1.);
|
|
|
|
|
|
|
|
vec4 _samp = texture2D(env_map, _map_pos);
|
|
|
|
env_sampled += _samp;
|
|
|
|
weight += _samp.a;
|
|
|
|
}
|
|
|
|
env_sampled /= weight;
|
|
|
|
env_sampled.a = 1.;
|
|
|
|
|
|
|
|
vec4 env_effect = mat_metalic == 1? env_sampled * final_color : env_sampled;
|
|
|
|
env_effect = 1. - ( mat_reflective * ( 1. - env_effect ));
|
|
|
|
|
|
|
|
final_color *= env_effect;
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
2023-08-16 20:16:31 +02:00
|
|
|
#region ++++ light ++++
|
2024-10-20 04:12:27 +02:00
|
|
|
int shadow_map_index = 0;
|
|
|
|
vec3 light_effect = light_ambient.rgb;
|
2023-08-22 11:51:45 +02:00
|
|
|
float val = 0.;
|
|
|
|
|
2023-08-24 11:59:05 +02:00
|
|
|
#region ++++ directional ++++
|
2023-08-22 11:51:45 +02:00
|
|
|
float light_map_depth;
|
|
|
|
float lightDistance;
|
|
|
|
float shadow_culled;
|
2023-08-16 20:16:31 +02:00
|
|
|
|
2023-08-22 11:51:45 +02:00
|
|
|
shadow_map_index = 0;
|
|
|
|
for(int i = 0; i < light_dir_count; i++) {
|
2023-08-23 20:01:09 +02:00
|
|
|
vec3 lightVector = normalize(light_dir_direction[i]);
|
2023-08-24 19:44:12 +02:00
|
|
|
|
2023-10-05 06:29:20 +02:00
|
|
|
if(light_dir_shadow_active[i] == 1) { //use shadow
|
2024-10-07 07:50:21 +02:00
|
|
|
vec4 l_cameraSpace = light_dir_view[i] * v_worldPosition;
|
|
|
|
vec4 l_screenSpace = light_dir_proj[i] * l_cameraSpace;
|
|
|
|
float l_lightDistance = l_screenSpace.z;
|
|
|
|
vec2 lightMapUV = (l_screenSpace.xy / l_screenSpace.w * 0.5) + 0.5;
|
2023-08-23 20:01:09 +02:00
|
|
|
|
2024-10-07 07:50:21 +02:00
|
|
|
if(lightMapUV.x >= 0. && lightMapUV.x <= 1. && lightMapUV.y >= 0. && lightMapUV.y <= 1.) {
|
|
|
|
light_map_depth = sampleDirShadowMap(shadow_map_index, lightMapUV);
|
2023-12-07 15:08:09 +01:00
|
|
|
|
2024-10-07 07:50:21 +02:00
|
|
|
//gl_FragData[0] = texture2D(light_dir_shadowmap_0, lightMapUV);
|
2023-12-07 15:08:09 +01:00
|
|
|
//return;
|
|
|
|
|
2023-11-21 11:54:45 +01:00
|
|
|
shadow_map_index++;
|
|
|
|
float shadowFactor = dot(normal, lightVector);
|
|
|
|
float bias = mix(light_dir_shadow_bias[i], 0., shadowFactor);
|
2023-12-07 15:08:09 +01:00
|
|
|
|
2024-10-07 07:50:21 +02:00
|
|
|
if(l_lightDistance > light_map_depth + bias)
|
2023-11-21 11:54:45 +01:00
|
|
|
continue;
|
|
|
|
}
|
2023-08-22 11:51:45 +02:00
|
|
|
}
|
2023-08-23 20:01:09 +02:00
|
|
|
|
2023-08-24 11:59:05 +02:00
|
|
|
vec3 light_phong = phongLight(normal, lightVector, viewDirection, light_dir_color[i].rgb);
|
|
|
|
|
2023-10-06 11:51:11 +02:00
|
|
|
light_effect += light_phong * light_dir_intensity[i];
|
2023-08-22 11:51:45 +02:00
|
|
|
}
|
|
|
|
#endregion
|
2023-08-24 11:59:05 +02:00
|
|
|
|
|
|
|
#region ++++ point ++++
|
2023-08-22 11:51:45 +02:00
|
|
|
float light_distance;
|
|
|
|
float light_attenuation;
|
2023-08-16 20:16:31 +02:00
|
|
|
|
2023-08-22 11:51:45 +02:00
|
|
|
shadow_map_index = 0;
|
|
|
|
for(int i = 0; i < light_pnt_count; i++) {
|
2024-10-07 07:50:21 +02:00
|
|
|
vec3 lightVector = light_pnt_position[i] - v_worldPosition.xyz;
|
2023-08-24 19:44:12 +02:00
|
|
|
|
2023-08-23 20:01:09 +02:00
|
|
|
light_distance = length(lightVector);
|
2024-10-07 07:50:21 +02:00
|
|
|
if(light_distance > light_pnt_radius[i]) {
|
2024-10-20 04:12:27 +02:00
|
|
|
// gl_FragData[0] = vec4(1., 0., 0., .5);
|
|
|
|
// return;
|
|
|
|
continue;
|
2024-10-07 07:50:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
lightVector = normalize(lightVector);
|
|
|
|
|
2023-10-05 06:29:20 +02:00
|
|
|
if(light_pnt_shadow_active[i] == 1) { //use shadow
|
2023-08-23 20:01:09 +02:00
|
|
|
vec3 dirAbs = abs(lightVector);
|
2024-10-07 07:50:21 +02:00
|
|
|
int side = dirAbs.x > dirAbs.y ? (dirAbs.x > dirAbs.z ? 0 : 2) : (dirAbs.y > dirAbs.z ? 1 : 2);
|
2023-08-22 11:51:45 +02:00
|
|
|
side *= 2;
|
2023-08-24 19:44:12 +02:00
|
|
|
if(side == 0 && lightVector.x > 0.) side += 1;
|
|
|
|
else if(side == 2 && lightVector.y > 0.) side += 1;
|
|
|
|
else if(side == 4 && lightVector.z > 0.) side += 1;
|
2023-08-22 11:51:45 +02:00
|
|
|
|
2024-10-07 07:50:21 +02:00
|
|
|
vec4 l_cameraSpace = light_pnt_view[i * 6 + side] * v_worldPosition;
|
|
|
|
vec4 l_screenSpace = light_pnt_proj[i] * l_cameraSpace;
|
|
|
|
float l_lightDistance = l_screenSpace.z;
|
|
|
|
vec2 lightMapUV = (l_screenSpace.xy / l_screenSpace.w * 0.5) + 0.5;
|
2023-08-24 19:44:12 +02:00
|
|
|
|
2024-10-07 07:50:21 +02:00
|
|
|
if(lightMapUV.x >= 0. && lightMapUV.x <= 1. && lightMapUV.y >= 0. && lightMapUV.y <= 1.) {
|
|
|
|
|
2023-11-21 11:54:45 +01:00
|
|
|
float shadowFactor = dot(normal, lightVector);
|
|
|
|
float bias = mix(light_pnt_shadow_bias[i], 0., shadowFactor);
|
2023-08-22 11:51:45 +02:00
|
|
|
|
2024-10-07 07:50:21 +02:00
|
|
|
light_map_depth = samplePntShadowMap(shadow_map_index, lightMapUV, side);
|
2023-11-21 11:54:45 +01:00
|
|
|
shadow_map_index++;
|
2024-10-07 07:50:21 +02:00
|
|
|
|
|
|
|
// gl_FragData[0] = vec4((l_lightDistance - (light_map_depth + bias)) * 10., ((light_map_depth + bias) - l_lightDistance) * 10., 0., 1.);
|
|
|
|
// return;
|
|
|
|
|
|
|
|
if(l_lightDistance > light_map_depth + bias)
|
2023-11-21 11:54:45 +01:00
|
|
|
continue;
|
|
|
|
}
|
2023-08-22 11:51:45 +02:00
|
|
|
}
|
2023-08-24 11:59:05 +02:00
|
|
|
|
2023-08-22 11:51:45 +02:00
|
|
|
light_attenuation = 1. - pow(light_distance / light_pnt_radius[i], 2.);
|
2023-08-24 11:59:05 +02:00
|
|
|
vec3 light_phong = phongLight(normal, lightVector, viewDirection, light_pnt_color[i].rgb * light_attenuation);
|
|
|
|
|
2023-10-06 11:51:11 +02:00
|
|
|
light_effect += light_phong * light_pnt_intensity[i];
|
2023-08-22 11:51:45 +02:00
|
|
|
}
|
|
|
|
#endregion
|
2023-08-14 19:22:04 +02:00
|
|
|
|
2023-08-16 20:16:31 +02:00
|
|
|
light_effect = max(light_effect, 0.);
|
2023-08-24 11:59:05 +02:00
|
|
|
|
|
|
|
if(gammaCorrection == 1) {
|
|
|
|
light_effect.r = pow(light_effect.r, 1. / 2.2);
|
|
|
|
light_effect.g = pow(light_effect.g, 1. / 2.2);
|
|
|
|
light_effect.b = pow(light_effect.b, 1. / 2.2);
|
|
|
|
}
|
|
|
|
|
2023-08-16 20:16:31 +02:00
|
|
|
final_color.rgb *= light_effect;
|
|
|
|
#endregion
|
2023-08-14 19:22:04 +02:00
|
|
|
|
2023-10-06 11:51:11 +02:00
|
|
|
if(final_color.a < 0.1) discard;
|
|
|
|
|
2023-08-23 20:01:09 +02:00
|
|
|
gl_FragData[0] = final_color;
|
2023-10-05 06:29:20 +02:00
|
|
|
gl_FragData[1] = vec4(0.5 + normal * 0.5, final_color.a);
|
2024-03-17 14:24:24 +01:00
|
|
|
gl_FragData[2] = vec4(vec3(1. - abs(v_cameraDistance)), final_color.a);
|
2023-08-14 19:22:04 +02:00
|
|
|
}
|