diff --git a/release/scripts/startup/bl_operators/object.py b/release/scripts/startup/bl_operators/object.py index e5e53d6a4dc..3218af06cc7 100644 --- a/release/scripts/startup/bl_operators/object.py +++ b/release/scripts/startup/bl_operators/object.py @@ -775,3 +775,154 @@ class DupliOffsetFromCursor(Operator): ob.users_group[group].dupli_offset = scene.cursor_location return {'FINISHED'} + + +class LodByName(Operator): + """Add levels of detail to this object based on object names""" + bl_idname = "object.lod_by_name" + bl_label = "Setup Levels of Detail By Name" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return (context.active_object is not None) + + def execute(self, context): + scene = context.scene + ob = context.active_object + + prefix = "" + suffix = "" + name = "" + if ob.name.lower().startswith("lod0"): + prefix = ob.name[:4] + name = ob.name[4:] + elif ob.name.lower().endswith("lod0"): + name = ob.name[:-4] + suffix = ob.name[-4:] + else: + return {'CANCELLED'} + + level = 0 + while True: + level += 1 + + if prefix: + prefix = prefix[:3] + str(level) + if suffix: + suffix = suffix[:3] + str(level) + + lod = None + try: + lod = bpy.data.objects[prefix + name + suffix] + except KeyError: + break + + try: + ob.lod_levels[level] + except IndexError: + bpy.ops.object.lod_add() + + ob.lod_levels[level].object = lod + + return {'FINISHED'} + + +class LodClearAll(Operator): + """Remove all levels of detail from this object""" + bl_idname = "object.lod_clear_all" + bl_label = "Clear All Levels of Detail" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return (context.active_object is not None) + + def execute(self, context): + scene = context.scene + ob = context.active_object + + if ob.lod_levels: + while 'CANCELLED' not in bpy.ops.object.lod_remove(): + pass + + return {'FINISHED'} + + +class LodGenerate(Operator): + """Generates levels of detail using the decimate modifier""" + bl_idname = "object.lod_generate" + bl_label = "Generate Levels of Detail" + bl_options = {'REGISTER', 'UNDO'} + + count = bpy.props.IntProperty(name="Count", default=3) + target = bpy.props.FloatProperty(name="Target Size", default=0.1, + min=0.0, max=1.0) + package = bpy.props.BoolProperty(name="Package into Group", default=False) + + @classmethod + def poll(cls, context): + return (context.active_object is not None) + + def execute(self, context): + scene = bpy.context.scene + ob = scene.objects.active + + lod_name = ob.name + lod_suffix = "lod" + lod_prefix = "" + if lod_name.lower().endswith("lod0"): + lod_suffix = lod_name[-3:-1] + lod_name = lod_name[:-3] + elif lod_name.lower().startswith("lod0"): + lod_suffix = "" + lod_prefix = lod_name[:3] + lod_name = lod_name[4:] + + group_name = lod_name.strip(' ._') + if self.package: + try: + bpy.ops.object.group_link(group=group_name) + except TypeError: + bpy.ops.group.create(name=group_name) + + step = (1.0 - self.target) / (self.count - 1) + for i in range(1, self.count): + scene.objects.active = ob + bpy.ops.object.duplicate() + lod = bpy.context.selected_objects[0] + + scene.objects.active = ob + bpy.ops.object.lod_add() + scene.objects.active = lod + + if lod_prefix: + lod.name = lod_prefix + str(i) + lod_name + else: + lod.name = lod_name + lod_suffix + str(i) + + lod.location.y = ob.location.y + 3.0 * i + + if i == 1: + modifier = lod.modifiers.new("lod_decimate", "DECIMATE") + else: + modifier = lod.modifiers[-1] + + modifier.ratio = 1.0 - step*(i) + + ob.lod_levels[i].object = lod + + if self.package: + bpy.ops.object.group_link(group=group_name) + lod.parent = ob + + if self.package: + for level in ob.lod_levels[1:]: + level.object.hide = level.object.hide_render = True + + lod.select = False + ob.select = True + scene.objects.active = ob + + return {'FINISHED'} + diff --git a/release/scripts/startup/bl_ui/properties_object.py b/release/scripts/startup/bl_ui/properties_object.py index cbebdafbf2e..dcd32b17cbd 100644 --- a/release/scripts/startup/bl_ui/properties_object.py +++ b/release/scripts/startup/bl_ui/properties_object.py @@ -18,7 +18,7 @@ # import bpy -from bpy.types import Panel +from bpy.types import Menu, Panel from rna_prop_ui import PropertyPanel @@ -125,6 +125,49 @@ class OBJECT_PT_transform_locks(ObjectButtonsPanel, Panel): sub.prop(ob, "lock_rotation_w", text="W") +class OBJECT_MT_lod_tools(Menu): + bl_label = "Level Of Detail Tools" + + def draw(self, context): + layout = self.layout + + layout.operator("object.lod_by_name", text="Set By Name") + layout.operator("object.lod_generate", text="Generate") + layout.operator("object.lod_clear_all", text="Clear All", icon='PANEL_CLOSE') + + +class OBJECT_PT_levels_of_detail(ObjectButtonsPanel, Panel): + bl_label = "Levels of Detail" + COMPAT_ENGINES = {'BLENDER_GAME'} + + @classmethod + def poll(cls, context): + return context.scene.render.engine in cls.COMPAT_ENGINES + + def draw(self, context): + layout = self.layout + ob = context.object + + col = layout.column() + + for i, level in enumerate(ob.lod_levels): + if i == 0: continue + box = col.box() + row = box.row() + row.prop(level, "object", text="") + row.operator("object.lod_remove", text="", icon='PANEL_CLOSE').index = i + + row = box.row() + row.prop(level, "distance") + row = row.row(align=True) + row.prop(level, "use_mesh", text="") + row.prop(level, "use_material", text="") + + row = col.row(align=True) + row.operator("object.lod_add", text="Add", icon='ZOOMIN') + row.menu("OBJECT_MT_lod_tools", text="", icon='TRIA_DOWN') + + class OBJECT_PT_relations(ObjectButtonsPanel, Panel): bl_label = "Relations" diff --git a/source/blender/blenkernel/BKE_object.h b/source/blender/blenkernel/BKE_object.h index 434175624b7..e6e6d621ef3 100644 --- a/source/blender/blenkernel/BKE_object.h +++ b/source/blender/blenkernel/BKE_object.h @@ -87,6 +87,14 @@ struct Object *BKE_object_add_only_object(struct Main *bmain, int type, const ch struct Object *BKE_object_add(struct Main *bmain, struct Scene *scene, int type); void *BKE_object_obdata_add_from_type(struct Main *bmain, int type); +void BKE_object_lod_add(struct Object *ob); +void BKE_object_lod_sort(struct Object *ob); +bool BKE_object_lod_remove(struct Object *ob, int level); +bool BKE_object_lod_update(struct Object *ob, float camera_position[3]); +bool BKE_object_lod_is_usable(struct Object *ob, struct Scene *scene); +struct Object *BKE_object_lod_meshob_get(struct Object *ob, struct Scene *scene); +struct Object *BKE_object_lod_matob_get(struct Object *ob, struct Scene *scene); + struct Object *BKE_object_copy_ex(struct Main *bmain, struct Object *ob, int copy_caches); struct Object *BKE_object_copy(struct Object *ob); void BKE_object_make_local(struct Object *ob); diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index cf9b838a7c2..9df523a5bfe 100644 --- a/source/blender/blenkernel/intern/material.c +++ b/source/blender/blenkernel/intern/material.c @@ -675,7 +675,7 @@ Material *give_current_material(Object *ob, short act) { Material ***matarar, *ma; short *totcolp; - + if (ob == NULL) return NULL; /* if object cannot have material, (totcolp == NULL) */ diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c index 0694bde7b91..70b5fb9d420 100644 --- a/source/blender/blenkernel/intern/object.c +++ b/source/blender/blenkernel/intern/object.c @@ -388,6 +388,8 @@ void BKE_object_free(Object *ob) if (ob->pc_ids.first) BLI_freelistN(&ob->pc_ids); + BLI_freelistN(&ob->lodlevels); + /* Free runtime curves data. */ if (ob->curve_cache) { BLI_freelistN(&ob->curve_cache->bev); @@ -427,6 +429,7 @@ void BKE_object_unlink(Object *ob) ModifierData *md; ARegion *ar; RegionView3D *rv3d; + LodLevel *lod; int a, found; unlink_controllers(&ob->controllers); @@ -608,6 +611,12 @@ void BKE_object_unlink(Object *ob) DAG_id_tag_update(&obt->id, OB_RECALC_DATA); } + /* levels of detail */ + for (lod = obt->lodlevels.first; lod; lod = lod->next) { + if (lod->source == ob) + lod->source = NULL; + } + obt = obt->id.next; } @@ -1015,6 +1024,138 @@ Object *BKE_object_add(Main *bmain, Scene *scene, int type) return ob; } +void BKE_object_lod_add(Object *ob) +{ + LodLevel *lod = MEM_callocN(sizeof(LodLevel), "LoD Level"); + LodLevel *last = ob->lodlevels.last; + + /* If the lod list is empty, initialize it with the base lod level */ + if (!last) { + LodLevel *base = MEM_callocN(sizeof(LodLevel), "Base LoD Level"); + BLI_addtail(&ob->lodlevels, base); + base->flags = OB_LOD_USE_MESH | OB_LOD_USE_MAT; + base->source = ob; + last = ob->currentlod = base; + } + + lod->distance = last->distance + 25.0f; + lod->flags = OB_LOD_USE_MESH | OB_LOD_USE_MAT; + + BLI_addtail(&ob->lodlevels, lod); +} + +static int lod_cmp(void *a, void *b) +{ + LodLevel *loda = (LodLevel*)a; + LodLevel *lodb = (LodLevel*)b; + + if (loda->distance < lodb->distance) return -1; + return loda->distance > lodb->distance; +} + +void BKE_object_lod_sort(Object *ob) +{ + BLI_sortlist(&ob->lodlevels, lod_cmp); +} + +bool BKE_object_lod_remove(Object *ob, int level) +{ + LodLevel *rem; + + if (level < 1 || level > BLI_countlist(&ob->lodlevels) - 1) + return false; + + rem = BLI_findlink(&ob->lodlevels, level); + + if (rem == ob->currentlod) { + ob->currentlod = rem->prev; + } + + BLI_remlink(&ob->lodlevels, rem); + MEM_freeN(rem); + + /* If there are no user defined lods, remove the base lod as well */ + if (BLI_countlist(&ob->lodlevels) == 1) { + LodLevel *base = ob->lodlevels.first; + BLI_remlink(&ob->lodlevels, base); + MEM_freeN(base); + ob->currentlod = NULL; + } + + return true; +} + +static LodLevel* lod_level_select(Object *ob, float cam_loc[3]) +{ + LodLevel *current = ob->currentlod; + float ob_loc[3], delta[3]; + float distance2; + + if (!current) return NULL; + + copy_v3_v3(ob_loc, ob->obmat[3]); + sub_v3_v3v3(delta, ob_loc, cam_loc); + distance2 = len_squared_v3(delta); + + /* check for higher LoD */ + if (distance2 < current->distance*current->distance) { + while (current->prev && distance2 < current->distance*current->distance) { + current = current->prev; + } + } + /* check for lower LoD */ + else { + while (current->next && distance2 > current->next->distance*current->next->distance) { + current = current->next; + } + } + + return current; +} + +bool BKE_object_lod_is_usable(Object *ob, Scene *scene) +{ + bool active = (scene) ? ob == OBACT : 0; + return (ob->mode == OB_MODE_OBJECT || !active); +} + +bool BKE_object_lod_update(Object *ob, float camera_position[3]) +{ + LodLevel* cur_level = ob->currentlod; + LodLevel* new_level = lod_level_select(ob, camera_position); + + if (new_level != cur_level) { + ob->currentlod = new_level; + return true; + } + + return false; +} + +static Object *lod_ob_get(Object *ob, Scene *scene, int flag) +{ + LodLevel *current = ob->currentlod; + + if (!current || !BKE_object_lod_is_usable(ob, scene)) + return ob; + + while( current->prev && (!(current->flags & flag) || !current->source || current->source->type != OB_MESH)) { + current = current->prev; + } + + return current->source; +} + +struct Object *BKE_object_lod_meshob_get(Object *ob, Scene *scene) +{ + return lod_ob_get(ob, scene, OB_LOD_USE_MESH); +} + +struct Object *BKE_object_lod_matob_get(Object *ob, Scene *scene) +{ + return lod_ob_get(ob, scene, OB_LOD_USE_MAT); +} + SoftBody *copy_softbody(SoftBody *sb, int copy_caches) { SoftBody *sbn; @@ -1226,6 +1367,16 @@ static void copy_object_pose(Object *obn, Object *ob) } } +static void copy_object_lod(Object *obn, Object *ob) +{ + BLI_duplicatelist(&obn->lodlevels, &ob->lodlevels); + + if (obn->lodlevels.first) + ((LodLevel*)obn->lodlevels.first)->source = obn; + + obn->currentlod = (LodLevel*) obn->lodlevels.first; +} + bool BKE_object_pose_context_check(Object *ob) { if ((ob) && @@ -1346,6 +1497,9 @@ Object *BKE_object_copy_ex(Main *bmain, Object *ob, int copy_caches) obn->mpath = NULL; + copy_object_lod(obn, ob); + + /* Copy runtime surve data. */ obn->curve_cache = NULL; diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index ac34f81907a..f3244a63a8b 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -4492,6 +4492,16 @@ static void lib_link_object(FileData *fd, Main *main) ob->rigidbody_constraint->ob1 = newlibadr(fd, ob->id.lib, ob->rigidbody_constraint->ob1); ob->rigidbody_constraint->ob2 = newlibadr(fd, ob->id.lib, ob->rigidbody_constraint->ob2); } + + { + LodLevel *level; + for (level = ob->lodlevels.first; level; level = level->next) { + level->source = newlibadr(fd, ob->id.lib, level->source); + + if (!level->source && level == ob->lodlevels.first) + level->source = ob; + } + } } } @@ -5026,6 +5036,9 @@ static void direct_link_object(FileData *fd, Object *ob) if (ob->sculpt) { ob->sculpt = MEM_callocN(sizeof(SculptSession), "reload sculpt session"); } + + link_list(fd, &ob->lodlevels); + ob->currentlod = ob->lodlevels.first; } /* ************ READ SCENE ***************** */ @@ -8396,6 +8409,12 @@ static void expand_object(FileData *fd, Main *mainvar, Object *ob) expand_doit(fd, mainvar, ob->rigidbody_constraint->ob2); } + if (ob->currentlod) { + LodLevel *level; + for (level = ob->lodlevels.first; level; level = level->next) { + expand_doit(fd, mainvar, level->source); + } + } } static void expand_scene(FileData *fd, Main *mainvar, Scene *sce) diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c index be81f355e26..fd8c711fb1a 100644 --- a/source/blender/blenloader/intern/writefile.c +++ b/source/blender/blenloader/intern/writefile.c @@ -1537,6 +1537,8 @@ static void write_objects(WriteData *wd, ListBase *idbase) write_particlesystems(wd, &ob->particlesystem); write_modifiers(wd, &ob->modifiers); + + writelist(wd, DATA, "LodLevel", &ob->lodlevels); } ob= ob->id.next; } diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt index 143a33c3fdd..ecc967d5662 100644 --- a/source/blender/editors/object/CMakeLists.txt +++ b/source/blender/editors/object/CMakeLists.txt @@ -47,6 +47,7 @@ set(SRC object_group.c object_hook.c object_lattice.c + object_lod.c object_modifier.c object_ops.c object_relations.c diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index 536f3f05ab2..8b0ddbbeb9b 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -255,5 +255,9 @@ void OBJECT_OT_group_remove(struct wmOperatorType *ot); /* object_bake.c */ void OBJECT_OT_bake_image(wmOperatorType *ot); +/* object_lod.c */ +void OBJECT_OT_lod_add(struct wmOperatorType *ot); +void OBJECT_OT_lod_remove(struct wmOperatorType *ot); + #endif /* __OBJECT_INTERN_H__ */ diff --git a/source/blender/editors/object/object_lod.c b/source/blender/editors/object/object_lod.c new file mode 100644 index 00000000000..952451ee21d --- /dev/null +++ b/source/blender/editors/object/object_lod.c @@ -0,0 +1,102 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) Blender Foundation + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): none yet. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/editors/object/object_lod.c + * \ingroup edobj + */ + + +#include "DNA_object_types.h" + +#include "BKE_context.h" +#include "BKE_main.h" +#include "BKE_object.h" + +#include "ED_screen.h" +#include "ED_object.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "object_intern.h" + +static int object_lod_add_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + BKE_object_lod_add(ob); + return OPERATOR_FINISHED; +} + +void OBJECT_OT_lod_add(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Add Level of Detail"; + ot->description = "Add a level of detail to this object"; + ot->idname = "OBJECT_OT_lod_add"; + + /* api callbacks */ + ot->exec = object_lod_add_exec; + ot->poll = ED_operator_objectmode; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +static int object_lod_remove_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + int index = RNA_int_get(op->ptr, "index"); + if(!BKE_object_lod_remove(ob, index)) + return OPERATOR_CANCELLED; + + WM_event_add_notifier(C, NC_OBJECT|ND_LOD, CTX_wm_view3d(C)); + return OPERATOR_FINISHED; +} + +void OBJECT_OT_lod_remove(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Remove Level of Detail"; + ot->description = "Remove a level of detail from this object"; + ot->idname = "OBJECT_OT_lod_remove"; + + /* api callbacks */ + ot->exec = object_lod_remove_exec; + ot->poll = ED_operator_objectmode; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_int(ot->srna, "index", 1, 1, INT_MAX, "Index", "", 1, INT_MAX); +} \ No newline at end of file diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c index efebbe8ddd7..a06a6d9781f 100644 --- a/source/blender/editors/object/object_ops.c +++ b/source/blender/editors/object/object_ops.c @@ -243,6 +243,9 @@ void ED_operatortypes_object(void) WM_operatortype_append(OBJECT_OT_bake_image); WM_operatortype_append(OBJECT_OT_drop_named_material); WM_operatortype_append(OBJECT_OT_laplaciandeform_bind); + + WM_operatortype_append(OBJECT_OT_lod_add); + WM_operatortype_append(OBJECT_OT_lod_remove); } void ED_operatormacros_object(void) diff --git a/source/blender/editors/space_view3d/drawobject.c b/source/blender/editors/space_view3d/drawobject.c index 225c58207e7..1e48be2ffab 100644 --- a/source/blender/editors/space_view3d/drawobject.c +++ b/source/blender/editors/space_view3d/drawobject.c @@ -3359,7 +3359,7 @@ static void draw_mesh_object_outline(View3D *v3d, Object *ob, DerivedMesh *dm) static void draw_mesh_fancy(Scene *scene, ARegion *ar, View3D *v3d, RegionView3D *rv3d, Base *base, const char dt, const unsigned char ob_wire_col[4], const short dflag) { - Object *ob = base->object; + Object *ob = BKE_object_lod_meshob_get(base->object, scene); Mesh *me = ob->data; Material *ma = give_current_material(ob, 1); const short hasHaloMat = (ma && (ma->material_type == MA_TYPE_HALO) && !BKE_scene_use_new_shading_nodes(scene)); diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index 03b89972e55..9125d439432 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -809,6 +809,7 @@ static void view3d_main_area_listener(bScreen *sc, ScrArea *sa, ARegion *ar, wmN case ND_CONSTRAINT: case ND_KEYS: case ND_PARTICLE: + case ND_LOD: ED_region_tag_redraw(ar); break; } diff --git a/source/blender/editors/space_view3d/view3d_draw.c b/source/blender/editors/space_view3d/view3d_draw.c index 9dd6ab8f841..701fb26a562 100644 --- a/source/blender/editors/space_view3d/view3d_draw.c +++ b/source/blender/editors/space_view3d/view3d_draw.c @@ -1956,6 +1956,7 @@ static void draw_dupli_objects_color(Scene *scene, ARegion *ar, View3D *v3d, Bas { RegionView3D *rv3d = ar->regiondata; ListBase *lb; + LodLevel *savedlod; DupliObject *dob_prev = NULL, *dob, *dob_next = NULL; Base tbase = {NULL}; BoundBox bb, *bb_tmp; /* use a copy because draw_object, calls clear_mesh_caches */ @@ -1976,6 +1977,13 @@ static void draw_dupli_objects_color(Scene *scene, ARegion *ar, View3D *v3d, Bas for (; dob; dob_prev = dob, dob = dob_next, dob_next = dob_next ? dupli_step(dob_next->next) : NULL) { tbase.object = dob->ob; + /* Make sure lod is updated from dupli's position */ + + copy_m4_m4(dob->ob->obmat, dob->mat); + savedlod = dob->ob->currentlod; + BKE_object_lod_update(dob->ob, rv3d->viewinv[3]); + + /* extra service: draw the duplicator in drawtype of parent, minimum taken * to allow e.g. boundbox box objects in groups for LOD */ dt = tbase.object->dt; @@ -2051,13 +2059,13 @@ static void draw_dupli_objects_color(Scene *scene, ARegion *ar, View3D *v3d, Bas glLoadMatrixf(rv3d->viewmat); } else { - copy_m4_m4(dob->ob->obmat, dob->mat); draw_object(scene, ar, v3d, &tbase, DRAW_CONSTCOLOR); } tbase.object->dt = dt; tbase.object->dtx = dtx; tbase.object->transflag = transflag; + tbase.object->currentlod = savedlod; } /* Transp afterdraw disabled, afterdraw only stores base pointers, and duplis can be same obj */ @@ -3183,6 +3191,18 @@ static void view3d_main_area_clear(Scene *scene, View3D *v3d, ARegion *ar) } } +static void update_lods(Scene *scene, float camera_pos[3]) +{ + Scene *sce_iter; + Base *base; + Object *ob; + + for (SETLOOPER(scene, sce_iter, base)) { + ob = base->object; + BKE_object_lod_update(ob, camera_pos); + } +} + /* warning: this function has duplicate drawing in ED_view3d_draw_offscreen() */ static void view3d_main_area_draw_objects(const bContext *C, ARegion *ar, const char **grid_unit) { @@ -3205,6 +3225,9 @@ static void view3d_main_area_draw_objects(const bContext *C, ARegion *ar, const /* setup view matrices */ view3d_main_area_setup_view(scene, v3d, ar, NULL, NULL); + /* Make sure LoDs are up to date */ + update_lods(scene, rv3d->viewinv[3]); + /* clear the background */ view3d_main_area_clear(scene, v3d, ar); diff --git a/source/blender/gpu/intern/gpu_draw.c b/source/blender/gpu/intern/gpu_draw.c index ee59cf418bf..1f0e20a9210 100644 --- a/source/blender/gpu/intern/gpu_draw.c +++ b/source/blender/gpu/intern/gpu_draw.c @@ -1392,6 +1392,8 @@ void GPU_begin_object_materials(View3D *v3d, RegionView3D *rv3d, Scene *scene, O int gamma = BKE_scene_check_color_management_enabled(scene); int new_shading_nodes = BKE_scene_use_new_shading_nodes(scene); int use_matcap = (v3d->flag2 & V3D_SHOW_SOLID_MATCAP); /* assumes v3d->defmaterial->preview is set */ + + ob = BKE_object_lod_matob_get(ob, scene); /* initialize state */ memset(&GMS, 0, sizeof(GMS)); diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index 9d07e046096..bff3ef75a5a 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -105,6 +105,13 @@ enum { BOUNDBOX_DIRTY = (1 << 1), }; +typedef struct LodLevel { + struct LodLevel *next, *prev; + struct Object *source; + int flags; + float distance; +} LodLevel; + typedef struct Object { ID id; struct AnimData *adt; /* animation data (must be immediately after id for utilities to use it) */ @@ -279,6 +286,9 @@ typedef struct Object { float ima_ofs[2]; /* offset for image empties */ + ListBase lodlevels; /* contains data for levels of detail */ + LodLevel *currentlod; + /* Runtime valuated curve-specific data, not stored in the file */ struct CurveCache *curve_cache; } Object; @@ -470,6 +480,12 @@ enum { OB_BOUND_CAPSULE = 7, }; +/* lod flags */ +enum { + OB_LOD_USE_MESH = 1 << 0, + OB_LOD_USE_MAT = 1 << 1, +}; + /* **************** BASE ********************* */ diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index 9c1d0c4cc2b..40bea3a6051 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -40,6 +40,7 @@ #include "DNA_meta_types.h" #include "BLI_utildefines.h" +#include "BLI_listbase.h" #include "BKE_paint.h" #include "BKE_editmesh.h" @@ -1471,6 +1472,11 @@ int rna_Object_use_dynamic_topology_sculpting_get(PointerRNA *ptr) return (ss && ss->bm); } +static void rna_Object_lod_distance_update(Main *bmain, Scene *scene, PointerRNA *ptr) +{ + Object *ob = (Object *)ptr->id.data; + BKE_object_lod_sort(ob); +} #else static void rna_def_vertex_group(BlenderRNA *brna) @@ -2030,6 +2036,41 @@ static void rna_def_object_vertex_groups(BlenderRNA *brna, PropertyRNA *cprop) } +static void rna_def_object_lodlevel(BlenderRNA* brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "LodLevel", NULL); + RNA_def_struct_sdna(srna, "LodLevel"); + + prop = RNA_def_property(srna, "distance", PROP_FLOAT, PROP_DISTANCE); + RNA_def_property_float_sdna(prop, NULL, "distance"); + RNA_def_property_range(prop, 0.0, FLT_MAX); + RNA_def_property_ui_text(prop, "Distance", "Distance to begin using this level of detail"); + RNA_def_property_update(prop, NC_OBJECT|ND_LOD, "rna_Object_lod_distance_update"); + + prop = RNA_def_property(srna, "object", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "source"); + RNA_def_property_struct_type(prop, "Object"); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Object", "Object to use for this level of detail"); + RNA_def_property_update(prop, NC_OBJECT|ND_LOD, NULL); + + prop = RNA_def_property(srna, "use_mesh", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flags", OB_LOD_USE_MESH); + RNA_def_property_ui_text(prop, "Use Mesh", "Use the mesh from this object at this level of detail"); + RNA_def_property_ui_icon(prop, ICON_MESH_DATA, 0); + RNA_def_property_update(prop, NC_OBJECT|ND_LOD, NULL); + + prop = RNA_def_property(srna, "use_material", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flags", OB_LOD_USE_MAT); + RNA_def_property_ui_text(prop, "Use Material", "Use the material from this object at this level of detail"); + RNA_def_property_ui_icon(prop, ICON_MATERIAL, 0); + RNA_def_property_update(prop, NC_OBJECT|ND_LOD, NULL); +} + + static void rna_def_object(BlenderRNA *brna) { StructRNA *srna; @@ -2690,6 +2731,13 @@ static void rna_def_object(BlenderRNA *brna) RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_ui_text(prop, "Dynamic Topology Sculpting", NULL); + /* Levels of Detail */ + prop = RNA_def_property(srna, "lod_levels", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, NULL, "lodlevels", NULL); + RNA_def_property_struct_type(prop, "LodLevel"); + RNA_def_property_ui_text(prop, "Level of Detail Levels", "A collection of detail levels to automatically switch between"); + RNA_def_property_update(prop, NC_OBJECT|ND_LOD, NULL); + RNA_api_object(srna); } @@ -2800,6 +2848,7 @@ void RNA_def_object(BlenderRNA *brna) rna_def_material_slot(brna); rna_def_dupli_object(brna); RNA_define_animate_sdna(true); + rna_def_object_lodlevel(brna); } #endif diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index dabb90b2ba0..83c17170f9e 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -297,6 +297,7 @@ typedef struct wmNotifier { #define ND_PARTICLE (27<<16) #define ND_POINTCACHE (28<<16) #define ND_PARENT (29<<16) +#define ND_LOD (30<<16) /* NC_MATERIAL Material */ #define ND_SHADING (30<<16) diff --git a/source/gameengine/Converter/BL_BlenderDataConversion.cpp b/source/gameengine/Converter/BL_BlenderDataConversion.cpp index eeaffd9005a..9998f10e417 100644 --- a/source/gameengine/Converter/BL_BlenderDataConversion.cpp +++ b/source/gameengine/Converter/BL_BlenderDataConversion.cpp @@ -93,6 +93,7 @@ #include "KX_SoftBodyDeformer.h" //#include "BL_ArmatureController.h" #include "BLI_utildefines.h" +#include "BLI_listbase.h" #include "BlenderWorldInfo.h" #include "KX_KetsjiEngine.h" @@ -938,8 +939,15 @@ RAS_MeshObject* BL_ConvertMesh(Mesh* mesh, Object* blenderobj, KX_Scene* scene, RAS_MeshObject *meshobj; int lightlayer = blenderobj ? blenderobj->lay:(1<<20)-1; // all layers if no object. - if ((meshobj = converter->FindGameMesh(mesh/*, ob->lay*/)) != NULL) - return meshobj; + // Without checking names, we get some reuse we don't want that can cause + // problems with material LoDs. + if ((meshobj = converter->FindGameMesh(mesh/*, ob->lay*/)) != NULL) { + STR_String bge_name = meshobj->GetName(); + STR_String blender_name = ((Mesh*)blenderobj->data)->id.name+2; + if (bge_name == blender_name) + return meshobj; + } + // Get DerivedMesh data DerivedMesh *dm = CDDM_from_mesh(mesh, blenderobj); DM_ensure_tessface(dm); @@ -1852,6 +1860,24 @@ static KX_GameObject *gameobject_from_blenderobject( // set transformation gameobj->AddMesh(meshobj); + + // gather levels of detail + if (BLI_countlist(&ob->lodlevels) > 1) { + LodLevel *lod = ((LodLevel*)ob->lodlevels.first)->next; + Mesh* lodmesh = mesh; + Object* lodmatob = ob; + gameobj->AddLodMesh(meshobj); + for (; lod; lod = lod->next) { + if (!lod->source || lod->source->type != OB_MESH) continue; + if (lod->flags & OB_LOD_USE_MESH) { + lodmesh = static_cast(lod->source->data); + } + if (lod->flags & OB_LOD_USE_MAT) { + lodmatob = lod->source; + } + gameobj->AddLodMesh(BL_ConvertMesh(lodmesh, lodmatob, kxscene, converter, libloading)); + } + } // for all objects: check whether they want to // respond to updates diff --git a/source/gameengine/Ketsji/KX_Dome.cpp b/source/gameengine/Ketsji/KX_Dome.cpp index f87d4799abc..43d74487542 100644 --- a/source/gameengine/Ketsji/KX_Dome.cpp +++ b/source/gameengine/Ketsji/KX_Dome.cpp @@ -2045,5 +2045,8 @@ void KX_Dome::RenderDomeFrame(KX_Scene* scene, KX_Camera* cam, int i) scene->CalculateVisibleMeshes(m_rasterizer,cam); scene->RenderBuckets(camtrans, m_rasterizer); + + // update levels of detail + scene->UpdateObjectLods(); } diff --git a/source/gameengine/Ketsji/KX_GameObject.cpp b/source/gameengine/Ketsji/KX_GameObject.cpp index d3b5a987138..8b7e6667fae 100644 --- a/source/gameengine/Ketsji/KX_GameObject.cpp +++ b/source/gameengine/Ketsji/KX_GameObject.cpp @@ -71,6 +71,8 @@ typedef unsigned long uint_ptr; #include "NG_NetworkScene.h" //Needed for sendMessage() #include "KX_ObstacleSimulation.h" +#include "BKE_object.h" + #include "BL_ActionManager.h" #include "BL_Action.h" @@ -732,6 +734,43 @@ void KX_GameObject::RemoveMeshes() m_meshes.clear(); } +void KX_GameObject::AddLodMesh(RAS_MeshObject* mesh) +{ + m_lodmeshes.push_back(mesh); +} + +void KX_GameObject::UpdateLod(MT_Vector3 &cam_pos) +{ + // Handle dupligroups + if (this->m_pInstanceObjects) { + KX_GameObject * instob; + int count = this->m_pInstanceObjects->GetCount(); + for (int i = 0; i < count; i++) { + instob = (KX_GameObject*)this->m_pInstanceObjects->GetValue(i); + instob->UpdateLod(cam_pos); + } + } + + if (this->m_lodmeshes.empty()) return; + + MT_Vector3 delta = this->NodeGetWorldPosition() - cam_pos; + float distance2 = delta.length2(); + + int level = 0; + Object *bob = this->GetBlenderObject(); + LodLevel *lod = (LodLevel*) bob->lodlevels.first; + for (; lod; lod = lod->next, level++) { + if (!lod->source) level--; + if (!lod->next || lod->next->distance * lod->next->distance > distance2) break; + } + + RAS_MeshObject *mesh = this->m_lodmeshes[level]; + + if (mesh != this->m_meshes[0]) { + this->GetScene()->ReplaceMesh(this, mesh, true, false); + } +} + void KX_GameObject::UpdateTransform() { // HACK: saves function call for dynamic object, they are handled differently diff --git a/source/gameengine/Ketsji/KX_GameObject.h b/source/gameengine/Ketsji/KX_GameObject.h index 12aac68365b..ac0afca91eb 100644 --- a/source/gameengine/Ketsji/KX_GameObject.h +++ b/source/gameengine/Ketsji/KX_GameObject.h @@ -87,6 +87,7 @@ protected: STR_String m_text; int m_layer; std::vector m_meshes; + std::vector m_lodmeshes; SG_QList m_meshSlots; // head of mesh slots of this struct Object* m_pBlenderObject; struct Object* m_pBlenderGroupObject; @@ -771,6 +772,23 @@ public: m_meshes.push_back(mesh); } + /** + * Add a level of detail mesh to the object. These should + * be added in order. + */ + void + AddLodMesh( + RAS_MeshObject* mesh + ); + + /** + * Updates the current lod level based on distance from camera. + */ + void + UpdateLod( + MT_Vector3 &cam_pos + ); + /** * Pick out a mesh associated with the integer 'num'. */ diff --git a/source/gameengine/Ketsji/KX_KetsjiEngine.cpp b/source/gameengine/Ketsji/KX_KetsjiEngine.cpp index 0e9e2cd8328..3aa5a9f4f0e 100644 --- a/source/gameengine/Ketsji/KX_KetsjiEngine.cpp +++ b/source/gameengine/Ketsji/KX_KetsjiEngine.cpp @@ -1315,6 +1315,9 @@ void KX_KetsjiEngine::RenderFrame(KX_Scene* scene, KX_Camera* cam) scene->CalculateVisibleMeshes(m_rasterizer,cam); + // update levels of detail + scene->UpdateObjectLods(); + m_logger->StartLog(tc_rasterizer, m_kxsystem->GetTimeInSeconds(), true); SG_SetActiveStage(SG_STAGE_RENDER); diff --git a/source/gameengine/Ketsji/KX_Scene.cpp b/source/gameengine/Ketsji/KX_Scene.cpp index 70924c65519..4a147b4eb60 100644 --- a/source/gameengine/Ketsji/KX_Scene.cpp +++ b/source/gameengine/Ketsji/KX_Scene.cpp @@ -1726,6 +1726,19 @@ void KX_Scene::RenderFonts() } } +void KX_Scene::UpdateObjectLods(void) +{ + KX_GameObject* gameobj; + MT_Vector3 cam_pos = this->m_active_camera->NodeGetWorldPosition(); + + for (int i = 0; i < this->GetObjectList()->GetCount(); i++) { + gameobj = (KX_GameObject*) GetObjectList()->GetValue(i); + if (!gameobj->GetCulled()){ + gameobj->UpdateLod(cam_pos); + } + } +} + void KX_Scene::UpdateObjectActivity(void) { if (m_activity_culling) { diff --git a/source/gameengine/Ketsji/KX_Scene.h b/source/gameengine/Ketsji/KX_Scene.h index ee2a994d53c..50fac923fe2 100644 --- a/source/gameengine/Ketsji/KX_Scene.h +++ b/source/gameengine/Ketsji/KX_Scene.h @@ -544,6 +544,9 @@ public: // Resume a suspended scene. void Resume(); + + // Update the mesh for objects based on level of detail settings + void UpdateObjectLods(void); // Update the activity box settings for objects in this scene, if needed. void UpdateObjectActivity(void);