Overlay: Object Overlap overlay

- Added UInt R support to framebuffers
- Added the overlap as an overlay so should be reusable by other engines
(Scene lighted Solid mode)
This commit is contained in:
Jeroen Bakker 2018-04-23 14:46:52 +02:00
parent 574c0fe38f
commit 86f988cede
16 changed files with 223 additions and 24 deletions

View File

@ -3437,6 +3437,9 @@ class VIEW3D_PT_view3d_display(Panel):
col.prop(view, "show_only_render")
col.prop(view, "show_world")
if view.viewport_shade == "SOLID":
col.prop(view, "show_random_object_colors")
if context.mode in {'PAINT_WEIGHT', 'PAINT_VERTEX', 'PAINT_TEXTURE'}:
col.prop(view, "show_mode_shade_override")
@ -3447,7 +3450,7 @@ class VIEW3D_PT_view3d_display(Panel):
col.prop(view, "show_all_objects_origin")
col.prop(view, "show_relationship_lines")
col.prop(view, "show_face_orientation_overlay")
col.prop(view, "show_random_object_colors")
col.prop(view, "show_object_overlap_overlay")
col = layout.column()
col.active = display_all

View File

@ -237,6 +237,9 @@ data_to_c_simple(modes/shaders/edit_lattice_overlay_loosevert_vert.glsl SRC)
data_to_c_simple(modes/shaders/edit_normals_vert.glsl SRC)
data_to_c_simple(modes/shaders/edit_normals_geom.glsl SRC)
data_to_c_simple(modes/shaders/overlay_face_orientation_frag.glsl SRC)
data_to_c_simple(modes/shaders/overlay_object_data_vert.glsl SRC)
data_to_c_simple(modes/shaders/overlay_object_data_frag.glsl SRC)
data_to_c_simple(modes/shaders/overlay_object_overlap_frag.glsl SRC)
data_to_c_simple(modes/shaders/overlay_face_orientation_vert.glsl SRC)
data_to_c_simple(modes/shaders/object_empty_image_frag.glsl SRC)
data_to_c_simple(modes/shaders/object_empty_image_vert.glsl SRC)

View File

@ -48,6 +48,9 @@ static void workbench_layer_collection_settings_create(RenderEngine *UNUSED(engi
BLI_assert(props &&
props->type == IDP_GROUP &&
props->subtype == IDP_GROUP_SUB_ENGINE_RENDER);
BKE_collection_engine_property_add_float(props, "random_object_color_saturation", 0.5f);
BKE_collection_engine_property_add_float(props, "random_object_color_value", 0.9f);
}
/* Note: currently unused, we may want to register so we can see this when debugging the view. */

View File

@ -45,6 +45,8 @@ extern char datatoc_workbench_vert_glsl[];
extern char datatoc_workbench_studio_vert_glsl[];
extern char datatoc_workbench_diffuse_lib_glsl[];
extern DrawEngineType draw_engine_workbench_solid_studio;
/* Functions */
static uint get_material_hash(const float color[3])
{
@ -54,13 +56,20 @@ static uint get_material_hash(const float color[3])
return r + g * 4096 + b * 4096 * 4096;
}
static uint NEXT_RANDOM_COLOR_OFFSET = 0;
static void workbench_init_object_data(ObjectEngineData *engine_data) {
WORKBENCH_ObjectData *data = (WORKBENCH_ObjectData*)engine_data;
data->random_color_offset = NEXT_RANDOM_COLOR_OFFSET++;
}
static void get_material_solid_color(WORKBENCH_PrivateData *wpd, Object *ob, float *color)
static void get_material_solid_color(WORKBENCH_PrivateData *wpd, Object *ob, float *color, float hsv_saturation, float hsv_value)
{
if (wpd->drawtype_options & V3D_DRAWOPTION_RANDOMIZE) {
unsigned int obhash = BLI_ghashutil_strhash(ob->id.name);
cpack_to_rgb(obhash, &color[0], &color[1], &color[2]);
ObjectEngineData *engine_data = DRW_object_engine_data_ensure(ob, &draw_engine_workbench_solid_studio, sizeof(WORKBENCH_ObjectData), &workbench_init_object_data, NULL);
WORKBENCH_ObjectData *data = (WORKBENCH_ObjectData*)engine_data;
float offset = fmodf(data->random_color_offset * M_GOLDEN_RATION_CONJUGATE, 1.0);
float hsv[3] = {offset, hsv_saturation, hsv_value};
hsv_to_rgb_v(hsv, color);
}
else {
copy_v3_v3(color, ob->col);
@ -137,6 +146,8 @@ void workbench_materials_solid_cache_populate(WORKBENCH_Data *vedata, Object *ob
return;
struct Gwn_Batch *geom = DRW_cache_object_surface_get(ob);
IDProperty *props = BKE_layer_collection_engine_evaluated_get(ob, COLLECTION_MODE_NONE, RE_engine_id_BLENDER_WORKBENCH);
WORKBENCH_MaterialData *material;
if (geom) {
/* Depth */
@ -146,7 +157,9 @@ void workbench_materials_solid_cache_populate(WORKBENCH_Data *vedata, Object *ob
GPUShader *shader = wpd->drawtype_lighting == V3D_LIGHTING_FLAT ? e_data.solid_flat_sh : e_data.solid_studio_sh;
float color[3];
get_material_solid_color(wpd, ob, color);
const float hsv_saturation = BKE_collection_engine_property_value_get_float(props, "random_object_color_saturation");
const float hsv_value = BKE_collection_engine_property_value_get_float(props, "random_object_color_value");
get_material_solid_color(wpd, ob, color, hsv_saturation, hsv_value);
unsigned int hash = get_material_hash(color);
material = BLI_ghash_lookup(wpd->material_hash, SET_UINT_IN_POINTER(hash));

View File

@ -31,7 +31,7 @@
#include "DNA_view3d_types.h"
#define WORKBENCH_ENGINE "BLENDER_WORKBENCH"
#define M_GOLDEN_RATION_CONJUGATE 0.618033988749895
typedef struct WORKBENCH_StorageList {
struct WORKBENCH_PrivateData *g_data;
@ -68,6 +68,16 @@ typedef struct WORKBENCH_MaterialData {
DRWShadingGroup *shgrp;
} WORKBENCH_MaterialData;
typedef struct WORKBENCH_ObjectData {
struct ObjectEngineData *next, *prev;
struct DrawEngineType *engine_type;
/* Only nested data, NOT the engine data itself. */
ObjectEngineDataFreeCb free;
/* Accumulated recalc flags, which corresponds to ID->recalc flags. */
int recalc;
uint random_color_offset;
} WORKBENCH_ObjectData;
/* workbench_engine.c */
void workbench_solid_materials_init(void);

View File

@ -189,6 +189,7 @@ typedef enum {
DRW_TEX_R_16I,
DRW_TEX_R_16U,
DRW_TEX_R_32,
DRW_TEX_R_32U,
DRW_TEX_DEPTH_16,
DRW_TEX_DEPTH_24,
DRW_TEX_DEPTH_24_STENCIL_8,

View File

@ -38,6 +38,7 @@ void drw_texture_get_format(
case DRW_TEX_R_16I:
case DRW_TEX_R_16U:
case DRW_TEX_R_32:
case DRW_TEX_R_32U:
case DRW_TEX_RG_8:
case DRW_TEX_RG_16:
case DRW_TEX_RG_16I:
@ -75,6 +76,7 @@ void drw_texture_get_format(
case DRW_TEX_R_16I: *r_data_type = GPU_R16I; break;
case DRW_TEX_R_16U: *r_data_type = GPU_R16UI; break;
case DRW_TEX_R_32: *r_data_type = GPU_R32F; break;
case DRW_TEX_R_32U: *r_data_type = GPU_R32UI; break;
#if 0
case DRW_TEX_RGB_8: *r_data_type = GPU_RGB8; break;
case DRW_TEX_RGB_32: *r_data_type = GPU_RGB32F; break;

View File

@ -23,23 +23,31 @@
* \ingroup draw_engine
*/
#include "DRW_render.h"
#include "DNA_view3d_types.h"
#include "GPU_shader.h"
#include "DNA_view3d_types.h"
#include "DRW_render.h"
#include "draw_mode_engines.h"
/* Structures */
typedef struct OVERLAY_FramebufferList {
struct GPUFrameBuffer *object_data_fb;
} OVERLAY_FramebufferList;
typedef struct OVERLAY_StorageList {
struct OVERLAY_PrivateData *g_data;
} OVERLAY_StorageList;
typedef struct OVERLAY_PassList {
struct DRWPass *face_orientation_pass;
struct DRWPass *object_data_pass;
struct DRWPass *object_overlap_pass;
} OVERLAY_PassList;
typedef struct OVERLAY_Data {
void *engine_type;
DRWViewportEmptyList *fbl;
OVERLAY_FramebufferList *fbl;
DRWViewportEmptyList *txl;
OVERLAY_PassList *psl;
OVERLAY_StorageList *stl;
@ -48,34 +56,75 @@ typedef struct OVERLAY_Data {
typedef struct OVERLAY_PrivateData {
DRWShadingGroup *face_orientation_shgrp;
int overlays;
int next_object_id;
ListBase materials;
} OVERLAY_PrivateData; /* Transient data */
typedef struct OVERLAY_MaterialData {
/* Solid color */
float color[3];
/* Linked shgroup for drawing */
DRWShadingGroup *shgrp;
struct Link *next, *prev;
DRWShadingGroup *object_data_shgrp;
int object_id;
} OVERLAY_MaterialData;
typedef struct OVERLAY_ObjectData {
struct ObjectEngineData *next, *prev;
struct DrawEngineType *engine_type;
/* Only nested data, NOT the engine data itself. */
ObjectEngineDataFreeCb free;
/* Accumulated recalc flags, which corresponds to ID->recalc flags. */
int recalc;
} OVERLAY_ObjectData;
/* *********** STATIC *********** */
static struct {
/* Face orientation shader */
struct GPUShader *face_orientation_sh;
struct GPUShader *object_data_sh;
struct GPUShader *object_overlap_sh;
struct GPUTexture *object_id_tx; /* ref only, not alloced */
} e_data = {NULL};
/* Shaders */
extern char datatoc_overlay_face_orientation_frag_glsl[];
extern char datatoc_overlay_face_orientation_vert_glsl[];
extern char datatoc_overlay_object_data_frag_glsl[];
extern char datatoc_overlay_object_data_vert_glsl[];
extern char datatoc_overlay_object_overlap_frag_glsl[];
/* Functions */
static void overlay_engine_init(void *UNUSED(vedata))
static void overlay_engine_init(void *vedata)
{
OVERLAY_Data * data = (OVERLAY_Data *)vedata;
OVERLAY_FramebufferList *fbl = data->fbl;
OVERLAY_StorageList *stl = data->stl;
DefaultTextureList *dtxl = DRW_viewport_texture_list_get();
if (!stl->g_data) {
/* Alloc transient pointers */
stl->g_data = MEM_mallocN(sizeof(*stl->g_data), __func__);
BLI_listbase_clear(&stl->g_data->materials);
}
if (!e_data.face_orientation_sh) {
/* Face orientation */
e_data.face_orientation_sh = DRW_shader_create(datatoc_overlay_face_orientation_vert_glsl, NULL, datatoc_overlay_face_orientation_frag_glsl, "\n");
e_data.object_data_sh = DRW_shader_create(datatoc_overlay_object_data_vert_glsl, NULL, datatoc_overlay_object_data_frag_glsl, "\n");
e_data.object_overlap_sh = DRW_shader_create_fullscreen(datatoc_overlay_object_overlap_frag_glsl, NULL);
}
{
const float *viewport_size = DRW_viewport_size_get();
const int size[2] = {(int)viewport_size[0], (int)viewport_size[1]};
e_data.object_id_tx = DRW_texture_pool_query_2D(size[0], size[1], DRW_TEX_R_32U, &draw_engine_overlay_type);
GPU_framebuffer_ensure_config(&fbl->object_data_fb, {
GPU_ATTACHMENT_TEXTURE(dtxl->depth),
GPU_ATTACHMENT_TEXTURE(e_data.object_id_tx)
});
}
}
@ -87,11 +136,8 @@ static void overlay_cache_init(void *vedata)
OVERLAY_StorageList *stl = data->stl;
const DRWContextState *DCS = DRW_context_state_get();
DRWShadingGroup *grp;
if (!stl->g_data) {
/* Alloc transient pointers */
stl->g_data = MEM_mallocN(sizeof(*stl->g_data), __func__);
}
View3D *v3d = DCS->v3d;
if (v3d) {
@ -100,6 +146,7 @@ static void overlay_cache_init(void *vedata)
else {
stl->g_data->overlays = 0;
}
stl->g_data->next_object_id = 0;
/* Face Orientation Pass */
if (stl->g_data->overlays & V3D_OVERLAY_FACE_ORIENTATION) {
@ -107,11 +154,22 @@ static void overlay_cache_init(void *vedata)
psl->face_orientation_pass = DRW_pass_create("Face Orientation", state);
stl->g_data->face_orientation_shgrp = DRW_shgroup_create(e_data.face_orientation_sh, psl->face_orientation_pass);
}
if (stl->g_data->overlays & V3D_OVERLAY_OBJECT_OVERLAP) {
int state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL;
psl->object_data_pass = DRW_pass_create("Object Data", state);
psl->object_overlap_pass = DRW_pass_create("Overlap", DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND);
grp = DRW_shgroup_create(e_data.object_overlap_sh, psl->object_overlap_pass);
DRW_shgroup_uniform_texture_ref(grp, "objectId", &e_data.object_id_tx);
DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
}
}
static void overlay_cache_populate(void *vedata, Object *ob)
{
OVERLAY_Data * data = (OVERLAY_Data *)vedata;
OVERLAY_PassList *psl = data->psl;
OVERLAY_StorageList *stl = data->stl;
OVERLAY_PrivateData *pd = stl->g_data;
@ -124,6 +182,15 @@ static void overlay_cache_populate(void *vedata, Object *ob)
if (stl->g_data->overlays & V3D_OVERLAY_FACE_ORIENTATION) {
DRW_shgroup_call_add(pd->face_orientation_shgrp, geom, ob->obmat);
}
if (stl->g_data->overlays & V3D_OVERLAY_OBJECT_OVERLAP) {
OVERLAY_MaterialData *material_data = (OVERLAY_MaterialData*)MEM_mallocN(sizeof(OVERLAY_MaterialData), __func__);
material_data->object_data_shgrp = DRW_shgroup_create(e_data.object_data_sh, psl->object_data_pass);
material_data->object_id = pd->next_object_id ++;
DRW_shgroup_uniform_int(material_data->object_data_shgrp, "object_id", &material_data->object_id, 1);
DRW_shgroup_call_add(material_data->object_data_shgrp, geom, ob->obmat);
BLI_addhead(&pd->materials, material_data);
}
}
}
@ -135,13 +202,34 @@ static void overlay_draw_scene(void *vedata)
{
OVERLAY_Data * data = (OVERLAY_Data *)vedata;
OVERLAY_PassList *psl = data->psl;
OVERLAY_StorageList *stl = data->stl;
OVERLAY_FramebufferList *fbl = data->fbl;
OVERLAY_PrivateData *pd = stl->g_data;
DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
DRW_draw_pass(psl->face_orientation_pass);
if (pd->overlays & V3D_OVERLAY_FACE_ORIENTATION) {
DRW_draw_pass(psl->face_orientation_pass);
}
if (pd->overlays & V3D_OVERLAY_OBJECT_OVERLAP) {
const float clear_col[4] = {0.0f, 0.0f, 0.0f, 0.0f};
GPU_framebuffer_bind(fbl->object_data_fb);
GPU_framebuffer_clear_color(fbl->object_data_fb, clear_col);
DRW_draw_pass(psl->object_data_pass);
GPU_framebuffer_bind(dfbl->color_only_fb);
DRW_draw_pass(psl->object_overlap_pass);
}
BLI_freelistN(&pd->materials);
}
static void overlay_engine_free(void)
{
DRW_SHADER_FREE_SAFE(e_data.face_orientation_sh);
DRW_SHADER_FREE_SAFE(e_data.object_data_sh);
DRW_SHADER_FREE_SAFE(e_data.object_overlap_sh);
}
static const DrawEngineDataSize overlay_data_size = DRW_VIEWPORT_DATA_SIZE(OVERLAY_Data);

View File

@ -0,0 +1,8 @@
uniform int object_id = 0;
out int objectId;
void main()
{
objectId = object_id;
}

View File

@ -0,0 +1,7 @@
uniform mat4 ModelViewProjectionMatrix;
in vec3 pos;
void main()
{
gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
}

View File

@ -0,0 +1,30 @@
out vec4 fragColor;
uniform usampler2D objectId;
#define CROSS_OFFSET 1
void main()
{
ivec2 uv = ivec2(gl_FragCoord.xy);
ivec2 uvNE = ivec2(uv.x-CROSS_OFFSET, uv.y+CROSS_OFFSET);
ivec2 uvNW = ivec2(uv.x-CROSS_OFFSET, uv.y-CROSS_OFFSET);
ivec2 uvSE = ivec2(uv.x+CROSS_OFFSET, uv.y+CROSS_OFFSET);
ivec2 uvSW = ivec2(uv.x+CROSS_OFFSET, uv.y-CROSS_OFFSET);
uint oid = texelFetch(objectId, uv, 0).r;
uint oidNE = texelFetch(objectId, uvNE, 0).r;
uint oidNW = texelFetch(objectId, uvNW, 0).r;
uint oidSE = texelFetch(objectId, uvSE, 0).r;
uint oidSW = texelFetch(objectId, uvSW, 0).r;
float result = 1.0;
int same = 0;
if (oid == oidNE) same ++;
if (oid == oidNW) same ++;
if (oid == oidSE) same ++;
if (oid == oidSW) same ++;
result = float(same) / 4.0;
fragColor = vec4(0.0, 0.0, 0.0, 1.0-result);
}

View File

@ -69,6 +69,7 @@ typedef enum GPUTextureFormat {
GPU_RG16F,
GPU_RG16I,
GPU_R32F,
GPU_R32UI,
GPU_R16F,
GPU_R16I,
GPU_R16UI,
@ -89,7 +90,6 @@ typedef enum GPUTextureFormat {
GPU_RG8I,
GPU_RG8UI,
GPU_R32I,
GPU_R32UI,
GPU_R16,
GPU_R8I,
GPU_R8UI,

View File

@ -147,8 +147,8 @@ static GLenum gpu_texture_get_format(
}
else {
/* Integer formats */
if (ELEM(data_type, GPU_RG16I, GPU_R16I, GPU_R16UI)) {
if (ELEM(data_type, GPU_R16UI)) {
if (ELEM(data_type, GPU_RG16I, GPU_R16I, GPU_R16UI, GPU_R32UI)) {
if (ELEM(data_type, GPU_R16UI, GPU_R32UI)) {
*data_format = GL_UNSIGNED_INT;
}
else {
@ -197,6 +197,7 @@ static GLenum gpu_texture_get_format(
case GPU_RGBA8:
case GPU_R11F_G11F_B10F:
case GPU_R32F:
case GPU_R32UI:
*bytesize = 4;
break;
case GPU_DEPTH_COMPONENT24:
@ -228,6 +229,7 @@ static GLenum gpu_texture_get_format(
case GPU_RG16I: return GL_RG16I;
case GPU_RGBA8: return GL_RGBA8;
case GPU_R32F: return GL_R32F;
case GPU_R32UI: return GL_R32UI;
case GPU_R16F: return GL_R16F;
case GPU_R16I: return GL_R16I;
case GPU_R16UI: return GL_R16UI;

View File

@ -86,6 +86,7 @@ enum {
enum {
V3D_OVERLAY_FACE_ORIENTATION = (1 << 0),
V3D_OVERLAY_OBJECT_OVERLAP = (2 << 0),
};
typedef struct RegionView3D {

View File

@ -313,6 +313,9 @@ static void rna_LayerEngineSettings_##_ENGINE_##_##_NAME_##_set(PointerRNA *ptr,
#define RNA_LAYER_ENGINE_WORKBENCH_GET_SET_FLOAT_ARRAY(_NAME_, _LEN_) \
RNA_LAYER_ENGINE_GET_SET_ARRAY(float, Workbench, COLLECTION_MODE_NONE, _NAME_, _LEN_)
#define RNA_LAYER_ENGINE_WORKBENCH_GET_SET_FLOAT(_NAME_) \
RNA_LAYER_ENGINE_GET_SET(float, Workbench, COLLECTION_MODE_NONE, _NAME_)
#define RNA_LAYER_ENGINE_WORKBENCH_GET_SET_INT(_NAME_) \
RNA_LAYER_ENGINE_GET_SET(int, Workbench, COLLECTION_MODE_NONE, _NAME_)
@ -360,6 +363,10 @@ RNA_LAYER_ENGINE_CLAY_GET_SET_FLOAT(ssao_attenuation)
RNA_LAYER_ENGINE_CLAY_GET_SET_FLOAT(hair_brightness_randomness)
#endif /* WITH_CLAY_ENGINE */
/* workbench engine */
RNA_LAYER_ENGINE_WORKBENCH_GET_SET_FLOAT(random_object_color_saturation)
RNA_LAYER_ENGINE_WORKBENCH_GET_SET_FLOAT(random_object_color_value)
/* eevee engine */
/* ViewLayer settings. */
RNA_LAYER_ENGINE_EEVEE_GET_SET_BOOL(gtao_enable)
@ -1675,11 +1682,26 @@ static void rna_def_layer_collection_engine_settings_clay(BlenderRNA *brna)
static void rna_def_layer_collection_engine_settings_workbench(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "LayerCollectionEngineSettingsWorkbench", "LayerCollectionSettings");
RNA_def_struct_ui_text(srna, "Collections Workbench Engine Settings", "Workbench specific settings for this collection");
RNA_define_verify_sdna(0); /* not in sdna */
prop = RNA_def_property(srna, "random_object_color_saturation", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_funcs(prop, "rna_LayerEngineSettings_Workbench_random_object_color_saturation_get", "rna_LayerEngineSettings_Workbench_random_object_color_saturation_set", NULL);
RNA_def_property_ui_text(prop, "Random Saturation", "Random Object Color Saturation");
RNA_def_property_range(prop, 0.0f, 1.0f);
RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE);
RNA_def_property_update(prop, 0, "rna_LayerCollectionEngineSettings_update");
prop = RNA_def_property(srna, "random_object_color_value", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_funcs(prop, "rna_LayerEngineSettings_Workbench_random_object_color_value_get", "rna_LayerEngineSettings_Workbench_random_object_color_value_set", NULL);
RNA_def_property_ui_text(prop, "Random Value", "Random Object Color Value");
RNA_def_property_range(prop, 0.0f, 1.0f);
RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE);
RNA_def_property_update(prop, 0, "rna_LayerCollectionEngineSettings_update");
}
static void rna_def_layer_collection_mode_settings_object(BlenderRNA *brna)

View File

@ -2291,6 +2291,12 @@ static void rna_def_space_view3d(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Face Orientation", "Show the Face Orientation Overlay");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_SpaceView3D_viewport_shade_update");
prop = RNA_def_property(srna, "show_object_overlap_overlay", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "overlays", V3D_OVERLAY_OBJECT_OVERLAP);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Object Overlap", "Show the Object Overlap Overlay");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_SpaceView3D_viewport_shade_update");
prop = RNA_def_property(srna, "show_random_object_colors", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "drawtype_options", V3D_DRAWOPTION_RANDOMIZE);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);