diff options
author | Lukas Stockner <lukas.stockner@freenet.de> | 2018-05-24 04:50:16 +0300 |
---|---|---|
committer | Lukas Stockner <lukas.stockner@freenet.de> | 2018-05-24 17:43:47 +0300 |
commit | 5505ba8d4753ee6b3c0101ba3e4abf337546a1b0 (patch) | |
tree | 1c0fdbed36127f670e8aa0d49a53f72988cc34c5 /source | |
parent | e8c8ff4f86c276e4dca884ff1a411fcbbf003fed (diff) |
Cycles/Eevee: Implement disk and ellipse shapes for area lamps
The implementation is pretty straightforward.
In Cycles, sampling the shapes is currently done w.r.t. area instead of solid angle.
There is a paper on solid angle sampling for disks [1], but the described algorithm is based on
simply sampling the enclosing square and rejecting samples outside of the disk, which is not exactly
great for Cycles' RNG (we'd need to setup a LCG for the repeated sampling) and for GPU divergence.
Even worse, the algorithm is only defined for disks. For ellipses, the basic idea still works, but a
way to analytically calculate the solid angle is required. This is technically possible [2], but the
calculation is extremely complex and still requires a lookup table for the Heuman Lambda function.
Therefore, I've decided to not implement that for now, we could still look into it later on.
In Eevee, the code uses the existing ltc_evaluate_disk to implement the lighting calculations.
[1]: "Solid Angle Sampling of Disk and Cylinder Lights"
[2]: "Analytical solution for the solid angle subtended at any point by an ellipse via a point source radiation vector potential"
Reviewers: sergey, brecht, fclem
Differential Revision: https://developer.blender.org/D3171
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/draw/engines/eevee/eevee_lights.c | 13 | ||||
-rw-r--r-- | source/blender/draw/engines/eevee/eevee_private.h | 3 | ||||
-rw-r--r-- | source/blender/draw/engines/eevee/shaders/bsdf_direct_lib.glsl | 30 | ||||
-rw-r--r-- | source/blender/draw/engines/eevee/shaders/lamps_lib.glsl | 31 | ||||
-rw-r--r-- | source/blender/draw/engines/eevee/shaders/volumetric_lib.glsl | 5 | ||||
-rw-r--r-- | source/blender/draw/intern/draw_cache.c | 42 | ||||
-rw-r--r-- | source/blender/draw/intern/draw_cache.h | 3 | ||||
-rw-r--r-- | source/blender/draw/modes/object_mode.c | 19 | ||||
-rw-r--r-- | source/blender/editors/object/object_transform.c | 4 | ||||
-rw-r--r-- | source/blender/editors/space_view3d/view3d_manipulator_lamp.c | 12 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_lamp_types.h | 2 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_lamp.c | 2 |
12 files changed, 135 insertions, 31 deletions
diff --git a/source/blender/draw/engines/eevee/eevee_lights.c b/source/blender/draw/engines/eevee/eevee_lights.c index 123dabbe226..9f3295e7095 100644 --- a/source/blender/draw/engines/eevee/eevee_lights.c +++ b/source/blender/draw/engines/eevee/eevee_lights.c @@ -591,7 +591,7 @@ static void eevee_light_setup(Object *ob, EEVEE_Light *evli) } else if (la->type == LA_AREA) { evli->sizex = max_ff(0.0001f, la->area_size * scale[0] * 0.5f); - if (la->area_shape == LA_AREA_RECT) { + if (ELEM(la->area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE)) { evli->sizey = max_ff(0.0001f, la->area_sizey * scale[1] * 0.5f); } else { @@ -602,10 +602,18 @@ static void eevee_light_setup(Object *ob, EEVEE_Light *evli) evli->radius = max_ff(0.001f, la->area_size); } + /* Lamp Type */ + evli->lamptype = (float)la->type; + /* Make illumination power constant */ if (la->type == LA_AREA) { power = 1.0f / (evli->sizex * evli->sizey * 4.0f * M_PI) * /* 1/(w*h*Pi) */ 80.0f; /* XXX : Empirical, Fit cycles power */ + if (ELEM(la->area_shape, LA_AREA_DISK, LA_AREA_ELLIPSE)) { + evli->lamptype = LAMPTYPE_AREA_ELLIPSE; + /* Scale power to account for the lower area of the ellipse compared to the surrouding rectangle. */ + power *= 4.0f / M_PI; + } } else if (la->type == LA_SPOT || la->type == LA_LOCAL) { power = 1.0f / (4.0f * evli->radius * evli->radius * M_PI * M_PI) * /* 1/(4*r²*Pi²) */ @@ -620,9 +628,6 @@ static void eevee_light_setup(Object *ob, EEVEE_Light *evli) } mul_v3_fl(evli->color, power * la->energy); - /* Lamp Type */ - evli->lamptype = (float)la->type; - /* No shadow by default */ evli->shadowid = -1.0f; } diff --git a/source/blender/draw/engines/eevee/eevee_private.h b/source/blender/draw/engines/eevee/eevee_private.h index c95e51548d0..a5f4f6d85a8 100644 --- a/source/blender/draw/engines/eevee/eevee_private.h +++ b/source/blender/draw/engines/eevee/eevee_private.h @@ -289,6 +289,9 @@ typedef struct EEVEE_Light { float forwardvec[3], lamptype; } EEVEE_Light; +/* Special type for elliptic area lamps, matches lamps_lib.glsl */ +#define LAMPTYPE_AREA_ELLIPSE 100.0f + typedef struct EEVEE_Shadow { float near, far, bias, exp; float shadow_start, data_start, multi_shadow_count, shadow_blur; diff --git a/source/blender/draw/engines/eevee/shaders/bsdf_direct_lib.glsl b/source/blender/draw/engines/eevee/shaders/bsdf_direct_lib.glsl index d5b54097638..aad71b3e48e 100644 --- a/source/blender/draw/engines/eevee/shaders/bsdf_direct_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/bsdf_direct_lib.glsl @@ -47,6 +47,16 @@ float direct_diffuse_rectangle(LightData ld, vec3 N, vec3 V, vec4 l_vector) return ltc_evaluate_quad(corners, N); } +float direct_diffuse_ellipse(LightData ld, vec3 N, vec3 V, vec4 l_vector) +{ + vec3 points[3]; + points[0] = l_vector.xyz + ld.l_right * -ld.l_sizex + ld.l_up * -ld.l_sizey; + points[1] = l_vector.xyz + ld.l_right * ld.l_sizex + ld.l_up * -ld.l_sizey; + points[2] = l_vector.xyz + ld.l_right * ld.l_sizex + ld.l_up * ld.l_sizey; + + return ltc_evaluate_disk(N, V, mat3(1), points); +} + float direct_diffuse_unit_disc(LightData ld, vec3 N, vec3 V) { float NL = dot(N, -ld.l_forward); @@ -107,6 +117,26 @@ vec3 direct_ggx_sphere(LightData ld, vec3 N, vec3 V, vec4 l_vector, float roughn return spec; } +vec3 direct_ggx_ellipse(LightData ld, vec3 N, vec3 V, vec4 l_vector, float roughness, vec3 f0) +{ + vec3 points[3]; + points[0] = l_vector.xyz + ld.l_right * -ld.l_sizex + ld.l_up * -ld.l_sizey; + points[1] = l_vector.xyz + ld.l_right * ld.l_sizex + ld.l_up * -ld.l_sizey; + points[2] = l_vector.xyz + ld.l_right * ld.l_sizex + ld.l_up * ld.l_sizey; + + vec2 uv = lut_coords(dot(N, V), sqrt(roughness)); + vec3 brdf_lut = texture(utilTex, vec3(uv, 1.0)).rgb; + vec4 ltc_lut = texture(utilTex, vec3(uv, 0.0)).rgba; + mat3 ltc_mat = ltc_matrix(ltc_lut); + + float bsdf = ltc_evaluate_disk(N, V, ltc_mat, points); + bsdf *= brdf_lut.b; /* Bsdf intensity */ + + vec3 spec = F_area(f0, brdf_lut.xy) * bsdf; + + return spec; +} + vec3 direct_ggx_rectangle(LightData ld, vec3 N, vec3 V, vec4 l_vector, float roughness, vec3 f0) { vec3 corners[4]; diff --git a/source/blender/draw/engines/eevee/shaders/lamps_lib.glsl b/source/blender/draw/engines/eevee/shaders/lamps_lib.glsl index 198a05ccf97..0e6f976187a 100644 --- a/source/blender/draw/engines/eevee/shaders/lamps_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/lamps_lib.glsl @@ -15,11 +15,13 @@ layout(std140) uniform light_block { }; /* type */ -#define POINT 0.0 -#define SUN 1.0 -#define SPOT 2.0 -#define HEMI 3.0 -#define AREA 4.0 +#define POINT 0.0 +#define SUN 1.0 +#define SPOT 2.0 +#define HEMI 3.0 +#define AREA_RECT 4.0 +/* Used to define the area lamp shape, doesn't directly correspond to a Blender lamp type. */ +#define AREA_ELLIPSE 100.0 #if defined(SHADOW_VSM) #define ShadowSample vec2 @@ -174,7 +176,7 @@ float light_visibility(LightData ld, vec3 W, vis *= spotmask; vis *= step(0.0, -dot(l_vector.xyz, ld.l_forward)); } - else if (ld.l_type == AREA) { + else if (ld.l_type == AREA_RECT || ld.l_type == AREA_ELLIPSE) { vis *= step(0.0, -dot(l_vector.xyz, ld.l_forward)); } @@ -253,9 +255,12 @@ float light_diffuse(LightData ld, vec3 N, vec3 V, vec4 l_vector) if (ld.l_type == SUN) { return direct_diffuse_unit_disc(ld, N, V); } - else if (ld.l_type == AREA) { + else if (ld.l_type == AREA_RECT) { return direct_diffuse_rectangle(ld, N, V, l_vector); } + else if (ld.l_type == AREA_ELLIPSE) { + return direct_diffuse_ellipse(ld, N, V, l_vector); + } else { return direct_diffuse_sphere(ld, N, l_vector); } @@ -275,9 +280,12 @@ vec3 light_specular(LightData ld, vec3 N, vec3 V, vec4 l_vector, float roughness if (ld.l_type == SUN) { return direct_ggx_unit_disc(ld, N, V, roughness, f0); } - else if (ld.l_type == AREA) { + else if (ld.l_type == AREA_RECT) { return direct_ggx_rectangle(ld, N, V, l_vector, roughness, f0); } + else if (ld.l_type == AREA_ELLIPSE) { + return direct_ggx_ellipse(ld, N, V, l_vector, roughness, f0); + } else { return direct_ggx_sphere(ld, N, V, l_vector, roughness, f0); } @@ -373,8 +381,11 @@ vec3 light_translucent(LightData ld, vec3 W, vec3 N, vec4 l_vector, float scale) /* XXX : Removing Area Power. */ /* TODO : put this out of the shader. */ float falloff; - if (ld.l_type == AREA) { + if (ld.l_type == AREA_RECT || ld.l_type == AREA_ELLIPSE) { vis *= (ld.l_sizex * ld.l_sizey * 4.0 * M_PI) * (1.0 / 80.0); + if (ld.l_type == AREA_ELLIPSE) { + vis *= M_PI * 0.25; + } vis *= 0.3 * 20.0 * max(0.0, dot(-ld.l_forward, l_vector.xyz / l_vector.w)); /* XXX ad hoc, empirical */ vis /= (l_vector.w * l_vector.w); falloff = dot(N, l_vector.xyz / l_vector.w); @@ -412,7 +423,7 @@ vec3 light_translucent(LightData ld, vec3 W, vec3 N, vec4 l_vector, float scale) vis *= spotmask; vis *= step(0.0, -dot(l_vector.xyz, ld.l_forward)); } - else if (ld.l_type == AREA) { + else if (ld.l_type == AREA_RECT || ld.l_type == AREA_ELLIPSE) { vis *= step(0.0, -dot(l_vector.xyz, ld.l_forward)); } } diff --git a/source/blender/draw/engines/eevee/shaders/volumetric_lib.glsl b/source/blender/draw/engines/eevee/shaders/volumetric_lib.glsl index 1a8167c2830..5f641c5d490 100644 --- a/source/blender/draw/engines/eevee/shaders/volumetric_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/volumetric_lib.glsl @@ -66,8 +66,11 @@ vec3 light_volume(LightData ld, vec4 l_vector) /* XXX : Removing Area Power. */ /* TODO : put this out of the shader. */ /* See eevee_light_setup(). */ - if (ld.l_type == AREA) { + if (ld.l_type == AREA_RECT || ld.l_type == AREA_ELLIPSE) { power = (ld.l_sizex * ld.l_sizey * 4.0 * M_PI) * (1.0 / 80.0); + if (ld.l_type == AREA_ELLIPSE) { + power *= M_PI * 0.25; + } power *= 20.0 * max(0.0, dot(-ld.l_forward, l_vector.xyz / l_vector.w)); /* XXX ad hoc, empirical */ } else if (ld.l_type == SUN) { diff --git a/source/blender/draw/intern/draw_cache.c b/source/blender/draw/intern/draw_cache.c index 6065e7f0fc5..96edb7cd429 100644 --- a/source/blender/draw/intern/draw_cache.c +++ b/source/blender/draw/intern/draw_cache.c @@ -72,7 +72,8 @@ static struct DRWShapeCache { Gwn_Batch *drw_lamp; Gwn_Batch *drw_lamp_shadows; Gwn_Batch *drw_lamp_sunrays; - Gwn_Batch *drw_lamp_area; + Gwn_Batch *drw_lamp_area_square; + Gwn_Batch *drw_lamp_area_disk; Gwn_Batch *drw_lamp_hemi; Gwn_Batch *drw_lamp_spot; Gwn_Batch *drw_lamp_spot_square; @@ -1122,9 +1123,9 @@ Gwn_Batch *DRW_cache_lamp_sunrays_get(void) return SHC.drw_lamp_sunrays; } -Gwn_Batch *DRW_cache_lamp_area_get(void) +Gwn_Batch *DRW_cache_lamp_area_square_get(void) { - if (!SHC.drw_lamp_area) { + if (!SHC.drw_lamp_area_square) { float v1[3] = {0.0f, 0.0f, 0.0f}; /* Position Only 3D format */ @@ -1151,9 +1152,40 @@ Gwn_Batch *DRW_cache_lamp_area_get(void) v1[1] = 0.5f; GWN_vertbuf_attr_set(vbo, attr_id.pos, 7, v1); - SHC.drw_lamp_area = GWN_batch_create_ex(GWN_PRIM_LINES, vbo, NULL, GWN_BATCH_OWNS_VBO); + SHC.drw_lamp_area_square = GWN_batch_create_ex(GWN_PRIM_LINES, vbo, NULL, GWN_BATCH_OWNS_VBO); } - return SHC.drw_lamp_area; + return SHC.drw_lamp_area_square; +} + +Gwn_Batch *DRW_cache_lamp_area_disk_get(void) +{ +#define NSEGMENTS 32 + if (!SHC.drw_lamp_area_disk) { + /* Position Only 3D format */ + static Gwn_VertFormat format = { 0 }; + static struct { uint pos; } attr_id; + if (format.attrib_ct == 0) { + attr_id.pos = GWN_vertformat_attr_add(&format, "pos", GWN_COMP_F32, 3, GWN_FETCH_FLOAT); + } + + Gwn_VertBuf *vbo = GWN_vertbuf_create_with_format(&format); + GWN_vertbuf_data_alloc(vbo, 2*NSEGMENTS); + + float v[3] = {0.0f, 0.5f, 0.0f}; + GWN_vertbuf_attr_set(vbo, attr_id.pos, 0, v); + for (int a = 1; a < NSEGMENTS; a++) { + v[0] = 0.5f*sinf(2.f * (float)M_PI * a / NSEGMENTS); + v[1] = 0.5f*cosf(2.f * (float)M_PI * a / NSEGMENTS); + GWN_vertbuf_attr_set(vbo, attr_id.pos, 2*a-1, v); + GWN_vertbuf_attr_set(vbo, attr_id.pos, 2*a, v); + } + copy_v3_fl3(v, 0.0f, 0.5f, 0.0f); + GWN_vertbuf_attr_set(vbo, attr_id.pos, 2*NSEGMENTS-1, v); + + SHC.drw_lamp_area_disk = GWN_batch_create_ex(GWN_PRIM_LINES, vbo, NULL, GWN_BATCH_OWNS_VBO); + } + return SHC.drw_lamp_area_disk; +#undef NSEGMENTS } Gwn_Batch *DRW_cache_lamp_hemi_get(void) diff --git a/source/blender/draw/intern/draw_cache.h b/source/blender/draw/intern/draw_cache.h index 8bc609ffe1f..8cf38cdd123 100644 --- a/source/blender/draw/intern/draw_cache.h +++ b/source/blender/draw/intern/draw_cache.h @@ -78,7 +78,8 @@ struct Gwn_Batch *DRW_cache_field_cone_limit_get(void); struct Gwn_Batch *DRW_cache_lamp_get(void); struct Gwn_Batch *DRW_cache_lamp_shadows_get(void); struct Gwn_Batch *DRW_cache_lamp_sunrays_get(void); -struct Gwn_Batch *DRW_cache_lamp_area_get(void); +struct Gwn_Batch *DRW_cache_lamp_area_square_get(void); +struct Gwn_Batch *DRW_cache_lamp_area_disk_get(void); struct Gwn_Batch *DRW_cache_lamp_hemi_get(void); struct Gwn_Batch *DRW_cache_lamp_spot_get(void); struct Gwn_Batch *DRW_cache_lamp_spot_square_get(void); diff --git a/source/blender/draw/modes/object_mode.c b/source/blender/draw/modes/object_mode.c index 58cdfd4e413..40c721e64de 100644 --- a/source/blender/draw/modes/object_mode.c +++ b/source/blender/draw/modes/object_mode.c @@ -175,7 +175,8 @@ typedef struct OBJECT_PrivateData { DRWShadingGroup *lamp_distance; DRWShadingGroup *lamp_buflimit; DRWShadingGroup *lamp_buflimit_points; - DRWShadingGroup *lamp_area; + DRWShadingGroup *lamp_area_square; + DRWShadingGroup *lamp_area_disk; DRWShadingGroup *lamp_hemi; DRWShadingGroup *lamp_spot_cone; DRWShadingGroup *lamp_spot_blend; @@ -1164,8 +1165,11 @@ static void OBJECT_cache_init(void *vedata) stl->g_data->lamp_groundline = shgroup_groundlines_uniform_color(psl->non_meshes, ts.colorLamp); stl->g_data->lamp_groundpoint = shgroup_groundpoints_uniform_color(psl->non_meshes, ts.colorLamp); - geom = DRW_cache_lamp_area_get(); - stl->g_data->lamp_area = shgroup_instance(psl->non_meshes, geom); + geom = DRW_cache_lamp_area_square_get(); + stl->g_data->lamp_area_square = shgroup_instance(psl->non_meshes, geom); + + geom = DRW_cache_lamp_area_disk_get(); + stl->g_data->lamp_area_disk = shgroup_instance(psl->non_meshes, geom); geom = DRW_cache_lamp_hemi_get(); stl->g_data->lamp_hemi = shgroup_instance(psl->non_meshes, geom); @@ -1398,13 +1402,18 @@ static void DRW_shgroup_lamp(OBJECT_StorageList *stl, Object *ob, ViewLayer *vie else if (la->type == LA_AREA) { float size[3] = {1.0f, 1.0f, 1.0f}, sizemat[4][4]; - if (la->area_shape == LA_AREA_RECT) { + if (ELEM(la->area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE)) { size[1] = la->area_sizey / la->area_size; size_to_mat4(sizemat, size); mul_m4_m4m4(shapemat, shapemat, sizemat); } - DRW_shgroup_call_dynamic_add(stl->g_data->lamp_area, color, &la->area_size, shapemat); + if (ELEM(la->area_shape, LA_AREA_DISK, LA_AREA_ELLIPSE)) { + DRW_shgroup_call_dynamic_add(stl->g_data->lamp_area_disk, color, &la->area_size, shapemat); + } + else { + DRW_shgroup_call_dynamic_add(stl->g_data->lamp_area_square, color, &la->area_size, shapemat); + } } /* Line and point going to the ground */ diff --git a/source/blender/editors/object/object_transform.c b/source/blender/editors/object/object_transform.c index 6b22521eedd..77514ca1e8e 100644 --- a/source/blender/editors/object/object_transform.c +++ b/source/blender/editors/object/object_transform.c @@ -631,6 +631,10 @@ static int apply_objects_internal( la->area_shape = LA_AREA_RECT; la->area_sizey = la->area_size; } + else if ((la->area_shape == LA_AREA_DISK) && !keeps_aspect_ratio) { + la->area_shape = LA_AREA_ELLIPSE; + la->area_sizey = la->area_size; + } la->area_size *= rsmat[0][0]; la->area_sizey *= rsmat[1][1]; diff --git a/source/blender/editors/space_view3d/view3d_manipulator_lamp.c b/source/blender/editors/space_view3d/view3d_manipulator_lamp.c index 93b6b69a105..88c36fc2c0b 100644 --- a/source/blender/editors/space_view3d/view3d_manipulator_lamp.c +++ b/source/blender/editors/space_view3d/view3d_manipulator_lamp.c @@ -129,7 +129,7 @@ static void manipulator_area_lamp_prop_matrix_get( const Lamp *la = mpr_prop->custom_func.user_data; matrix[0][0] = la->area_size; - matrix[1][1] = (la->area_shape == LA_AREA_RECT) ? la->area_sizey : la->area_size; + matrix[1][1] = ELEM(la->area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE) ? la->area_sizey : la->area_size; } static void manipulator_area_lamp_prop_matrix_set( @@ -140,7 +140,7 @@ static void manipulator_area_lamp_prop_matrix_set( BLI_assert(mpr_prop->type->array_length == 16); Lamp *la = mpr_prop->custom_func.user_data; - if (la->area_shape == LA_AREA_RECT) { + if (ELEM(la->area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE)) { la->area_size = len_v3(matrix[0]); la->area_sizey = len_v3(matrix[1]); } @@ -185,9 +185,11 @@ static void WIDGETGROUP_lamp_area_refresh(const bContext *C, wmManipulatorGroup copy_m4_m4(mpr->matrix_basis, ob->obmat); - RNA_enum_set(mpr->ptr, "transform", - ED_MANIPULATOR_CAGE2D_XFORM_FLAG_SCALE | - ((la->area_shape == LA_AREA_SQUARE) ? ED_MANIPULATOR_CAGE2D_XFORM_FLAG_SCALE_UNIFORM : 0)); + int flag = ED_MANIPULATOR_CAGE2D_XFORM_FLAG_SCALE; + if (ELEM(la->area_shape, LA_AREA_SQUARE, LA_AREA_DISK)) { + flag |= ED_MANIPULATOR_CAGE2D_XFORM_FLAG_SCALE_UNIFORM; + } + RNA_enum_set(mpr->ptr, "transform", flag); /* need to set property here for undo. TODO would prefer to do this in _init */ WM_manipulator_target_property_def_func( diff --git a/source/blender/makesdna/DNA_lamp_types.h b/source/blender/makesdna/DNA_lamp_types.h index 6294afe00e7..3a4b07cc791 100644 --- a/source/blender/makesdna/DNA_lamp_types.h +++ b/source/blender/makesdna/DNA_lamp_types.h @@ -148,6 +148,8 @@ typedef struct Lamp { #define LA_AREA_RECT 1 #define LA_AREA_CUBE 2 #define LA_AREA_BOX 3 +#define LA_AREA_DISK 4 +#define LA_AREA_ELLIPSE 5 #endif /* __DNA_LAMP_TYPES_H__ */ diff --git a/source/blender/makesrna/intern/rna_lamp.c b/source/blender/makesrna/intern/rna_lamp.c index 5bb24f0270d..d123cdb9773 100644 --- a/source/blender/makesrna/intern/rna_lamp.c +++ b/source/blender/makesrna/intern/rna_lamp.c @@ -394,6 +394,8 @@ static void rna_def_area_lamp(BlenderRNA *brna) static const EnumPropertyItem prop_areashape_items[] = { {LA_AREA_SQUARE, "SQUARE", 0, "Square", ""}, {LA_AREA_RECT, "RECTANGLE", 0, "Rectangle", ""}, + {LA_AREA_DISK, "DISK", 0, "Disk", ""}, + {LA_AREA_ELLIPSE, "ELLIPSE", 0, "Ellipse", ""}, {0, NULL, 0, NULL, NULL} }; |