BGE: New hysteresis offset to improve LOD level transitions
authorJorge Bernal <jbernalmartinez@gmail.com>
Sun, 22 Mar 2015 17:13:53 +0000 (18:13 +0100)
committerJorge Bernal <jbernalmartinez@gmail.com>
Sun, 22 Mar 2015 17:19:49 +0000 (18:19 +0100)
This change introduces a new hysteresis parameter that it will be added
or subtracted to/from the LOD distance to avoid popping when a LOD
object moves close to the LOD transition continuously.

Then, we have the following:

- a new LOD Hysteresis setting per scene (default 10%) which is located
in Scene context --> Level of Detail panel. This scene parameter also
will active/deactive the scene hysteresis.
- and a new LOD Hysteresis setting per object (default 10%) which is
located in Object context --> Levels of Detail panel. The LOD hysteresis
setting per object (if active) will overwrite the hysteresis setting per
scene value.

For the new blends: the hysteresis setting per scene would be active by
default and the per object would be inactive by default.
For the old blends: both hysteresis settings (per scene and per object)
would be inactive by default. A quick way to take advantage of this
feature for old blends would be to activate the hysteresis parameter in
the scene context -> Level of Detail panel

Reviewers: campbellbarton, kupoman, moguri

Reviewed By: kupoman, moguri

Subscribers: nonamejuju, lordodin

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

14 files changed:
release/scripts/startup/bl_ui/properties_game.py
source/blender/blenkernel/intern/object.c
source/blender/blenkernel/intern/scene.c
source/blender/blenloader/intern/versioning_270.c
source/blender/blenloader/intern/versioning_defaults.c
source/blender/makesdna/DNA_object_types.h
source/blender/makesdna/DNA_scene_types.h
source/blender/makesrna/intern/rna_object.c
source/blender/makesrna/intern/rna_scene.c
source/gameengine/Converter/BL_BlenderDataConversion.cpp
source/gameengine/Ketsji/KX_GameObject.cpp
source/gameengine/Ketsji/KX_GameObject.h
source/gameengine/Ketsji/KX_Scene.cpp
source/gameengine/Ketsji/KX_Scene.h

index 32a8e734ab63ed11c0795e52dbbb2e905c0c11d1..8ef9a083104d7fb0ac551462dd4a6a48bcd9ca11 100644 (file)
@@ -523,7 +523,27 @@ class SCENE_PT_game_navmesh(SceneButtonsPanel, Panel):
         row.prop(rd, "sample_max_error")
 
 
-class WorldButtonsPanel:
+class SCENE_PT_game_hysteresis(SceneButtonsPanel, Panel):
+    bl_label = "Level of Detail"
+    COMPAT_ENGINES = {'BLENDER_GAME'}
+
+    @classmethod
+    def poll(cls, context):
+        scene = context.scene
+        return (scene and scene.render.engine in cls.COMPAT_ENGINES)
+
+    def draw(self, context):
+        layout = self.layout
+        gs = context.scene.game_settings
+
+        row = layout.row()
+        row.prop(gs, "use_scene_hysteresis", text="Hysteresis")
+        row = layout.row()
+        row.active = gs.use_scene_hysteresis
+        row.prop(gs, "scene_hysteresis_percentage", text="")
+
+
+class WorldButtonsPanel():
     bl_space_type = 'PROPERTIES'
     bl_region_type = 'WINDOW'
     bl_context = "world"
@@ -765,6 +785,7 @@ class OBJECT_PT_levels_of_detail(ObjectButtonsPanel, Panel):
     def draw(self, context):
         layout = self.layout
         ob = context.object
+        gs = context.scene.game_settings
 
         col = layout.column()
 
@@ -782,6 +803,13 @@ class OBJECT_PT_levels_of_detail(ObjectButtonsPanel, Panel):
             row.prop(level, "use_mesh", text="")
             row.prop(level, "use_material", text="")
 
+            row = box.row()
+            row.active = gs.use_scene_hysteresis
+            row.prop(level, "use_object_hysteresis", text="Hysteresis Override")
+            row = box.row()
+            row.active = gs.use_scene_hysteresis and level.use_object_hysteresis
+            row.prop(level, "object_hysteresis_percentage", 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')
index 08a74d0c6cd92faf0b6d7ae184ce404761b42586..174fb38aadaacf853709f76850b61ff0ec557617 100644 (file)
@@ -1098,10 +1098,12 @@ void BKE_object_lod_add(Object *ob)
                BLI_addtail(&ob->lodlevels, base);
                base->flags = OB_LOD_USE_MESH | OB_LOD_USE_MAT;
                base->source = ob;
+               base->obhysteresis = 10;
                last = ob->currentlod = base;
        }
        
        lod->distance = last->distance + 25.0f;
+       lod->obhysteresis = 10;
        lod->flags = OB_LOD_USE_MESH | OB_LOD_USE_MAT;
 
        BLI_addtail(&ob->lodlevels, lod);
index c28d741d7eca8cea04fcd5909b63c9e85705bb0d..d2c3f473e11209e68db3e8485ad7dd4865db4f41 100644 (file)
@@ -677,6 +677,9 @@ Scene *BKE_scene_add(Main *bmain, const char *name)
        sce->gm.recastData.detailsampledist = 6.0f;
        sce->gm.recastData.detailsamplemaxerror = 1.0f;
 
+       sce->gm.lodflag = SCE_LOD_USE_HYST;
+       sce->gm.scehysteresis = 10;
+
        sce->gm.exitkey = 218; // Blender key code for ESC
 
        sound_create_scene(sce);
index adf5f93ae1632a69e4923c945fd847ac15be5bcf..0853c93750c435da54d54bdaa1a553ef7073f8b6 100644 (file)
@@ -654,5 +654,23 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *main)
                                }
                        }
                }
+
+               /* hysteresis setted to 10% but not actived */
+               if (!DNA_struct_elem_find(fd->filesdna, "LodLevel", "int", "obhysteresis")) {
+                       Object* ob;
+                       for (ob = main->object.first; ob; ob = ob->id.next) {
+                               LodLevel *level;
+                               for (level = ob->lodlevels.first; level; level = level->next) {
+                                       level->obhysteresis = 10;
+                               }
+                       }
+               }
+
+               if (!DNA_struct_elem_find(fd->filesdna, "GameData", "int", "scehysteresis")) {
+                       Scene *scene;
+                       for (scene = main->scene.first; scene; scene = scene->id.next) {
+                               scene->gm.scehysteresis = 10;
+                       }
+               }
        }
 }
index 4c7b011097b1009fd4566ddfb99ae246a7b0c179..d320b305c06e29e0ff6187fcb06dc6e0e0d2b9fa 100644 (file)
@@ -92,6 +92,9 @@ void BLO_update_defaults_startup_blend(Main *bmain)
                                sculpt->detail_size = 12;
                        }
                }
+
+               scene->gm.lodflag |= SCE_LOD_USE_HYST;
+               scene->gm.scehysteresis = 10;
        }
 
        for (linestyle = bmain->linestyle.first; linestyle; linestyle = linestyle->id.next) {
index 2699bb5f22f33d781f27483d27699f90652ab8c8..4cb979c57b72dc2706fd83ec633044eca91acbe4 100644 (file)
@@ -109,7 +109,8 @@ typedef struct LodLevel {
        struct LodLevel *next, *prev;
        struct Object *source;
        int flags;
-       float distance;
+       float distance, pad;
+       int obhysteresis;
 } LodLevel;
 
 typedef struct Object {
@@ -484,6 +485,7 @@ enum {
 enum {
        OB_LOD_USE_MESH         = 1 << 0,
        OB_LOD_USE_MAT          = 1 << 1,
+       OB_LOD_USE_HYST         = 1 << 2,
 };
 
 
index 0eae50a2562a26297a1a620c98895485c87eafdc..ac0803d494c8863a3aa8105c635d6de479d7014f 100644 (file)
@@ -718,7 +718,12 @@ typedef struct GameData {
        short obstacleSimulation;
        short raster_storage;
        float levelHeight;
-       float deactivationtime, lineardeactthreshold, angulardeactthreshold, pad2;
+       float deactivationtime, lineardeactthreshold, angulardeactthreshold;
+
+       /* Scene LoD */
+       short lodflag, pad2;
+       int scehysteresis, pad5;
+
 } GameData;
 
 #define STEREO_NOSTEREO                1
@@ -791,6 +796,9 @@ enum {
 #pragma GCC poison GAME_MAT_TEXFACE
 #endif
 
+/* GameData.lodflag */
+#define SCE_LOD_USE_HYST               (1 << 0)
+
 /* UV Paint */
 #define UV_SCULPT_LOCK_BORDERS                         1
 #define UV_SCULPT_ALL_ISLANDS                          2
index d8aa659b68ebd6f3f15f93ccf6b6ff6c0d5c49ab..a49388866a657c403761417b4316fec6b88f9945 100644 (file)
@@ -2089,6 +2089,14 @@ static void rna_def_object_lodlevel(BlenderRNA *brna)
        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_hysteresis_percentage", PROP_INT, PROP_PERCENTAGE);
+       RNA_def_property_int_sdna(prop, NULL, "obhysteresis");
+       RNA_def_property_range(prop, 0, 100);
+       RNA_def_property_ui_range(prop, 0, 100, 10, 1);
+       RNA_def_property_ui_text(prop, "Hysteresis %", "Minimum distance change required to transition to the previous"
+                                                                  " level of detail");
+       RNA_def_property_update(prop, NC_OBJECT | ND_LOD, NULL);
+
        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");
@@ -2107,6 +2115,11 @@ static void rna_def_object_lodlevel(BlenderRNA *brna)
        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);
+
+       prop = RNA_def_property(srna, "use_object_hysteresis", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_sdna(prop, NULL, "flags", OB_LOD_USE_HYST);
+       RNA_def_property_ui_text(prop, "Hysteresis Override", "Override LoD Hysteresis scene setting for this Lod Level");
+       RNA_def_property_update(prop, NC_OBJECT | ND_LOD, NULL);
 }
 
 
index 2e941487e98ae0d0fae5284c6f2c9c3e3a77cb60..a0a0eedcfed8fb4d1211c527daa94b83344ccc62 100644 (file)
@@ -3886,6 +3886,20 @@ static void rna_def_scene_game_data(BlenderRNA *brna)
 
        /* Nestled Data  */
        rna_def_scene_game_recast_data(brna);
+
+       /* LoD */
+       prop = RNA_def_property(srna, "use_scene_hysteresis", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_sdna(prop, NULL, "lodflag", SCE_LOD_USE_HYST);
+       RNA_def_property_ui_text(prop, "Hysteresis", "Use LoD Hysteresis setting for the scene");
+       RNA_def_property_update(prop, NC_SCENE, NULL);
+
+       prop = RNA_def_property(srna, "scene_hysteresis_percentage", PROP_INT, PROP_PERCENTAGE);
+       RNA_def_property_int_sdna(prop, NULL, "scehysteresis");
+       RNA_def_property_range(prop, 0, 100);
+       RNA_def_property_ui_range(prop, 0, 100, 10, 1);
+       RNA_def_property_ui_text(prop, "Hysteresis %", "Minimum distance change required to transition to the previous"
+                                                                  " level of detail");
+       RNA_def_property_update(prop, NC_SCENE, NULL);
 }
 
 static void rna_def_gpu_dof_fx(BlenderRNA *brna)
index cd2e2151034b7530b7b96153eebea9f5c78a6df0..7b5cc457ee66f439b8a66316365a29454bd682ac 100644 (file)
@@ -1554,6 +1554,10 @@ static KX_GameObject *gameobject_from_blenderobject(
                                }
                                gameobj->AddLodMesh(BL_ConvertMesh(lodmesh, lodmatob, kxscene, converter, libloading));
                        }
+                       if (blenderscene->gm.lodflag & SCE_LOD_USE_HYST) {
+                               kxscene->SetLodHysteresis(true);
+                               gameobj->SetLodHysteresisValue(blenderscene->gm.scehysteresis);
+                       }
                }
        
                // for all objects: check whether they want to
index c8fc64f4679ce860a439dabf95a5a4d47f482aba..16dfe5bd9de0bc9115f28224f58718deb4326a4a 100644 (file)
@@ -92,6 +92,8 @@ KX_GameObject::KX_GameObject(
       m_bDyna(false),
       m_layer(0),
       m_currentLodLevel(0),
+      m_previousLodLevel(0),
+      m_lodHysteresis(0),
       m_pBlenderObject(NULL),
       m_pBlenderGroupObject(NULL),
       m_bSuspendDynamics(false),
@@ -784,6 +786,11 @@ void KX_GameObject::AddLodMesh(RAS_MeshObject* mesh)
        m_lodmeshes.push_back(mesh);
 }
 
+void KX_GameObject::SetLodHysteresisValue(int hysteresis)
+{
+       m_lodHysteresis = hysteresis;
+}
+
 void KX_GameObject::UpdateLod(MT_Vector3 &cam_pos)
 {
        // Handle dupligroups
@@ -804,14 +811,47 @@ void KX_GameObject::UpdateLod(MT_Vector3 &cam_pos)
        int level = 0;
        Object *bob = this->GetBlenderObject();
        LodLevel *lod = (LodLevel*) bob->lodlevels.first;
+       KX_Scene *sce = this->GetScene();
+
        for (; lod; lod = lod->next, level++) {
                if (!lod->source || lod->source->type != OB_MESH) level--;
-               if (!lod->next || lod->next->distance * lod->next->distance > distance2) break;
+               if (!lod->next) break;
+               if (level == (this->m_previousLodLevel) || (level == (this->m_previousLodLevel + 1))) {
+                       short hysteresis = 0;
+                       if (sce->IsActivedLodHysteresis()) {
+                               // if exists, LoD level hysteresis will override scene hysteresis
+                               if (lod->next->flags & OB_LOD_USE_HYST) {
+                                       hysteresis = lod->next->obhysteresis;
+                               }
+                               else if (this->m_lodHysteresis != 0) {
+                                       hysteresis = m_lodHysteresis;
+                               }
+                       }
+                       float hystvariance = MT_abs(lod->next->distance - lod->distance) * hysteresis / 100;
+                       if ((lod->next->distance + hystvariance) * (lod->next->distance + hystvariance) > distance2)
+                               break;
+               }
+               else if (level == (this->m_previousLodLevel - 1)) {
+                       short hysteresis = 0;
+                       if (sce->IsActivedLodHysteresis()) {
+                               // if exists, LoD level hysteresis will override scene hysteresis
+                               if (lod->next->flags & OB_LOD_USE_HYST) {
+                                       hysteresis = lod->next->obhysteresis;
+                               }
+                               else if (this->m_lodHysteresis != 0) {
+                                       hysteresis = m_lodHysteresis;
+                               }
+                       }
+                       float hystvariance = MT_abs(lod->next->distance - lod->distance) * hysteresis / 100;
+                       if ((lod->next->distance - hystvariance) * (lod->next->distance - hystvariance) > distance2)
+                               break;
+               }
        }
 
        RAS_MeshObject *mesh = this->m_lodmeshes[level];
        this->m_currentLodLevel = level;
        if (mesh != this->m_meshes[0]) {
+               this->m_previousLodLevel = level;
                this->GetScene()->ReplaceMesh(this, mesh, true, false);
        }
 }
index 132e72c45e21c14433a8e7677c5c4a05617b5085..d9810b89c90908bb2af01f3c025a6fef530343e7 100644 (file)
@@ -90,6 +90,8 @@ protected:
        std::vector<RAS_MeshObject*>            m_meshes;
        std::vector<RAS_MeshObject*>            m_lodmeshes;
        int                                 m_currentLodLevel;
+       short                                                           m_previousLodLevel;
+       int                                                                     m_lodHysteresis;
        SG_QList                                                        m_meshSlots;    // head of mesh slots of this 
        struct Object*                                          m_pBlenderObject;
        struct Object*                                          m_pBlenderGroupObject;
@@ -804,6 +806,14 @@ public:
                RAS_MeshObject* mesh
        );
 
+       /**
+        * Set lod hysteresis value
+        */
+               void
+       SetLodHysteresisValue(
+               int hysteresis
+       );
+
        /**
         * Updates the current lod level based on distance from camera.
         */
index 47510baa436e159294fface4de0edde44f7d1967..d9d07e78c1712b4e366c24ca02d835960092c250 100644 (file)
@@ -156,7 +156,8 @@ KX_Scene::KX_Scene(class SCA_IInputDevice* keyboarddevice,
        m_networkDeviceInterface(ndi),
        m_active_camera(NULL),
        m_ueberExecutionPriority(0),
-       m_blenderScene(scene)
+       m_blenderScene(scene),
+       m_isActivedHysteresis(false)
 {
        m_suspendedtime = 0.0;
        m_suspendeddelta = 0.0;
@@ -1763,6 +1764,16 @@ void KX_Scene::UpdateObjectLods(void)
        }
 }
 
+void KX_Scene::SetLodHysteresis(bool active)
+{
+       m_isActivedHysteresis = active;
+}
+
+bool KX_Scene::IsActivedLodHysteresis(void)
+{
+       return m_isActivedHysteresis;
+}
+
 void KX_Scene::UpdateObjectActivity(void) 
 {
        if (m_activity_culling) {
index 2e1ee9f101d6b890b0c8dbc5dc077d1019a6bd84..19873daabb3631fdc295522acf54f49bbd63afb3 100644 (file)
@@ -295,6 +295,11 @@ protected:
 
        KX_ObstacleSimulation* m_obstacleSimulation;
 
+       /**
+        * Does this scene active the LoD Hysteresis?
+        */
+       bool m_isActivedHysteresis;
+
 public:
        KX_Scene(class SCA_IInputDevice* keyboarddevice,
                class SCA_IInputDevice* mousedevice,
@@ -546,6 +551,10 @@ public:
 
        // Update the mesh for objects based on level of detail settings
        void UpdateObjectLods(void);
+
+       // Enable/disable LoD Hysteresis
+       void SetLodHysteresis(bool active);
+       bool IsActivedLodHysteresis();
        
        // Update the activity box settings for objects in this scene, if needed.
        void UpdateObjectActivity(void);