Skip to content

Commit ff37673

Browse files
committed
ScreenSpaceShaders_Update15.4
1 parent 4c780ac commit ff37673

23 files changed

+368
-69
lines changed
Binary file not shown.

Game/Resources_SoC_1.0006/gamedata/shaders/r3/deffer_grass.vs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "common.h"
22
#include "check_screenspace.h"
33

4-
float4 benders_pos[16];
4+
float4 benders_pos[32];
55
float4 benders_setup;
66

77
float4 consts; // {1/quant,1/quant,diffusescale,ambient}
@@ -37,20 +37,34 @@ v2p_bumped main(v_detail v)
3737
// Add wind
3838
pos = float4(pos.x + result.x, pos.y, pos.z + result.y, 1);
3939

40-
// INTERACTIVE GRASS - SSS Update 15.2
40+
// INTERACTIVE GRASS - SSS Update 15.4
4141
// https://www.moddb.com/mods/stalker-anomaly/addons/screen-space-shaders/
4242
#ifdef SSFX_INTER_GRASS
4343
for (int b = 0; b < benders_setup.w; b++)
4444
{
45-
float dist = distance(float2(pos.x, pos.z), float2(benders_pos[b].x, benders_pos[b].z)); // Distance from Vertex to Bender.
46-
float height_limit =
47-
saturate(1.0f - abs(pos.y - benders_pos[b].y) * 0.5f); // Limit the effect vertically. We don't want a Stalker walking on a platform and affecting the grass bellow.
48-
float bend = saturate(benders_setup.x - dist * dist) * height_limit; // Bend intensity, Radius - Dist. ( Square Dist to soft the end )
49-
float3 dir = normalize(pos.xyz - benders_pos[b].xyz) * bend; // Direction of the bend.
45+
// Direction, Radius & Bending Strength, Distance and Height Limit
46+
float3 dir = benders_pos[b + 16].xyz;
47+
float3 rstr = float3(benders_pos[b].w, benders_pos[b + 16].ww);
48+
bool non_dynamic = rstr.x <= 0 ? true : false;
49+
float dist = distance(pos.xz, benders_pos[b].xz);
50+
float height_limit = 1.0f - saturate(abs(pos.y - benders_pos[b].y) / (non_dynamic ? 2.0f : rstr.x));
51+
height_limit *= H;
52+
53+
// Adjustments ( Fix Radius or Dynamic Radius )
54+
rstr.x = non_dynamic ? benders_setup.x : rstr.x;
55+
rstr.yz *= non_dynamic ? benders_setup.yz : 1.0f;
56+
57+
// Strength through distance and bending direction.
58+
float bend = 1.0f - saturate(dist / (rstr.x + 0.001f));
59+
float3 bend_dir = normalize(pos.xyz - benders_pos[b].xyz) * bend;
60+
float3 dir_limit = dir.y >= -1 ? saturate(dot(bend_dir.xyz, dir.xyz) * 5.0f) : 1.0f; // Limit if nedeed
61+
62+
// Apply direction limit
63+
bend_dir.xz *= dir_limit.xz;
5064

5165
// Apply vertex displacement
52-
pos.xz += dir.xz * 2.0f * benders_setup.y * H; // Horizontal
53-
pos.y -= bend * 0.4f * benders_setup.z * H; // Vertical
66+
pos.xz += bend_dir.xz * 2.0f * rstr.yy * height_limit; // Horizontal
67+
pos.y -= bend * 0.6f * rstr.z * height_limit * dir_limit.y; // Vertical
5468
}
5569
#endif
5670

Game/Resources_SoC_1.0006/gamedata/shaders/r3/deffer_tree_branch_bump-hq.vs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "common.h"
22
#include "check_screenspace.h"
33

4-
float4 benders_pos[16];
4+
float4 benders_pos[32];
55
float4 benders_setup;
66

77
uniform float3x4 m_xform;
@@ -30,26 +30,39 @@ v2p_bumped main(v_tree I)
3030
result = 0;
3131
#endif
3232
float4 w_pos = float4(pos.x + result.x, pos.y, pos.z + result.y, 1);
33+
float2 tc = (I.tc * consts).xy;
3334

34-
// INTERACTIVE GRASS ( Bushes ) - SSS Update 15.2
35+
// INTERACTIVE GRASS ( Bushes ) - SSS Update 15.4
3536
// https://www.moddb.com/mods/stalker-anomaly/addons/screen-space-shaders/
3637
#ifdef SSFX_INTER_GRASS
3738
for (int b = 0; b < benders_setup.w; b++)
3839
{
39-
float dist = distance(float2(w_pos.x, w_pos.z), float2(benders_pos[b].x, benders_pos[b].z)); // Distance from Vertex to Bender.
40-
float height_limit =
41-
saturate(1.0f - abs(w_pos.y - benders_pos[b].y) * 0.5f); // Limit the effect vertically. We don't want a Stalker walking on a platform and affecting the grass bellow.
42-
float bend = saturate(benders_setup.x - dist * dist) * height_limit; // Bend intensity, Radius - Dist. ( Square Dist to soft the end )
43-
float3 dir = normalize(w_pos.xyz - benders_pos[b].xyz) * bend; // Direction of the bend.
44-
float VHeight = clamp(H, 0, 1.5f); // Clamp H to stay at a range of 0.0f ~ 1.5f
40+
// Direction, Radius & Bending Strength, Distance and Height Limit
41+
float3 dir = benders_pos[b + 16].xyz;
42+
float3 rstr = float3(benders_pos[b].w, benders_pos[b + 16].ww); // .x = Radius | .yz = Str
43+
bool non_dynamic = rstr.x <= 0 ? true : false;
44+
float dist = distance(w_pos.xz, benders_pos[b].xz);
45+
float height_limit = 1.0f - saturate(abs(pos.y - benders_pos[b].y) / (non_dynamic ? 2.0f : rstr.x));
46+
height_limit *= (1.0f - tc.y); // Bushes uses UV Coor instead of H to limit displacement
47+
48+
// Adjustments ( Fix Radius or Dynamic Radius )
49+
rstr.x = non_dynamic ? benders_setup.x : rstr.x;
50+
rstr.yz *= non_dynamic ? benders_setup.yz : 1.0f;
51+
52+
// Strength through distance and bending direction.
53+
float bend = 1.0f - saturate(dist / (rstr.x + 0.001f));
54+
float3 bend_dir = normalize(w_pos.xyz - benders_pos[b].xyz) * bend;
55+
float3 dir_limit = dir.y >= -1 ? saturate(dot(bend_dir.xyz, dir.xyz) * 5.0f) : 1.0f; // Limit if nedeed
56+
57+
// Apply direction limit
58+
bend_dir.xz *= dir_limit.xz;
4559

4660
// Apply vertex displacement
47-
w_pos.xz += dir.xz * 1.4f * benders_setup.y * VHeight; // Horizontal
48-
w_pos.y += dir.y * 0.8f * benders_setup.z * VHeight; // Vertical
61+
w_pos.xz += bend_dir.xz * 2.25f * rstr.yy * height_limit; // Horizontal
62+
w_pos.y -= bend * 0.67f * rstr.z * height_limit * dir_limit.y; // Vertical
4963
}
5064
#endif
5165

52-
float2 tc = (I.tc * consts).xy;
5366
float hemi = clamp(I.Nh.w * c_scale.w + c_bias.w, 0.3f, 1.0f); // Limit hemi - SSS Update 14.5
5467
// float hemi = I.Nh.w;
5568

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
#define USE_TDETAIL
22
#define USE_PARALLAX
3-
#include "deffer_tree_bump.vs"
3+
#include "deffer_tree_branch_bump-hq.vs"
Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,95 @@
11
#define USE_TDETAIL
2-
#include "deffer_tree_flat.vs"
2+
3+
#include "common.h"
4+
#include "check_screenspace.h"
5+
6+
float4 benders_pos[32];
7+
float4 benders_setup;
8+
9+
uniform float3x4 m_xform;
10+
uniform float3x4 m_xform_v;
11+
uniform float4 consts; // {1/quant,1/quant,???,???}
12+
uniform float4 c_scale, c_bias, wind, wave;
13+
uniform float2 c_sun; // x=*, y=+
14+
15+
v2p_flat main(v_tree I)
16+
{
17+
I.Nh = unpack_D3DCOLOR(I.Nh);
18+
I.T = unpack_D3DCOLOR(I.T);
19+
I.B = unpack_D3DCOLOR(I.B);
20+
21+
v2p_flat o;
22+
23+
// Transform to world coords
24+
float3 pos = mul(m_xform, I.P);
25+
26+
//
27+
float base = m_xform._24; // take base height from matrix
28+
float dp = calc_cyclic(wave.w + dot(pos, (float3)wave));
29+
float H = pos.y - base; // height of vertex (scaled, rotated, etc.)
30+
float frac = I.tc.z * consts.x; // fractional (or rigidity)
31+
float inten = H * dp; // intensity
32+
float2 result = calc_xz_wave(wind.xz * inten, frac);
33+
#ifdef USE_TREEWAVE
34+
result = 0;
35+
#endif
36+
float4 w_pos = float4(pos.x + result.x, pos.y, pos.z + result.y, 1);
37+
float2 tc = (I.tc * consts).xy;
38+
39+
// INTERACTIVE GRASS ( Bushes ) - SSS Update 15.4
40+
// https://www.moddb.com/mods/stalker-anomaly/addons/screen-space-shaders/
41+
#ifdef SSFX_INTER_GRASS
42+
for (int b = 0; b < benders_setup.w; b++)
43+
{
44+
// Direction, Radius & Bending Strength, Distance and Height Limit
45+
float3 dir = benders_pos[b + 16].xyz;
46+
float3 rstr = float3(benders_pos[b].w, benders_pos[b + 16].ww); // .x = Radius | .yz = Str
47+
bool non_dynamic = rstr.x <= 0 ? true : false;
48+
float dist = distance(w_pos.xz, benders_pos[b].xz);
49+
float height_limit = 1.0f - saturate(abs(pos.y - benders_pos[b].y) / (non_dynamic ? 2.0f : rstr.x));
50+
height_limit *= (1.0f - tc.y); // Bushes uses UV Coor instead of H to limit displacement
51+
52+
// Adjustments ( Fix Radius or Dynamic Radius )
53+
rstr.x = non_dynamic ? benders_setup.x : rstr.x;
54+
rstr.yz *= non_dynamic ? benders_setup.yz : 1.0f;
55+
56+
// Strength through distance and bending direction.
57+
float bend = 1.0f - saturate(dist / (rstr.x + 0.001f));
58+
float3 bend_dir = normalize(w_pos.xyz - benders_pos[b].xyz) * bend;
59+
float3 dir_limit = dir.y >= -1 ? saturate(dot(bend_dir.xyz, dir.xyz) * 5.0f) : 1.0f; // Limit if nedeed
60+
61+
// Apply direction limit
62+
bend_dir.xz *= dir_limit.xz;
63+
64+
// Apply vertex displacement
65+
w_pos.xz += bend_dir.xz * 2.25f * rstr.yy * height_limit; // Horizontal
66+
w_pos.y -= bend * 0.67f * rstr.z * height_limit * dir_limit.y; // Vertical
67+
}
68+
#endif
69+
70+
// Final xform(s)
71+
// Final xform
72+
float3 Pe = mul(m_V, w_pos);
73+
float hemi = I.Nh.w * c_scale.w + c_bias.w;
74+
// float hemi = I.Nh.w;
75+
o.hpos = mul(m_VP, w_pos);
76+
o.N = mul((float3x3)m_xform_v, unpack_bx2(I.Nh));
77+
o.tcdh = float4((I.tc * consts).xyyy);
78+
o.position = float4(Pe, hemi);
79+
80+
#if defined(USE_R2_STATIC_SUN) && !defined(USE_LM_HEMI)
81+
float suno = I.Nh.w * c_sun.x + c_sun.y;
82+
o.tcdh.w = suno; // (,,,dir-occlusion)
83+
#endif
84+
85+
#ifdef USE_GRASS_WAVE
86+
o.tcdh.z = 1.f;
87+
#endif
88+
89+
#ifdef USE_TDETAIL
90+
o.tcdbump = o.tcdh * dt_params; // dt tc
91+
#endif
92+
93+
return o;
94+
}
95+
FXVS;

Game/Resources_SoC_1.0006/gamedata/shaders/r3/shadow.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -762,9 +762,8 @@ uniform float3x4 m_sunmask; // ortho-projection
762762
#ifdef USE_SUNMASK
763763
float sunmask(float4 P)
764764
{
765-
float2 tc = mul(m_sunmask, P); //
766-
// return tex2D( s_lmap, tc ).w; // A8
767-
return s_lmap.Sample(smp_linear, tc).w; // A8
765+
float2 tc = mul(m_sunmask, P);
766+
return s_lmap.SampleLevel(smp_linear, tc, 0).w; //Hemi map - ambient occlusion
768767
}
769768
#else
770769
float sunmask(float4 P) { return 1.h; } //

ogsr_engine/Layers/xrRender/FTreeVisual.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,12 @@ void FTreeVisual::Render(float LOD)
123123
if (ps_r2_ls_flags_ext.test(SSFX_INTER_GRASS))
124124
{
125125
Fvector4* c_grass{};
126-
RCache.get_ConstantDirect(strBendersPos, sizeof grass_shader_data.pos, reinterpret_cast<void**>(&c_grass), nullptr, nullptr);
126+
RCache.get_ConstantDirect(strBendersPos, sizeof grass_shader_data.pos + sizeof grass_shader_data.dir, reinterpret_cast<void**>(&c_grass), nullptr, nullptr);
127127
if (c_grass)
128+
{
128129
std::memcpy(c_grass, &grass_shader_data.pos, sizeof grass_shader_data.pos);
130+
std::memcpy(c_grass + std::size(grass_shader_data.pos), &grass_shader_data.dir, sizeof grass_shader_data.dir);
131+
}
129132
}
130133
}
131134

ogsr_engine/Layers/xrRender/xrRender_console.cpp

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,10 @@ float ps_r2_lt_smooth = 1.f; // 1.f
177177
float ps_r2_slight_fade = 2.0f; // 1.f
178178

179179
// Screen Space Shaders Stuff
180-
Fvector3 ps_ssfx_grass_interactive{1.f, static_cast<float>(GRASS_SHADER_DATA_COUNT), 150.f};
181-
Fvector3 ps_ssfx_int_grass_params_1{1.0f, 1.0f, 1.0f};
180+
Fvector3 ps_ssfx_shadow_cascades{20.f, 40.f, 160.f};
181+
Fvector4 ps_ssfx_grass_interactive{1.f, static_cast<float>(GRASS_SHADER_DATA_COUNT), 2000.f, 1.0f};
182+
Fvector3 ps_ssfx_int_grass_params_1{2.0f, 1.0f, 1.0f};
183+
Fvector4 ps_ssfx_int_grass_params_2{1.0f, 5.0f, 1.0f, 1.0f};
182184
float ps_ssfx_wpn_dof_2 = 0.5f;
183185

184186
// x - min (0), y - focus (1.4), z - max (100)
@@ -600,7 +602,25 @@ class CCC_Fog_Reload : public IConsole_Command
600602
#endif // DEBUG
601603
#endif // (RENDER == R_R4)
602604

603-
//-----------------------------------------------------------------------
605+
class CCC_ssfx_cascades final: public CCC_Vector3
606+
{
607+
void apply() { RImplementation.init_cacades(); }
608+
609+
public:
610+
CCC_ssfx_cascades(LPCSTR N, Fvector3* V, const Fvector3 _min, const Fvector3 _max) : CCC_Vector3(N, V, _min, _max) {}
611+
void Execute(LPCSTR args) override
612+
{
613+
CCC_Vector3::Execute(args);
614+
apply();
615+
}
616+
void Status(TStatus& S) override
617+
{
618+
CCC_Vector3::Status(S);
619+
apply();
620+
}
621+
};
622+
623+
604624
void xrRender_initconsole()
605625
{
606626
if (!FS.path_exist("$game_weathers$"))
@@ -846,9 +866,11 @@ void xrRender_initconsole()
846866
CMD1(CCC_memory_stats, "render_memory_stats");
847867

848868
// Screen Space Shaders
869+
CMD4(CCC_ssfx_cascades, "ssfx_shadow_cascades", &ps_ssfx_shadow_cascades, (Fvector3{1.0f, 1.0f, 1.0f}), (Fvector3{300.f, 300.f, 300.f}));
849870
CMD4(CCC_Float, "ssfx_wpn_dof_2", &ps_ssfx_wpn_dof_2, 0, 1);
850-
CMD4(CCC_Vector3, "ssfx_grass_interactive", &ps_ssfx_grass_interactive, (Fvector3{}), (Fvector3{1.f, static_cast<float>(GRASS_SHADER_DATA_COUNT), 1500.f}));
871+
CMD4(CCC_Vector4, "ssfx_grass_interactive", &ps_ssfx_grass_interactive, (Fvector4{}), (Fvector4{1.f, static_cast<float>(GRASS_SHADER_DATA_COUNT), 5000.f, 1.f}));
851872
CMD4(CCC_Vector3, "ssfx_int_grass_params_1", &ps_ssfx_int_grass_params_1, (Fvector3{}), (Fvector3{5.f, 5.f, 5.f}));
873+
CMD4(CCC_Vector4, "ssfx_int_grass_params_2", &ps_ssfx_int_grass_params_2, (Fvector4{}), (Fvector4{5.f, 20.f, 1.f, 5.f}));
852874

853875
CMD3(CCC_Mask, "ssfx_height_fog", &ps_r2_ls_flags_ext, SSFX_HEIGHT_FOG);
854876
CMD3(CCC_Mask, "ssfx_sky_debanding", &ps_r2_ls_flags_ext, SSFX_SKY_DEBANDING);

ogsr_engine/Layers/xrRender/xrRender_console.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ extern ECORE_API float ps_r2_visor_refl_radius;
140140

141141
extern ECORE_API float ps_ssfx_wpn_dof_2;
142142
extern Fvector3 ps_ssfx_int_grass_params_1;
143+
extern Fvector3 ps_ssfx_shadow_cascades;
143144

144145
// textures
145146
extern ECORE_API int psTextureLOD;

ogsr_engine/Layers/xrRenderDX10/dx10DetailManager_VS.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,12 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave
103103
if (ps_r2_ls_flags_ext.test(SSFX_INTER_GRASS))
104104
{
105105
Fvector4* c_grass{};
106-
RCache.get_ConstantDirect(strBendersPos, sizeof grass_shader_data.pos, reinterpret_cast<void**>(&c_grass), nullptr, nullptr);
106+
RCache.get_ConstantDirect(strBendersPos, sizeof grass_shader_data.pos + sizeof grass_shader_data.dir, reinterpret_cast<void**>(&c_grass), nullptr, nullptr);
107107
if (c_grass)
108+
{
108109
std::memcpy(c_grass, &grass_shader_data.pos, sizeof grass_shader_data.pos);
110+
std::memcpy(c_grass + std::size(grass_shader_data.pos), &grass_shader_data.dir, sizeof grass_shader_data.dir);
111+
}
109112
}
110113

111114
Fvector4* c_storage{};

ogsr_engine/Layers/xrRenderPC_R4/r2_R_sun.cpp

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,23 +1038,16 @@ void CRender::init_cacades()
10381038
m_sun_cascades.resize(cascade_count);
10391039

10401040
float fBias = -0.0000025f;
1041-
// float size = MAP_SIZE_START;
1041+
10421042
m_sun_cascades[0].reset_chain = true;
1043-
m_sun_cascades[0].size = 40;
1043+
m_sun_cascades[0].size = ps_ssfx_shadow_cascades.x; //40;
10441044
m_sun_cascades[0].bias = m_sun_cascades[0].size * fBias;
10451045

1046-
m_sun_cascades[1].size = 120;
1046+
m_sun_cascades[1].size = ps_ssfx_shadow_cascades.y; // 120;
10471047
m_sun_cascades[1].bias = m_sun_cascades[1].size * fBias;
10481048

1049-
m_sun_cascades[2].size = 320;
1049+
m_sun_cascades[2].size = ps_ssfx_shadow_cascades.z; // 320;
10501050
m_sun_cascades[2].bias = m_sun_cascades[2].size * fBias;
1051-
1052-
// for( u32 i = 0; i < cascade_count; ++i )
1053-
// {
1054-
// m_sun_cascades[i].size = size;
1055-
// size *= MAP_GROW_FACTOR;
1056-
// }
1057-
/// m_sun_cascades[m_sun_cascades.size()-1].size = 80;
10581051
}
10591052

10601053
void CRender::render_sun_cascades()

ogsr_engine/Layers/xrRenderPC_R4/r4.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,8 @@ void CRender::OnFrame()
508508
// MT-HOM (@front)
509509
Device.seqParallel.insert(Device.seqParallel.begin(), fastdelegate::MakeDelegate(&HOM, &CHOM::MT_RENDER));
510510
}
511+
512+
g_pGamePersistent->GrassBendersUpdateExplosions();
511513
}
512514

513515
// Перед началом рендера мира --#SM+#-- +SecondVP+

ogsr_engine/xrGame/Actor.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -926,12 +926,13 @@ void CActor::UpdateCL()
926926
if (ps_ssfx_grass_interactive.x > 0.f)
927927
{
928928
// Не знаю что лучше использовать - позицию камеры или актора. Вроде для травы с позицией актора получше выглядит. Для кустов - не понятно, что лучше.
929-
// grass_shader_data.pos[0].set(Device.vCameraPosition.x, Device.vCameraPosition.y, Device.vCameraPosition.z);
929+
// grass_shader_data.pos[0].set(Device.vCameraPosition.x, Device.vCameraPosition.y, Device.vCameraPosition.z, -1);
930930
const auto& pos = Position();
931-
grass_shader_data.pos[0].set(pos.x, pos.y, pos.z);
931+
grass_shader_data.pos[0].set(pos.x, pos.y, pos.z, -1);
932932
}
933933
else
934934
grass_shader_data.pos[0].set(0.f, 0.f, 0.f);
935+
grass_shader_data.dir[0].set(0.0f, -99.0f, 0.0f, 1.0f);
935936
}
936937

937938
constexpr u32 TASKS_UPDATE_TIME = 1u;

ogsr_engine/xrGame/Explosive.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "game_base_space.h"
3333
#include "profiler.h"
3434
#include "..\Include/xrRender/Kinematics.h"
35+
#include "../xr_3da/IGame_Persistent.h"
3536

3637
#define EFFECTOR_RADIUS 30.f
3738
const u16 TEST_RAYS_PER_OBJECT = 5;
@@ -329,6 +330,9 @@ void CExplosive::Explode()
329330
DBG_DrawPoint(pos, 0.3f, D3DCOLOR_XRGB(255, 0, 0));
330331
}
331332
#endif
333+
334+
g_pGamePersistent->GrassBendersAddExplosion(cast_game_object()->ID(), pos, {0, -99, 0}, 1.33f, ps_ssfx_int_grass_params_2.y, ps_ssfx_int_grass_params_2.x, m_fBlastRadius * 2.0f);
335+
332336
// Msg("---------CExplosive Explode [%d] frame[%d]",cast_game_object()->ID(), Device.dwFrame);
333337
OnBeforeExplosion();
334338
//играем звук взрыва

0 commit comments

Comments
 (0)