Smoke, Dynamic Paint: de-duplicate object subframe update function.
authorKévin Dietrich <kevin.dietrich@mailoo.org>
Sat, 9 Jan 2016 03:37:53 +0000 (04:37 +0100)
committerKévin Dietrich <kevin.dietrich@mailoo.org>
Sat, 9 Jan 2016 03:37:53 +0000 (04:37 +0100)
As in the title. In the smoke version, there was also an extra
'for_render' parameter that wasn't used, and wasn't used by the callers
either, so it was removed altogether.

Reviewers: brecht

Reviewed By: brecht

Subscribers: brecht

Differential Revision: https://developer.blender.org/D1718

source/blender/blenkernel/BKE_object.h
source/blender/blenkernel/BKE_smoke.h
source/blender/blenkernel/intern/dynamicpaint.c
source/blender/blenkernel/intern/object.c
source/blender/blenkernel/intern/smoke.c
source/blender/modifiers/intern/MOD_smoke.c

index af3185becf4ec72e4a681e5f0c64d6c4aa1f1fdf..d6635f8705419813080fc7083db32cd377b6e514 100644 (file)
@@ -49,6 +49,8 @@ struct RigidBodyWorld;
 struct HookModifierData;
 struct ModifierData;
 
+enum ModifierType;
+
 void BKE_object_workob_clear(struct Object *workob);
 void BKE_object_workob_calc_parent(struct Scene *scene, struct Object *ob, struct Object *workob);
 
@@ -266,6 +268,10 @@ struct KDTree *BKE_object_as_kdtree(struct Object *ob, int *r_tot);
 
 bool BKE_object_modifier_use_time(struct Object *ob, struct ModifierData *md);
 
+bool BKE_object_modifier_update_subframe(struct Scene *scene, struct Object *ob, bool update_mesh,
+                                         int parent_recursion, float frame,
+                                         enum ModifierType type);
+
 #ifdef __cplusplus
 }
 #endif
index 07d156cfa0297e2d07dfddce1919d6a6d948ae39..20366f00df68f96dd7241349e8b14406f284e812 100644 (file)
@@ -35,7 +35,7 @@
 
 typedef float (*bresenham_callback)(float *result, float *input, int res[3], int *pixel, float *tRay, float correct);
 
-struct DerivedMesh *smokeModifier_do(struct SmokeModifierData *smd, struct Scene *scene, struct Object *ob, struct DerivedMesh *dm, bool for_render);
+struct DerivedMesh *smokeModifier_do(struct SmokeModifierData *smd, struct Scene *scene, struct Object *ob, struct DerivedMesh *dm);
 
 void smoke_reallocate_fluid(struct SmokeDomainSettings *sds, float dx, int res[3], int free_old);
 void smoke_reallocate_highres_fluid(struct SmokeDomainSettings *sds, float dx, int res[3], int free_old);
index 0a9a7a33ec63f19318917ddee64482947f35bf27..67a6d067aba178652e2e9f3f97caf1a208f54120 100644 (file)
@@ -103,8 +103,6 @@ static int neighY[8] = {0, 1, 1, 1, 0, -1, -1, -1};
 
 /* subframe_updateObject() flags */
 #define SUBFRAME_RECURSION 5
-#define UPDATE_MESH (1 << 1)
-#define UPDATE_EVERYTHING (UPDATE_MESH) // | UPDATE_PARENTS
 /* surface_getBrushFlags() return vals */
 #define BRUSH_USES_VELOCITY (1 << 0)
 /* brush mesh raycast status */
@@ -484,93 +482,6 @@ static float mixColors(float a_color[3], float a_weight, const float b_color[3],
        return (1.0f - factor) * a_weight + factor * b_weight;
 }
 
-/* set "ignore cache" flag for all caches on this object */
-static void object_cacheIgnoreClear(Object *ob, int state)
-{
-       ListBase pidlist;
-       PTCacheID *pid;
-       BKE_ptcache_ids_from_object(&pidlist, ob, NULL, 0);
-
-       for (pid = pidlist.first; pid; pid = pid->next) {
-               if (pid->cache) {
-                       if (state)
-                               pid->cache->flag |= PTCACHE_IGNORE_CLEAR;
-                       else
-                               pid->cache->flag &= ~PTCACHE_IGNORE_CLEAR;
-               }
-       }
-
-       BLI_freelistN(&pidlist);
-}
-
-static int subframe_updateObject(Scene *scene, Object *ob, int flags, int parent_recursion, float frame)
-{
-       DynamicPaintModifierData *pmd = (DynamicPaintModifierData *)modifiers_findByType(ob, eModifierType_DynamicPaint);
-       bConstraint *con;
-
-       /* if other is dynamic paint canvas, don't update */
-       if (pmd && pmd->canvas)
-               return 1;
-
-       /* if object has parents, update them too */
-       if (parent_recursion) {
-               int recursion = parent_recursion - 1;
-               int is_canvas = 0;
-               if (ob->parent) is_canvas += subframe_updateObject(scene, ob->parent, 0, recursion, frame);
-               if (ob->track) is_canvas += subframe_updateObject(scene, ob->track, 0, recursion, frame);
-
-               /* skip subframe if object is parented
-                *  to vertex of a dynamic paint canvas */
-               if (is_canvas && (ob->partype == PARVERT1 || ob->partype == PARVERT3))
-                       return 0;
-
-               /* also update constraint targets */
-               for (con = ob->constraints.first; con; con = con->next) {
-                       const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con);
-                       ListBase targets = {NULL, NULL};
-
-                       if (cti && cti->get_constraint_targets) {
-                               bConstraintTarget *ct;
-                               cti->get_constraint_targets(con, &targets);
-                               for (ct = targets.first; ct; ct = ct->next) {
-                                       if (ct->tar)
-                                               subframe_updateObject(scene, ct->tar, 0, recursion, frame);
-                               }
-                               /* free temp targets */
-                               if (cti->flush_constraint_targets)
-                                       cti->flush_constraint_targets(con, &targets, 0);
-                       }
-               }
-       }
-
-       /* was originally OB_RECALC_ALL - TODO - which flags are really needed??? */
-       ob->recalc |= OB_RECALC_OB | OB_RECALC_DATA | OB_RECALC_TIME;
-       BKE_animsys_evaluate_animdata(scene, &ob->id, ob->adt, frame, ADT_RECALC_ANIM);
-       if (flags & UPDATE_MESH) {
-               /* ignore cache clear during subframe updates
-                *  to not mess up cache validity */
-               object_cacheIgnoreClear(ob, 1);
-               BKE_object_handle_update(G.main->eval_ctx, scene, ob);
-               object_cacheIgnoreClear(ob, 0);
-       }
-       else
-               BKE_object_where_is_calc_time(scene, ob, frame);
-
-       /* for curve following objects, parented curve has to be updated too */
-       if (ob->type == OB_CURVE) {
-               Curve *cu = ob->data;
-               BKE_animsys_evaluate_animdata(scene, &cu->id, cu->adt, frame, ADT_RECALC_ANIM);
-       }
-       /* and armatures... */
-       if (ob->type == OB_ARMATURE) {
-               bArmature *arm = ob->data;
-               BKE_animsys_evaluate_animdata(scene, &arm->id, arm->adt, frame, ADT_RECALC_ANIM);
-               BKE_pose_where_is(scene, ob);
-       }
-
-       return 0;
-}
-
 static void scene_setSubframe(Scene *scene, float subframe)
 {
        /* dynamic paint subframes must be done on previous frame */
@@ -3156,7 +3067,7 @@ static void dynamicPaint_brushMeshCalculateVelocity(Scene *scene, Object *ob, Dy
        scene->r.cfra = prev_fra;
        scene->r.subframe = prev_sfra;
 
-       subframe_updateObject(scene, ob, UPDATE_EVERYTHING, SUBFRAME_RECURSION, BKE_scene_frame_get(scene));
+       BKE_object_modifier_update_subframe(scene, ob, true, SUBFRAME_RECURSION, BKE_scene_frame_get(scene), eModifierType_DynamicPaint);
        dm_p = CDDM_copy(brush->dm);
        numOfVerts_p = dm_p->getNumVerts(dm_p);
        mvert_p = dm_p->getVertArray(dm_p);
@@ -3166,7 +3077,7 @@ static void dynamicPaint_brushMeshCalculateVelocity(Scene *scene, Object *ob, Dy
        scene->r.cfra = cur_fra;
        scene->r.subframe = cur_sfra;
 
-       subframe_updateObject(scene, ob, UPDATE_EVERYTHING, SUBFRAME_RECURSION, BKE_scene_frame_get(scene));
+       BKE_object_modifier_update_subframe(scene, ob, true, SUBFRAME_RECURSION, BKE_scene_frame_get(scene), eModifierType_DynamicPaint);
        dm_c = brush->dm;
        numOfVerts_c = dm_c->getNumVerts(dm_c);
        mvert_c = dm_p->getVertArray(dm_c);
@@ -3216,13 +3127,13 @@ static void dynamicPaint_brushObjectCalculateVelocity(Scene *scene, Object *ob,
        /* previous frame dm */
        scene->r.cfra = prev_fra;
        scene->r.subframe = prev_sfra;
-       subframe_updateObject(scene, ob, 0, SUBFRAME_RECURSION, BKE_scene_frame_get(scene));
+       BKE_object_modifier_update_subframe(scene, ob, false, SUBFRAME_RECURSION, BKE_scene_frame_get(scene), eModifierType_DynamicPaint);
        copy_m4_m4(prev_obmat, ob->obmat);
 
        /* current frame dm */
        scene->r.cfra = cur_fra;
        scene->r.subframe = cur_sfra;
-       subframe_updateObject(scene, ob, 0, SUBFRAME_RECURSION, BKE_scene_frame_get(scene));
+       BKE_object_modifier_update_subframe(scene, ob, false, SUBFRAME_RECURSION, BKE_scene_frame_get(scene), eModifierType_DynamicPaint);
 
        /* calculate speed */
        mul_m4_v3(prev_obmat, prev_loc);
@@ -4947,7 +4858,7 @@ static int dynamicPaint_doStep(Scene *scene, Object *ob, DynamicPaintSurface *su
                                        /* update object data on this subframe */
                                        if (subframe) {
                                                scene_setSubframe(scene, subframe);
-                                               subframe_updateObject(scene, brushObj, UPDATE_EVERYTHING, SUBFRAME_RECURSION, BKE_scene_frame_get(scene));
+                                               BKE_object_modifier_update_subframe(scene, brushObj, true, SUBFRAME_RECURSION, BKE_scene_frame_get(scene), eModifierType_DynamicPaint);
                                        }
                                        /* Prepare materials if required        */
                                        if (brush_usesMaterial(brush, scene))
@@ -4981,7 +4892,7 @@ static int dynamicPaint_doStep(Scene *scene, Object *ob, DynamicPaintSurface *su
                                        if (subframe) {
                                                scene->r.cfra = scene_frame;
                                                scene->r.subframe = scene_subframe;
-                                               subframe_updateObject(scene, brushObj, UPDATE_EVERYTHING, SUBFRAME_RECURSION, BKE_scene_frame_get(scene));
+                                               BKE_object_modifier_update_subframe(scene, brushObj, true, SUBFRAME_RECURSION, BKE_scene_frame_get(scene), eModifierType_DynamicPaint);
                                        }
 
                                        /* process special brush effects, like smudge */
index b448bec84e3c0c36647241a355fa1d8931418390..ba7daee9f3f61a4dca03ef6b9c6980af93744220 100644 (file)
@@ -4099,3 +4099,105 @@ bool BKE_object_modifier_use_time(Object *ob, ModifierData *md)
 
        return false;
 }
+
+/* set "ignore cache" flag for all caches on this object */
+static void object_cacheIgnoreClear(Object *ob, int state)
+{
+       ListBase pidlist;
+       PTCacheID *pid;
+       BKE_ptcache_ids_from_object(&pidlist, ob, NULL, 0);
+
+       for (pid = pidlist.first; pid; pid = pid->next) {
+               if (pid->cache) {
+                       if (state)
+                               pid->cache->flag |= PTCACHE_IGNORE_CLEAR;
+                       else
+                               pid->cache->flag &= ~PTCACHE_IGNORE_CLEAR;
+               }
+       }
+
+       BLI_freelistN(&pidlist);
+}
+
+/* Note: this function should eventually be replaced by depsgraph functionality.
+ * Avoid calling this in new code unless there is a very good reason for it!
+ */
+bool BKE_object_modifier_update_subframe(Scene *scene, Object *ob, bool update_mesh,
+                                         int parent_recursion, float frame,
+                                         ModifierType type)
+{
+       ModifierData *md = modifiers_findByType(ob, type);
+       bConstraint *con;
+
+       if (type == eModifierType_DynamicPaint) {
+               DynamicPaintModifierData *pmd = (DynamicPaintModifierData *)md;
+
+               /* if other is dynamic paint canvas, don't update */
+               if (pmd && pmd->canvas)
+                       return true;
+       }
+       else if (type == eModifierType_Smoke) {
+               SmokeModifierData *smd = (SmokeModifierData *)md;
+
+               if (smd && (smd->type & MOD_SMOKE_TYPE_DOMAIN) != 0)
+                       return true;
+       }
+
+       /* if object has parents, update them too */
+       if (parent_recursion) {
+               int recursion = parent_recursion - 1;
+               bool no_update = false;
+               if (ob->parent) no_update |= BKE_object_modifier_update_subframe(scene, ob->parent, 0, recursion, frame, type);
+               if (ob->track) no_update |= BKE_object_modifier_update_subframe(scene, ob->track, 0, recursion, frame, type);
+
+               /* skip subframe if object is parented
+                *  to vertex of a dynamic paint canvas */
+               if (no_update && (ob->partype == PARVERT1 || ob->partype == PARVERT3))
+                       return false;
+
+               /* also update constraint targets */
+               for (con = ob->constraints.first; con; con = con->next) {
+                       const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con);
+                       ListBase targets = {NULL, NULL};
+
+                       if (cti && cti->get_constraint_targets) {
+                               bConstraintTarget *ct;
+                               cti->get_constraint_targets(con, &targets);
+                               for (ct = targets.first; ct; ct = ct->next) {
+                                       if (ct->tar)
+                                               BKE_object_modifier_update_subframe(scene, ct->tar, 0, recursion, frame, type);
+                               }
+                               /* free temp targets */
+                               if (cti->flush_constraint_targets)
+                                       cti->flush_constraint_targets(con, &targets, 0);
+                       }
+               }
+       }
+
+       /* was originally OB_RECALC_ALL - TODO - which flags are really needed??? */
+       ob->recalc |= OB_RECALC_OB | OB_RECALC_DATA | OB_RECALC_TIME;
+       BKE_animsys_evaluate_animdata(scene, &ob->id, ob->adt, frame, ADT_RECALC_ANIM);
+       if (update_mesh) {
+               /* ignore cache clear during subframe updates
+                *  to not mess up cache validity */
+               object_cacheIgnoreClear(ob, 1);
+               BKE_object_handle_update(G.main->eval_ctx, scene, ob);
+               object_cacheIgnoreClear(ob, 0);
+       }
+       else
+               BKE_object_where_is_calc_time(scene, ob, frame);
+
+       /* for curve following objects, parented curve has to be updated too */
+       if (ob->type == OB_CURVE) {
+               Curve *cu = ob->data;
+               BKE_animsys_evaluate_animdata(scene, &cu->id, cu->adt, frame, ADT_RECALC_ANIM);
+       }
+       /* and armatures... */
+       if (ob->type == OB_ARMATURE) {
+               bArmature *arm = ob->data;
+               BKE_animsys_evaluate_animdata(scene, &arm->id, arm->adt, frame, ADT_RECALC_ANIM);
+               BKE_pose_where_is(scene, ob);
+       }
+
+       return false;
+}
index dae4a6080312ed10cd4713d1af50a01309736519..c3e9d7d793cec5782aeddb6453b5cf98bd4c6107 100644 (file)
@@ -918,98 +918,6 @@ static void update_obstacles(Scene *scene, Object *ob, SmokeDomainSettings *sds,
        }
 }
 
-
-/**********************************************************
- *     Object subframe update method from dynamicpaint.c
- **********************************************************/
-
-/* set "ignore cache" flag for all caches on this object */
-static void object_cacheIgnoreClear(Object *ob, bool state)
-{
-       ListBase pidlist;
-       PTCacheID *pid;
-       BKE_ptcache_ids_from_object(&pidlist, ob, NULL, 0);
-
-       for (pid = pidlist.first; pid; pid = pid->next) {
-               if (pid->cache) {
-                       if (state)
-                               pid->cache->flag |= PTCACHE_IGNORE_CLEAR;
-                       else
-                               pid->cache->flag &= ~PTCACHE_IGNORE_CLEAR;
-               }
-       }
-
-       BLI_freelistN(&pidlist);
-}
-
-static bool subframe_updateObject(Scene *scene, Object *ob, int update_mesh, int parent_recursion, float frame, bool for_render)
-{
-       SmokeModifierData *smd = (SmokeModifierData *)modifiers_findByType(ob, eModifierType_Smoke);
-       bConstraint *con;
-
-       /* if other is dynamic paint canvas, don't update */
-       if (smd && (smd->type & MOD_SMOKE_TYPE_DOMAIN))
-               return true;
-
-       /* if object has parents, update them too */
-       if (parent_recursion) {
-               int recursion = parent_recursion - 1;
-               bool is_domain = false;
-               if (ob->parent) is_domain |= subframe_updateObject(scene, ob->parent, 0, recursion, frame, for_render);
-               if (ob->track) is_domain |= subframe_updateObject(scene, ob->track, 0, recursion, frame, for_render);
-
-               /* skip subframe if object is parented
-                *  to vertex of a dynamic paint canvas */
-               if (is_domain && (ob->partype == PARVERT1 || ob->partype == PARVERT3))
-                       return false;
-
-               /* also update constraint targets */
-               for (con = ob->constraints.first; con; con = con->next) {
-                       const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con);
-                       ListBase targets = {NULL, NULL};
-
-                       if (cti && cti->get_constraint_targets) {
-                               bConstraintTarget *ct;
-                               cti->get_constraint_targets(con, &targets);
-                               for (ct = targets.first; ct; ct = ct->next) {
-                                       if (ct->tar)
-                                               subframe_updateObject(scene, ct->tar, 0, recursion, frame, for_render);
-                               }
-                               /* free temp targets */
-                               if (cti->flush_constraint_targets)
-                                       cti->flush_constraint_targets(con, &targets, 0);
-                       }
-               }
-       }
-
-       /* was originally OB_RECALC_ALL - TODO - which flags are really needed??? */
-       ob->recalc |= OB_RECALC_OB | OB_RECALC_DATA | OB_RECALC_TIME;
-       BKE_animsys_evaluate_animdata(scene, &ob->id, ob->adt, frame, ADT_RECALC_ANIM);
-       if (update_mesh) {
-               /* ignore cache clear during subframe updates
-                *  to not mess up cache validity */
-               object_cacheIgnoreClear(ob, 1);
-               BKE_object_handle_update(G.main->eval_ctx, scene, ob);
-               object_cacheIgnoreClear(ob, 0);
-       }
-       else
-               BKE_object_where_is_calc_time(scene, ob, frame);
-
-       /* for curve following objects, parented curve has to be updated too */
-       if (ob->type == OB_CURVE) {
-               Curve *cu = ob->data;
-               BKE_animsys_evaluate_animdata(scene, &cu->id, cu->adt, frame, ADT_RECALC_ANIM);
-       }
-       /* and armatures... */
-       if (ob->type == OB_ARMATURE) {
-               bArmature *arm = ob->data;
-               BKE_animsys_evaluate_animdata(scene, &arm->id, arm->adt, frame, ADT_RECALC_ANIM);
-               BKE_pose_where_is(scene, ob);
-       }
-
-       return false;
-}
-
 /**********************************************************
  *     Flow emission code
  **********************************************************/
@@ -2111,7 +2019,7 @@ BLI_INLINE void apply_inflow_fields(SmokeFlowSettings *sfs, float emission_value
        }
 }
 
-static void update_flowsfluids(Scene *scene, Object *ob, SmokeDomainSettings *sds, float dt, bool for_render)
+static void update_flowsfluids(Scene *scene, Object *ob, SmokeDomainSettings *sds, float dt)
 {
        Object **flowobjs = NULL;
        EmissionMap *emaps = NULL;
@@ -2218,7 +2126,7 @@ static void update_flowsfluids(Scene *scene, Object *ob, SmokeDomainSettings *sd
                                        else { /* MOD_SMOKE_FLOW_SOURCE_MESH */
                                                /* update flow object frame */
                                                BLI_mutex_lock(&object_update_lock);
-                                               subframe_updateObject(scene, collob, 1, 5, BKE_scene_frame_get(scene), for_render);
+                                               BKE_object_modifier_update_subframe(scene, collob, true, 5, BKE_scene_frame_get(scene), eModifierType_Smoke);
                                                BLI_mutex_unlock(&object_update_lock);
 
                                                /* apply flow */
@@ -2554,7 +2462,7 @@ static void update_effectors(Scene *scene, Object *ob, SmokeDomainSettings *sds,
        pdEndEffectors(&effectors);
 }
 
-static void step(Scene *scene, Object *ob, SmokeModifierData *smd, DerivedMesh *domain_dm, float fps, bool for_render)
+static void step(Scene *scene, Object *ob, SmokeModifierData *smd, DerivedMesh *domain_dm, float fps)
 {
        SmokeDomainSettings *sds = smd->domain;
        /* stability values copied from wturbulence.cpp */
@@ -2624,7 +2532,7 @@ static void step(Scene *scene, Object *ob, SmokeModifierData *smd, DerivedMesh *
        for (substep = 0; substep < totalSubsteps; substep++)
        {
                // calc animated obstacle velocities
-               update_flowsfluids(scene, ob, sds, dtSubdiv, for_render);
+               update_flowsfluids(scene, ob, sds, dtSubdiv);
                update_obstacles(scene, ob, sds, dtSubdiv, substep, totalSubsteps);
 
                if (sds->total_cells > 1) {
@@ -2721,7 +2629,7 @@ static DerivedMesh *createDomainGeometry(SmokeDomainSettings *sds, Object *ob)
        return result;
 }
 
-static void smokeModifier_process(SmokeModifierData *smd, Scene *scene, Object *ob, DerivedMesh *dm, bool for_render)
+static void smokeModifier_process(SmokeModifierData *smd, Scene *scene, Object *ob, DerivedMesh *dm)
 {
        if ((smd->type & MOD_SMOKE_TYPE_FLOW))
        {
@@ -2845,7 +2753,7 @@ static void smokeModifier_process(SmokeModifierData *smd, Scene *scene, Object *
 
                        }
 
-                       step(scene, ob, smd, dm, scene->r.frs_sec / scene->r.frs_sec_base, for_render);
+                       step(scene, ob, smd, dm, scene->r.frs_sec / scene->r.frs_sec_base);
                }
 
                // create shadows before writing cache so they get stored
@@ -2867,13 +2775,13 @@ static void smokeModifier_process(SmokeModifierData *smd, Scene *scene, Object *
        }
 }
 
-struct DerivedMesh *smokeModifier_do(SmokeModifierData *smd, Scene *scene, Object *ob, DerivedMesh *dm, bool for_render)
+struct DerivedMesh *smokeModifier_do(SmokeModifierData *smd, Scene *scene, Object *ob, DerivedMesh *dm)
 {
        /* lock so preview render does not read smoke data while it gets modified */
        if ((smd->type & MOD_SMOKE_TYPE_DOMAIN) && smd->domain)
                BLI_rw_mutex_lock(smd->domain->fluid_mutex, THREAD_LOCK_WRITE);
 
-       smokeModifier_process(smd, scene, ob, dm, for_render);
+       smokeModifier_process(smd, scene, ob, dm);
 
        if ((smd->type & MOD_SMOKE_TYPE_DOMAIN) && smd->domain)
                BLI_rw_mutex_unlock(smd->domain->fluid_mutex);
index 3dcb03da52ce0bbcc9afad293bd4dfc445be11a5..aea688858de813ad356f45205cc73cab81cc5154 100644 (file)
@@ -105,12 +105,11 @@ static DerivedMesh *applyModifier(ModifierData *md, Object *ob,
                                   ModifierApplyFlag flag)
 {
        SmokeModifierData *smd = (SmokeModifierData *) md;
-       bool for_render = (flag & MOD_APPLY_RENDER) != 0;
 
        if (flag & MOD_APPLY_ORCO)
                return dm;
 
-       return smokeModifier_do(smd, md->scene, ob, dm, for_render);
+       return smokeModifier_do(smd, md->scene, ob, dm);
 }
 
 static bool dependsOnTime(ModifierData *UNUSED(md))