diff --git a/data/gui.glade b/data/gui.glade index 11a43ab..81622de 100644 --- a/data/gui.glade +++ b/data/gui.glade @@ -1064,6 +1064,337 @@ A small entropy will make the noise repeat more often. False + + + True + False + + + True + False + 5 + vertical + 5 + + + True + False + Horizon preview + + + False + True + 0 + + + + + True + False + GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK + + + 256 + 256 + True + False + gtk-missing-image + + + + + False + False + 1 + + + + + False + True + 0 + + + + + True + False + vertical + + + True + False + 5 + 5 + 5 + True + 7 + 2 + + + True + False + Day time + + + 0 + 0 + 1 + 1 + + + + + 250 + True + True + 2 + 2 + + + 1 + 0 + 1 + 1 + + + + + True + False + Sun color + + + 0 + 1 + 1 + 1 + + + + + 20 + True + False + gtk-missing-image + + + 1 + 1 + 1 + 1 + + + + + True + False + Sun apparent size + + + 0 + 2 + 1 + 1 + + + + + 250 + True + True + 2 + 2 + + + 1 + 2 + 1 + 1 + + + + + True + False + Zenith color + + + 0 + 3 + 1 + 1 + + + + + 20 + True + False + gtk-missing-image + + + 1 + 3 + 1 + 1 + + + + + True + False + Haze color + + + 0 + 4 + 1 + 1 + + + + + 20 + True + False + gtk-missing-image + + + 1 + 4 + 1 + 1 + + + + + True + False + Haze height + + + 0 + 5 + 1 + 1 + + + + + 250 + True + True + 2 + 2 + + + 1 + 5 + 1 + 1 + + + + + True + False + Haze smoothing + + + 0 + 6 + 1 + 1 + + + + + 250 + True + True + 2 + 2 + + + 1 + 6 + 1 + 1 + + + + + False + False + 0 + + + + + True + False + 5 + 10 + spread + + + Apply + True + True + True + False + + + False + True + 0 + + + + + Cancel + True + True + True + False + + + False + True + 1 + + + + + False + False + 1 + + + + + False + True + 1 + + + + + 2 + + + + + True + False + Sky + + + 2 + False + + True @@ -1096,9 +1427,6 @@ A small entropy will make the noise repeat more often. True True clouds_layers_model - - - Altitude @@ -1223,7 +1551,7 @@ A small entropy will make the noise repeat more often. - 2 + 3 @@ -1233,7 +1561,7 @@ A small entropy will make the noise repeat more often. Clouds - 2 + 3 False @@ -1483,7 +1811,7 @@ This method is currently largely inaccurate and slower than two-pass rendering.< - 3 + 4 False @@ -1494,7 +1822,7 @@ This method is currently largely inaccurate and slower than two-pass rendering.< Render - 3 + 4 False diff --git a/src/auto.c b/src/auto.c index a63c10e..66dcf4f 100644 --- a/src/auto.c +++ b/src/auto.c @@ -11,6 +11,7 @@ #include "water.h" #include "clouds.h" +#include "sky.h" static int _cpu_count = 1; static int _is_rendering = 0; @@ -19,7 +20,7 @@ void autoInit() { _cpu_count = (int)sysconf(_SC_NPROCESSORS_ONLN); renderSetBackgroundColor(&COLOR_BLACK); - + terrainInit(); waterInit(); } @@ -67,15 +68,16 @@ void autoSetDaytime(int hour, int minute) void autoSetDaytimeFraction(double daytime) { + SkyDefinition sky; + ColorGradation grad_sun; + Color sun; + daytime = fmod(daytime, 1.0); if (daytime < 0.0) { daytime += 1.0; } - ColorGradation grad_zenith, grad_haze, grad_sky, grad_sun; - Color sun, zenith, haze; - lightingSetSunAngle(0.0, (daytime + 0.25) * M_PI * 2.0); grad_sun = colorGradationCreate(); @@ -89,41 +91,18 @@ void autoSetDaytimeFraction(double daytime) sun = colorGradationGet(&grad_sun, daytime); lightingSetSunColor(sun); - grad_zenith = colorGradationCreate(); - colorGradationAddRgba(&grad_zenith, 0.2, 0.03, 0.03, 0.05, 1.0); - colorGradationAddRgba(&grad_zenith, 0.25, 0.25, 0.33, 0.37, 1.0); - colorGradationAddRgba(&grad_zenith, 0.35, 0.52, 0.63, 0.8, 1.0); - colorGradationAddRgba(&grad_zenith, 0.65, 0.52, 0.63, 0.8, 1.0); - colorGradationAddRgba(&grad_zenith, 0.75, 0.25, 0.33, 0.37, 1.0); - colorGradationAddRgba(&grad_zenith, 0.8, 0.03, 0.03, 0.05, 1.0); - zenith = colorGradationGet(&grad_zenith, daytime); + sky = skyGetDefinition(); + sky.daytime = daytime; + skySetDefinition(sky); - grad_haze = colorGradationCreate(); - colorGradationAddRgba(&grad_haze, 0.2, 0.05, 0.05, 0.08, 1.0); - colorGradationAddRgba(&grad_haze, 0.25, 0.55, 0.42, 0.42, 1.0); - colorGradationAddRgba(&grad_haze, 0.3, 0.6, 0.6, 0.6, 1.0); - colorGradationAddRgba(&grad_haze, 0.4, 0.92, 0.93, 1.0, 1.0); - colorGradationAddRgba(&grad_haze, 0.6, 0.92, 0.93, 1.0, 1.0); - colorGradationAddRgba(&grad_haze, 0.7, 0.6, 0.6, 0.8, 1.0); - colorGradationAddRgba(&grad_haze, 0.75, 0.62, 0.50, 0.42, 1.0); - colorGradationAddRgba(&grad_haze, 0.8, 0.05, 0.05, 0.08, 1.0); - haze = colorGradationGet(&grad_haze, daytime); - - grad_sky = colorGradationCreate(); - colorGradationAdd(&grad_sky, 0.0, &haze); - colorGradationAdd(&grad_sky, 0.45, &haze); - colorGradationAdd(&grad_sky, 0.75, &zenith); - colorGradationAdd(&grad_sky, 1.0, &zenith); - skySetGradation(grad_sky); - - fogSetColor(haze); + fogSetColor(colorGradationGet(&sky.haze_color, daytime)); } void autoSetRenderQuality(int quality) { WaterQuality water; CloudsQuality clouds; - + if (quality < 1) { quality = 1; @@ -150,6 +129,7 @@ void autoGenRealisticLandscape(int seed) Texture* tex; WaterDefinition water; CloudsDefinition cloud; + SkyDefinition sky; int layer; HeightModifier* mod; Zone* zone; @@ -187,7 +167,7 @@ void autoGenRealisticLandscape(int seed) noiseAddLevelSimple(cloud.noise, 50.0 / 1000.0, 0.0005); layer = cloudsAddLayer(); cloudsSetDefinition(layer, cloud); - + /* Water */ water.height = 0.0; water.transparency = 0.4; @@ -203,6 +183,28 @@ void autoGenRealisticLandscape(int seed) waterSetDefinition(water); noiseDeleteGenerator(water.height_noise); + /* Sky */ + sky.zenith_color = colorGradationCreate(); + colorGradationAddRgba(&sky.zenith_color, 0.2, 0.03, 0.03, 0.05, 1.0); + colorGradationAddRgba(&sky.zenith_color, 0.25, 0.25, 0.33, 0.37, 1.0); + colorGradationAddRgba(&sky.zenith_color, 0.35, 0.52, 0.63, 0.8, 1.0); + colorGradationAddRgba(&sky.zenith_color, 0.65, 0.52, 0.63, 0.8, 1.0); + colorGradationAddRgba(&sky.zenith_color, 0.75, 0.25, 0.33, 0.37, 1.0); + colorGradationAddRgba(&sky.zenith_color, 0.8, 0.03, 0.03, 0.05, 1.0); + sky.haze_color = colorGradationCreate(); + colorGradationAddRgba(&sky.haze_color, 0.2, 0.05, 0.05, 0.08, 1.0); + colorGradationAddRgba(&sky.haze_color, 0.25, 0.55, 0.42, 0.42, 1.0); + colorGradationAddRgba(&sky.haze_color, 0.3, 0.6, 0.6, 0.6, 1.0); + colorGradationAddRgba(&sky.haze_color, 0.4, 0.92, 0.93, 1.0, 1.0); + colorGradationAddRgba(&sky.haze_color, 0.6, 0.92, 0.93, 1.0, 1.0); + colorGradationAddRgba(&sky.haze_color, 0.7, 0.6, 0.6, 0.8, 1.0); + colorGradationAddRgba(&sky.haze_color, 0.75, 0.62, 0.50, 0.42, 1.0); + colorGradationAddRgba(&sky.haze_color, 0.8, 0.05, 0.05, 0.08, 1.0); + sky.daytime = 0.0; + sky.haze_height = 0.75; + sky.haze_smoothing = 0.3; + skySetDefinition(sky); + noise = noiseCreateGenerator(); noiseGenerateBaseNoise(noise, 1048576); noiseAddLevelsSimple(noise, 10, 10.0, 1.0); @@ -233,7 +235,7 @@ void autoGenRealisticLandscape(int seed) zone = zoneCreate(0.0); zoneAddHeightRange(zone, 1.0, 3.0, 4.0, 100.0, 100.0); terrainAddTexture(tex, 0.5, zone, 0.1);*/ - + /* DEBUG */ mod = modifierCreate(); zone = modifierGetZone(mod); @@ -313,13 +315,13 @@ void autoRenderSceneTwoPass(int postonly) static int _postProcessRayTracingOverlay(RenderFragment* fragment) { Vector3 terrain_hit, look; - + look = v3Sub(fragment->vertex.location, camera_location); if (!terrainProjectRay(camera_location, look, &terrain_hit, &fragment->vertex.color)) { fragment->vertex.color = skyProjectRay(camera_location, look); } - + return 1; } @@ -328,7 +330,7 @@ void autoRenderSceneRayTracing() renderClear(); cameraPushOverlay(COLOR_RED, _postProcessRayTracingOverlay); renderUpdate(); - + if (renderSetNextProgressStep(0.0, 1.0)) { renderPostProcess(_cpu_count); diff --git a/src/color.c b/src/color.c index b3f395c..76d0f66 100644 --- a/src/color.c +++ b/src/color.c @@ -58,7 +58,7 @@ void colorMask(Color* base, Color* mask) base->g = (mask->g * mask->a + base->g * base->a - base->g * base->a * mask->a) / new_a; base->b = (mask->b * mask->a + base->b * base->a - base->b * base->a * mask->a) / new_a; base->a = new_a; - + /*double mask_weight = mask->a; double base_weight = 1.0 - mask_weight; @@ -182,3 +182,28 @@ Color colorGradationGet(ColorGradation* gradation, double value) } } +void colorGradationSave(FILE* f, ColorGradation gradation) +{ + int i; + + toolsSaveInt(f, gradation.nbparts); + for (i = 0; i < gradation.nbparts; i++) + { + toolsSaveDouble(f, gradation.parts[i].start); + colorSave(gradation.parts[i].col, f); + } +} + +ColorGradation colorGradationLoad(FILE* f) +{ + ColorGradation result; + int i; + + result.nbparts = toolsLoadInt(f); + for (i = 0; i < result.nbparts; i++) + { + result.parts[i].start = toolsLoadDouble(f); + result.parts[i].col = colorLoad(f); + } + +} diff --git a/src/gui/tab_sky.c b/src/gui/tab_sky.c new file mode 100644 index 0000000..2987a8f --- /dev/null +++ b/src/gui/tab_sky.c @@ -0,0 +1,177 @@ +/* Terrain tab */ + +#include "common.h" +#include "../shared/functions.h" +#include "../shared/constants.h" +#include "../sky.h" +#include + +static SmallPreview* _preview_horizon; +static SkyDefinition _definition; + +static Color _cbPreviewHorizon(SmallPreview* preview, double x, double y, double xoffset, double yoffset, double scaling) +{ + Color result; + double height; + + height = terrainGetHeight(x, y); + if (height <= _definition.height) + { + return _definition.main_color; + } + else + { + result.r = result.g = result.b = terrainGetHeightNormalized(x, y); + result.a = 1.0; + + return result; + } +} + +static Color _cbPreviewRender(SmallPreview* preview, double x, double y, double xoffset, double yoffset, double scaling) +{ + Vector3 eye, look, location; + WaterDefinition definition; + WaterEnvironment environment; + WaterQuality quality; + + eye.x = 0.0; + eye.y = scaling; + eye.z = -10.0 * scaling; + look.x = x * 0.01 / scaling; + look.y = -y * 0.01 / scaling - 0.3; + look.z = 1.0; + look = v3Normalize(look); + + if (look.y > -0.0001) + { + return _rayCastFromWater(eye, look).hit_color; + } + + location.x = eye.x - look.x * eye.y / look.y; + location.y = 0.0; + location.z = eye.z - look.z * eye.y / look.y; + + if (location.z > 0.0) + { + return _rayCastFromWater(eye, look).hit_color; + } + + definition = _definition; + definition.height = 0.0; + environment.reflection_function = _rayCastFromWater; + environment.refraction_function = _rayCastFromWater; + environment.toggle_fog = 0; + environment.toggle_shadows = 0; + quality.force_detail = 0.0001; + + return waterGetColorCustom(location, look, &definition, &quality, &environment).final; +} + +static void _cbEditNoiseDone(NoiseGenerator* generator) +{ + noiseCopy(generator, _definition.height_noise); + guiPreviewRedraw(_preview_render); +} + +static void _cbEditNoise(GtkWidget* widget, gpointer data) +{ + guiNoiseEdit(_definition.height_noise, _cbEditNoiseDone); +} + +static void _cbHeightChanged(GtkRange* range, gpointer data) +{ + _definition.height = gtk_range_get_value(range); + guiPreviewRedraw(_preview_coverage); +} + +static void _cbTransparencyChanged(GtkRange* range, gpointer data) +{ + _definition.transparency = gtk_range_get_value(range); + guiPreviewRedraw(_preview_render); +} + +static void _cbReflectionChanged(GtkRange* range, gpointer data) +{ + _definition.reflection = gtk_range_get_value(range); + guiPreviewRedraw(_preview_render); +} + +static void _cbColorChanged(GtkColorButton* colorbutton, gpointer data) +{ + GdkRGBA col; + + gtk_color_button_get_rgba(colorbutton, &col); + _definition.main_color.r = col.red; + _definition.main_color.g = col.green; + _definition.main_color.b = col.blue; + _definition.main_color.a = 1.0; + + guiPreviewRedraw(_preview_render); + guiPreviewRedraw(_preview_coverage); +} + +static void _cbRevertConfig(GtkWidget* widget, gpointer data) +{ + GdkRGBA col; + + waterCopyDefinition(waterGetDefinition(), &_definition); + + gtk_range_set_value(GTK_RANGE(GET_WIDGET("water_height")), _definition.height); + gtk_range_set_value(GTK_RANGE(GET_WIDGET("water_transparency")), _definition.transparency); + gtk_range_set_value(GTK_RANGE(GET_WIDGET("water_reflection")), _definition.reflection); + col.red = _definition.main_color.r; + col.green = _definition.main_color.g; + col.blue = _definition.main_color.b; + col.alpha = 1.0; + gtk_color_button_set_rgba(GTK_COLOR_BUTTON(GET_WIDGET("water_color")), &col); + + guiPreviewRedraw(_preview_render); + guiPreviewRedraw(_preview_coverage); +} + +static void _cbApplyConfig(GtkWidget* widget, gpointer data) +{ + waterSetDefinition(_definition); + guiUpdate(); +} + +void guiWaterInit() +{ + _definition = waterCreateDefinition(); + + /* Buttons */ + g_signal_connect(GET_WIDGET("water_noise_edit"), "clicked", G_CALLBACK(_cbEditNoise), NULL); + g_signal_connect(GET_WIDGET("water_apply"), "clicked", G_CALLBACK(_cbApplyConfig), NULL); + g_signal_connect(GET_WIDGET("water_revert"), "clicked", G_CALLBACK(_cbRevertConfig), NULL); + + /* Configs */ + gtk_range_set_range(GTK_RANGE(GET_WIDGET("water_height")), -20.0, 20.0); + gtk_range_set_range(GTK_RANGE(GET_WIDGET("water_transparency")), 0.0, 1.0); + gtk_range_set_range(GTK_RANGE(GET_WIDGET("water_reflection")), 0.0, 1.0); + + /* Config signals */ + g_signal_connect(GET_WIDGET("water_height"), "value-changed", G_CALLBACK(_cbHeightChanged), NULL); + g_signal_connect(GET_WIDGET("water_transparency"), "value-changed", G_CALLBACK(_cbTransparencyChanged), NULL); + g_signal_connect(GET_WIDGET("water_reflection"), "value-changed", G_CALLBACK(_cbReflectionChanged), NULL); + g_signal_connect(GET_WIDGET("water_color"), "color-set", G_CALLBACK(_cbColorChanged), NULL); + + /* Previews */ + _preview_coverage = guiPreviewNew(GTK_IMAGE(GET_WIDGET("water_preview_coverage"))); + guiPreviewConfigScaling(_preview_coverage, 0.01, 1.0, 0.05); + guiPreviewConfigScrolling(_preview_coverage, -1000.0, 1000.0, -1000.0, 1000.0); + guiPreviewSetViewport(_preview_coverage, 0.0, 0.0, 0.2); + guiPreviewSetRenderer(_preview_coverage, _cbPreviewCoverage); + _preview_render = guiPreviewNew(GTK_IMAGE(GET_WIDGET("water_preview_render"))); + guiPreviewConfigScaling(_preview_render, 0.1, 1.0, 0.1); + guiPreviewConfigScrolling(_preview_render, -10.0, 10.0, -10.0, 10.0); + guiPreviewSetViewport(_preview_render, 0.0, 0.0, 0.5); + guiPreviewSetRenderer(_preview_render, _cbPreviewRender); + + guiWaterUpdate(); +} + +void guiWaterUpdate() +{ + _cbRevertConfig(NULL, NULL); +} diff --git a/src/shared/functions.h b/src/shared/functions.h index 3276c27..30c6277 100644 --- a/src/shared/functions.h +++ b/src/shared/functions.h @@ -49,6 +49,8 @@ ColorGradation colorGradationCreate(); void colorGradationAdd(ColorGradation* gradation, double value, Color* col); void colorGradationAddRgba(ColorGradation* gradation, double value, double r, double g, double b, double a); Color colorGradationGet(ColorGradation* gradation, double value); +void colorGradationSave(FILE* f, ColorGradation gradation); +ColorGradation colorGradationLoad(FILE* f); /* euclid.c */ void v3Save(Vector3 v, FILE* f); diff --git a/src/sky.c b/src/sky.c index df4afbb..8b05ec1 100644 --- a/src/sky.c +++ b/src/sky.c @@ -6,22 +6,106 @@ #include "shared/globals.h" #include "shared/constants.h" #include "clouds.h" +#include "sky.h" -ColorGradation _gradation; #define SPHERE_SIZE 1000.0 +SkyDefinition _definition; +SkyQuality _quality; +SkyEnvironment _environment; + +void skyInit() +{ + skySetDefinition(skyCreateDefinition()); +} + void skySave(FILE* f) { + toolsSaveDouble(f, _definition.daytime); + colorGradationSave(f, _definition.sun_color); + toolsSaveDouble(f, _definition.sun_radius); + colorGradationSave(f, _definition.zenith_color); + colorGradationSave(f, _definition.haze_color); + toolsSaveDouble(f, _definition.haze_height); + toolsSaveDouble(f, _definition.haze_smoothing); } void skyLoad(FILE* f) { + + SkyDefinition def; + + def.daytime = toolsLoadDouble(f); + def.sun_color = colorGradationLoad(f); + def.sun_radius = toolsLoadDouble(f); + def.zenith_color = colorGradationLoad(f); + def.haze_color = colorGradationLoad(f); + def.haze_height = toolsLoadDouble(f); + def.haze_smoothing = toolsLoadDouble(f); + + skySetDefinition(def); } -Color skyGetColor(Vector3 start, Vector3 direction) +SkyDefinition skyCreateDefinition() { - direction = v3Normalize(direction); - return colorGradationGet(&_gradation, direction.y * 0.5 + 0.5); + SkyDefinition def; + + def.daytime = 0.0; + def.sun_color = colorGradationCreate(); + def.sun_radius = 1.0; + def.zenith_color = colorGradationCreate(); + def.haze_color = colorGradationCreate(); + def.haze_height = 0.0; + def.haze_smoothing = 0.0; + + skyValidateDefinition(&def); + + return def; +} + +void skyDeleteDefinition(SkyDefinition definition) +{ +} + +void skyCopyDefinition(SkyDefinition source, SkyDefinition* destination) +{ + *destination = source; +} + +void skyValidateDefinition(SkyDefinition* definition) +{ + Color zenith, haze; + + zenith = colorGradationGet(&definition->zenith_color, definition->daytime); + haze = colorGradationGet(&definition->haze_color, definition->daytime); + + definition->_sky_gradation = colorGradationCreate(); + colorGradationAdd(&definition->_sky_gradation, 0.0, &haze); + colorGradationAdd(&definition->_sky_gradation, definition->haze_height - definition->haze_smoothing, &haze); + colorGradationAdd(&definition->_sky_gradation, definition->haze_height, &zenith); + colorGradationAdd(&definition->_sky_gradation, 1.0, &zenith); +} + +void skySetDefinition(SkyDefinition definition) +{ + skyValidateDefinition(&definition); + _definition = definition; +} + +SkyDefinition skyGetDefinition() +{ + return _definition; +} + +Color skyGetColorCustom(Vector3 eye, Vector3 look, SkyDefinition* definition, SkyQuality* quality, SkyEnvironment* environment) +{ + look = v3Normalize(look); + return colorGradationGet(&_definition._sky_gradation, look.y * 0.5 + 0.5); +} + +Color skyGetColor(Vector3 eye, Vector3 look) +{ + return skyGetColorCustom(eye, look, &_definition, &_quality, &_environment); } Color skyProjectRay(Vector3 start, Vector3 direction) @@ -29,20 +113,15 @@ Color skyProjectRay(Vector3 start, Vector3 direction) Color color_sky, color_clouds; direction = v3Normalize(direction); - + color_sky = skyGetColor(start, direction); color_clouds = cloudsGetColor(start, v3Add(start, v3Scale(direction, SPHERE_SIZE))); - + colorMask(&color_sky, &color_clouds); return color_sky; } -void skySetGradation(ColorGradation grad) -{ - _gradation = grad; -} - static int _postProcessFragment(RenderFragment* fragment) { Vector3 location, direction; @@ -53,7 +132,7 @@ static int _postProcessFragment(RenderFragment* fragment) color_sky = skyGetColor(camera_location, v3Normalize(direction)); color_clouds = cloudsGetColor(camera_location, v3Add(camera_location, v3Scale(direction, 10.0))); - + colorMask(&color_sky, &color_clouds); fragment->vertex.color = color_sky; @@ -85,7 +164,7 @@ void skyRender(RenderProgressCallback callback) { return; } - + current_j = (double)(j - res_j / 2) * step_j; for (i = 0; i < res_i; i++) diff --git a/src/sky.h b/src/sky.h new file mode 100644 index 0000000..39bd97f --- /dev/null +++ b/src/sky.h @@ -0,0 +1,49 @@ +#ifndef _PAYSAGES_SKY_H_ +#define _PAYSAGES_SKY_H_ + +#include "shared/types.h" +#include + +typedef struct +{ + double daytime; + ColorGradation sun_color; + double sun_radius; + ColorGradation zenith_color; + ColorGradation haze_color; + double haze_height; + double haze_smoothing; + ColorGradation _sky_gradation; +} SkyDefinition; + +typedef struct +{ + int unused; +} SkyQuality; + +typedef struct +{ + int unused; +} SkyEnvironment; + +void skyInit(); +void skySave(FILE* f); +void skyLoad(FILE* f); + +SkyDefinition skyCreateDefinition(); +void skyDeleteDefinition(SkyDefinition definition); +void skyCopyDefinition(SkyDefinition source, SkyDefinition* destination); +void skyValidateDefinition(SkyDefinition* definition); +void skySetDefinition(SkyDefinition definition); +SkyDefinition skyGetDefinition(); + +void skySetQuality(SkyQuality quality); +SkyQuality skyGetQuality(); + +Color skyGetColorCustom(Vector3 eye, Vector3 look, SkyDefinition* definition, SkyQuality* quality, SkyEnvironment* environment); +Color skyGetColor(Vector3 eye, Vector3 look); + +Color skyProjectRay(Vector3 start, Vector3 direction); +void skyRender(RenderProgressCallback callback); + +#endif